fix: Address PR #7589 review items - prefix rename, cleanup, copyright

- Rename all saikei.* operator idnames to civil.* per Bonsai convention
- Rename SAIKEI_OT_*, SAIKEI_PT_*, SAIKEI_UL_* classes to CIVIL_* prefix
- Rename SaikeiAlignmentProperties -> CivilAlignmentProperties
- Rename saikei_* Blender object custom property keys to civil_*
- Remove IOS-version-compat try/except fallback in _get_segment_vertices_in_model_units()
  now that Rick's segment_vertices() API accepts IfcAlignmentSegment directly
- Remove try/except wrapper around get_alignment() - call directly
- Add Michael Yoder copyright to __init__.py and operator.py
- Fix misleading coordinate comment (IFC -> global easting/northing)
- Document props.pis coordinate system (global E/N) in AlignmentPI and operator comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DesertSpringsCivil
2026-03-03 13:39:19 -07:00
parent 9a7cd5b373
commit 0b3967609c
5 changed files with 104 additions and 111 deletions
@@ -1,5 +1,5 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>, 2026 Michael Yoder <myoder@desertspringscivil.com>
#
# This file is part of Bonsai.
#
@@ -25,28 +25,28 @@ classes = (
# Property groups (must be registered before classes that use them)
prop.AlignmentPI,
prop.AlignmentDisplayRow,
prop.SaikeiAlignmentProperties,
prop.CivilAlignmentProperties,
# UILists
ui.SAIKEI_UL_alignment_pis,
ui.CIVIL_UL_alignment_pis,
operator.ImportAlignmentCSV,
# Operators - PI Management
operator.SAIKEI_OT_add_pi,
operator.SAIKEI_OT_remove_pi,
operator.SAIKEI_OT_pick_pi_from_viewport,
operator.SAIKEI_OT_recalculate_pis,
operator.SAIKEI_OT_clear_pis,
operator.CIVIL_OT_add_pi,
operator.CIVIL_OT_remove_pi,
operator.CIVIL_OT_pick_pi_from_viewport,
operator.CIVIL_OT_recalculate_pis,
operator.CIVIL_OT_clear_pis,
# Operators - Creation
operator.SAIKEI_OT_create_alignment_by_pi,
operator.SAIKEI_OT_import_alignment_csv,
operator.CIVIL_OT_create_alignment_by_pi,
operator.CIVIL_OT_import_alignment_csv,
# Operators - Stationing
operator.SAIKEI_OT_add_stationing_referent,
operator.SAIKEI_OT_name_segments,
operator.CIVIL_OT_add_stationing_referent,
operator.CIVIL_OT_name_segments,
# Operators - PI Edit Mode
operator.SAIKEI_OT_enter_pi_edit_mode,
operator.CIVIL_OT_enter_pi_edit_mode,
# UI Panels (appear in Properties sidebar under CIVIL tab)
ui.SAIKEI_PT_alignment_creation,
ui.SAIKEI_PT_pi_editor,
ui.SAIKEI_PT_alignment_stationing,
ui.CIVIL_PT_alignment_creation,
ui.CIVIL_PT_pi_editor,
ui.CIVIL_PT_alignment_stationing,
)
@@ -55,10 +55,10 @@ def menu_func_import(self, context):
def register():
bpy.types.Scene.SaikeiAlignmentProperties = bpy.props.PointerProperty(type=prop.SaikeiAlignmentProperties)
bpy.types.Scene.CivilAlignmentProperties = bpy.props.PointerProperty(type=prop.CivilAlignmentProperties)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
del bpy.types.Scene.SaikeiAlignmentProperties
del bpy.types.Scene.CivilAlignmentProperties
@@ -1,5 +1,5 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>, 2026 Michael Yoder <myoder@desertspringscivil.com>
#
# This file is part of Bonsai.
#
@@ -190,7 +190,7 @@ def on_radius_changed(pi, context):
2. Rebuild of display_rows (Mid point becomes Curve segment)
3. If an active alignment exists, regeneration of IFC entities
"""
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
recalculate_pi_geometry(props)
# If there's an active alignment, trigger IFC regeneration
@@ -311,10 +311,10 @@ def rebuild_display_rows(props):
# =============================================================================
class SAIKEI_OT_add_pi(Operator):
class CIVIL_OT_add_pi(Operator):
"""Add a new PI point to the list"""
bl_idname = "saikei.add_pi"
bl_idname = "civil.add_pi"
bl_label = "Add PI"
bl_description = "Add a new PI (Point of Intersection) to the alignment"
bl_options = {"REGISTER", "UNDO"}
@@ -324,7 +324,7 @@ class SAIKEI_OT_add_pi(Operator):
return poll_ifc4x3(cls, context)
def execute(self, context):
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# Add new PI
pi = props.pis.add()
@@ -363,10 +363,10 @@ class SAIKEI_OT_add_pi(Operator):
return {"FINISHED"}
class SAIKEI_OT_remove_pi(Operator):
class CIVIL_OT_remove_pi(Operator):
"""Remove the selected PI point"""
bl_idname = "saikei.remove_pi"
bl_idname = "civil.remove_pi"
bl_label = "Remove PI"
bl_description = "Remove the selected PI from the alignment"
bl_options = {"REGISTER", "UNDO"}
@@ -375,7 +375,7 @@ class SAIKEI_OT_remove_pi(Operator):
def poll(cls, context):
if not poll_ifc4x3(cls, context):
return False
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
if len(props.pis) == 0:
cls.poll_message_set("No PIs to remove")
return False
@@ -389,7 +389,7 @@ class SAIKEI_OT_remove_pi(Operator):
return True
def execute(self, context):
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# Get the PI index from the selected display row
pi_index = -1
@@ -420,10 +420,10 @@ class SAIKEI_OT_remove_pi(Operator):
return {"FINISHED"}
class SAIKEI_OT_pick_pi_from_viewport(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator):
class CIVIL_OT_pick_pi_from_viewport(bpy.types.Operator, PolylineOperator, tool.Ifc.Operator):
"""Add PI points by clicking in the 3D viewport using polyline tools"""
bl_idname = "saikei.pick_pi_from_viewport"
bl_idname = "civil.pick_pi_from_viewport"
bl_label = "Pick PI from Viewport"
bl_description = "Click in the viewport to add PI points with snapping and numeric input. RMB/Enter to finish, ESC to cancel."
bl_options = {"REGISTER", "UNDO"}
@@ -554,7 +554,7 @@ class SAIKEI_OT_pick_pi_from_viewport(bpy.types.Operator, PolylineOperator, tool
Polyline points are in Blender coordinate space. This method converts
each point to IFC coordinate space before storing in props.pis.
"""
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
polyline_props = tool.Model.get_polyline_props()
polyline_data = polyline_props.insertion_polyline
if not polyline_data:
@@ -566,7 +566,7 @@ class SAIKEI_OT_pick_pi_from_viewport(bpy.types.Operator, PolylineOperator, tool
num_points = len(polyline_points)
for i, point in enumerate(polyline_points):
# Convert Blender space -> IFC easting/northing
# Convert Blender space -> global easting/northing (props.pis stores global E/N coords)
ifc_coord = tool.Georeference.xyz2enh((point.x, point.y, 0.0))
pi = props.pis.add()
@@ -584,10 +584,10 @@ class SAIKEI_OT_pick_pi_from_viewport(bpy.types.Operator, PolylineOperator, tool
rebuild_display_rows(props)
class SAIKEI_OT_recalculate_pis(Operator, tool.Ifc.Operator):
class CIVIL_OT_recalculate_pis(Operator, tool.Ifc.Operator):
"""Recalculate PI geometry and update IFC/visualization"""
bl_idname = "saikei.recalculate_pis"
bl_idname = "civil.recalculate_pis"
bl_label = "Recalculate PIs"
bl_description = "Recalculate geometry, update IFC segments, and refresh visualization"
bl_options = {"REGISTER", "UNDO"}
@@ -596,7 +596,7 @@ class SAIKEI_OT_recalculate_pis(Operator, tool.Ifc.Operator):
def poll(cls, context):
if not poll_ifc4x3(cls, context):
return False
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
if len(props.pis) < 2:
cls.poll_message_set("Need at least 2 PIs to recalculate")
return False
@@ -606,7 +606,7 @@ class SAIKEI_OT_recalculate_pis(Operator, tool.Ifc.Operator):
import ifcopenshell.api.alignment as align_api
ifc = tool.Ifc.get()
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# Recalculate geometry in UI properties
recalculate_pi_geometry(props)
@@ -619,7 +619,7 @@ class SAIKEI_OT_recalculate_pis(Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Alignment has no horizontal layout")
return
# Collect updated PI data
# Convert global E/N coords (stored in props.pis) -> local IFC coords for the IfcOpenShell API
hpoints = [[float(o) for o in tool.Georeference.enh2xyz((float(pi.e), float(pi.n), 0.), to_blender=False)[:2]] for pi in props.pis]
radii = [pi.radius for pi in props.pis[1:-1]]
@@ -648,10 +648,10 @@ class SAIKEI_OT_recalculate_pis(Operator, tool.Ifc.Operator):
self.report({"INFO"}, f"Recalculated {len(props.pis)} PIs, total length: {total_length:.2f}")
class SAIKEI_OT_clear_pis(Operator, tool.Ifc.Operator):
class CIVIL_OT_clear_pis(Operator, tool.Ifc.Operator):
"""Clear all PI points and optionally remove visualization/IFC data"""
bl_idname = "saikei.clear_pis"
bl_idname = "civil.clear_pis"
bl_label = "Clear All PIs"
bl_description = "Remove all PI points and clear segment visualization"
bl_options = {"REGISTER", "UNDO"}
@@ -660,7 +660,7 @@ class SAIKEI_OT_clear_pis(Operator, tool.Ifc.Operator):
def poll(cls, context):
if not poll_ifc4x3(cls, context):
return False
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
if len(props.pis) == 0:
cls.poll_message_set("No PIs to clear")
return False
@@ -671,7 +671,7 @@ class SAIKEI_OT_clear_pis(Operator, tool.Ifc.Operator):
def _execute(self, context):
ifc = tool.Ifc.get()
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
removed_objects = 0
@@ -703,10 +703,10 @@ class SAIKEI_OT_clear_pis(Operator, tool.Ifc.Operator):
# =============================================================================
class SAIKEI_OT_create_alignment_by_pi(Operator, tool.Ifc.Operator):
class CIVIL_OT_create_alignment_by_pi(Operator, tool.Ifc.Operator):
"""Create alignment using the PI (Point of Intersection) method"""
bl_idname = "saikei.create_alignment_by_pi"
bl_idname = "civil.create_alignment_by_pi"
bl_label = "Create by PI Method"
bl_description = "Create alignment using PI points and curve radii. If an active alignment exists with no segments, adds to it instead of creating new."
bl_options = {"REGISTER", "UNDO"}
@@ -715,7 +715,7 @@ class SAIKEI_OT_create_alignment_by_pi(Operator, tool.Ifc.Operator):
def poll(cls, context):
if not poll_ifc4x3(cls, context):
return False
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
if len(props.pis) < 2:
cls.poll_message_set("Need at least 2 PI points")
return False
@@ -725,8 +725,9 @@ class SAIKEI_OT_create_alignment_by_pi(Operator, tool.Ifc.Operator):
return True
def _execute(self, context):
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# Convert global E/N coords (stored in props.pis) -> local IFC coords for the IfcOpenShell API
hpoints = [[float(o) for o in tool.Georeference.enh2xyz((float(pi.e), float(pi.n), 0.), to_blender=False)[:2]] for pi in props.pis]
radii = [pi.radius for pi in props.pis[1:-1]]
@@ -759,10 +760,10 @@ class SAIKEI_OT_create_alignment_by_pi(Operator, tool.Ifc.Operator):
)
class SAIKEI_OT_import_alignment_csv(Operator, tool.Ifc.Operator, ImportHelper):
class CIVIL_OT_import_alignment_csv(Operator, tool.Ifc.Operator, ImportHelper):
"""Import alignment from CSV file"""
bl_idname = "saikei.import_alignment_csv"
bl_idname = "civil.import_alignment_csv"
bl_label = "Import Alignment CSV"
bl_description = "Import alignment definition from a CSV file"
bl_options = {"REGISTER", "UNDO"}
@@ -776,7 +777,7 @@ class SAIKEI_OT_import_alignment_csv(Operator, tool.Ifc.Operator, ImportHelper):
def _execute(self, context):
ifc = tool.Ifc.get()
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
alignment = ifcopenshell.api.alignment.create_from_csv(ifc, self.filepath)
@@ -795,10 +796,10 @@ class SAIKEI_OT_import_alignment_csv(Operator, tool.Ifc.Operator, ImportHelper):
# =============================================================================
class SAIKEI_OT_add_stationing_referent(Operator, tool.Ifc.Operator):
class CIVIL_OT_add_stationing_referent(Operator, tool.Ifc.Operator):
"""Add a stationing referent to the alignment"""
bl_idname = "saikei.add_stationing_referent"
bl_idname = "civil.add_stationing_referent"
bl_label = "Add Stationing Referent"
bl_description = "Add an IfcReferent for stationing"
bl_options = {"REGISTER", "UNDO"}
@@ -819,7 +820,7 @@ class SAIKEI_OT_add_stationing_referent(Operator, tool.Ifc.Operator):
def poll(cls, context):
if not poll_ifc4x3(cls, context):
return False
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
if props.active_alignment_id == 0:
cls.poll_message_set("Select an alignment first")
return False
@@ -827,7 +828,7 @@ class SAIKEI_OT_add_stationing_referent(Operator, tool.Ifc.Operator):
def invoke(self, context, event):
# Default station to start_station from props
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
self.station = props.start_station
return context.window_manager.invoke_props_dialog(self)
@@ -841,7 +842,7 @@ class SAIKEI_OT_add_stationing_referent(Operator, tool.Ifc.Operator):
def _execute(self, context):
ifc = tool.Ifc.get()
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
alignment = tool.Alignment.get_active_alignment()
if alignment is None:
@@ -883,10 +884,10 @@ def format_station(station_value):
return f"{main}+{offset:05.2f}"
class SAIKEI_OT_name_segments(Operator, tool.Ifc.Operator):
class CIVIL_OT_name_segments(Operator, tool.Ifc.Operator):
"""Auto-name segments based on station values"""
bl_idname = "saikei.name_segments"
bl_idname = "civil.name_segments"
bl_label = "Name Segments"
bl_description = "Automatically name segments with station-based labels"
bl_options = {"REGISTER", "UNDO"}
@@ -895,7 +896,7 @@ class SAIKEI_OT_name_segments(Operator, tool.Ifc.Operator):
def poll(cls, context):
if not poll_ifc4x3(cls, context):
return False
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
if props.active_alignment_id == 0:
cls.poll_message_set("Select an alignment first")
return False
@@ -903,7 +904,7 @@ class SAIKEI_OT_name_segments(Operator, tool.Ifc.Operator):
def _execute(self, context):
ifc = tool.Ifc.get()
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
alignment = tool.Alignment.get_active_alignment()
if alignment is None:
@@ -920,10 +921,10 @@ class SAIKEI_OT_name_segments(Operator, tool.Ifc.Operator):
# =============================================================================
class SAIKEI_OT_enter_pi_edit_mode(Operator, tool.Ifc.Operator):
class CIVIL_OT_enter_pi_edit_mode(Operator, tool.Ifc.Operator):
"""Enter PI editing mode - move PIs with G key, press Enter to apply or Escape to cancel"""
bl_idname = "saikei.enter_pi_edit_mode"
bl_idname = "civil.enter_pi_edit_mode"
bl_label = "Edit PIs"
bl_description = "Enter PI edit mode. Move PI points with G key. Press Enter to apply changes, Escape to cancel."
bl_options = {"REGISTER", "UNDO"}
@@ -938,7 +939,7 @@ class SAIKEI_OT_enter_pi_edit_mode(Operator, tool.Ifc.Operator):
def poll(cls, context):
if not poll_ifc4x3(cls, context):
return False
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
if props.is_pi_edit_mode:
cls.poll_message_set("Already in PI edit mode")
return False
@@ -956,7 +957,7 @@ class SAIKEI_OT_enter_pi_edit_mode(Operator, tool.Ifc.Operator):
return IfcStore.execute_ifc_operator(self, context, event, method="INVOKE")
def _invoke(self, context, event):
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
self._alignment_id = props.active_alignment_id
# Enter edit mode via core layer (validates and creates empties)
@@ -999,7 +1000,7 @@ class SAIKEI_OT_enter_pi_edit_mode(Operator, tool.Ifc.Operator):
return IfcStore.execute_ifc_operator(self, context, event, method="MODAL")
def _modal(self, context, event):
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# Safety: check if empties still exist (handles undo edge case)
if not self._empties_still_exist():
@@ -1040,7 +1041,7 @@ class SAIKEI_OT_enter_pi_edit_mode(Operator, tool.Ifc.Operator):
def _cleanup_and_finish(self, context, apply: bool):
"""Exit edit mode, optionally applying changes."""
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
try:
if apply:
@@ -50,9 +50,11 @@ class AlignmentPI(PropertyGroup):
- Interior PIs: Points where tangents intersect, optionally with curves
"""
# Coordinates
e: StringProperty(name="E", description="Easting", default="0.0")
n: StringProperty(name="N", description="Northing", default="0.0")
# Coordinates stored as global easting/northing (map coordinates).
# Coordinate flow: Blender coords -> xyz2enh() -> global E/N (stored here)
# global E/N -> enh2xyz(to_blender=False) -> local IFC coords (for IfcOpenShell API)
e: StringProperty(name="E", description="Easting (global map coordinates)", default="0.0")
n: StringProperty(name="N", description="Northing (global map coordinates)", default="0.0")
# PI Type
pi_type: EnumProperty(
@@ -146,7 +148,7 @@ class AlignmentDisplayRow(PropertyGroup):
arc_length: FloatProperty(name="Arc Length", default=0.0, precision=2, unit="LENGTH")
class SaikeiAlignmentProperties(PropertyGroup):
class CivilAlignmentProperties(PropertyGroup):
"""Properties for the alignment module"""
# Active alignment selection
+20 -20
View File
@@ -38,7 +38,7 @@ def is_ifc4x3():
# =============================================================================
class SAIKEI_UL_alignment_pis(UIList):
class CIVIL_UL_alignment_pis(UIList):
"""UIList for displaying interleaved points and segments (Civil 3D style)
Row types:
@@ -123,11 +123,11 @@ class SAIKEI_UL_alignment_pis(UIList):
# =============================================================================
class SAIKEI_PT_alignment_creation(Panel):
class CIVIL_PT_alignment_creation(Panel):
"""Sub-panel for alignment creation tools"""
bl_label = "Creation"
bl_idname = "SAIKEI_PT_alignment_creation"
bl_idname = "CIVIL_PT_alignment_creation"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -140,7 +140,7 @@ class SAIKEI_PT_alignment_creation(Panel):
def draw(self, context):
layout = self.layout
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# New alignment properties
box = layout.box()
@@ -150,7 +150,7 @@ class SAIKEI_PT_alignment_creation(Panel):
# Creation operators
col = layout.column(align=True)
col.operator("saikei.create_alignment_by_pi", icon="CURVE_DATA")
col.operator("civil.create_alignment_by_pi", icon="CURVE_DATA")
# =============================================================================
@@ -158,11 +158,11 @@ class SAIKEI_PT_alignment_creation(Panel):
# =============================================================================
class SAIKEI_PT_pi_editor(Panel):
class CIVIL_PT_pi_editor(Panel):
"""Sub-panel for PI point table editor (Civil 3D style grid view)"""
bl_label = "PI Editor"
bl_idname = "SAIKEI_PT_pi_editor"
bl_idname = "CIVIL_PT_pi_editor"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -175,7 +175,7 @@ class SAIKEI_PT_pi_editor(Panel):
def draw(self, context):
layout = self.layout
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# PI Edit Mode indicator
if props.is_pi_edit_mode:
@@ -193,7 +193,7 @@ class SAIKEI_PT_pi_editor(Panel):
if props.active_alignment_id != 0:
box = layout.box()
box.label(text="Edit Alignment:", icon="EDITMODE_HLT")
box.operator("saikei.enter_pi_edit_mode", icon="PIVOT_CURSOR", text="Edit PIs (G key)")
box.operator("civil.enter_pi_edit_mode", icon="PIVOT_CURSOR", text="Edit PIs (G key)")
layout.separator()
# Header row with column labels
@@ -208,7 +208,7 @@ class SAIKEI_PT_pi_editor(Panel):
# Combined point/segment list (interleaved view)
row = layout.row()
row.template_list(
"SAIKEI_UL_alignment_pis",
"CIVIL_UL_alignment_pis",
"",
props,
"display_rows",
@@ -219,16 +219,16 @@ class SAIKEI_PT_pi_editor(Panel):
# Side buttons for list management
col = row.column(align=True)
col.operator("saikei.add_pi", icon="ADD", text="")
col.operator("saikei.remove_pi", icon="REMOVE", text="")
col.operator("civil.add_pi", icon="ADD", text="")
col.operator("civil.remove_pi", icon="REMOVE", text="")
col.separator()
col.operator("saikei.pick_pi_from_viewport", icon="EYEDROPPER", text="")
col.operator("civil.pick_pi_from_viewport", icon="EYEDROPPER", text="")
# Bottom actions
layout.separator()
row = layout.row(align=True)
row.operator("saikei.recalculate_pis", icon="FILE_REFRESH", text="Recalculate")
row.operator("saikei.clear_pis", icon="TRASH", text="Clear All")
row.operator("civil.recalculate_pis", icon="FILE_REFRESH", text="Recalculate")
row.operator("civil.clear_pis", icon="TRASH", text="Clear All")
# =============================================================================
@@ -236,11 +236,11 @@ class SAIKEI_PT_pi_editor(Panel):
# =============================================================================
class SAIKEI_PT_alignment_stationing(Panel):
class CIVIL_PT_alignment_stationing(Panel):
"""Sub-panel for stationing and referents"""
bl_label = "Stationing"
bl_idname = "SAIKEI_PT_alignment_stationing"
bl_idname = "CIVIL_PT_alignment_stationing"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
@@ -253,7 +253,7 @@ class SAIKEI_PT_alignment_stationing(Panel):
def draw(self, context):
layout = self.layout
props = context.scene.SaikeiAlignmentProperties
props = context.scene.CivilAlignmentProperties
# Station display options
box = layout.box()
@@ -265,5 +265,5 @@ class SAIKEI_PT_alignment_stationing(Panel):
# Stationing operators
col = layout.column(align=True)
col.operator("saikei.add_stationing_referent", icon="EMPTY_AXIS")
col.operator("saikei.name_segments", icon="FONT_DATA")
col.operator("civil.add_stationing_referent", icon="EMPTY_AXIS")
col.operator("civil.name_segments", icon="FONT_DATA")
+15 -25
View File
@@ -273,17 +273,10 @@ class Alignment:
return None
return (point[0] / unit_scale, point[1] / unit_scale)
try:
start, end, ti, ni = align_api.segment_vertices(ifc_file, segment)
except Exception:
try:
mapped = align_api.get_mapped_segments(segment)
curve_segment = mapped[0]
if curve_segment is None:
return None
start, end, ti, ni = align_api.segment_vertices(ifc_file, curve_segment)
except Exception:
return None
result = align_api.segment_vertices(ifc_file, segment)
if result is None:
return None
start, end, ti, ni = result
return (convert(start), convert(end), convert(ti), convert(ni))
@@ -658,7 +651,7 @@ class Alignment:
the Blender UI properties.
Args:
props: The SaikeiAlignmentProperties PropertyGroup
props: The CivilAlignmentProperties PropertyGroup
geometry_result: PIGeometryResult from core.alignment
"""
pis = props.pis
@@ -780,12 +773,9 @@ class Alignment:
Returns:
The parent IfcAlignment if found, None otherwise
"""
try:
import ifcopenshell.api.alignment as align_api
import ifcopenshell.api.alignment as align_api
return align_api.get_alignment(layout)
except Exception:
return None
return align_api.get_alignment(layout)
@classmethod
def safe_layout_horizontal_by_pi_method(
@@ -970,11 +960,11 @@ class Alignment:
empty.location = blender_pos
# Tag with custom properties for identification
empty["saikei_is_pi_empty"] = True
empty["saikei_pi_index"] = i
empty["saikei_pi_radius"] = pi["radius"]
empty["saikei_alignment_id"] = alignment_id
empty["saikei_pi_type"] = pi["pi_type"]
empty["civil_is_pi_empty"] = True
empty["civil_pi_index"] = i
empty["civil_pi_radius"] = pi["radius"]
empty["civil_alignment_id"] = alignment_id
empty["civil_pi_type"] = pi["pi_type"]
# Parent to alignment object
empty.parent = alignment_obj
@@ -1002,11 +992,11 @@ class Alignment:
empties = []
for obj in bpy.data.objects:
if obj.get("saikei_is_pi_empty") and obj.get("saikei_alignment_id") == alignment_id:
if obj.get("civil_is_pi_empty") and obj.get("civil_alignment_id") == alignment_id:
empties.append(obj)
# Sort by PI index
empties.sort(key=lambda e: e.get("saikei_pi_index", 0))
empties.sort(key=lambda e: e.get("civil_pi_index", 0))
return empties
@@ -1059,7 +1049,7 @@ class Alignment:
# Collect radii for interior PIs only (not first or last)
if 0 < i < len(empties) - 1:
radius = empty.get("saikei_pi_radius", 0.0)
radius = empty.get("civil_pi_radius", 0.0)
radii.append(radius)
return (hpoints, radii)