Compare commits
10 Commits
feat/appea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b234d042eb | |||
|
|
08e439b9ca | ||
| 4e01fdf68b | |||
| 55be41e697 | |||
| 29ca89e533 | |||
|
|
ef16ecbaa2 | ||
|
|
2bf969c62a | ||
|
|
04f9df75cb | ||
|
|
2132c4e64c | ||
|
|
12e332240a |
@@ -1,109 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<FCParameters>
|
||||
<FCParamGroup Name="Root">
|
||||
<FCParamGroup Name="BaseApp">
|
||||
<FCParamGroup Name="Preferences">
|
||||
<FCParamGroup Name="Editor">
|
||||
<FCUInt Name="Text" Value="3453416703"/>
|
||||
<FCUInt Name="Bookmark" Value="3032415999"/>
|
||||
<FCUInt Name="Breakpoint" Value="4086016255"/>
|
||||
<FCUInt Name="Keyword" Value="3416717311"/>
|
||||
<FCUInt Name="Comment" Value="2139095295"/>
|
||||
<FCUInt Name="Block comment" Value="2139095295"/>
|
||||
<FCUInt Name="Number" Value="4206069759"/>
|
||||
<FCUInt Name="String" Value="2799935999"/>
|
||||
<FCUInt Name="Character" Value="4073902335"/>
|
||||
<FCUInt Name="Class name" Value="2310339327"/>
|
||||
<FCUInt Name="Define name" Value="2310339327"/>
|
||||
<FCUInt Name="Operator" Value="2312199935"/>
|
||||
<FCUInt Name="Python output" Value="2796290303"/>
|
||||
<FCUInt Name="Python error" Value="4086016255"/>
|
||||
<FCUInt Name="Current line highlight" Value="1162304255"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="OutputWindow">
|
||||
<FCUInt Name="colorText" Value="3453416703"/>
|
||||
<FCUInt Name="colorLogging" Value="2497893887"/>
|
||||
<FCUInt Name="colorWarning" Value="4192382975"/>
|
||||
<FCUInt Name="colorError" Value="4086016255"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="View">
|
||||
<FCUInt Name="BackgroundColor" Value="505294591"/>
|
||||
<FCUInt Name="BackgroundColor2" Value="286333951"/>
|
||||
<FCUInt Name="BackgroundColor3" Value="404235775"/>
|
||||
<FCUInt Name="BackgroundColor4" Value="825378047"/>
|
||||
<FCBool Name="Simple" Value="0"/>
|
||||
<FCBool Name="Gradient" Value="1"/>
|
||||
<FCBool Name="UseBackgroundColorMid" Value="0"/>
|
||||
<FCUInt Name="HighlightColor" Value="3416717311"/>
|
||||
<FCUInt Name="SelectionColor" Value="3032415999"/>
|
||||
<FCUInt Name="PreselectColor" Value="2497893887"/>
|
||||
<FCUInt Name="DefaultShapeColor" Value="1482387711"/>
|
||||
<FCBool Name="RandomColor" Value="0"/>
|
||||
<FCUInt Name="DefaultShapeLineColor" Value="2470768383"/>
|
||||
<FCUInt Name="DefaultShapeVertexColor" Value="2470768383"/>
|
||||
<FCUInt Name="BoundingBoxColor" Value="1819509759"/>
|
||||
<FCUInt Name="AnnotationTextColor" Value="3453416703"/>
|
||||
<FCUInt Name="SketchEdgeColor" Value="3453416703"/>
|
||||
<FCUInt Name="SketchVertexColor" Value="3453416703"/>
|
||||
<FCUInt Name="EditedEdgeColor" Value="3416717311"/>
|
||||
<FCUInt Name="EditedVertexColor" Value="4123402495"/>
|
||||
<FCUInt Name="ConstructionColor" Value="4206069759"/>
|
||||
<FCUInt Name="ExternalColor" Value="4192382975"/>
|
||||
<FCUInt Name="FullyConstrainedColor" Value="2799935999"/>
|
||||
<FCUInt Name="InternalAlignedGeoColor" Value="1959907071"/>
|
||||
<FCUInt Name="FullyConstraintElementColor" Value="2799935999"/>
|
||||
<FCUInt Name="FullyConstraintConstructionElementColor" Value="2497893887"/>
|
||||
<FCUInt Name="FullyConstraintInternalAlignmentColor" Value="2312199935"/>
|
||||
<FCUInt Name="FullyConstraintConstructionPointColor" Value="2799935999"/>
|
||||
<FCUInt Name="ConstrainedIcoColor" Value="2310339327"/>
|
||||
<FCUInt Name="NonDrivingConstrDimColor" Value="2139095295"/>
|
||||
<FCUInt Name="ConstrainedDimColor" Value="3416717311"/>
|
||||
<FCUInt Name="ExprBasedConstrDimColor" Value="4206069759"/>
|
||||
<FCUInt Name="DeactivatedConstrDimColor" Value="1819509759"/>
|
||||
<FCUInt Name="CursorTextColor" Value="3453416703"/>
|
||||
<FCUInt Name="CursorCrosshairColor" Value="3416717311"/>
|
||||
<FCUInt Name="CreateLineColor" Value="2799935999"/>
|
||||
<FCUInt Name="ShadowLightColor" Value="2470768128"/>
|
||||
<FCUInt Name="ShadowGroundColor" Value="286333952"/>
|
||||
<FCUInt Name="HiddenLineColor" Value="825378047"/>
|
||||
<FCUInt Name="HiddenLineFaceColor" Value="505294591"/>
|
||||
<FCUInt Name="HiddenLineBackground" Value="505294591"/>
|
||||
<FCBool Name="EnableBacklight" Value="1"/>
|
||||
<FCUInt Name="BacklightColor" Value="1162304255"/>
|
||||
<FCFloat Name="BacklightIntensity" Value="0.30"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="TreeView">
|
||||
<FCUInt Name="TreeEditColor" Value="3416717311"/>
|
||||
<FCUInt Name="TreeActiveColor" Value="2799935999"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="MainWindow">
|
||||
<FCText Name="StyleSheet">CatppuccinMocha.qss</FCText>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="Mod">
|
||||
<FCParamGroup Name="Start">
|
||||
<FCUInt Name="BackgroundColor1" Value="404235775"/>
|
||||
<FCUInt Name="BackgroundTextColor" Value="3453416703"/>
|
||||
<FCUInt Name="PageColor" Value="505294591"/>
|
||||
<FCUInt Name="PageTextColor" Value="3453416703"/>
|
||||
<FCUInt Name="BoxColor" Value="825378047"/>
|
||||
<FCUInt Name="LinkColor" Value="2310339327"/>
|
||||
<FCUInt Name="BackgroundColor2" Value="286333951"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="Part">
|
||||
<FCUInt Name="VertexColor" Value="3032415999"/>
|
||||
<FCUInt Name="EdgeColor" Value="2310339327"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="PartDesign">
|
||||
<FCUInt Name="DefaultDatumColor" Value="3416717311"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="Draft">
|
||||
<FCUInt Name="snapcolor" Value="2799935999"/>
|
||||
</FCParamGroup>
|
||||
<FCParamGroup Name="Sketcher">
|
||||
<FCUInt Name="GridLineColor" Value="1162304255"/>
|
||||
</FCParamGroup>
|
||||
</FCParamGroup>
|
||||
</FCParamGroup>
|
||||
</FCParamGroup>
|
||||
</FCParamGroup>
|
||||
</FCParameters>
|
||||
File diff suppressed because it is too large
Load Diff
26
package.xml
26
package.xml
@@ -3,7 +3,7 @@
|
||||
|
||||
<name>ZTools</name>
|
||||
|
||||
<description>Extended PartDesign workbench with velocity-focused tools, advanced datum creation, and Catppuccin Mocha theme.</description>
|
||||
<description>Extended PartDesign workbench with velocity-focused tools and advanced datum creation.</description>
|
||||
|
||||
<version>0.1.0</version>
|
||||
|
||||
@@ -17,16 +17,20 @@
|
||||
<classname>ZToolsWorkbench</classname>
|
||||
<subdirectory>./ztools</subdirectory>
|
||||
</workbench>
|
||||
<preferencepack>
|
||||
<name>CatppuccinMocha</name>
|
||||
<description>Catppuccin Mocha dark theme - soothing pastel colors for the high-spirited</description>
|
||||
<subdirectory>./CatppuccinMocha</subdirectory>
|
||||
<tag>color</tag>
|
||||
<tag>dark</tag>
|
||||
<tag>catppuccin</tag>
|
||||
<tag>mocha</tag>
|
||||
<tag>theme</tag>
|
||||
</preferencepack>
|
||||
</content>
|
||||
|
||||
<!-- Kindred Create extensions -->
|
||||
<kindred>
|
||||
<min_create_version>0.1.0</min_create_version>
|
||||
<load_priority>50</load_priority>
|
||||
<pure_python>true</pure_python>
|
||||
<dependencies>
|
||||
<dependency>sdk</dependency>
|
||||
</dependencies>
|
||||
<contexts>
|
||||
<context id="partdesign.body" action="inject"/>
|
||||
<context id="partdesign.feature" action="inject"/>
|
||||
</contexts>
|
||||
</kindred>
|
||||
|
||||
</package>
|
||||
|
||||
@@ -66,14 +66,8 @@ class ZToolsWorkbench(Gui.Workbench):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from ztools.commands import (
|
||||
appearance_commands,
|
||||
assembly_pattern_commands,
|
||||
datum_commands,
|
||||
pattern_commands,
|
||||
pocket_commands,
|
||||
spreadsheet_commands,
|
||||
)
|
||||
# Command imports moved to module scope (after Gui.addWorkbench) so they
|
||||
# are available before Initialize() runs. See end of file.
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Structure Tools
|
||||
@@ -254,18 +248,6 @@ class ZToolsWorkbench(Gui.Workbench):
|
||||
"ZTools_SpreadsheetQuickAlias",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# ZTools Appearance Mode Tools
|
||||
# =====================================================================
|
||||
self.ztools_appearance_tools = [
|
||||
"ZTools_AppearanceMode",
|
||||
"ZTools_SetCategory",
|
||||
]
|
||||
self.ztools_appearance_menu_tools = [
|
||||
"ZTools_AppearanceRealistic",
|
||||
"ZTools_AppearanceEngineering",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# Append Toolbars
|
||||
# =====================================================================
|
||||
@@ -288,7 +270,6 @@ class ZToolsWorkbench(Gui.Workbench):
|
||||
self.appendToolbar("ztools Assembly", self.ztools_assembly_tools)
|
||||
self.appendToolbar("Spreadsheet", self.spreadsheet_tools)
|
||||
self.appendToolbar("ztools Spreadsheet", self.ztools_spreadsheet_tools)
|
||||
self.appendToolbar("ztools Appearance", self.ztools_appearance_tools)
|
||||
|
||||
# =====================================================================
|
||||
# Append Menus
|
||||
@@ -311,57 +292,24 @@ class ZToolsWorkbench(Gui.Workbench):
|
||||
self.appendMenu(["Assembly", "Management"], self.assembly_management_tools)
|
||||
self.appendMenu(["Spreadsheet", "Edit"], self.spreadsheet_tools)
|
||||
self.appendMenu(["Spreadsheet", "Format"], self.ztools_spreadsheet_tools)
|
||||
self.appendMenu(
|
||||
["View", "Appearance Mode"],
|
||||
self.ztools_appearance_menu_tools + ["ZTools_SetCategory"],
|
||||
)
|
||||
self.appendMenu(
|
||||
"ztools",
|
||||
self.ztools_datum_tools
|
||||
+ self.ztools_pattern_tools
|
||||
+ self.ztools_pocket_tools
|
||||
+ self.ztools_assembly_tools
|
||||
+ self.ztools_spreadsheet_tools
|
||||
+ self.ztools_appearance_menu_tools
|
||||
+ ["ZTools_SetCategory"],
|
||||
+ self.ztools_spreadsheet_tools,
|
||||
)
|
||||
|
||||
# Register the PartDesign manipulator now that commands exist.
|
||||
# Guard so it only registers once even if Initialize is called again.
|
||||
if not getattr(ZToolsWorkbench, "_manipulator_installed", False):
|
||||
ZToolsWorkbench._manipulator_installed = True
|
||||
Gui.addWorkbenchManipulator(_ZToolsPartDesignManipulator())
|
||||
|
||||
App.Console.PrintMessage("ztools workbench initialized\n")
|
||||
|
||||
def Activated(self):
|
||||
"""Called when workbench is activated."""
|
||||
# Apply Catppuccin Mocha colors to Spreadsheet preferences
|
||||
try:
|
||||
from ztools.resources.theme import apply_spreadsheet_colors
|
||||
|
||||
apply_spreadsheet_colors()
|
||||
except Exception as e:
|
||||
App.Console.PrintWarning(f"Could not apply spreadsheet colors: {e}\n")
|
||||
|
||||
# Activate appearance mode system
|
||||
try:
|
||||
from ztools.appearance import get_manager
|
||||
|
||||
get_manager().activate()
|
||||
except Exception as e:
|
||||
App.Console.PrintWarning(f"Could not activate appearance system: {e}\n")
|
||||
|
||||
App.Console.PrintMessage("ztools workbench activated\n")
|
||||
|
||||
def Deactivated(self):
|
||||
"""Called when workbench is deactivated."""
|
||||
try:
|
||||
from ztools.appearance import get_manager
|
||||
|
||||
get_manager().deactivate()
|
||||
except Exception:
|
||||
pass
|
||||
pass
|
||||
|
||||
def GetClassName(self):
|
||||
return "Gui::PythonWorkbench"
|
||||
@@ -370,11 +318,24 @@ class ZToolsWorkbench(Gui.Workbench):
|
||||
Gui.addWorkbench(ZToolsWorkbench())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Eager command registration
|
||||
# ---------------------------------------------------------------------------
|
||||
# Import command modules at module scope so Gui.addCommand() calls run before
|
||||
# any workbench activates. This ensures the PartDesign manipulator can
|
||||
# reference them regardless of workbench activation order (#52).
|
||||
|
||||
from ztools.commands import (
|
||||
assembly_pattern_commands,
|
||||
datum_commands,
|
||||
pattern_commands,
|
||||
pocket_commands,
|
||||
spreadsheet_commands,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WorkbenchManipulator: inject ZTools commands into PartDesign workbench
|
||||
# ---------------------------------------------------------------------------
|
||||
# Registered in ZToolsWorkbench.Initialize() after commands are imported,
|
||||
# so the commands exist before the manipulator references them.
|
||||
|
||||
|
||||
class _ZToolsPartDesignManipulator:
|
||||
@@ -396,24 +357,11 @@ class _ZToolsPartDesignManipulator:
|
||||
|
||||
def modifyMenuBar(self):
|
||||
return [
|
||||
{
|
||||
"insert": "ZTools_DatumCreator",
|
||||
"menuItem": "PartDesign_Boolean",
|
||||
"after": "",
|
||||
},
|
||||
{
|
||||
"insert": "ZTools_DatumManager",
|
||||
"menuItem": "ZTools_DatumCreator",
|
||||
"after": "",
|
||||
},
|
||||
{
|
||||
"insert": "ZTools_EnhancedPocket",
|
||||
"menuItem": "ZTools_DatumManager",
|
||||
"after": "",
|
||||
},
|
||||
{
|
||||
"insert": "ZTools_RotatedLinearPattern",
|
||||
"menuItem": "ZTools_EnhancedPocket",
|
||||
"after": "",
|
||||
},
|
||||
{"append": "ZTools_DatumCreator", "menuItem": "PartDesign_Body"},
|
||||
{"append": "ZTools_DatumManager", "menuItem": "PartDesign_Body"},
|
||||
{"append": "ZTools_EnhancedPocket", "menuItem": "PartDesign_Body"},
|
||||
{"append": "ZTools_RotatedLinearPattern", "menuItem": "PartDesign_Body"},
|
||||
]
|
||||
|
||||
|
||||
Gui.addWorkbenchManipulator(_ZToolsPartDesignManipulator())
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# ztools/appearance — Appearance mode system
|
||||
from .manager import get_manager
|
||||
|
||||
__all__ = ["get_manager"]
|
||||
@@ -1,79 +0,0 @@
|
||||
# ztools/appearance/engineering.py
|
||||
# Engineering mode — colors parts by KindredCategory using palette
|
||||
|
||||
import FreeCAD as App
|
||||
|
||||
from .mode import AppearanceMode
|
||||
from .palette import load_palette
|
||||
|
||||
KINDRED_CATEGORIES = [
|
||||
"custom_body",
|
||||
"fastener",
|
||||
"structural",
|
||||
"electrical",
|
||||
"seal_gasket",
|
||||
"bearing_bushing",
|
||||
"spring_compliant",
|
||||
"moving_part",
|
||||
]
|
||||
|
||||
DEFAULT_CATEGORY = "custom_body"
|
||||
|
||||
|
||||
def _is_eligible(obj) -> bool:
|
||||
"""Check if an object should be processed by appearance modes."""
|
||||
return (
|
||||
hasattr(obj, "isDerivedFrom")
|
||||
and obj.isDerivedFrom("Part::Feature")
|
||||
and hasattr(obj, "ViewObject")
|
||||
and obj.ViewObject is not None
|
||||
and hasattr(obj.ViewObject, "ShapeColor")
|
||||
)
|
||||
|
||||
|
||||
def ensure_category_property(obj) -> None:
|
||||
"""Add KindredCategory enum property to an object if it doesn't exist."""
|
||||
if not hasattr(obj, "KindredCategory"):
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"KindredCategory",
|
||||
"Kindred",
|
||||
"Part category for engineering appearance mode",
|
||||
)
|
||||
obj.KindredCategory = KINDRED_CATEGORIES
|
||||
obj.KindredCategory = DEFAULT_CATEGORY
|
||||
|
||||
|
||||
class EngineeringMode(AppearanceMode):
|
||||
"""Colors parts by their KindredCategory using the active palette."""
|
||||
|
||||
name = "engineering"
|
||||
|
||||
def apply(self, doc) -> None:
|
||||
for obj in doc.Objects:
|
||||
if _is_eligible(obj):
|
||||
self.apply_to_object(obj)
|
||||
|
||||
def reset(self, doc) -> None:
|
||||
from . import get_manager
|
||||
|
||||
manager = get_manager()
|
||||
for obj in doc.Objects:
|
||||
if _is_eligible(obj):
|
||||
manager.restore_original(obj)
|
||||
|
||||
def apply_to_object(self, obj) -> None:
|
||||
if not _is_eligible(obj):
|
||||
return
|
||||
|
||||
from . import get_manager
|
||||
|
||||
manager = get_manager()
|
||||
manager.save_original(obj)
|
||||
|
||||
ensure_category_property(obj)
|
||||
category = obj.KindredCategory if obj.KindredCategory else DEFAULT_CATEGORY
|
||||
|
||||
palette = load_palette()
|
||||
color = palette.get(category, palette[DEFAULT_CATEGORY])
|
||||
obj.ViewObject.ShapeColor = color
|
||||
@@ -1,198 +0,0 @@
|
||||
# ztools/appearance/manager.py
|
||||
# Appearance mode manager and document observer
|
||||
|
||||
import FreeCAD as App
|
||||
|
||||
from .engineering import EngineeringMode, _is_eligible
|
||||
from .realistic import RealisticMode
|
||||
|
||||
_PREF_GROUP = "User parameter:BaseApp/Preferences/Mod/Kindred"
|
||||
_PREF_KEY = "AppearanceMode"
|
||||
|
||||
_MODES = {
|
||||
"realistic": RealisticMode,
|
||||
"engineering": EngineeringMode,
|
||||
}
|
||||
|
||||
_RECOLOR_DEBOUNCE_MS = 100
|
||||
|
||||
|
||||
class _AppearanceObserver:
|
||||
"""Document observer that auto-applies appearance mode to new/changed objects."""
|
||||
|
||||
def __init__(self, manager: "AppearanceManager"):
|
||||
self._manager = manager
|
||||
self._pending_recolor: set = set() # object names awaiting recolor
|
||||
self._debounce_scheduled = False
|
||||
|
||||
def slotCreatedObject(self, obj) -> None:
|
||||
if self._manager.active_mode.name != "engineering":
|
||||
return
|
||||
if not (hasattr(obj, "isDerivedFrom") and obj.isDerivedFrom("Part::Feature")):
|
||||
return
|
||||
# Defer to allow ViewObject to be fully initialized
|
||||
try:
|
||||
from PySide.QtCore import QTimer
|
||||
|
||||
QTimer.singleShot(0, lambda o=obj: self._apply_deferred(o))
|
||||
except ImportError:
|
||||
self._apply_deferred(obj)
|
||||
|
||||
def slotChangedObject(self, obj, prop: str) -> None:
|
||||
if self._manager.active_mode.name != "engineering":
|
||||
return
|
||||
if not _is_eligible(obj):
|
||||
return
|
||||
|
||||
if prop == "KindredCategory":
|
||||
# Immediate recolor on category change
|
||||
self._manager.active_mode.apply_to_object(obj)
|
||||
elif prop == "Shape":
|
||||
# Shape changed (recompute) — debounce to batch multiple updates
|
||||
self._pending_recolor.add((obj.Document.Name, obj.Name))
|
||||
self._schedule_flush()
|
||||
|
||||
def _schedule_flush(self) -> None:
|
||||
if self._debounce_scheduled:
|
||||
return
|
||||
self._debounce_scheduled = True
|
||||
try:
|
||||
from PySide.QtCore import QTimer
|
||||
|
||||
QTimer.singleShot(_RECOLOR_DEBOUNCE_MS, self._flush_recolor)
|
||||
except ImportError:
|
||||
self._flush_recolor()
|
||||
|
||||
def _flush_recolor(self) -> None:
|
||||
self._debounce_scheduled = False
|
||||
pending = list(self._pending_recolor)
|
||||
self._pending_recolor.clear()
|
||||
for doc_name, obj_name in pending:
|
||||
doc = App.getDocument(doc_name)
|
||||
if doc is None:
|
||||
continue
|
||||
obj = doc.getObject(obj_name)
|
||||
if obj is not None and _is_eligible(obj):
|
||||
self._manager.active_mode.apply_to_object(obj)
|
||||
|
||||
def _apply_deferred(self, obj) -> None:
|
||||
if _is_eligible(obj):
|
||||
self._manager.active_mode.apply_to_object(obj)
|
||||
|
||||
|
||||
class AppearanceManager:
|
||||
"""Singleton manager coordinating appearance mode switching."""
|
||||
|
||||
def __init__(self):
|
||||
self._active_mode = RealisticMode()
|
||||
self._originals: dict = {} # {doc_name: {obj_name: (ShapeColor, Transparency)}}
|
||||
self._observer = None
|
||||
|
||||
@property
|
||||
def active_mode(self):
|
||||
return self._active_mode
|
||||
|
||||
def set_mode(self, mode_name: str) -> None:
|
||||
"""Switch appearance mode, applying to all open documents."""
|
||||
if mode_name == self._active_mode.name:
|
||||
return
|
||||
|
||||
mode_cls = _MODES.get(mode_name)
|
||||
if mode_cls is None:
|
||||
App.Console.error(f"ztools: unknown appearance mode '{mode_name}'\n")
|
||||
return
|
||||
|
||||
# Reset current mode, then apply new one
|
||||
for doc in App.listDocuments().values():
|
||||
self._active_mode.reset(doc)
|
||||
|
||||
self._active_mode = mode_cls()
|
||||
|
||||
for doc in App.listDocuments().values():
|
||||
self._active_mode.apply(doc)
|
||||
|
||||
# Persist preference
|
||||
App.ParamGet(_PREF_GROUP).SetString(_PREF_KEY, mode_name)
|
||||
|
||||
# Update status bar
|
||||
try:
|
||||
from .ui import show_status_bar
|
||||
|
||||
show_status_bar(mode_name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
App.Console.log(f"ztools: appearance mode set to '{mode_name}'\n")
|
||||
|
||||
def save_original(self, obj) -> None:
|
||||
"""Snapshot ShapeColor and Transparency (save-once semantics)."""
|
||||
doc_name = obj.Document.Name
|
||||
obj_name = obj.Name
|
||||
doc_cache = self._originals.setdefault(doc_name, {})
|
||||
if obj_name in doc_cache:
|
||||
return # Already saved
|
||||
vo = obj.ViewObject
|
||||
doc_cache[obj_name] = (
|
||||
tuple(vo.ShapeColor),
|
||||
vo.Transparency if hasattr(vo, "Transparency") else 0,
|
||||
)
|
||||
|
||||
def restore_original(self, obj) -> None:
|
||||
"""Restore saved ShapeColor and Transparency, removing cache entry."""
|
||||
doc_name = obj.Document.Name
|
||||
obj_name = obj.Name
|
||||
doc_cache = self._originals.get(doc_name, {})
|
||||
saved = doc_cache.pop(obj_name, None)
|
||||
if saved is None:
|
||||
return
|
||||
shape_color, transparency = saved
|
||||
vo = obj.ViewObject
|
||||
vo.ShapeColor = shape_color
|
||||
if hasattr(vo, "Transparency"):
|
||||
vo.Transparency = transparency
|
||||
|
||||
def activate(self) -> None:
|
||||
"""Start observer and apply persisted mode. Called from Workbench.Activated()."""
|
||||
if self._observer is None:
|
||||
self._observer = _AppearanceObserver(self)
|
||||
App.addDocumentObserver(self._observer)
|
||||
|
||||
# Apply persisted mode
|
||||
mode_name = App.ParamGet(_PREF_GROUP).GetString(_PREF_KEY, "realistic")
|
||||
if mode_name != self._active_mode.name:
|
||||
self._active_mode = _MODES.get(mode_name, RealisticMode)()
|
||||
for doc in App.listDocuments().values():
|
||||
self._active_mode.apply(doc)
|
||||
|
||||
# Show status bar
|
||||
try:
|
||||
from .ui import show_status_bar
|
||||
|
||||
show_status_bar(self._active_mode.name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def deactivate(self) -> None:
|
||||
"""Stop observer and remove status bar. Called from Workbench.Deactivated()."""
|
||||
if self._observer is not None:
|
||||
App.removeDocumentObserver(self._observer)
|
||||
self._observer = None
|
||||
|
||||
try:
|
||||
from .ui import hide_status_bar
|
||||
|
||||
hide_status_bar()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# Module-level singleton
|
||||
_manager = None
|
||||
|
||||
|
||||
def get_manager() -> AppearanceManager:
|
||||
"""Get the global AppearanceManager singleton."""
|
||||
global _manager
|
||||
if _manager is None:
|
||||
_manager = AppearanceManager()
|
||||
return _manager
|
||||
@@ -1,22 +0,0 @@
|
||||
# ztools/appearance/mode.py
|
||||
# Abstract base class for appearance modes
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class AppearanceMode(ABC):
|
||||
"""Base class for viewport appearance modes."""
|
||||
|
||||
name: str = ""
|
||||
|
||||
@abstractmethod
|
||||
def apply(self, doc) -> None:
|
||||
"""Apply this mode to all eligible objects in the document."""
|
||||
|
||||
@abstractmethod
|
||||
def reset(self, doc) -> None:
|
||||
"""Undo this mode's visual changes on all objects in the document."""
|
||||
|
||||
@abstractmethod
|
||||
def apply_to_object(self, obj) -> None:
|
||||
"""Apply this mode to a single object."""
|
||||
@@ -1,36 +0,0 @@
|
||||
# ztools/appearance/palette.py
|
||||
# Palette loading for appearance modes
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Tuple
|
||||
|
||||
_PALETTES_DIR = os.path.join(os.path.dirname(__file__), "palettes")
|
||||
_cache: Dict[str, Dict[str, Tuple[float, float, float]]] = {}
|
||||
|
||||
|
||||
def hex_to_rgb(hex_str: str) -> Tuple[float, float, float]:
|
||||
"""Convert hex color string to RGB tuple with 0.0-1.0 floats."""
|
||||
h = hex_str.lstrip("#")
|
||||
return (int(h[0:2], 16) / 255.0, int(h[2:4], 16) / 255.0, int(h[4:6], 16) / 255.0)
|
||||
|
||||
|
||||
def load_palette(
|
||||
name: str = "catppuccin_mocha",
|
||||
) -> Dict[str, Tuple[float, float, float]]:
|
||||
"""Load a palette by name, returning {category: (r, g, b)} with 0.0-1.0 floats.
|
||||
|
||||
Palettes are JSON files in the palettes/ directory. Results are cached.
|
||||
"""
|
||||
if name in _cache:
|
||||
return _cache[name]
|
||||
|
||||
path = os.path.join(_PALETTES_DIR, f"{name}.json")
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
palette = {
|
||||
cat: hex_to_rgb(hex_color) for cat, hex_color in data["categories"].items()
|
||||
}
|
||||
_cache[name] = palette
|
||||
return palette
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "Catppuccin Mocha",
|
||||
"categories": {
|
||||
"custom_body": "#fab387",
|
||||
"fastener": "#a6e3a1",
|
||||
"structural": "#89b4fa",
|
||||
"electrical": "#f9e2af",
|
||||
"seal_gasket": "#cba6f7",
|
||||
"bearing_bushing": "#74c7ec",
|
||||
"spring_compliant": "#f2cdcd",
|
||||
"moving_part": "#f38ba8"
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
# ztools/appearance/realistic.py
|
||||
# Realistic mode — restores original material appearances
|
||||
|
||||
from .mode import AppearanceMode
|
||||
|
||||
|
||||
def _is_eligible(obj) -> bool:
|
||||
"""Check if an object should be processed by appearance modes."""
|
||||
return (
|
||||
hasattr(obj, "isDerivedFrom")
|
||||
and obj.isDerivedFrom("Part::Feature")
|
||||
and hasattr(obj, "ViewObject")
|
||||
and obj.ViewObject is not None
|
||||
and hasattr(obj.ViewObject, "ShapeColor")
|
||||
)
|
||||
|
||||
|
||||
class RealisticMode(AppearanceMode):
|
||||
"""Material passthrough — restores original ShapeColor and Transparency."""
|
||||
|
||||
name = "realistic"
|
||||
|
||||
def apply(self, doc) -> None:
|
||||
from . import get_manager
|
||||
|
||||
manager = get_manager()
|
||||
for obj in doc.Objects:
|
||||
if _is_eligible(obj):
|
||||
manager.restore_original(obj)
|
||||
|
||||
def reset(self, doc) -> None:
|
||||
pass # Nothing to undo — realistic is the baseline
|
||||
|
||||
def apply_to_object(self, obj) -> None:
|
||||
pass # No-op — material passthrough
|
||||
@@ -1,72 +0,0 @@
|
||||
# ztools/appearance/ui.py
|
||||
# Status bar widget for appearance mode indicator
|
||||
|
||||
import FreeCADGui as Gui
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
_MODE_LABELS = {
|
||||
"realistic": "Realistic",
|
||||
"engineering": "Engineering",
|
||||
}
|
||||
|
||||
_MODE_COLORS = {
|
||||
"realistic": "#7f849c", # overlay1
|
||||
"engineering": "#fab387", # peach
|
||||
}
|
||||
|
||||
|
||||
class AppearanceStatusBar(QtGui.QLabel):
|
||||
"""Status bar label showing the current appearance mode. Clickable to toggle."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setCursor(QtCore.Qt.PointingHandCursor)
|
||||
self.setToolTip("Click to toggle appearance mode")
|
||||
self.setStyleSheet("padding: 2px 6px;")
|
||||
self._current_mode = "realistic"
|
||||
self._update_display()
|
||||
|
||||
def set_mode(self, mode_name: str) -> None:
|
||||
self._current_mode = mode_name
|
||||
self._update_display()
|
||||
|
||||
def _update_display(self):
|
||||
label = _MODE_LABELS.get(self._current_mode, self._current_mode)
|
||||
color = _MODE_COLORS.get(self._current_mode, "#cdd6f4")
|
||||
self.setText(f"<span style='color:{color};'>●</span> {label}")
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
from . import get_manager
|
||||
|
||||
manager = get_manager()
|
||||
next_mode = "engineering" if self._current_mode == "realistic" else "realistic"
|
||||
manager.set_mode(next_mode)
|
||||
super().mousePressEvent(event)
|
||||
|
||||
|
||||
_widget = None
|
||||
|
||||
|
||||
def show_status_bar(mode_name: str) -> None:
|
||||
"""Add or update the appearance mode status bar widget."""
|
||||
global _widget
|
||||
mw = Gui.getMainWindow()
|
||||
if mw is None:
|
||||
return
|
||||
status_bar = mw.statusBar()
|
||||
if _widget is None:
|
||||
_widget = AppearanceStatusBar(status_bar)
|
||||
status_bar.addPermanentWidget(_widget)
|
||||
_widget.set_mode(mode_name)
|
||||
_widget.show()
|
||||
|
||||
|
||||
def hide_status_bar() -> None:
|
||||
"""Remove the appearance mode status bar widget."""
|
||||
global _widget
|
||||
if _widget is not None:
|
||||
mw = Gui.getMainWindow()
|
||||
if mw is not None:
|
||||
mw.statusBar().removeWidget(_widget)
|
||||
_widget.deleteLater()
|
||||
_widget = None
|
||||
@@ -1,6 +1,5 @@
|
||||
# ztools/commands - GUI commands
|
||||
from . import (
|
||||
appearance_commands,
|
||||
assembly_pattern_commands,
|
||||
datum_commands,
|
||||
datum_viewprovider,
|
||||
@@ -10,7 +9,6 @@ from . import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"appearance_commands",
|
||||
"datum_commands",
|
||||
"datum_viewprovider",
|
||||
"pattern_commands",
|
||||
|
||||
Binary file not shown.
@@ -1,127 +0,0 @@
|
||||
# ztools/commands/appearance_commands.py
|
||||
# Commands for switching appearance modes and setting part categories
|
||||
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
|
||||
from ztools.appearance import get_manager
|
||||
from ztools.appearance.engineering import KINDRED_CATEGORIES, ensure_category_property
|
||||
from ztools.resources.icons import get_icon
|
||||
|
||||
|
||||
class ZTools_AppearanceRealistic:
|
||||
"""Switch to Realistic appearance mode (material passthrough)."""
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": get_icon("appearance_realistic"),
|
||||
"MenuText": "Realistic",
|
||||
"ToolTip": "Show parts with their original material appearance",
|
||||
}
|
||||
|
||||
def Activated(self):
|
||||
get_manager().set_mode("realistic")
|
||||
|
||||
def IsActive(self):
|
||||
return App.ActiveDocument is not None
|
||||
|
||||
|
||||
class ZTools_AppearanceEngineering:
|
||||
"""Switch to Engineering appearance mode (category-based coloring)."""
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": get_icon("appearance_engineering"),
|
||||
"MenuText": "Engineering",
|
||||
"ToolTip": "Color parts by KindredCategory using engineering palette",
|
||||
}
|
||||
|
||||
def Activated(self):
|
||||
get_manager().set_mode("engineering")
|
||||
|
||||
def IsActive(self):
|
||||
return App.ActiveDocument is not None
|
||||
|
||||
|
||||
class ZTools_AppearanceMode:
|
||||
"""Grouped dropdown for appearance mode switching."""
|
||||
|
||||
def GetCommands(self):
|
||||
return ("ZTools_AppearanceRealistic", "ZTools_AppearanceEngineering")
|
||||
|
||||
def GetDefaultCommand(self):
|
||||
return 0
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": get_icon("appearance_engineering"),
|
||||
"MenuText": "Appearance Mode",
|
||||
"ToolTip": "Switch viewport appearance mode",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return App.ActiveDocument is not None
|
||||
|
||||
|
||||
class ZTools_SetCategory:
|
||||
"""Set KindredCategory on selected parts via popup menu."""
|
||||
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": get_icon("set_category"),
|
||||
"MenuText": "Set Category",
|
||||
"ToolTip": "Set KindredCategory on selected parts",
|
||||
}
|
||||
|
||||
def Activated(self):
|
||||
from PySide import QtGui
|
||||
|
||||
sel = Gui.Selection.getSelection()
|
||||
eligible = [
|
||||
obj
|
||||
for obj in sel
|
||||
if hasattr(obj, "isDerivedFrom") and obj.isDerivedFrom("Part::Feature")
|
||||
]
|
||||
if not eligible:
|
||||
return
|
||||
|
||||
# Build popup menu with category choices
|
||||
menu = QtGui.QMenu()
|
||||
labels = {
|
||||
"custom_body": "Custom Body",
|
||||
"fastener": "Fastener",
|
||||
"structural": "Structural",
|
||||
"electrical": "Electrical",
|
||||
"seal_gasket": "Seal/Gasket",
|
||||
"bearing_bushing": "Bearing/Bushing",
|
||||
"spring_compliant": "Spring/Compliant",
|
||||
"moving_part": "Moving Part",
|
||||
}
|
||||
for cat in KINDRED_CATEGORIES:
|
||||
action = menu.addAction(labels.get(cat, cat))
|
||||
action.setData(cat)
|
||||
|
||||
chosen = menu.exec_(QtGui.QCursor.pos())
|
||||
if chosen is None:
|
||||
return
|
||||
|
||||
category = chosen.data()
|
||||
doc = eligible[0].Document
|
||||
doc.openTransaction("Set KindredCategory")
|
||||
try:
|
||||
for obj in eligible:
|
||||
ensure_category_property(obj)
|
||||
obj.KindredCategory = category
|
||||
doc.commitTransaction()
|
||||
except Exception:
|
||||
doc.abortTransaction()
|
||||
raise
|
||||
|
||||
def IsActive(self):
|
||||
return App.ActiveDocument is not None and bool(Gui.Selection.getSelection())
|
||||
|
||||
|
||||
Gui.addCommand("ZTools_AppearanceRealistic", ZTools_AppearanceRealistic())
|
||||
Gui.addCommand("ZTools_AppearanceEngineering", ZTools_AppearanceEngineering())
|
||||
Gui.addCommand("ZTools_AppearanceMode", ZTools_AppearanceMode())
|
||||
Gui.addCommand("ZTools_SetCategory", ZTools_SetCategory())
|
||||
@@ -2,6 +2,7 @@
|
||||
# Custom ViewProvider for ZTools datum objects
|
||||
|
||||
import json
|
||||
import math
|
||||
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
@@ -141,6 +142,23 @@ class ZToolsDatumViewProvider:
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_source_refs(datum_obj):
|
||||
"""Parse ZTools_SourceRefs and resolve to (object, subname, shape) tuples."""
|
||||
refs_json = getattr(datum_obj, "ZTools_SourceRefs", "[]")
|
||||
try:
|
||||
refs = json.loads(refs_json)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
doc = datum_obj.Document
|
||||
resolved = []
|
||||
for ref in refs:
|
||||
obj = doc.getObject(ref.get("object", ""))
|
||||
sub = ref.get("subname", "")
|
||||
shape = obj.getSubObject(sub) if obj and sub else None
|
||||
resolved.append((obj, sub, shape))
|
||||
return resolved
|
||||
|
||||
|
||||
class DatumEditTaskPanel:
|
||||
"""
|
||||
Task panel for editing existing ZTools datum objects.
|
||||
@@ -364,8 +382,57 @@ class DatumEditTaskPanel:
|
||||
self.datum_obj.AttachmentOffset = new_offset
|
||||
self._update_params({"distance": distance})
|
||||
|
||||
elif ztools_type in ("angled", "tangent_cylinder"):
|
||||
self._update_params({"angle": self.angle_spin.value()})
|
||||
elif ztools_type == "angled":
|
||||
angle = self.angle_spin.value()
|
||||
if self._has_attachment():
|
||||
refs = _resolve_source_refs(self.datum_obj)
|
||||
if len(refs) >= 2 and refs[0][2] and refs[1][2]:
|
||||
face_normal = refs[0][2].normalAt(0, 0)
|
||||
edge_shape = refs[1][2]
|
||||
edge_dir = (
|
||||
edge_shape.Vertexes[-1].Point - edge_shape.Vertexes[0].Point
|
||||
).normalize()
|
||||
face_rot = App.Rotation(App.Vector(0, 0, 1), face_normal)
|
||||
local_edge_dir = face_rot.inverted().multVec(edge_dir)
|
||||
angle_rot = App.Rotation(local_edge_dir, angle)
|
||||
self.datum_obj.AttachmentOffset = App.Placement(
|
||||
App.Vector(0, 0, 0), angle_rot
|
||||
)
|
||||
self._update_params({"angle": angle})
|
||||
|
||||
elif ztools_type == "tangent_cylinder":
|
||||
angle = self.angle_spin.value()
|
||||
if self._has_attachment():
|
||||
params_json = getattr(self.datum_obj, "ZTools_Params", "{}")
|
||||
try:
|
||||
params = json.loads(params_json)
|
||||
except json.JSONDecodeError:
|
||||
params = {}
|
||||
vertex_angle = params.get("vertex_angle", 0.0)
|
||||
offset_rot = App.Rotation(App.Vector(0, 0, 1), angle - vertex_angle)
|
||||
self.datum_obj.AttachmentOffset = App.Placement(
|
||||
App.Vector(0, 0, 0), offset_rot
|
||||
)
|
||||
else:
|
||||
refs = _resolve_source_refs(self.datum_obj)
|
||||
if refs and refs[0][2]:
|
||||
face = refs[0][2]
|
||||
if isinstance(face.Surface, Part.Cylinder):
|
||||
cyl = face.Surface
|
||||
axis = cyl.Axis
|
||||
center = cyl.Center
|
||||
radius = cyl.Radius
|
||||
rad = math.radians(angle)
|
||||
if abs(axis.dot(App.Vector(1, 0, 0))) < 0.99:
|
||||
local_x = axis.cross(App.Vector(1, 0, 0)).normalize()
|
||||
else:
|
||||
local_x = axis.cross(App.Vector(0, 1, 0)).normalize()
|
||||
local_y = axis.cross(local_x)
|
||||
radial = local_x * math.cos(rad) + local_y * math.sin(rad)
|
||||
tangent_point = center + radial * radius
|
||||
rot = App.Rotation(App.Vector(0, 0, 1), radial)
|
||||
self.datum_obj.Placement = App.Placement(tangent_point, rot)
|
||||
self._update_params({"angle": angle})
|
||||
|
||||
elif ztools_type in ("normal_to_edge", "on_edge"):
|
||||
parameter = self.param_spin.value()
|
||||
|
||||
@@ -903,6 +903,41 @@ def plane_angled(
|
||||
return plane
|
||||
|
||||
|
||||
def _find_cylinder_vertex(obj, face_subname):
|
||||
"""Find a vertex subname from a cylindrical face's edges."""
|
||||
face = obj.getSubObject(face_subname)
|
||||
if not face or not face.Edges:
|
||||
return None
|
||||
edge = face.Edges[0]
|
||||
if not edge.Vertexes:
|
||||
return None
|
||||
vertex_point = edge.Vertexes[0].Point
|
||||
for i, v in enumerate(obj.Shape.Vertexes, 1):
|
||||
if v.Point.isEqual(vertex_point, 1e-6):
|
||||
return f"Vertex{i}"
|
||||
return None
|
||||
|
||||
|
||||
def _vertex_angle_on_cylinder(obj, vertex_sub, cylinder):
|
||||
"""Compute the angular position of a vertex on a cylinder surface."""
|
||||
vertex = obj.getSubObject(vertex_sub)
|
||||
if not vertex:
|
||||
return 0.0
|
||||
point = vertex.Point
|
||||
relative = point - cylinder.Center
|
||||
axis = cylinder.Axis
|
||||
radial = relative - axis * relative.dot(axis)
|
||||
if radial.Length < 1e-10:
|
||||
return 0.0
|
||||
radial.normalize()
|
||||
if abs(axis.dot(App.Vector(1, 0, 0))) < 0.99:
|
||||
local_x = axis.cross(App.Vector(1, 0, 0)).normalize()
|
||||
else:
|
||||
local_x = axis.cross(App.Vector(0, 1, 0)).normalize()
|
||||
local_y = axis.cross(local_x)
|
||||
return math.degrees(math.atan2(radial.dot(local_y), radial.dot(local_x)))
|
||||
|
||||
|
||||
def plane_tangent_to_cylinder(
|
||||
face: Part.Face,
|
||||
angle: float = 0,
|
||||
@@ -966,16 +1001,41 @@ def plane_tangent_to_cylinder(
|
||||
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
# TangentPlane mode needs (face, vertex). Without a vertex reference,
|
||||
# fall back to manual placement.
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"tangent_cylinder",
|
||||
{"angle": angle, "radius": radius},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
# TangentPlane MapMode needs (face, vertex). Derive a vertex from
|
||||
# the cylinder face's edges and encode the angular offset.
|
||||
vertex_sub = (
|
||||
_find_cylinder_vertex(source_object, source_subname)
|
||||
if source_object and source_subname
|
||||
else None
|
||||
)
|
||||
if vertex_sub:
|
||||
vertex_angle = _vertex_angle_on_cylinder(source_object, vertex_sub, cyl)
|
||||
offset_angle = angle - vertex_angle
|
||||
offset_rot = App.Rotation(App.Vector(0, 0, 1), offset_angle)
|
||||
att_offset = App.Placement(App.Vector(0, 0, 0), offset_rot)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"tangent_cylinder",
|
||||
{"angle": angle, "radius": radius, "vertex_angle": vertex_angle},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
map_mode="TangentPlane",
|
||||
support=[
|
||||
(source_object, source_subname),
|
||||
(source_object, vertex_sub),
|
||||
],
|
||||
offset=att_offset,
|
||||
)
|
||||
else:
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"tangent_cylinder",
|
||||
{"angle": angle, "radius": radius},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# ztools/resources - Icons and assets
|
||||
from .icons import MOCHA, get_icon, save_icons_to_disk
|
||||
from .theme import get_stylesheet
|
||||
|
||||
__all__ = [
|
||||
"get_icon",
|
||||
"save_icons_to_disk",
|
||||
"MOCHA",
|
||||
"get_stylesheet",
|
||||
]
|
||||
|
||||
@@ -1,35 +1,10 @@
|
||||
# ztools/resources/icons.py
|
||||
# Catppuccin Mocha themed icons for ztools
|
||||
|
||||
# Catppuccin Mocha Palette
|
||||
MOCHA = {
|
||||
"rosewater": "#f5e0dc",
|
||||
"flamingo": "#f2cdcd",
|
||||
"pink": "#f5c2e7",
|
||||
"mauve": "#cba6f7",
|
||||
"red": "#f38ba8",
|
||||
"maroon": "#eba0ac",
|
||||
"peach": "#fab387",
|
||||
"yellow": "#f9e2af",
|
||||
"green": "#a6e3a1",
|
||||
"teal": "#94e2d5",
|
||||
"sky": "#89dceb",
|
||||
"sapphire": "#74c7ec",
|
||||
"blue": "#89b4fa",
|
||||
"lavender": "#b4befe",
|
||||
"text": "#cdd6f4",
|
||||
"subtext1": "#bac2de",
|
||||
"subtext0": "#a6adc8",
|
||||
"overlay2": "#9399b2",
|
||||
"overlay1": "#7f849c",
|
||||
"overlay0": "#6c7086",
|
||||
"surface2": "#585b70",
|
||||
"surface1": "#45475a",
|
||||
"surface0": "#313244",
|
||||
"base": "#1e1e2e",
|
||||
"mantle": "#181825",
|
||||
"crust": "#11111b",
|
||||
}
|
||||
# Catppuccin Mocha Palette — sourced from kindred-addon-sdk
|
||||
from kindred_sdk.theme import get_theme_tokens
|
||||
|
||||
MOCHA = get_theme_tokens()
|
||||
|
||||
|
||||
def _svg_to_base64(svg_content: str) -> str:
|
||||
@@ -425,50 +400,6 @@ ICON_SPREADSHEET_QUICK_ALIAS_SVG = f'''<svg xmlns="http://www.w3.org/2000/svg" v
|
||||
</svg>'''
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Appearance Mode Icons
|
||||
# =============================================================================
|
||||
|
||||
# Realistic mode icon - eye with material sphere
|
||||
ICON_APPEARANCE_REALISTIC_SVG = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="{MOCHA["surface0"]}"/>
|
||||
<!-- Eye shape -->
|
||||
<path d="M4 16 Q16 6 28 16 Q16 26 4 16 Z" fill="none" stroke="{MOCHA["text"]}" stroke-width="1.5"/>
|
||||
<!-- Material sphere with gradient-like shading -->
|
||||
<circle cx="16" cy="16" r="5" fill="{MOCHA["overlay1"]}"/>
|
||||
<circle cx="14" cy="14" r="2" fill="{MOCHA["overlay2"]}" opacity="0.6"/>
|
||||
</svg>'''
|
||||
|
||||
# Engineering mode icon - eye with colored segments
|
||||
ICON_APPEARANCE_ENGINEERING_SVG = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="{MOCHA["surface0"]}"/>
|
||||
<!-- Eye shape -->
|
||||
<path d="M4 16 Q16 6 28 16 Q16 26 4 16 Z" fill="none" stroke="{MOCHA["text"]}" stroke-width="1.5"/>
|
||||
<!-- Segmented iris showing category colors -->
|
||||
<path d="M16 11 L19 14 L19 18 L16 21 L13 18 L13 14 Z" fill="{MOCHA["peach"]}"/>
|
||||
<path d="M16 11 L19 14 L16 16 Z" fill="{MOCHA["green"]}"/>
|
||||
<path d="M19 14 L19 18 L16 16 Z" fill="{MOCHA["blue"]}"/>
|
||||
<path d="M19 18 L16 21 L16 16 Z" fill="{MOCHA["yellow"]}"/>
|
||||
<path d="M16 21 L13 18 L16 16 Z" fill="{MOCHA["mauve"]}"/>
|
||||
<path d="M13 18 L13 14 L16 16 Z" fill="{MOCHA["sapphire"]}"/>
|
||||
<path d="M13 14 L16 11 L16 16 Z" fill="{MOCHA["red"]}"/>
|
||||
<!-- Pupil -->
|
||||
<circle cx="16" cy="16" r="2" fill="{MOCHA["crust"]}"/>
|
||||
</svg>'''
|
||||
|
||||
# Set Category icon - tag with color swatches
|
||||
ICON_SET_CATEGORY_SVG = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="{MOCHA["surface0"]}"/>
|
||||
<!-- Tag shape -->
|
||||
<path d="M5 8 L18 8 L25 16 L18 24 L5 24 Z" fill="{MOCHA["surface1"]}" stroke="{MOCHA["overlay1"]}" stroke-width="1.5"/>
|
||||
<circle cx="10" cy="16" r="2" fill="{MOCHA["surface0"]}"/>
|
||||
<!-- Color dots representing categories -->
|
||||
<circle cx="22" cy="8" r="3" fill="{MOCHA["peach"]}"/>
|
||||
<circle cx="28" cy="8" r="3" fill="{MOCHA["green"]}"/>
|
||||
<circle cx="22" cy="14" r="3" fill="{MOCHA["blue"]}"/>
|
||||
<circle cx="28" cy="14" r="3" fill="{MOCHA["yellow"]}"/>
|
||||
</svg>'''
|
||||
|
||||
# =============================================================================
|
||||
# Icon Registry - Base64 encoded for FreeCAD
|
||||
# =============================================================================
|
||||
@@ -516,9 +447,6 @@ def get_icon(name: str) -> str:
|
||||
"spreadsheet_bg_color": ICON_SPREADSHEET_BG_COLOR_SVG,
|
||||
"spreadsheet_text_color": ICON_SPREADSHEET_TEXT_COLOR_SVG,
|
||||
"spreadsheet_quick_alias": ICON_SPREADSHEET_QUICK_ALIAS_SVG,
|
||||
"appearance_realistic": ICON_APPEARANCE_REALISTIC_SVG,
|
||||
"appearance_engineering": ICON_APPEARANCE_ENGINEERING_SVG,
|
||||
"set_category": ICON_SET_CATEGORY_SVG,
|
||||
}
|
||||
|
||||
if name not in icons:
|
||||
@@ -576,9 +504,6 @@ def save_icons_to_disk(directory: str):
|
||||
"ztools_spreadsheet_bg_color": ICON_SPREADSHEET_BG_COLOR_SVG,
|
||||
"ztools_spreadsheet_text_color": ICON_SPREADSHEET_TEXT_COLOR_SVG,
|
||||
"ztools_spreadsheet_quick_alias": ICON_SPREADSHEET_QUICK_ALIAS_SVG,
|
||||
"ztools_appearance_realistic": ICON_APPEARANCE_REALISTIC_SVG,
|
||||
"ztools_appearance_engineering": ICON_APPEARANCE_ENGINEERING_SVG,
|
||||
"ztools_set_category": ICON_SET_CATEGORY_SVG,
|
||||
}
|
||||
|
||||
for name, svg in icons.items():
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user