AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2025 Samuel Abels <knipknap@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import pathlib
import re
import xml.etree.ElementTree as ET
from typing import Mapping, Optional
from functools import cached_property
from ...assets import Asset, AssetUri, AssetSerializer, DummyAssetSerializer
import Path.Tool.shape.util as util
from PySide import QtCore, QtGui, QtSvg
_svg_ns = {"s": "http://www.w3.org/2000/svg"}
class ToolBitShapeIcon(Asset):
"""Abstract base class for tool bit shape icons."""
def __init__(self, id: str, data: bytes):
"""
Initialize the icon.
Args:
id (str): The unique identifier for the icon, including extension.
data (bytes): The raw icon data (e.g., SVG or PNG bytes).
"""
self.id: str = id
self.data: bytes = data
def get_id(self) -> str:
"""
Get the ID of the icon.
Returns:
str: The ID of the icon.
"""
return self.id
@classmethod
def from_bytes(
cls,
data: bytes,
id: str,
dependencies: Optional[Mapping[AssetUri, Asset]],
serializer: AssetSerializer,
) -> "ToolBitShapeIcon":
"""
Create a ToolBitShapeIcon instance from raw bytes.
Args:
data (bytes): The raw bytes of the icon file.
id (str): The ID of the asset, including extension.
dependencies (Optional[Mapping[AssetUri, Asset]]): A mapping of resolved dependencies (not used for icons).
Returns:
ToolBitShapeIcon: An instance of ToolBitShapeIcon.
"""
assert serializer == DummyAssetSerializer, "ToolBitShapeIcon supports only native import"
return cls(id=id, data=data)
def to_bytes(self, serializer: AssetSerializer) -> bytes:
"""
Serializes a ToolBitShapeIcon object to bytes.
"""
assert serializer == DummyAssetSerializer, "ToolBitShapeIcon supports only native export"
return self.data
@classmethod
def from_file(cls, filepath: pathlib.Path, id: str) -> "ToolBitShapeIcon":
"""
Create a ToolBitShapeIcon instance from a file.
Args:
filepath (pathlib.Path): Path to the icon file (.svg or .png).
shape_id_base (str): The base ID of the associated shape.
Returns:
ToolBitShapeIcon: An instance of ToolBitShapeIcon.
Raises:
FileNotFoundError: If the file does not exist.
"""
if not filepath.exists():
raise FileNotFoundError(f"Icon file not found: {filepath}")
data = filepath.read_bytes()
if filepath.suffix.lower() == ".png":
return ToolBitShapePngIcon(id, data)
elif filepath.suffix.lower() == ".svg":
return ToolBitShapeSvgIcon(id, data)
else:
raise NotImplementedError(f"unsupported icon file: {filepath}")
@classmethod
def from_shape_data(cls, shape_data: bytes, id: str) -> Optional["ToolBitShapeIcon"]:
"""
Create a thumbnail icon from shape data bytes.
Args:
shape_data (bytes): The raw bytes of the shape file (.FCStd).
shape_id_base (str): The base ID of the associated shape.
Returns:
Optional[ToolBitShapeIcon]: An instance of ToolBitShapeIcon (PNG), or None.
"""
image_bytes = util.create_thumbnail_from_data(shape_data)
if not image_bytes:
return None
# Assuming create_thumbnail_from_data returns PNG data
return ToolBitShapePngIcon(id=id, data=image_bytes)
def get_size_in_bytes(self) -> int:
"""
Get the size of the icon data in bytes.
"""
return len(self.data)
@cached_property
def abbreviations(self) -> Mapping[str, str]:
"""
Returns a cached mapping of parameter abbreviations from the icon data.
"""
return {}
def get_abbr(self, param_name: str) -> Optional[str]:
"""
Retrieves the abbreviation for a given parameter name.
Args:
param_name: The name of the parameter.
Returns:
The abbreviation string, or None if not found.
"""
normalized_param_name = param_name.lower().replace(" ", "_")
return self.abbreviations.get(normalized_param_name)
def get_png(self, icon_size: Optional[QtCore.QSize] = None) -> bytes:
"""
Returns the icon data as PNG bytes.
"""
raise NotImplementedError
def get_qpixmap(self, icon_size: Optional[QtCore.QSize] = None) -> QtGui.QPixmap:
"""
Returns the icon data as a QPixmap.
"""
raise NotImplementedError
class ToolBitShapeSvgIcon(ToolBitShapeIcon):
asset_type: str = "toolbitshapesvg"
def get_png(self, icon_size: Optional[QtCore.QSize] = None) -> bytes:
"""
Converts SVG icon data to PNG and returns it using QtSvg.
"""
if icon_size is None:
icon_size = QtCore.QSize(48, 48)
image = QtGui.QImage(icon_size, QtGui.QImage.Format_ARGB32)
image.fill(QtGui.Qt.transparent)
painter = QtGui.QPainter(image)
buffer = QtCore.QBuffer(QtCore.QByteArray(self.data))
buffer.open(QtCore.QIODevice.ReadOnly)
svg_renderer = QtSvg.QSvgRenderer(buffer)
svg_renderer.setAspectRatioMode(QtCore.Qt.KeepAspectRatio)
svg_renderer.render(painter)
painter.end()
byte_array = QtCore.QByteArray()
buffer = QtCore.QBuffer(byte_array)
buffer.open(QtCore.QIODevice.WriteOnly)
image.save(buffer, "PNG")
return bytes(byte_array)
def get_qpixmap(self, icon_size: Optional[QtCore.QSize] = None) -> QtGui.QPixmap:
"""
Returns the SVG icon data as a QPixmap using QtSvg.
"""
if icon_size is None:
icon_size = QtCore.QSize(48, 48)
icon_ba = QtCore.QByteArray(self.data)
image = QtGui.QImage(icon_size, QtGui.QImage.Format_ARGB32)
image.fill(QtGui.Qt.transparent)
painter = QtGui.QPainter(image)
buffer = QtCore.QBuffer(icon_ba) # PySide6
buffer.open(QtCore.QIODevice.ReadOnly)
data = QtCore.QXmlStreamReader(buffer)
renderer = QtSvg.QSvgRenderer(data)
renderer.setAspectRatioMode(QtCore.Qt.KeepAspectRatio)
renderer.render(painter)
painter.end()
return QtGui.QPixmap.fromImage(image)
@cached_property
def abbreviations(self) -> Mapping[str, str]:
"""
Returns a cached mapping of parameter abbreviations from the icon data.
Only applicable for SVG icons.
"""
if self.data:
return self.get_abbreviations_from_svg(self.data)
return {}
def get_abbr(self, param_name: str) -> Optional[str]:
"""
Retrieves the abbreviation for a given parameter name.
Args:
param_name: The name of the parameter.
Returns:
The abbreviation string, or None if not found.
"""
return self.abbreviations.get(param_name)
@staticmethod
def get_abbreviations_from_svg(svg: bytes) -> Mapping[str, str]:
"""
Extract abbreviations from SVG text elements.
"""
try:
tree = ET.fromstring(svg)
except ET.ParseError:
return {}
result = {}
for text_elem in tree.findall(".//s:text", _svg_ns):
id = text_elem.attrib.get("id", _svg_ns)
if id is None or not isinstance(id, str):
continue
# Backward compatibility: Normalize to match FreeCAD property
# name structure:
# Old: property_name New: PropertyName
def _upper(match):
return match.group(1).upper()
id = re.sub(r"_(\w)", _upper, id.capitalize())
abbr = text_elem.text
if abbr is not None:
result[id] = abbr
span_elem = text_elem.find(".//s:tspan", _svg_ns)
if span_elem is None:
continue
abbr = span_elem.text
result[id] = abbr
return result
class ToolBitShapePngIcon(ToolBitShapeIcon):
asset_type: str = "toolbitshapepng"
def get_png(self, icon_size: Optional[QtCore.QSize] = None) -> bytes:
"""
Returns the PNG icon data.
"""
# For PNG, resizing might be needed if icon_size is different
# from the original size. Simple return for now.
return self.data
def get_qpixmap(self, icon_size: Optional[QtCore.QSize] = None) -> QtGui.QPixmap:
"""
Returns the PNG icon data as a QPixmap.
"""
if icon_size is None:
icon_size = QtCore.QSize(48, 48)
pixmap = QtGui.QPixmap()
pixmap.loadFromData(self.data, "PNG")
# Scale the pixmap if the requested size is different
if pixmap.size() != icon_size:
pixmap = pixmap.scaled(
icon_size,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation,
)
return pixmap