mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-18 11:20:21 +00:00
Fix anchor click, undo, layer regen, and ForcePerpendicularToFace for layer anchors
- ClickNearestDimensionAnchor: scan all selected objects instead of active object so clicking a green dot doesn't lose to the underlying IFC geometry - GizmoAnchorHandle: remove draw_select entirely (any entry in the select buffer causes Blender's gizmo system to consume clicks); keep purely visual - Scale anchor dots to scale_basis = 0.2 - Fix ReferenceError in decoration.py draw loop after undo by catching ReferenceError and resetting DecoratorData.is_loaded - SetDimensionAnchor: inherit tool.Ifc.Operator so IFC pset writes are tracked for undo; finish the modal after each face write so each anchor gets its own undo step - Fix ReferenceError in _modal after undo when annotation RNA is freed - handler.py: add regenerate_dims_for_layer; call it from EditMaterialSetItem._execute so dimensions update when layer thickness changes - regenerate_dimension.py: fix ForcePerpendicularToFace for LAYER_BOUNDARY anchors by deriving the thickness-axis normal from LayerSetDirection (AXIS2→Y, AXIS1→X, AXIS3→Z) instead of requiring a stored normal_local Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,7 @@ from .. import wrap_usecases
|
||||
from .assign_product import assign_product
|
||||
from .edit_text_literal import edit_text_literal
|
||||
from .regenerate_dimension import regenerate_dimension, get_dimension_segment_lengths
|
||||
from .resolve_anchor import build_anchor_from_hit, make_world_anchor, resolve_anchor
|
||||
from .resolve_anchor import build_anchor_from_hit, build_anchor_from_layer_boundary, build_anchor_from_profile_vert, build_anchor_from_profile_edge, get_layer_snap_candidates, get_profile_snap_candidates, make_world_anchor, resolve_anchor
|
||||
from .unassign_product import unassign_product
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
@@ -34,8 +34,13 @@ wrap_usecases(__path__, __name__)
|
||||
__all__ = [
|
||||
"assign_product",
|
||||
"build_anchor_from_hit",
|
||||
"build_anchor_from_layer_boundary",
|
||||
"build_anchor_from_profile_edge",
|
||||
"build_anchor_from_profile_vert",
|
||||
"edit_text_literal",
|
||||
"get_dimension_segment_lengths",
|
||||
"get_layer_snap_candidates",
|
||||
"get_profile_snap_candidates",
|
||||
"make_world_anchor",
|
||||
"regenerate_dimension",
|
||||
"resolve_anchor",
|
||||
|
||||
@@ -296,33 +296,44 @@ def _get_anchor_face_normal_world(
|
||||
) -> Optional[tuple[float, float, float]]:
|
||||
"""Return the world-space unit face normal stored in a FACE anchor, or None.
|
||||
|
||||
Prefers ``normal_local`` (element-local, rotation-invariant) transformed by
|
||||
the current element placement. Falls back to the stored world-space normal.
|
||||
Reads ``normal_local`` (element-local, rotation-invariant) from the anchor
|
||||
addr and rotates it to world space via the current element placement.
|
||||
Also accepts the legacy ``addr.fingerprint.normal_local`` format.
|
||||
"""
|
||||
if anchor.get("type") != "FACE":
|
||||
return None
|
||||
guid = anchor.get("guid")
|
||||
if not guid:
|
||||
return None
|
||||
fp = (anchor.get("addr") or {}).get("fingerprint") or {}
|
||||
try:
|
||||
element = file.by_guid(guid)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
normal_local = fp.get("normal_local")
|
||||
if normal_local:
|
||||
try:
|
||||
element = file.by_guid(guid)
|
||||
except Exception:
|
||||
addr = anchor.get("addr") or {}
|
||||
from .resolve_anchor import _rotate_local_to_world
|
||||
|
||||
if addr.get("method") == "LAYER_BOUNDARY":
|
||||
import ifcopenshell.util.element as _ifc_elem
|
||||
usage = _ifc_elem.get_material(element, should_inherit=True)
|
||||
if not usage or not usage.is_a("IfcMaterialLayerSetUsage"):
|
||||
return None
|
||||
axis = (getattr(usage, "LayerSetDirection", None) or "AXIS2")
|
||||
if axis == "AXIS1":
|
||||
normal_local: tuple = (1.0, 0.0, 0.0)
|
||||
elif axis == "AXIS3":
|
||||
normal_local = (0.0, 0.0, 1.0)
|
||||
else:
|
||||
normal_local = (0.0, 1.0, 0.0)
|
||||
else:
|
||||
# FACE_NORMAL: normal_local stored in addr (new) or addr.fingerprint (legacy).
|
||||
normal_local = addr.get("normal_local") or (addr.get("fingerprint") or {}).get("normal_local")
|
||||
if not normal_local:
|
||||
return None
|
||||
from .resolve_anchor import _rotate_local_to_world
|
||||
n = _rotate_local_to_world(element, normal_local, placement_override)
|
||||
mag = math.sqrt(n[0] ** 2 + n[1] ** 2 + n[2] ** 2)
|
||||
return (n[0] / mag, n[1] / mag, n[2] / mag) if mag > 1e-12 else None
|
||||
|
||||
normal_world = fp.get("normal")
|
||||
if normal_world:
|
||||
mag = math.sqrt(sum(x * x for x in normal_world))
|
||||
return tuple(x / mag for x in normal_world) if mag > 1e-12 else None # type: ignore[return-value]
|
||||
|
||||
return None
|
||||
n = _rotate_local_to_world(element, normal_local, placement_override)
|
||||
mag = math.sqrt(n[0] ** 2 + n[1] ** 2 + n[2] ** 2)
|
||||
return (n[0] / mag, n[1] / mag, n[2] / mag) if mag > 1e-12 else None
|
||||
|
||||
|
||||
def _get_line_offset_direction(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user