Hide zero-length terminator segments from UI and suppress empty alignment warnings

Zero-length segments are required by IFC to mark alignment ends but should
be invisible to users. This change:
- Adds helper methods to detect zero-length and empty layouts
- Silently skips geometry generation for empty alignments (no error messages)
- Excludes zero-length segments from Outliner display
- Uses separate visible segment counter for consistent naming

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
DesertSpringsCivil
2026-02-02 15:34:07 -07:00
parent 95952d03f8
commit fd85b29777
+75 -4
View File
@@ -196,6 +196,57 @@ class Alignment:
return ((bc_x, bc_y), (ec_x, ec_y)) return ((bc_x, bc_y), (ec_x, ec_y))
# =========================================================================
# Zero-Length Segment Utilities
# =========================================================================
@classmethod
def is_zero_length_segment(cls, segment: "ifcopenshell.entity_instance") -> bool:
"""Check if a segment is a zero-length terminator segment.
Zero-length segments are required by IFC to mark the end of an alignment
but should be invisible to users in the UI.
Args:
segment: The IfcAlignmentSegment entity
Returns:
True if this is a zero-length segment
"""
if not hasattr(segment, "DesignParameters") or not segment.DesignParameters:
return False
dp = segment.DesignParameters
# Check based on segment type
if dp.is_a("IfcAlignmentHorizontalSegment"):
return abs(dp.SegmentLength) < 1e-6
elif dp.is_a("IfcAlignmentVerticalSegment"):
return abs(dp.HorizontalLength) < 1e-6
elif dp.is_a("IfcAlignmentCantSegment"):
return abs(dp.HorizontalLength) < 1e-6
return False
@classmethod
def layout_has_real_segments(cls, layout: "ifcopenshell.entity_instance") -> bool:
"""Check if a layout has any real (non-zero-length) segments.
An empty layout only has the mandatory zero-length terminator segment.
Args:
layout: The IFC layout entity (IfcAlignmentHorizontal, etc.)
Returns:
True if the layout has at least one real segment
"""
for rel in getattr(layout, "IsNestedBy", []) or []:
for segment in rel.RelatedObjects or []:
if segment.is_a("IfcAlignmentSegment"):
if not cls.is_zero_length_segment(segment):
return True
return False
# ========================================================================= # =========================================================================
# Blender Object Creation # Blender Object Creation
# ========================================================================= # =========================================================================
@@ -290,18 +341,24 @@ class Alignment:
is configured (for handling large geospatial coordinates), the vertices is configured (for handling large geospatial coordinates), the vertices
are transformed to Blender local coordinates. are transformed to Blender local coordinates.
Empty alignments (only zero-length terminator segment) are silently skipped.
Args: Args:
layout: The IFC layout entity (IfcAlignmentHorizontal, etc.) layout: The IFC layout entity (IfcAlignmentHorizontal, etc.)
parent_obj: The parent Blender object (alignment object) parent_obj: The parent Blender object (alignment object)
Returns: Returns:
The created Blender curve object, or None if no representation The created Blender curve object, or None if no representation or empty
""" """
import ifcopenshell.api.alignment as align_api import ifcopenshell.api.alignment as align_api
from ifcopenshell.api.alignment import util as align_util from ifcopenshell.api.alignment import util as align_util
import ifcopenshell.util.geolocation import ifcopenshell.util.geolocation
import ifcopenshell.util.unit import ifcopenshell.util.unit
# Skip empty layouts (only zero-length terminator) - no error message needed
if not cls.layout_has_real_segments(layout):
return None
# Get the layout's curve representation # Get the layout's curve representation
try: try:
rep_curve = align_api.get_layout_curve(layout) rep_curve = align_api.get_layout_curve(layout)
@@ -397,14 +454,22 @@ class Alignment:
Creates an empty (no geometry) for segment selection/editing. Creates an empty (no geometry) for segment selection/editing.
The layout curve handles visualization. The layout curve handles visualization.
Zero-length segments (required terminators) are skipped as they should
be invisible to users.
Args: Args:
segment: The IfcAlignmentSegment entity segment: The IfcAlignmentSegment entity
index: The segment index (for naming) index: The segment index (for naming)
parent_obj: The parent Blender object (layout object) parent_obj: The parent Blender object (layout object)
Returns: Returns:
The created Blender object, or existing one if already linked The created Blender object, or existing one if already linked,
or None for zero-length segments
""" """
# Skip zero-length segments - they are required terminators but should be invisible
if cls.is_zero_length_segment(segment):
return None
# Check if a Blender object already exists for this IFC element # Check if a Blender object already exists for this IFC element
existing_obj = tool.Ifc.get_object(segment) existing_obj = tool.Ifc.get_object(segment)
if existing_obj: if existing_obj:
@@ -508,12 +573,18 @@ class Alignment:
result_objs.append(curve_obj) result_objs.append(curve_obj)
# Create empty objects for each segment (for selection/editing) # Create empty objects for each segment (for selection/editing)
# Use a separate counter for visible segments to ensure consistent numbering
visible_index = 0
for rel in getattr(layout, "IsNestedBy", []) or []: for rel in getattr(layout, "IsNestedBy", []) or []:
for i, segment in enumerate(rel.RelatedObjects or []): for segment in rel.RelatedObjects or []:
if segment.is_a() == "IfcAlignmentSegment": if segment.is_a() == "IfcAlignmentSegment":
seg_obj = cls._create_segment_empty(segment, i, layout_obj) # Skip zero-length segments (they are invisible terminators)
if cls.is_zero_length_segment(segment):
continue
seg_obj = cls._create_segment_empty(segment, visible_index, layout_obj)
if seg_obj: if seg_obj:
result_objs.append(seg_obj) result_objs.append(seg_obj)
visible_index += 1 # Always increment for consistent numbering
return result_objs return result_objs