mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 22:50:21 +00:00
Forgot to commit these files
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Saikei Civil - Civil Engineering Tools for Blender
|
||||||
|
# Copyright (c) 2025 Michael Yoder / Desert Springs Civil Engineering PLLC
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
# You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Primary Author: Michael Yoder
|
||||||
|
# Company: Desert Springs Civil Engineering PLLC
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
"""Data caching layer for the alignment module
|
||||||
|
|
||||||
|
This module provides cached access to alignment data for UI display,
|
||||||
|
following Bonsai's data loading pattern.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_ifc_file():
|
||||||
|
"""Get the current IFC file from Bonsai"""
|
||||||
|
try:
|
||||||
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
return tool.Ifc.get()
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AlignmentData:
|
||||||
|
"""Cached alignment data for UI display"""
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
is_loaded = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls):
|
||||||
|
"""Load alignment data from IFC file"""
|
||||||
|
cls.data = {
|
||||||
|
"alignments": [],
|
||||||
|
"active_alignment": None,
|
||||||
|
"segments": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
ifc = get_ifc_file()
|
||||||
|
if ifc is None:
|
||||||
|
cls.is_loaded = True
|
||||||
|
return
|
||||||
|
|
||||||
|
# Load all alignments
|
||||||
|
alignments = ifc.by_type("IfcAlignment")
|
||||||
|
cls.data["alignments"] = [
|
||||||
|
{
|
||||||
|
"id": a.id(),
|
||||||
|
"name": a.Name or f"Alignment {a.id()}",
|
||||||
|
"global_id": a.GlobalId,
|
||||||
|
}
|
||||||
|
for a in alignments
|
||||||
|
]
|
||||||
|
|
||||||
|
cls.is_loaded = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def refresh(cls):
|
||||||
|
"""Force refresh of alignment data"""
|
||||||
|
cls.is_loaded = False
|
||||||
|
cls.load()
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Saikei Civil - Civil Engineering Tools for Blender
|
||||||
|
# Copyright (c) 2025 Michael Yoder / Desert Springs Civil Engineering PLLC
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
# You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Primary Author: Michael Yoder
|
||||||
|
# Company: Desert Springs Civil Engineering PLLC
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
"""Property groups for the alignment module"""
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import PropertyGroup
|
||||||
|
from bpy.props import (
|
||||||
|
StringProperty,
|
||||||
|
FloatProperty,
|
||||||
|
IntProperty,
|
||||||
|
BoolProperty,
|
||||||
|
CollectionProperty,
|
||||||
|
EnumProperty,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pi_type_items(self, context):
|
||||||
|
"""Get available PI types based on position in list"""
|
||||||
|
# First and last PIs are always endpoints (no curve)
|
||||||
|
# Interior PIs can have curves
|
||||||
|
return [
|
||||||
|
("ENDPOINT", "Endpoint", "Start or end point (no curve)"),
|
||||||
|
("TANGENT", "Tangent", "Pass-through point (no curve)"),
|
||||||
|
("CURVE", "Curve", "Point of intersection with curve"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _on_radius_update(self, context):
|
||||||
|
"""Callback when radius property changes.
|
||||||
|
|
||||||
|
This dynamically imports the operator module to call on_radius_changed,
|
||||||
|
avoiding circular imports since prop.py is imported before operator.py.
|
||||||
|
"""
|
||||||
|
from . import operator as ops
|
||||||
|
|
||||||
|
ops.on_radius_changed(self, context)
|
||||||
|
|
||||||
|
|
||||||
|
class AlignmentPI(PropertyGroup):
|
||||||
|
"""Property group for a single PI (Point of Intersection)
|
||||||
|
|
||||||
|
In the PI method, alignments are defined by:
|
||||||
|
- Endpoint PIs: Start (POB) and End (POE) points
|
||||||
|
- Interior PIs: Points where tangents intersect, optionally with curves
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Coordinates
|
||||||
|
x: FloatProperty(
|
||||||
|
name="X",
|
||||||
|
description="X coordinate (Easting)",
|
||||||
|
default=0.0,
|
||||||
|
precision=3,
|
||||||
|
unit="LENGTH",
|
||||||
|
)
|
||||||
|
|
||||||
|
y: FloatProperty(
|
||||||
|
name="Y",
|
||||||
|
description="Y coordinate (Northing)",
|
||||||
|
default=0.0,
|
||||||
|
precision=3,
|
||||||
|
unit="LENGTH",
|
||||||
|
)
|
||||||
|
|
||||||
|
# PI Type
|
||||||
|
pi_type: EnumProperty(
|
||||||
|
name="Type",
|
||||||
|
description="Type of PI point",
|
||||||
|
items=[
|
||||||
|
("ENDPOINT", "Endpoint", "Start or end point (no curve)"),
|
||||||
|
("TANGENT", "Tangent", "Pass-through point (no curve)"),
|
||||||
|
("CURVE", "Curve", "Point of intersection with curve"),
|
||||||
|
],
|
||||||
|
default="TANGENT",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Curve parameters (only used when pi_type == "CURVE")
|
||||||
|
radius: FloatProperty(
|
||||||
|
name="Radius",
|
||||||
|
description="Curve radius (0 = no curve, sharp angle)",
|
||||||
|
default=0.0,
|
||||||
|
min=0.0,
|
||||||
|
precision=3,
|
||||||
|
unit="LENGTH",
|
||||||
|
update=_on_radius_update,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Computed/display values (updated by recalculate operator)
|
||||||
|
length_to_next: FloatProperty(
|
||||||
|
name="Length",
|
||||||
|
description="Length of tangent to next PI",
|
||||||
|
default=0.0,
|
||||||
|
precision=3,
|
||||||
|
unit="LENGTH",
|
||||||
|
)
|
||||||
|
|
||||||
|
direction_to_next: FloatProperty(
|
||||||
|
name="Direction",
|
||||||
|
description="Bearing/direction to next PI (degrees)",
|
||||||
|
default=0.0,
|
||||||
|
precision=4,
|
||||||
|
subtype="ANGLE",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Station at this PI (computed)
|
||||||
|
station: FloatProperty(
|
||||||
|
name="Station",
|
||||||
|
description="Station value at this PI",
|
||||||
|
default=0.0,
|
||||||
|
precision=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Selection state
|
||||||
|
is_selected: BoolProperty(
|
||||||
|
name="Selected",
|
||||||
|
description="Whether this PI is selected for editing",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AlignmentSegmentItem(PropertyGroup):
|
||||||
|
"""Property group for displaying alignment segments in a UIList"""
|
||||||
|
|
||||||
|
name: StringProperty(name="Name", default="")
|
||||||
|
segment_type: StringProperty(name="Type", default="LINE")
|
||||||
|
length: FloatProperty(name="Length", default=0.0, unit="LENGTH")
|
||||||
|
ifc_id: IntProperty(name="IFC ID", default=0)
|
||||||
|
|
||||||
|
|
||||||
|
class AlignmentDisplayRow(PropertyGroup):
|
||||||
|
"""Property group for interleaved point/segment display in the table.
|
||||||
|
|
||||||
|
This creates the Civil 3D-style view where points and segments
|
||||||
|
are shown on separate rows:
|
||||||
|
Point 1 (End)
|
||||||
|
Segment 1 (Tan)
|
||||||
|
Point 2 (Tan)
|
||||||
|
Segment 2 (Tan)
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Row type discriminator
|
||||||
|
row_type: EnumProperty(
|
||||||
|
name="Row Type",
|
||||||
|
items=[
|
||||||
|
("POINT", "Point", "A PI point row"),
|
||||||
|
("SEGMENT", "Segment", "A segment row between points"),
|
||||||
|
],
|
||||||
|
default="POINT",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Segment number (1, 2, 3...) - only for SEGMENT rows
|
||||||
|
segment_number: IntProperty(name="Segment #", default=0)
|
||||||
|
|
||||||
|
# Point index in the pis collection - for both types
|
||||||
|
# For POINT rows: the PI index
|
||||||
|
# For SEGMENT rows: the starting PI index of this segment
|
||||||
|
pi_index: IntProperty(name="PI Index", default=0)
|
||||||
|
|
||||||
|
# Display type string (End, Tan, Curve for points; Tan, Curve for segments)
|
||||||
|
display_type: StringProperty(name="Type", default="")
|
||||||
|
|
||||||
|
# Point coordinates (only for POINT rows)
|
||||||
|
x: FloatProperty(name="X", default=0.0, precision=3, unit="LENGTH")
|
||||||
|
y: FloatProperty(name="Y", default=0.0, precision=3, unit="LENGTH")
|
||||||
|
|
||||||
|
# Segment properties (only for SEGMENT rows)
|
||||||
|
length: FloatProperty(name="Length", default=0.0, precision=2, unit="LENGTH")
|
||||||
|
radius: FloatProperty(name="Radius", default=0.0, precision=2, unit="LENGTH")
|
||||||
|
arc_length: FloatProperty(name="Arc Length", default=0.0, precision=2, unit="LENGTH")
|
||||||
|
|
||||||
|
|
||||||
|
class SaikeiAlignmentProperties(PropertyGroup):
|
||||||
|
"""Properties for the alignment module"""
|
||||||
|
|
||||||
|
# Active alignment selection
|
||||||
|
active_alignment_id: IntProperty(
|
||||||
|
name="Active Alignment ID",
|
||||||
|
description="IFC entity ID of the active alignment",
|
||||||
|
default=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
active_alignment_name: StringProperty(
|
||||||
|
name="Active Alignment",
|
||||||
|
description="Name of the currently active alignment",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
|
# New alignment creation properties
|
||||||
|
new_alignment_name: StringProperty(
|
||||||
|
name="Name",
|
||||||
|
description="Name for new alignment",
|
||||||
|
default="Alignment 1",
|
||||||
|
)
|
||||||
|
|
||||||
|
start_station: FloatProperty(
|
||||||
|
name="Start Station",
|
||||||
|
description="Starting station value (e.g., 10000 for 100+00)",
|
||||||
|
default=10000.0,
|
||||||
|
min=0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# PI collection for PI method creation
|
||||||
|
pis: CollectionProperty(type=AlignmentPI)
|
||||||
|
active_pi_index: IntProperty(name="Active PI", default=0)
|
||||||
|
|
||||||
|
# Segment display
|
||||||
|
segments: CollectionProperty(type=AlignmentSegmentItem)
|
||||||
|
active_segment_index: IntProperty(name="Active Segment", default=0)
|
||||||
|
|
||||||
|
# Combined point/segment display rows (for Civil 3D-style table)
|
||||||
|
display_rows: CollectionProperty(type=AlignmentDisplayRow)
|
||||||
|
active_display_row_index: IntProperty(name="Active Display Row", default=0)
|
||||||
|
|
||||||
|
# Editing state
|
||||||
|
is_editing: BoolProperty(
|
||||||
|
name="Is Editing",
|
||||||
|
description="Whether alignment is being edited",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Display options
|
||||||
|
show_pi_markers: BoolProperty(
|
||||||
|
name="Show PI Markers",
|
||||||
|
description="Show PI markers in viewport",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
show_station_labels: BoolProperty(
|
||||||
|
name="Show Station Labels",
|
||||||
|
description="Show station labels along alignment",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
station_interval: FloatProperty(
|
||||||
|
name="Station Interval",
|
||||||
|
description="Interval between station markers",
|
||||||
|
default=100.0,
|
||||||
|
min=1.0,
|
||||||
|
unit="LENGTH",
|
||||||
|
)
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
# ==============================================================================
|
||||||
|
# Saikei Civil - Civil Engineering Tools for Blender
|
||||||
|
# Copyright (c) 2025 Michael Yoder / Desert Springs Civil Engineering PLLC
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
# You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Primary Author: Michael Yoder
|
||||||
|
# Company: Desert Springs Civil Engineering PLLC
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
"""UI panels for the alignment module
|
||||||
|
|
||||||
|
All panels appear in the VIEW_3D N-panel under the "Saikei Civil" tab.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Panel, UIList
|
||||||
|
|
||||||
|
|
||||||
|
def get_ifc_file():
|
||||||
|
"""Get the current IFC file from Bonsai"""
|
||||||
|
try:
|
||||||
|
import bonsai.tool as tool
|
||||||
|
|
||||||
|
return tool.Ifc.get()
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_ifc4x3():
|
||||||
|
"""Check if the current IFC file is IFC4X3 schema"""
|
||||||
|
ifc = get_ifc_file()
|
||||||
|
return ifc is not None and ifc.schema == "IFC4X3"
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# UILists
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SAIKEI_UL_alignment_pis(UIList):
|
||||||
|
"""UIList for displaying interleaved points and segments (Civil 3D style)
|
||||||
|
|
||||||
|
Row types:
|
||||||
|
- POINT rows: End (endpoint), Mid (interior PI without curve)
|
||||||
|
- SEGMENT rows: Tan (tangent line), Curve (circular arc)
|
||||||
|
|
||||||
|
When a Mid point has radius > 0, it becomes a Curve segment row.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
|
if self.layout_type in {"DEFAULT", "COMPACT"}:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
|
||||||
|
if item.row_type == "POINT":
|
||||||
|
# Point row: No., Type, X, Y, Length, Radius
|
||||||
|
row.label(text="") # No segment number for points
|
||||||
|
|
||||||
|
# Type with point/dot icon
|
||||||
|
# "End" = endpoint (POB/POE), "Mid" = interior PI point
|
||||||
|
row.label(text=item.display_type, icon="DOT")
|
||||||
|
|
||||||
|
# X, Y coordinates - get actual PI for editing
|
||||||
|
pi = data.pis[item.pi_index] if item.pi_index < len(data.pis) else None
|
||||||
|
if pi:
|
||||||
|
sub = row.row(align=True)
|
||||||
|
sub.prop(pi, "x", text="")
|
||||||
|
sub.prop(pi, "y", text="")
|
||||||
|
else:
|
||||||
|
row.label(text=f"{item.x:.2f}")
|
||||||
|
row.label(text=f"{item.y:.2f}")
|
||||||
|
|
||||||
|
# Length column - empty for point rows
|
||||||
|
row.label(text="")
|
||||||
|
|
||||||
|
# Radius column - editable for Mid points (where curves can be added)
|
||||||
|
if item.display_type == "Mid" and pi:
|
||||||
|
row.prop(pi, "radius", text="")
|
||||||
|
else:
|
||||||
|
row.label(text="")
|
||||||
|
|
||||||
|
elif item.row_type == "SEGMENT":
|
||||||
|
if item.display_type == "Curve":
|
||||||
|
# Curve segment row: No., Type (arc icon), X, Y, Arc Length, Radius
|
||||||
|
row.label(text=f"{item.segment_number}")
|
||||||
|
row.label(text="Curve", icon="SPHERECURVE")
|
||||||
|
|
||||||
|
# Show PI coordinates on curve row
|
||||||
|
row.label(text=f"{item.x:.2f}")
|
||||||
|
row.label(text=f"{item.y:.2f}")
|
||||||
|
|
||||||
|
# Arc length
|
||||||
|
row.label(text=f"{item.arc_length:.2f}")
|
||||||
|
|
||||||
|
# Radius - editable so user can modify or delete curve (set to 0)
|
||||||
|
pi = data.pis[item.pi_index] if item.pi_index < len(data.pis) else None
|
||||||
|
if pi:
|
||||||
|
row.prop(pi, "radius", text="")
|
||||||
|
else:
|
||||||
|
row.label(text=f"{item.radius:.2f}")
|
||||||
|
else:
|
||||||
|
# Tangent segment row: No., Type (line icon), -, -, Length, -
|
||||||
|
row.label(text=f"{item.segment_number}")
|
||||||
|
row.label(text="Tan", icon="IPO_LINEAR")
|
||||||
|
|
||||||
|
# No X, Y for tangent segments
|
||||||
|
row.label(text="")
|
||||||
|
row.label(text="")
|
||||||
|
|
||||||
|
# Length
|
||||||
|
row.label(text=f"{item.length:.2f}")
|
||||||
|
|
||||||
|
# No radius for tangent segments
|
||||||
|
row.label(text="-")
|
||||||
|
|
||||||
|
elif self.layout_type == "GRID":
|
||||||
|
layout.alignment = "CENTER"
|
||||||
|
layout.label(text="", icon="DECORATE")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Main Panel
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SAIKEI_PT_horizontal_alignment(Panel):
|
||||||
|
"""Main Horizontal Alignment panel in the N-panel"""
|
||||||
|
|
||||||
|
bl_label = "Horizontal Alignment"
|
||||||
|
bl_idname = "SAIKEI_PT_horizontal_alignment"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Saikei Civil"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = context.scene.SaikeiAlignmentProperties
|
||||||
|
|
||||||
|
# Status box
|
||||||
|
box = layout.box()
|
||||||
|
ifc = get_ifc_file()
|
||||||
|
|
||||||
|
if ifc is None:
|
||||||
|
box.label(text="No IFC file loaded", icon="ERROR")
|
||||||
|
box.label(text="Open an IFC4X3 file via Bonsai")
|
||||||
|
return
|
||||||
|
|
||||||
|
if ifc.schema != "IFC4X3":
|
||||||
|
box.label(text=f"Schema: {ifc.schema}", icon="ERROR")
|
||||||
|
box.label(text="Alignments require IFC4X3")
|
||||||
|
return
|
||||||
|
|
||||||
|
# IFC file is loaded and correct schema
|
||||||
|
row = box.row()
|
||||||
|
row.label(text="IFC4X3", icon="CHECKMARK")
|
||||||
|
|
||||||
|
# Count alignments
|
||||||
|
alignments = ifc.by_type("IfcAlignment")
|
||||||
|
row.label(text=f"Alignments: {len(alignments)}")
|
||||||
|
|
||||||
|
# Active alignment selector
|
||||||
|
if alignments:
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text="Active Alignment:", icon="CURVE_PATH")
|
||||||
|
row = box.row()
|
||||||
|
row.prop(props, "active_alignment_name", text="")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Creation Sub-Panel
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SAIKEI_PT_alignment_creation(Panel):
|
||||||
|
"""Sub-panel for alignment creation tools"""
|
||||||
|
|
||||||
|
bl_label = "Creation"
|
||||||
|
bl_idname = "SAIKEI_PT_alignment_creation"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Saikei Civil"
|
||||||
|
bl_parent_id = "SAIKEI_PT_horizontal_alignment"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return is_ifc4x3()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = context.scene.SaikeiAlignmentProperties
|
||||||
|
|
||||||
|
# New alignment properties
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text="New Alignment:", icon="ADD")
|
||||||
|
box.prop(props, "new_alignment_name")
|
||||||
|
box.prop(props, "start_station")
|
||||||
|
|
||||||
|
# Creation operators
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator("saikei.create_alignment", icon="ADD")
|
||||||
|
col.operator("saikei.create_alignment_by_pi", icon="CURVE_PATH")
|
||||||
|
col.operator("saikei.import_alignment_csv", icon="IMPORT")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PI Editor Sub-Panel
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SAIKEI_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_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Saikei Civil"
|
||||||
|
bl_parent_id = "SAIKEI_PT_horizontal_alignment"
|
||||||
|
bl_options = set() # Open by default
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return is_ifc4x3()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = context.scene.SaikeiAlignmentProperties
|
||||||
|
|
||||||
|
# Header row with column labels
|
||||||
|
header = layout.row(align=True)
|
||||||
|
header.label(text="No.")
|
||||||
|
header.label(text="Type")
|
||||||
|
header.label(text="X")
|
||||||
|
header.label(text="Y")
|
||||||
|
header.label(text="Length")
|
||||||
|
header.label(text="Radius")
|
||||||
|
|
||||||
|
# Combined point/segment list (interleaved view)
|
||||||
|
row = layout.row()
|
||||||
|
row.template_list(
|
||||||
|
"SAIKEI_UL_alignment_pis",
|
||||||
|
"",
|
||||||
|
props,
|
||||||
|
"display_rows",
|
||||||
|
props,
|
||||||
|
"active_display_row_index",
|
||||||
|
rows=8,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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.separator()
|
||||||
|
col.operator("saikei.pick_pi_from_viewport", icon="EYEDROPPER", text="")
|
||||||
|
|
||||||
|
# Active item details - show details based on selected row
|
||||||
|
if props.display_rows and 0 <= props.active_display_row_index < len(props.display_rows):
|
||||||
|
active_row = props.display_rows[props.active_display_row_index]
|
||||||
|
|
||||||
|
if active_row.row_type == "POINT" and active_row.pi_index < len(props.pis):
|
||||||
|
pi = props.pis[active_row.pi_index]
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text=f"PI {active_row.pi_index + 1} Details:", icon="PROPERTIES")
|
||||||
|
|
||||||
|
row = box.row()
|
||||||
|
row.prop(pi, "pi_type", text="Type")
|
||||||
|
|
||||||
|
row = box.row(align=True)
|
||||||
|
row.prop(pi, "x", text="X")
|
||||||
|
row.prop(pi, "y", text="Y")
|
||||||
|
|
||||||
|
# Show radius for interior points (can add curve)
|
||||||
|
if pi.pi_type != "ENDPOINT":
|
||||||
|
row = box.row()
|
||||||
|
row.prop(pi, "radius", text="Radius")
|
||||||
|
|
||||||
|
# Display computed values
|
||||||
|
row = box.row()
|
||||||
|
row.label(text=f"Station: {pi.station:.2f}")
|
||||||
|
row.label(text=f"Length: {pi.length_to_next:.2f}")
|
||||||
|
|
||||||
|
elif active_row.row_type == "SEGMENT":
|
||||||
|
if active_row.display_type == "Curve":
|
||||||
|
# Curve segment - show curve details with editable radius
|
||||||
|
pi = props.pis[active_row.pi_index] if active_row.pi_index < len(props.pis) else None
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text=f"Curve {active_row.segment_number} Details:", icon="SPHERECURVE")
|
||||||
|
|
||||||
|
row = box.row()
|
||||||
|
row.label(text=f"PI Location: ({active_row.x:.2f}, {active_row.y:.2f})")
|
||||||
|
|
||||||
|
row = box.row()
|
||||||
|
row.label(text=f"Arc Length: {active_row.arc_length:.2f}")
|
||||||
|
|
||||||
|
# Editable radius
|
||||||
|
if pi:
|
||||||
|
row = box.row()
|
||||||
|
row.prop(pi, "radius", text="Radius")
|
||||||
|
else:
|
||||||
|
row = box.row()
|
||||||
|
row.label(text=f"Radius: {active_row.radius:.2f}")
|
||||||
|
else:
|
||||||
|
# Tangent segment
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text=f"Tangent {active_row.segment_number} Details:", icon="IPO_LINEAR")
|
||||||
|
|
||||||
|
row = box.row()
|
||||||
|
row.label(text=f"Length: {active_row.length:.2f}")
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stationing Sub-Panel
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class SAIKEI_PT_alignment_stationing(Panel):
|
||||||
|
"""Sub-panel for stationing and referents"""
|
||||||
|
|
||||||
|
bl_label = "Stationing"
|
||||||
|
bl_idname = "SAIKEI_PT_alignment_stationing"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Saikei Civil"
|
||||||
|
bl_parent_id = "SAIKEI_PT_horizontal_alignment"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return is_ifc4x3()
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
props = context.scene.SaikeiAlignmentProperties
|
||||||
|
|
||||||
|
# Station display options
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text="Display:", icon="HIDE_OFF")
|
||||||
|
box.prop(props, "show_station_labels")
|
||||||
|
box.prop(props, "station_interval")
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
# Stationing operators
|
||||||
|
col = layout.column(align=True)
|
||||||
|
col.operator("saikei.add_stationing_referent", icon="EMPTY_AXIS")
|
||||||
|
col.operator("saikei.name_segments", icon="FONT_DATA")
|
||||||
Reference in New Issue
Block a user