5 Commits

Author SHA1 Message Date
2bb2949b82 feat: dissolve ZTools workbench into context-injected command provider
ZTools no longer registers as a standalone workbench. Commands are now
injected into native workbench toolbars (PartDesign, Assembly,
Spreadsheet) via _ZToolsManipulator (WorkbenchManipulator) and the
EditingContextResolver system (injectEditingCommands).

- Remove ZToolsWorkbench class and Gui.addWorkbench() call
- Deferred registration via QTimer.singleShot(2000ms)
- Expand manipulator to cover Assembly and Spreadsheet toolbars
- Update package.xml description
2026-02-11 13:12:43 -06:00
forbes
2bf969c62a fix: use TangentPlane MapMode for tangent-to-cylinder datums (#58)
plane_tangent_to_cylinder() now derives a vertex from the cylinder
face's edges and uses TangentPlane MapMode with AttachmentOffset to
encode the angular position. This makes tangent datums parametric —
they auto-update when cylinder geometry changes.

Add _find_cylinder_vertex() and _vertex_angle_on_cylinder() helpers.
Store vertex_angle in ZTools_Params for the edit panel to compute
AttachmentOffset updates. Falls back to manual placement when no
vertex can be resolved (non-Body datums).
2026-02-08 18:53:13 -06:00
forbes
04f9df75cb fix: update AttachmentOffset during angled datum editing (#66)
on_param_changed() now recomputes AttachmentOffset.Rotation for angled
datums and recalculates Placement for tangent_cylinder datums when the
angle spinner changes. Previously only ZTools_Params was updated,
leaving the visual representation unchanged until a manual recompute.

Add _resolve_source_refs() helper to parse ZTools_SourceRefs and
resolve stored object/subname pairs to actual shapes for the rotation
and placement math.
2026-02-08 18:42:28 -06:00
forbes
2132c4e64c fix: use append instead of fragile insert chain in PartDesign menu (#57)
Replace the chained insert operations in modifyMenuBar() with independent
append operations. The old approach anchored on PartDesign_Boolean and
chained each subsequent command off the previous insertion — a single
missing anchor caused a cascade failure.

The new approach uses append with PartDesign_Body as the parent locator.
Each operation is independent, so a failure in one does not affect the
others.
2026-02-08 18:07:18 -06:00
forbes
12e332240a fix: register commands and manipulator at module scope (#52)
Move command imports and PartDesign manipulator installation from
ZToolsWorkbench.Initialize() to module scope. This ensures commands
are registered and the manipulator is available before any workbench
activates, fixing the case where PartDesign activates before ZTools
and ztools buttons never appear.
2026-02-08 17:57:54 -06:00
4 changed files with 240 additions and 339 deletions

View File

@@ -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>

View File

@@ -1,354 +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,
)
from ztools.commands import (
assembly_pattern_commands,
datum_commands,
pattern_commands,
pocket_commands,
spreadsheet_commands,
# Apply Catppuccin Mocha Spreadsheet colors
try:
from ztools.resources.theme import apply_spreadsheet_colors
apply_spreadsheet_colors()
except Exception as e:
App.Console.PrintWarning(f"ztools: could not apply spreadsheet colors: {e}\n")
# Register WorkbenchManipulator for injecting into native workbench toolbars
Gui.addWorkbenchManipulator(_ZToolsManipulator())
# 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"],
)
# =====================================================================
# PartDesign Structure Tools
# =====================================================================
self.structure_tools = [
"PartDesign_Body",
"PartDesign_NewSketch",
]
# =====================================================================
# PartDesign Reference Geometry (Datums)
# =====================================================================
self.partdesign_datum_tools = [
"PartDesign_Plane",
"PartDesign_Line",
"PartDesign_Point",
"PartDesign_CoordinateSystem",
"PartDesign_ShapeBinder",
"PartDesign_SubShapeBinder",
"PartDesign_Clone",
]
# =====================================================================
# 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
Gui.injectEditingCommands(
"partdesign.feature",
"Part Design Modeling Features",
["ZTools_EnhancedPocket"],
)
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 Transformation Features",
["ZTools_RotatedLinearPattern"],
)
self.appendMenu(
["PartDesign", "Subtractive"],
self.subtractive_tools + self.subtractive_primitives,
Gui.injectEditingCommands(
"assembly.edit",
"Assembly Management",
["ZTools_AssemblyLinearPattern", "ZTools_AssemblyPolarPattern"],
)
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(
"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")
# 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")
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")
# ---------------------------------------------------------------------------
# WorkbenchManipulator: inject ZTools commands into PartDesign workbench
# WorkbenchManipulator: inject ZTools commands into native workbench toolbars
# ---------------------------------------------------------------------------
# Registered in ZToolsWorkbench.Initialize() after commands are imported,
# so the commands exist before the manipulator references them.
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"},
{
@@ -359,6 +114,19 @@ 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):
@@ -384,3 +152,9 @@ class _ZToolsPartDesignManipulator:
"after": "",
},
]
# Deferred registration — wait for dependent workbenches to finish loading
from PySide.QtCore import QTimer # noqa: E402
QTimer.singleShot(2000, _register)

View File

@@ -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()

View File

@@ -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