Compare commits
3 Commits
fix/menu-i
...
feat/editi
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bb2949b82 | |||
|
|
2bf969c62a | ||
|
|
04f9df75cb |
@@ -3,7 +3,7 @@
|
||||
|
||||
<name>ZTools</name>
|
||||
|
||||
<description>Extended PartDesign workbench with velocity-focused tools, advanced datum creation, and Catppuccin Mocha theme.</description>
|
||||
<description>Velocity-focused CAD tools injected into PartDesign, Assembly, and Spreadsheet contexts, plus Catppuccin Mocha theme.</description>
|
||||
|
||||
<version>0.1.0</version>
|
||||
|
||||
|
||||
@@ -1,356 +1,109 @@
|
||||
# ztools Workbench for FreeCAD 1.0+
|
||||
# Extended PartDesign replacement with velocity-focused tools
|
||||
# ztools Command Provider for Kindred Create
|
||||
# Injects ZTools commands into context toolbars via EditingContextResolver.
|
||||
# No longer a standalone workbench — commands appear in the appropriate
|
||||
# editing context (PartDesign body/feature, Assembly, Spreadsheet).
|
||||
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
|
||||
|
||||
class ZToolsWorkbench(Gui.Workbench):
|
||||
"""Extended PartDesign workbench with velocity-focused tools."""
|
||||
def _ensure_workbenches_loaded():
|
||||
"""Activate dependent workbenches so their commands are registered."""
|
||||
wb_list = Gui.listWorkbenches()
|
||||
for wb_name in (
|
||||
"PartDesignWorkbench",
|
||||
"SketcherWorkbench",
|
||||
"AssemblyWorkbench",
|
||||
"SpreadsheetWorkbench",
|
||||
):
|
||||
if wb_name in wb_list:
|
||||
try:
|
||||
Gui.activateWorkbench(wb_name)
|
||||
except Exception as e:
|
||||
App.Console.PrintWarning(f"ztools: could not init {wb_name}: {e}\n")
|
||||
|
||||
MenuText = "ztools"
|
||||
ToolTip = "Extended PartDesign replacement for faster CAD workflows"
|
||||
|
||||
# Catppuccin Mocha themed icon
|
||||
Icon = """
|
||||
/* XPM */
|
||||
static char * ztools_xpm[] = {
|
||||
"16 16 5 1",
|
||||
" c None",
|
||||
". c #313244",
|
||||
"+ c #cba6f7",
|
||||
"@ c #94e2d5",
|
||||
"# c #45475a",
|
||||
" ",
|
||||
" ############ ",
|
||||
" #..........# ",
|
||||
" #.++++++++.# ",
|
||||
" #.+......+.# ",
|
||||
" #.....+++..# ",
|
||||
" #....++....# ",
|
||||
" #...++.....# ",
|
||||
" #..++......# ",
|
||||
" #.++.......# ",
|
||||
" #.++++++++@# ",
|
||||
" #..........# ",
|
||||
" ############ ",
|
||||
" ",
|
||||
" ",
|
||||
" "};
|
||||
"""
|
||||
def _register():
|
||||
"""Import ZTools commands and inject them into context toolbars."""
|
||||
_ensure_workbenches_loaded()
|
||||
|
||||
def Initialize(self):
|
||||
"""Called on workbench first activation."""
|
||||
# Load PartDesign and Sketcher workbenches to register their commands
|
||||
# We need to actually activate them briefly to ensure commands are registered
|
||||
# Activate dependent workbenches so their commands are registered.
|
||||
# Use activateWorkbench() instead of calling Initialize() directly,
|
||||
# since direct calls skip the C++ __Workbench__ injection step.
|
||||
# Wrap each individually so one failure doesn't block the rest.
|
||||
wb_list = Gui.listWorkbenches()
|
||||
current_wb = Gui.activeWorkbench()
|
||||
for wb_name in (
|
||||
"PartDesignWorkbench",
|
||||
"SketcherWorkbench",
|
||||
"AssemblyWorkbench",
|
||||
"SpreadsheetWorkbench",
|
||||
):
|
||||
if wb_name in wb_list:
|
||||
try:
|
||||
Gui.activateWorkbench(wb_name)
|
||||
except Exception as e:
|
||||
App.Console.PrintWarning(f"Could not initialize {wb_name}: {e}\n")
|
||||
# Restore ztools as the active workbench
|
||||
try:
|
||||
Gui.activateWorkbench("ZToolsWorkbench")
|
||||
except Exception:
|
||||
pass
|
||||
# Import all ZTools command modules (registers Gui commands)
|
||||
from ztools.commands import ( # noqa: F401
|
||||
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.
|
||||
# Apply Catppuccin Mocha Spreadsheet colors
|
||||
try:
|
||||
from ztools.resources.theme import apply_spreadsheet_colors
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Structure Tools
|
||||
# =====================================================================
|
||||
self.structure_tools = [
|
||||
"PartDesign_Body",
|
||||
"PartDesign_NewSketch",
|
||||
]
|
||||
apply_spreadsheet_colors()
|
||||
except Exception as e:
|
||||
App.Console.PrintWarning(f"ztools: could not apply spreadsheet colors: {e}\n")
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Reference Geometry (Datums)
|
||||
# =====================================================================
|
||||
self.partdesign_datum_tools = [
|
||||
"PartDesign_Plane",
|
||||
"PartDesign_Line",
|
||||
"PartDesign_Point",
|
||||
"PartDesign_CoordinateSystem",
|
||||
"PartDesign_ShapeBinder",
|
||||
"PartDesign_SubShapeBinder",
|
||||
"PartDesign_Clone",
|
||||
]
|
||||
# Register WorkbenchManipulator for injecting into native workbench toolbars
|
||||
Gui.addWorkbenchManipulator(_ZToolsManipulator())
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Additive Features
|
||||
# =====================================================================
|
||||
self.additive_tools = [
|
||||
"PartDesign_Pad",
|
||||
"PartDesign_Revolution",
|
||||
"PartDesign_AdditiveLoft",
|
||||
"PartDesign_AdditivePipe",
|
||||
"PartDesign_AdditiveHelix",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Additive Primitives (compound command with dropdown)
|
||||
# =====================================================================
|
||||
self.additive_primitives = [
|
||||
"PartDesign_CompPrimitiveAdditive",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Subtractive Features
|
||||
# =====================================================================
|
||||
self.subtractive_tools = [
|
||||
"PartDesign_Pocket",
|
||||
"PartDesign_Hole",
|
||||
"PartDesign_Groove",
|
||||
"PartDesign_SubtractiveLoft",
|
||||
"PartDesign_SubtractivePipe",
|
||||
"PartDesign_SubtractiveHelix",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Subtractive Primitives (compound command with dropdown)
|
||||
# =====================================================================
|
||||
self.subtractive_primitives = [
|
||||
"PartDesign_CompPrimitiveSubtractive",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Transformation Features (Patterns)
|
||||
# =====================================================================
|
||||
self.transformation_tools = [
|
||||
"PartDesign_Mirrored",
|
||||
"PartDesign_LinearPattern",
|
||||
"PartDesign_PolarPattern",
|
||||
"PartDesign_MultiTransform",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Dress-Up Features
|
||||
# =====================================================================
|
||||
self.dressup_tools = [
|
||||
"PartDesign_Fillet",
|
||||
"PartDesign_Chamfer",
|
||||
"PartDesign_Draft",
|
||||
"PartDesign_Thickness",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# PartDesign Boolean Operations
|
||||
# =====================================================================
|
||||
self.boolean_tools = [
|
||||
"PartDesign_Boolean",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# Sketcher Tools (commonly used with PartDesign)
|
||||
# =====================================================================
|
||||
self.sketcher_tools = [
|
||||
"Sketcher_NewSketch",
|
||||
"Sketcher_EditSketch",
|
||||
"Sketcher_MapSketch",
|
||||
"Sketcher_ValidateSketch",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# ZTools Custom Tools
|
||||
# =====================================================================
|
||||
self.ztools_datum_tools = [
|
||||
"ZTools_DatumCreator",
|
||||
"ZTools_DatumManager",
|
||||
]
|
||||
|
||||
self.ztools_pattern_tools = [
|
||||
"ZTools_RotatedLinearPattern",
|
||||
]
|
||||
|
||||
self.ztools_pocket_tools = [
|
||||
"ZTools_EnhancedPocket",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# Assembly Workbench Tools (FreeCAD 1.0+)
|
||||
# =====================================================================
|
||||
self.assembly_structure_tools = [
|
||||
"Assembly_CreateAssembly",
|
||||
"Assembly_InsertLink",
|
||||
"Assembly_InsertNewPart",
|
||||
]
|
||||
|
||||
self.assembly_joint_tools = [
|
||||
"Assembly_CreateJointFixed",
|
||||
"Assembly_CreateJointRevolute",
|
||||
"Assembly_CreateJointCylindrical",
|
||||
"Assembly_CreateJointSlider",
|
||||
"Assembly_CreateJointBall",
|
||||
"Assembly_CreateJointDistance",
|
||||
"Assembly_CreateJointParallel",
|
||||
"Assembly_CreateJointPerpendicular",
|
||||
"Assembly_CreateJointAngle",
|
||||
"Assembly_CreateJointRackPinion",
|
||||
"Assembly_CreateJointScrew",
|
||||
"Assembly_CreateJointGears",
|
||||
"Assembly_CreateJointBelt",
|
||||
]
|
||||
|
||||
self.assembly_management_tools = [
|
||||
"Assembly_ToggleGrounded",
|
||||
"Assembly_SolveAssembly",
|
||||
"Assembly_CreateView",
|
||||
"Assembly_CreateBom",
|
||||
"Assembly_ExportASMT",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# ZTools Assembly Pattern Tools
|
||||
# =====================================================================
|
||||
self.ztools_assembly_tools = [
|
||||
"ZTools_AssemblyLinearPattern",
|
||||
"ZTools_AssemblyPolarPattern",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# Spreadsheet Workbench Tools
|
||||
# =====================================================================
|
||||
self.spreadsheet_tools = [
|
||||
"Spreadsheet_CreateSheet",
|
||||
"Spreadsheet_Import",
|
||||
"Spreadsheet_Export",
|
||||
"Spreadsheet_SetAlias",
|
||||
"Spreadsheet_MergeCells",
|
||||
"Spreadsheet_SplitCell",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# ZTools Spreadsheet Formatting Tools
|
||||
# =====================================================================
|
||||
self.ztools_spreadsheet_tools = [
|
||||
"ZTools_SpreadsheetStyleBold",
|
||||
"ZTools_SpreadsheetStyleItalic",
|
||||
"ZTools_SpreadsheetStyleUnderline",
|
||||
"ZTools_SpreadsheetAlignLeft",
|
||||
"ZTools_SpreadsheetAlignCenter",
|
||||
"ZTools_SpreadsheetAlignRight",
|
||||
"ZTools_SpreadsheetBgColor",
|
||||
"ZTools_SpreadsheetTextColor",
|
||||
"ZTools_SpreadsheetQuickAlias",
|
||||
]
|
||||
|
||||
# =====================================================================
|
||||
# Append Toolbars
|
||||
# =====================================================================
|
||||
self.appendToolbar("Structure", self.structure_tools)
|
||||
self.appendToolbar("Sketcher", self.sketcher_tools)
|
||||
self.appendToolbar("Datums", self.partdesign_datum_tools)
|
||||
self.appendToolbar("Additive", self.additive_tools + self.additive_primitives)
|
||||
self.appendToolbar(
|
||||
"Subtractive", self.subtractive_tools + self.subtractive_primitives
|
||||
# Inject ZTools commands into editing context toolbars.
|
||||
# These calls append commands to the named toolbar within the given context,
|
||||
# so when the EditingContextResolver activates a context the injected
|
||||
# commands appear alongside the native ones.
|
||||
try:
|
||||
Gui.injectEditingCommands(
|
||||
"partdesign.body",
|
||||
"Part Design Helper Features",
|
||||
["ZTools_DatumCreator", "ZTools_DatumManager"],
|
||||
)
|
||||
self.appendToolbar("Transformations", self.transformation_tools)
|
||||
self.appendToolbar("Dress-Up", self.dressup_tools)
|
||||
self.appendToolbar("Boolean", self.boolean_tools)
|
||||
self.appendToolbar("Assembly", self.assembly_structure_tools)
|
||||
self.appendToolbar("Assembly Joints", self.assembly_joint_tools)
|
||||
self.appendToolbar("Assembly Management", self.assembly_management_tools)
|
||||
self.appendToolbar("ztools Datums", self.ztools_datum_tools)
|
||||
self.appendToolbar("ztools Patterns", self.ztools_pattern_tools)
|
||||
self.appendToolbar("ztools Features", self.ztools_pocket_tools)
|
||||
self.appendToolbar("ztools Assembly", self.ztools_assembly_tools)
|
||||
self.appendToolbar("Spreadsheet", self.spreadsheet_tools)
|
||||
self.appendToolbar("ztools Spreadsheet", self.ztools_spreadsheet_tools)
|
||||
|
||||
# =====================================================================
|
||||
# Append Menus
|
||||
# =====================================================================
|
||||
self.appendMenu("Structure", self.structure_tools)
|
||||
self.appendMenu("Sketch", self.sketcher_tools)
|
||||
self.appendMenu(["PartDesign", "Datums"], self.partdesign_datum_tools)
|
||||
self.appendMenu(
|
||||
["PartDesign", "Additive"], self.additive_tools + self.additive_primitives
|
||||
Gui.injectEditingCommands(
|
||||
"partdesign.feature",
|
||||
"Part Design Modeling Features",
|
||||
["ZTools_EnhancedPocket"],
|
||||
)
|
||||
self.appendMenu(
|
||||
["PartDesign", "Subtractive"],
|
||||
self.subtractive_tools + self.subtractive_primitives,
|
||||
Gui.injectEditingCommands(
|
||||
"partdesign.feature",
|
||||
"Part Design Transformation Features",
|
||||
["ZTools_RotatedLinearPattern"],
|
||||
)
|
||||
self.appendMenu(["PartDesign", "Transformations"], self.transformation_tools)
|
||||
self.appendMenu(["PartDesign", "Dress-Up"], self.dressup_tools)
|
||||
self.appendMenu(["PartDesign", "Boolean"], self.boolean_tools)
|
||||
self.appendMenu(["Assembly", "Structure"], self.assembly_structure_tools)
|
||||
self.appendMenu(["Assembly", "Joints"], self.assembly_joint_tools)
|
||||
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(
|
||||
"ztools",
|
||||
self.ztools_datum_tools
|
||||
+ self.ztools_pattern_tools
|
||||
+ self.ztools_pocket_tools
|
||||
+ self.ztools_assembly_tools
|
||||
+ self.ztools_spreadsheet_tools,
|
||||
Gui.injectEditingCommands(
|
||||
"assembly.edit",
|
||||
"Assembly Management",
|
||||
["ZTools_AssemblyLinearPattern", "ZTools_AssemblyPolarPattern"],
|
||||
)
|
||||
Gui.injectEditingCommands(
|
||||
"spreadsheet",
|
||||
"Spreadsheet",
|
||||
[
|
||||
"ZTools_SpreadsheetStyleBold",
|
||||
"ZTools_SpreadsheetStyleItalic",
|
||||
"ZTools_SpreadsheetStyleUnderline",
|
||||
"ZTools_SpreadsheetAlignLeft",
|
||||
"ZTools_SpreadsheetAlignCenter",
|
||||
"ZTools_SpreadsheetAlignRight",
|
||||
"ZTools_SpreadsheetBgColor",
|
||||
"ZTools_SpreadsheetTextColor",
|
||||
"ZTools_SpreadsheetQuickAlias",
|
||||
],
|
||||
)
|
||||
except Exception as e:
|
||||
App.Console.PrintWarning(f"ztools: could not inject context commands: {e}\n")
|
||||
|
||||
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")
|
||||
|
||||
App.Console.PrintMessage("ztools workbench activated\n")
|
||||
|
||||
def Deactivated(self):
|
||||
"""Called when workbench is deactivated."""
|
||||
pass
|
||||
|
||||
def GetClassName(self):
|
||||
return "Gui::PythonWorkbench"
|
||||
|
||||
|
||||
Gui.addWorkbench(ZToolsWorkbench())
|
||||
App.Console.PrintMessage("ztools commands registered\n")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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
|
||||
# WorkbenchManipulator: inject ZTools commands into native workbench toolbars
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _ZToolsPartDesignManipulator:
|
||||
"""Adds ZTools commands to PartDesign toolbars and menus."""
|
||||
class _ZToolsManipulator:
|
||||
"""Adds ZTools commands to PartDesign, Assembly, and Spreadsheet toolbars."""
|
||||
|
||||
def modifyToolBars(self):
|
||||
return [
|
||||
# PartDesign
|
||||
{"append": "ZTools_DatumCreator", "toolBar": "Part Design Helper Features"},
|
||||
{"append": "ZTools_DatumManager", "toolBar": "Part Design Helper Features"},
|
||||
{
|
||||
@@ -361,15 +114,47 @@ class _ZToolsPartDesignManipulator:
|
||||
"append": "ZTools_RotatedLinearPattern",
|
||||
"toolBar": "Part Design Transformation Features",
|
||||
},
|
||||
# Assembly
|
||||
{"append": "ZTools_AssemblyLinearPattern", "toolBar": "Assembly"},
|
||||
{"append": "ZTools_AssemblyPolarPattern", "toolBar": "Assembly"},
|
||||
# Spreadsheet
|
||||
{"append": "ZTools_SpreadsheetStyleBold", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetStyleItalic", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetStyleUnderline", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetAlignLeft", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetAlignCenter", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetAlignRight", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetBgColor", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetTextColor", "toolBar": "Spreadsheet"},
|
||||
{"append": "ZTools_SpreadsheetQuickAlias", "toolBar": "Spreadsheet"},
|
||||
]
|
||||
|
||||
def modifyMenuBar(self):
|
||||
return [
|
||||
{"append": "ZTools_DatumCreator", "menuItem": "PartDesign_Body"},
|
||||
{"append": "ZTools_DatumManager", "menuItem": "PartDesign_Body"},
|
||||
{"append": "ZTools_EnhancedPocket", "menuItem": "PartDesign_Body"},
|
||||
{"append": "ZTools_RotatedLinearPattern", "menuItem": "PartDesign_Body"},
|
||||
{
|
||||
"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": "",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Gui.addWorkbenchManipulator(_ZToolsPartDesignManipulator())
|
||||
# Deferred registration — wait for dependent workbenches to finish loading
|
||||
from PySide.QtCore import QTimer # noqa: E402
|
||||
|
||||
QTimer.singleShot(2000, _register)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user