Leverage FreeCAD AttachExtension for parametric datum updates
ZTools datum planes now use FreeCAD's built-in attachment engine instead of setting MapMode='Deactivated' with manual placement. This means datums automatically update when source geometry changes on recompute. Each datum type maps to a vanilla MapMode: offset_from_face -> FlatFace + AttachmentOffset.Base.z = distance offset_from_plane -> FlatFace + AttachmentOffset.Base.z = distance midplane -> FlatFace on face1 + offset = half gap distance 3_points -> ThreePointsPlane (3 vertex refs) normal_to_edge -> NormalToEdge + MapPathParameter for position angled -> FlatFace + AttachmentOffset.Rotation for angle tangent_cylinder -> manual fallback (TangentPlane needs vertex ref) The C++ AttachExtension handles: automatic recompute via extensionExecute(), dependency tracking via PropertyLinkSubList, topology change handling, and live preview via extensionOnChanged(). Non-Body datums (Part::Plane) lack AttachExtension and continue to use manual placement as before. Other changes: - New _configure_attachment() helper sets MapMode, AttachmentSupport, AttachmentOffset, and MapPathParameter on datum objects - _setup_ztools_datum() accepts optional attachment parameters and falls back to manual placement when not provided - DatumEditTaskPanel.on_param_changed() writes to AttachmentOffset and MapPathParameter for live editing instead of TODO stubs - AttachmentSupport added to hidden properties list - Spreadsheet links target AttachmentOffset.Base.z instead of Placement.Base.z for attached datums - ZTools metadata (ZTools_Type, ZTools_Params, ZTools_SourceRefs) preserved for edit UI and styling
This commit is contained in:
@@ -42,6 +42,7 @@ class ZToolsDatumViewProvider:
|
||||
"MapPathParameter",
|
||||
"MapReversed",
|
||||
"AttachmentOffset",
|
||||
"AttachmentSupport",
|
||||
"Support",
|
||||
]
|
||||
|
||||
@@ -332,8 +333,20 @@ class DatumEditTaskPanel:
|
||||
if not refs:
|
||||
self.refs_list.addItem("(no references)")
|
||||
|
||||
def _has_attachment(self):
|
||||
"""Check if datum uses vanilla AttachExtension (MapMode != Deactivated)."""
|
||||
return (
|
||||
hasattr(self.datum_obj, "MapMode")
|
||||
and self.datum_obj.MapMode != "Deactivated"
|
||||
)
|
||||
|
||||
def on_param_changed(self):
|
||||
"""Handle parameter value changes - update datum in real-time."""
|
||||
"""Handle parameter value changes - update datum in real-time.
|
||||
|
||||
For datums with active AttachExtension, writes to AttachmentOffset or
|
||||
MapPathParameter — the C++ engine recalculates placement automatically.
|
||||
For manual datums, updates Placement directly.
|
||||
"""
|
||||
ztools_type = getattr(self.datum_obj, "ZTools_Type", "")
|
||||
|
||||
# For coordinate-based points, update placement directly
|
||||
@@ -344,19 +357,21 @@ class DatumEditTaskPanel:
|
||||
self.datum_obj.Placement.Base = new_pos
|
||||
self._update_params({"x": new_pos.x, "y": new_pos.y, "z": new_pos.z})
|
||||
|
||||
elif ztools_type in ("offset_from_face", "offset_from_plane"):
|
||||
# For offset types, we need to recalculate from source
|
||||
# This is more complex - for now just update the stored param
|
||||
self._update_params({"distance": self.offset_spin.value()})
|
||||
# TODO: Recalculate placement from source geometry
|
||||
elif ztools_type in ("offset_from_face", "offset_from_plane", "midplane"):
|
||||
distance = self.offset_spin.value()
|
||||
if self._has_attachment():
|
||||
new_offset = App.Placement(App.Vector(0, 0, distance), App.Rotation())
|
||||
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()})
|
||||
# TODO: Recalculate placement from source geometry
|
||||
|
||||
elif ztools_type in ("normal_to_edge", "on_edge"):
|
||||
self._update_params({"parameter": self.param_spin.value()})
|
||||
# TODO: Recalculate placement from source geometry
|
||||
parameter = self.param_spin.value()
|
||||
if self._has_attachment() and hasattr(self.datum_obj, "MapPathParameter"):
|
||||
self.datum_obj.MapPathParameter = parameter
|
||||
self._update_params({"parameter": parameter})
|
||||
|
||||
# Update position display
|
||||
pos = self.datum_obj.Placement.Base
|
||||
|
||||
@@ -32,19 +32,25 @@ def _get_next_index(doc: App.Document, prefix: str) -> int:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ZTOOLS CUSTOM ATTACHMENT SYSTEM
|
||||
# ZTOOLS ATTACHMENT SYSTEM
|
||||
# =============================================================================
|
||||
#
|
||||
# FreeCAD's vanilla attachment system has reliability issues. ZTools uses its
|
||||
# own attachment approach:
|
||||
# ZTools leverages FreeCAD's built-in AttachExtension (MapMode, AttachmentSupport,
|
||||
# AttachmentOffset) on PartDesign datums for automatic parametric updates on
|
||||
# recompute. Each datum type maps to a vanilla MapMode:
|
||||
#
|
||||
# 1. Store source references as properties (ZTools_SourceRefs)
|
||||
# 2. Store creation method and parameters (ZTools_Type, ZTools_Params)
|
||||
# 3. Calculate placement directly from geometry at creation time
|
||||
# 4. Use MapMode='Deactivated' to prevent FreeCAD attachment interference
|
||||
# offset_from_face -> FlatFace + AttachmentOffset.Base.z = distance
|
||||
# offset_from_plane -> FlatFace + AttachmentOffset.Base.z = distance
|
||||
# midplane -> FlatFace on face1 + AttachmentOffset.Base.z = half_gap
|
||||
# 3_points -> ThreePointsPlane
|
||||
# normal_to_edge -> NormalToEdge + MapPathParameter
|
||||
# angled -> FlatFace + AttachmentOffset.Rotation = angle about edge
|
||||
# tangent_cylinder -> TangentPlane (with vertex ref) or manual fallback
|
||||
#
|
||||
# This gives us full control over datum positioning while maintaining
|
||||
# the ability to update datums when source geometry changes (future feature).
|
||||
# ZTools metadata (ZTools_Type, ZTools_Params, ZTools_SourceRefs) is kept for
|
||||
# the edit UI. Attachment properties are hidden from the property panel.
|
||||
#
|
||||
# Non-Body datums (Part::Plane) lack AttachExtension and use manual placement.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@@ -79,6 +85,7 @@ def _hide_attachment_properties(obj):
|
||||
"MapPathParameter",
|
||||
"MapReversed",
|
||||
"AttachmentOffset",
|
||||
"AttachmentSupport",
|
||||
"Support",
|
||||
]
|
||||
|
||||
@@ -123,6 +130,35 @@ def _setup_ztools_viewprovider(obj):
|
||||
pass # C++ ViewProvider doesn't support Python proxy assignment
|
||||
|
||||
|
||||
def _configure_attachment(obj, map_mode, support, offset=None, path_param=None):
|
||||
"""
|
||||
Configure FreeCAD's vanilla AttachExtension on a datum object.
|
||||
|
||||
This enables automatic placement updates on recompute — the C++ engine
|
||||
recalculates placement from source geometry via extensionExecute().
|
||||
|
||||
Args:
|
||||
obj: The datum object (must have AttachExtension, e.g. PartDesign::Plane)
|
||||
map_mode: MapMode string (e.g. "FlatFace", "NormalToEdge")
|
||||
support: List of (object, subname) tuples for AttachmentSupport
|
||||
offset: Optional App.Placement for AttachmentOffset
|
||||
path_param: Optional float for MapPathParameter (0-1, for curve modes)
|
||||
|
||||
Returns:
|
||||
True if attachment was configured, False if object lacks AttachExtension
|
||||
"""
|
||||
if not hasattr(obj, "MapMode"):
|
||||
return False
|
||||
|
||||
obj.AttachmentSupport = support
|
||||
obj.MapMode = map_mode
|
||||
if offset is not None:
|
||||
obj.AttachmentOffset = offset
|
||||
if path_param is not None and hasattr(obj, "MapPathParameter"):
|
||||
obj.MapPathParameter = path_param
|
||||
return True
|
||||
|
||||
|
||||
def _setup_ztools_datum(
|
||||
obj,
|
||||
placement: App.Placement,
|
||||
@@ -130,26 +166,37 @@ def _setup_ztools_datum(
|
||||
params: Dict[str, Any],
|
||||
source_refs: Optional[List[Tuple[App.DocumentObject, str]]] = None,
|
||||
is_plane: bool = False,
|
||||
map_mode: Optional[str] = None,
|
||||
support: Optional[list] = None,
|
||||
offset: Optional[App.Placement] = None,
|
||||
path_param: Optional[float] = None,
|
||||
):
|
||||
"""
|
||||
Set up a ZTools datum with custom attachment system.
|
||||
Set up a ZTools datum with attachment or manual placement.
|
||||
|
||||
When map_mode and support are provided and the object has AttachExtension,
|
||||
configures vanilla attachment for automatic recompute. Otherwise falls back
|
||||
to manual placement with MapMode="Deactivated".
|
||||
|
||||
Args:
|
||||
obj: The datum object (PartDesign::Plane, Line, or Point)
|
||||
placement: Calculated placement for the datum
|
||||
placement: Calculated placement (used as fallback for non-Body datums)
|
||||
datum_type: ZTools creation method identifier
|
||||
params: Creation parameters to store
|
||||
source_refs: List of (object, subname) tuples for source geometry
|
||||
is_plane: If True, apply transparent purple styling
|
||||
map_mode: Optional MapMode string for vanilla attachment
|
||||
support: Optional AttachmentSupport list for vanilla attachment
|
||||
offset: Optional AttachmentOffset placement
|
||||
path_param: Optional MapPathParameter value (0-1)
|
||||
"""
|
||||
# Disable FreeCAD's attachment system
|
||||
if hasattr(obj, "MapMode"):
|
||||
obj.MapMode = "Deactivated"
|
||||
if hasattr(obj, "Support"):
|
||||
obj.Support = None
|
||||
|
||||
# Apply calculated placement
|
||||
obj.Placement = placement
|
||||
if map_mode and support and hasattr(obj, "MapMode"):
|
||||
_configure_attachment(obj, map_mode, support, offset, path_param)
|
||||
else:
|
||||
# Fallback: manual placement (non-Body datums like Part::Plane)
|
||||
if hasattr(obj, "MapMode"):
|
||||
obj.MapMode = "Deactivated"
|
||||
obj.Placement = placement
|
||||
|
||||
# Store ZTools metadata
|
||||
_add_ztools_metadata(obj, datum_type, params, source_refs)
|
||||
@@ -363,30 +410,41 @@ def plane_offset_from_face(
|
||||
placement = App.Placement(base, rot)
|
||||
|
||||
# Create plane
|
||||
source_refs = [(source_object, source_subname)] if source_object else None
|
||||
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
att_support = [(source_object, source_subname)] if source_object else None
|
||||
att_offset = App.Placement(App.Vector(0, 0, distance), App.Rotation())
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"offset_from_face",
|
||||
{"distance": distance},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
map_mode="FlatFace" if att_support else None,
|
||||
support=att_support,
|
||||
offset=att_offset,
|
||||
)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
# Adjust placement for Part::Plane (centered differently)
|
||||
placement = App.Placement(base - rot.multVec(App.Vector(25, 25, 0)), rot)
|
||||
|
||||
# Set up with ZTools attachment system
|
||||
source_refs = [(source_object, source_subname)] if source_object else None
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"offset_from_face",
|
||||
{"distance": distance, "base": base, "normal": normal},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"offset_from_face",
|
||||
{"distance": distance},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
|
||||
# Spreadsheet link
|
||||
if link_spreadsheet and body:
|
||||
alias = f"{name}_offset"
|
||||
_link_to_spreadsheet(doc, plane, "Placement.Base.z", distance, alias)
|
||||
_link_to_spreadsheet(doc, plane, "AttachmentOffset.Base.z", distance, alias)
|
||||
|
||||
doc.recompute()
|
||||
return plane
|
||||
@@ -446,35 +504,40 @@ def plane_offset_from_plane(
|
||||
placement = App.Placement(base, rot)
|
||||
|
||||
# Create plane
|
||||
source_refs = [(source_plane, "")]
|
||||
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
att_offset = App.Placement(App.Vector(0, 0, distance), App.Rotation())
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"offset_from_plane",
|
||||
{"distance": distance, "source_plane": source_plane.Name},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
map_mode="FlatFace",
|
||||
support=[(source_plane, "")],
|
||||
offset=att_offset,
|
||||
)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
# Adjust placement for Part::Plane (centered differently)
|
||||
placement = App.Placement(base - rot.multVec(App.Vector(25, 25, 0)), rot)
|
||||
|
||||
# Set up with ZTools attachment system
|
||||
source_refs = [(source_plane, "")]
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"offset_from_plane",
|
||||
{
|
||||
"distance": distance,
|
||||
"base": base,
|
||||
"normal": normal,
|
||||
"source_plane": source_plane.Name,
|
||||
},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"offset_from_plane",
|
||||
{"distance": distance, "source_plane": source_plane.Name},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
|
||||
# Spreadsheet link
|
||||
if link_spreadsheet and body:
|
||||
alias = f"{name}_offset"
|
||||
_link_to_spreadsheet(doc, plane, "Placement.Base.z", distance, alias)
|
||||
_link_to_spreadsheet(doc, plane, "AttachmentOffset.Base.z", distance, alias)
|
||||
|
||||
doc.recompute()
|
||||
return plane
|
||||
@@ -520,9 +583,10 @@ def plane_midplane(
|
||||
if dot < 0.9999:
|
||||
raise ValueError("Faces must be parallel for midplane")
|
||||
|
||||
# Midpoint
|
||||
# Compute half-distance between faces along face1 normal
|
||||
c1 = face1.CenterOfMass
|
||||
c2 = face2.CenterOfMass
|
||||
half_dist = (c2 - c1).dot(n1) / 2.0
|
||||
mid = (c1 + c2) * 0.5
|
||||
|
||||
# Auto-name
|
||||
@@ -530,34 +594,47 @@ def plane_midplane(
|
||||
idx = _get_next_index(doc, "ZPlane_Mid")
|
||||
name = f"ZPlane_Mid_{idx:03d}"
|
||||
|
||||
# Calculate placement
|
||||
# Calculate placement (used as fallback for non-Body datums)
|
||||
rot = App.Rotation(App.Vector(0, 0, 1), n1)
|
||||
placement = App.Placement(mid, rot)
|
||||
|
||||
# Create plane
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
placement = App.Placement(mid - rot.multVec(App.Vector(25, 25, 0)), rot)
|
||||
|
||||
# Set up with ZTools attachment system
|
||||
source_refs = []
|
||||
if source_object1:
|
||||
source_refs.append((source_object1, source_subname1))
|
||||
if source_object2:
|
||||
source_refs.append((source_object2, source_subname2))
|
||||
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"midplane",
|
||||
{"center1": c1, "center2": c2, "midpoint": mid},
|
||||
source_refs if source_refs else None,
|
||||
is_plane=True,
|
||||
)
|
||||
if body and source_object1 and source_subname1:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
att_offset = App.Placement(App.Vector(0, 0, half_dist), App.Rotation())
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"midplane",
|
||||
{"half_distance": half_dist},
|
||||
source_refs if source_refs else None,
|
||||
is_plane=True,
|
||||
map_mode="FlatFace",
|
||||
support=[(source_object1, source_subname1)],
|
||||
offset=att_offset,
|
||||
)
|
||||
else:
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
placement = App.Placement(mid - rot.multVec(App.Vector(25, 25, 0)), rot)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"midplane",
|
||||
{"half_distance": half_dist},
|
||||
source_refs if source_refs else None,
|
||||
is_plane=True,
|
||||
)
|
||||
|
||||
doc.recompute()
|
||||
return plane
|
||||
@@ -608,21 +685,32 @@ def plane_from_3_points(
|
||||
# Create plane
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
att_support = [(ref[0], ref[1]) for ref in source_refs] if source_refs else None
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"3_points",
|
||||
{"p1": p1, "p2": p2, "p3": p3},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
map_mode="ThreePointsPlane"
|
||||
if att_support and len(att_support) == 3
|
||||
else None,
|
||||
support=att_support,
|
||||
)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
placement = App.Placement(center - rot.multVec(App.Vector(25, 25, 0)), rot)
|
||||
|
||||
# Set up with ZTools attachment system
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"3_points",
|
||||
{"p1": p1, "p2": p2, "p3": p3, "center": center, "normal": normal},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"3_points",
|
||||
{"p1": p1, "p2": p2, "p3": p3},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
|
||||
doc.recompute()
|
||||
return plane
|
||||
@@ -666,24 +754,35 @@ def plane_normal_to_edge(
|
||||
placement = App.Placement(point, rot)
|
||||
|
||||
# Create plane
|
||||
source_refs = [(source_object, source_subname)] if source_object else None
|
||||
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
att_support = [(source_object, source_subname)] if source_object else None
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"normal_to_edge",
|
||||
{"parameter": parameter},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
map_mode="NormalToEdge" if att_support else None,
|
||||
support=att_support,
|
||||
path_param=parameter,
|
||||
)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
placement = App.Placement(point - rot.multVec(App.Vector(25, 25, 0)), rot)
|
||||
|
||||
# Set up with ZTools attachment system
|
||||
source_refs = [(source_object, source_subname)] if source_object else None
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"normal_to_edge",
|
||||
{"parameter": parameter, "point": point, "tangent": tangent},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"normal_to_edge",
|
||||
{"parameter": parameter},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
|
||||
doc.recompute()
|
||||
return plane
|
||||
@@ -750,39 +849,55 @@ def plane_angled(
|
||||
placement = App.Placement(edge_mid, rot)
|
||||
|
||||
# Create plane
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
placement = App.Placement(edge_mid - rot.multVec(App.Vector(25, 25, 0)), rot)
|
||||
|
||||
# Set up with ZTools attachment system
|
||||
source_refs = []
|
||||
if source_face_obj:
|
||||
source_refs.append((source_face_obj, source_face_sub))
|
||||
if source_edge_obj:
|
||||
source_refs.append((source_edge_obj, source_edge_sub))
|
||||
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"angled",
|
||||
{
|
||||
"angle": angle,
|
||||
"edge_mid": edge_mid,
|
||||
"original_normal": face_normal,
|
||||
"new_normal": new_normal,
|
||||
},
|
||||
source_refs if source_refs else None,
|
||||
is_plane=True,
|
||||
)
|
||||
if body and source_face_obj and source_face_sub:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
# Encode the angle as a rotation about the edge direction in the
|
||||
# face-local coordinate system. The FlatFace attachment aligns Z with
|
||||
# face normal, so we rotate about the edge direction projected into
|
||||
# the face-local frame.
|
||||
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)
|
||||
att_offset = App.Placement(App.Vector(0, 0, 0), angle_rot)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"angled",
|
||||
{"angle": angle},
|
||||
source_refs if source_refs else None,
|
||||
is_plane=True,
|
||||
map_mode="FlatFace",
|
||||
support=[(source_face_obj, source_face_sub)],
|
||||
offset=att_offset,
|
||||
)
|
||||
else:
|
||||
if body:
|
||||
plane = body.newObject("PartDesign::Plane", name)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
plane.Width = 50
|
||||
placement = App.Placement(
|
||||
edge_mid - rot.multVec(App.Vector(25, 25, 0)), rot
|
||||
)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"angled",
|
||||
{"angle": angle},
|
||||
source_refs if source_refs else None,
|
||||
is_plane=True,
|
||||
)
|
||||
|
||||
if link_spreadsheet and body:
|
||||
alias = f"{name}_angle"
|
||||
# For ZTools system, we'd need custom expression handling
|
||||
# _link_to_spreadsheet(doc, plane, "...", angle, alias)
|
||||
_link_to_spreadsheet(doc, plane, "AttachmentOffset.Angle", angle, alias)
|
||||
|
||||
doc.recompute()
|
||||
return plane
|
||||
@@ -847,8 +962,20 @@ def plane_tangent_to_cylinder(
|
||||
placement = App.Placement(tangent_point, rot)
|
||||
|
||||
# Create plane
|
||||
source_refs = [(source_object, source_subname)] if source_object else None
|
||||
|
||||
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,
|
||||
)
|
||||
else:
|
||||
plane = doc.addObject("Part::Plane", name)
|
||||
plane.Length = 50
|
||||
@@ -856,17 +983,14 @@ def plane_tangent_to_cylinder(
|
||||
placement = App.Placement(
|
||||
tangent_point - rot.multVec(App.Vector(25, 25, 0)), rot
|
||||
)
|
||||
|
||||
# Set up with ZTools attachment system
|
||||
source_refs = [(source_object, source_subname)] if source_object else None
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"tangent_cylinder",
|
||||
{"angle": angle, "radius": radius, "tangent_point": tangent_point},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
_setup_ztools_datum(
|
||||
plane,
|
||||
placement,
|
||||
"tangent_cylinder",
|
||||
{"angle": angle, "radius": radius},
|
||||
source_refs,
|
||||
is_plane=True,
|
||||
)
|
||||
|
||||
doc.recompute()
|
||||
return plane
|
||||
|
||||
Reference in New Issue
Block a user