From 862697baae637c085028e5ffc12126e8daa48617 Mon Sep 17 00:00:00 2001 From: DesertSpringsCivil Date: Mon, 2 Feb 2026 19:00:58 -0700 Subject: [PATCH] Local: Add CLAUDE.md project context This file contains project-specific context for Claude Code sessions. It should NOT be pushed to origin (protected by pre-push hook). Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 804 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 804 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..39e6f794db --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,804 @@ +# CLAUDE.md - Saikei (Civil Engineering) Module Context + +## Project Identity + +**Name:** Saikei (Civil Engineering module for Bonsai) +**Pronunciation:** "SIGH-kay" (盆景 - Japanese for "planted landscape") +**Tagline:** "The landscape around the buildings" +**Repository:** `IfcOpenShell/IfcOpenShell` (branch: `saikei`) +**Location in repo:** `src/bonsai/bonsai/` (integrated into Bonsai) + +### Brand Philosophy +Saikei is the civil engineering complement to Bonsai in the open-source IFC ecosystem: +- **Bonsai** = Buildings (vertical construction) +- **Saikei** = Infrastructure (horizontal construction: roads, earthwork, drainage) + +> "While Bonsai crafts the buildings, Saikei shapes the world around them." + +--- + +## Current Status + +**Saikei has been merged into Bonsai** as of January 2026. It is no longer a separate extension but is now part of the core Bonsai codebase, following Bonsai's architecture patterns. + +### Code Locations +| Component | Path | +|-----------|------| +| Core logic (business rules) | `src/bonsai/bonsai/core/alignment.py` | +| Tool layer (implementations) | `src/bonsai/bonsai/tool/alignment.py` | +| UI module | `src/bonsai/bonsai/bim/module/alignment/` | +| GPU decorators | `src/bonsai/bonsai/bim/module/alignment/decorator.py` | + +--- + +## Mission & Vision + +### Mission +Democratize professional civil engineering tools by providing free, open-source alternatives to expensive commercial software like Civil 3D and OpenRoads. + +### Target Users +- Small engineering firms seeking cost-effective tools +- Engineers in developing countries without software budgets +- Students and educators +- Land surveyors and GIS professionals + +### Core Philosophy: Native IFC +**"We're not converting TO IFC. We ARE IFC."** + +Unlike traditional CAD software that exports to IFC, Saikei works **IN** IFC format from the very first action. The IFC file is the single source of truth, and Blender is the visualization/interaction layer. + +--- + +## Architecture (Bonsai Pattern) + +Saikei follows Bonsai's three-layer architecture: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ BONSAI │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ bim/module/alignment/ (UI Layer) │ │ +│ │ ├── ui.py Panels, UILists │ │ +│ │ ├── operator.py Blender operators │ │ +│ │ ├── prop.py PropertyGroups │ │ +│ │ ├── data.py Cached data for UI │ │ +│ │ └── decorator.py GPU drawing for visual feedback │ │ +│ │ │ │ +│ │ WHO: User interaction, visual feedback │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ tool/alignment.py (Tool Layer) │ │ +│ │ │ │ +│ │ HOW: ALL implementations including: │ │ +│ │ • Math and calculations (geometry, vectors, stations) │ │ +│ │ • Algorithms (curve fitting, segment generation) │ │ +│ │ • IFC entity creation and manipulation │ │ +│ │ • Blender object creation and management │ │ +│ │ • Coordinate transformations │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ core/alignment.py (Core Layer) │ │ +│ │ │ │ +│ │ WHAT: Business logic and workflow orchestration ONLY │ │ +│ │ • NO math, calculations, or algorithms │ │ +│ │ • NO IFC entity creation (delegates to Tool) │ │ +│ │ • NO bpy imports │ │ +│ │ • Defines business rules and validation │ │ +│ │ • Orchestrates workflow (calls Tool methods) │ │ +│ │ • Testable outside Blender │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Layer Responsibilities + +#### 1. Core Layer (`core/alignment.py`) — WHAT happens + +**Purpose:** Business logic and workflow orchestration ONLY + +**Contains:** +- Business rules ("an alignment must have at least 2 PIs") +- Validation logic ("radius must be positive") +- Workflow orchestration ("when PI moves, regenerate segments") +- Decision-making about what should happen + +**Does NOT contain:** +- Math or calculations (no `T = R × tan(Δ/2)`) +- Algorithms (no curve fitting, no geometry) +- IFC entity creation (no `ifc.create_entity()`) +- Blender operations (no `bpy` imports) + +**Example (CORRECT):** +```python +# core/alignment.py - Business logic ONLY +def add_curve_at_pi(ifc, alignment_tool, alignment_id, pi_index, radius): + """Orchestrates adding a curve - delegates math to Tool.""" + alignment = ifc.get_entity(alignment_id) + + # Business rule validation + if not alignment_tool.can_add_curve_at_pi(alignment, pi_index): + raise ValueError("Cannot add curve at this PI") + + if radius <= 0: + raise ValueError("Radius must be positive") + + # Delegate ALL math and IFC creation to Tool layer + curve_data = alignment_tool.create_curve_at_pi(alignment, pi_index, radius) + + # Business rule: trigger regeneration + alignment_tool.regenerate_segments(alignment) + + return curve_data +``` + +#### 2. Tool Layer (`tool/alignment.py`) — HOW it happens + +**Purpose:** All implementations - math, algorithms, IFC, Blender + +**Contains:** +- Mathematical calculations (tangent length, arc length, deflection angles) +- Geometric algorithms (curve geometry, vector operations) +- IFC entity creation and manipulation +- Blender object creation and management +- Coordinate transformations +- Station calculations + +**Example (CORRECT):** +```python +# tool/alignment.py - ALL implementations here +class Alignment: + @classmethod + def create_curve_at_pi(cls, alignment, pi_index, radius): + """Tool layer - math and IFC creation happens here.""" + pis = cls.get_pis(alignment) + p1, p2, p3 = pis[pi_index-1], pis[pi_index], pis[pi_index+1] + + # Math calculations (THIS BELONGS IN TOOL) + t1 = (p2 - p1).normalized() + t2 = (p3 - p2).normalized() + deflection = math.acos(t1.dot(t2)) + tangent_length = radius * math.tan(deflection / 2) + + bc = p2 - t1 * tangent_length + ec = p2 + t2 * tangent_length + arc_length = radius * deflection + + # IFC creation (THIS BELONGS IN TOOL) + ifc_file = tool.Ifc.get() + segment = ifc_file.create_entity("IfcAlignmentSegment", ...) + + return {'bc': bc, 'ec': ec, 'arc_length': arc_length, 'segment': segment} +``` + +#### 3. UI Layer (`bim/module/alignment/`) — WHO interacts + +**Purpose:** User interaction and visual feedback + +**Contains:** +- Blender operators (button handlers) +- UI panels +- Property groups +- Cached data for UI performance + +### Quick Reference: What Goes Where? + +| Code Type | Layer | Example | +|-----------|-------|---------| +| Math formula | **Tool** | `T = R × tan(Δ/2)` | +| Vector operations | **Tool** | `v1.dot(v2)`, `v.normalized()` | +| IFC creation | **Tool** | `ifc.create_entity("IfcAlignment")` | +| Blender objects | **Tool** | `bpy.data.objects.new()` | +| Station calculations | **Tool** | `get_station_at_point()` | +| Coordinate transforms | **Tool** | `pyproj.transform()` | +| Mesh generation | **Tool** | `bmesh.ops.create_*()` | +| "Should we do X?" | **Core** | `can_add_pi(alignment)` | +| "When X, do Y" | **Core** | workflow orchestration | +| Validation rules | **Core** | `validate_minimum_radius()` | +| Button handler | **UI** | `class CIVIL_OT_add_pi` | +| Panel layout | **UI** | `class CIVIL_PT_alignment` | + +**Simple Test:** If code contains a math formula or creates an IFC entity, it belongs in **Tool**, not **Core**. + +--- + +## Code Patterns + +### 1. IFC File Access + +```python +import bonsai.tool as tool + +# Get IFC file +ifc_file = tool.Ifc.get() + +# Get Blender object for IFC element +obj = tool.Ifc.get_object(ifc_element) + +# Link IFC element to Blender object +tool.Ifc.link(ifc_element, blender_obj) +``` + +### 2. Operator Pattern + +```python +class AddAlignment(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.add_alignment" + bl_label = "Add Alignment" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + # Use core function with tool injection + core.alignment.add_alignment(tool.Ifc, tool.Alignment) + return {"FINISHED"} +``` + +### 3. Core Function Pattern + +```python +# In core/alignment.py - NO bpy imports, NO math! +def add_alignment(ifc, alignment): + """ + Business logic only - orchestrates alignment creation. + + Args: + ifc: Tool class for IFC operations (injected) + alignment: Tool class for alignment operations (injected) + """ + # Business rule: check if we can add alignment + if not alignment.can_create_alignment(): + raise ValueError("Cannot create alignment in current context") + + # Delegate actual creation to Tool layer + result = alignment.create_alignment() + + return result +``` + +### 4. Tool Method Pattern + +```python +# In tool/alignment.py - ALL implementations here +class Alignment: + @classmethod + def create_alignment(cls): + """Tool layer - IFC creation and math happens here.""" + ifc_file = tool.Ifc.get() + + # IFC entity creation + alignment = ifc_file.create_entity("IfcAlignment", + GlobalId=ifcopenshell.guid.new(), + Name="New Alignment" + ) + + # Blender object creation + obj = bpy.data.objects.new("Alignment", None) + tool.Ifc.link(alignment, obj) + + return alignment +``` + +### 5. Data Caching (for UI) + +```python +# In bim/module/alignment/data.py +class AlignmentData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + # Load data from IFC + cls.is_loaded = True + + @classmethod + def refresh(cls): + cls.is_loaded = False +``` + +### 6. GPU Decorator Pattern (Visual Feedback) + +GPU decorators provide ephemeral visual feedback during modal operations. They draw directly to the viewport using Blender's GPU module and disappear automatically when the modal ends. + +**Location:** `bim/module/alignment/decorator.py` + +```python +# In decorator.py - GPU drawing for modal operations +class PIPickerDecorator: + """Decorator for visualizing PI placement during modal picking.""" + + # Class-level state (cleared on uninstall) + is_installed = False + handlers = [] + pi_points = [] # List of Vector - positions in Blender coords + mouse_3d = None # Current cursor position + + @classmethod + def install(cls, context, region, rv3d): + """Install GPU draw handlers.""" + if cls.is_installed: + cls.uninstall() + handler = cls() + cls.handlers.append( + SpaceView3D.draw_handler_add(handler.draw_tangent_lines, (context,), "WINDOW", "POST_PIXEL") + ) + cls.is_installed = True + + @classmethod + def uninstall(cls): + """Remove handlers and clear state.""" + for handler in cls.handlers: + try: + SpaceView3D.draw_handler_remove(handler, "WINDOW") + except ValueError: + pass + cls.handlers = [] + cls.is_installed = False + + @classmethod + def update(cls, pi_points_blender, mouse_3d): + """Update state from modal operator.""" + cls.pi_points = pi_points_blender + cls.mouse_3d = mouse_3d + + def draw_batch(self, shader_type, content_pos, color, indices=None): + """Standard Bonsai draw pattern - MUST call validate first.""" + if not tool.Blender.validate_shader_batch_data(content_pos, indices): + return + # ... shader drawing code +``` + +**Key Bonsai Utilities for Decorators:** + +| Utility | Purpose | +|---------|---------| +| `tool.Blender.validate_shader_batch_data()` | **Required** - prevents crashes on empty batches | +| `tool.Blender.scale_font_size()` | DPI-aware font scaling for HUD text | +| `draw_circle_2d()` from `gpu_extras.presets` | Built-in circle drawing | +| `location_3d_to_region_2d()` | Convert 3D coords to screen coords | + +**Modal Operator Integration:** + +```python +# In operator.py +def invoke(self, context, event): + # Install decorator for visual feedback + alignment_decorator.PIPickerDecorator.install(context, self._region, self._rv3d) + context.window_manager.modal_handler_add(self) + return {"RUNNING_MODAL"} + +def modal(self, context, event): + if event.type == "MOUSEMOVE": + # Update rubber band position + alignment_decorator.PIPickerDecorator.update(pi_points, mouse_3d) + self._area.tag_redraw() # Trigger viewport redraw + + if event.type in {"RIGHTMOUSE", "ESC"}: + alignment_decorator.PIPickerDecorator.uninstall() # Cleanup + return {"FINISHED"} +``` + +**Drawing Modes:** +- `POST_PIXEL` - 2D screen-space drawing (HUD text, lines in pixel coords) +- `POST_VIEW` - 3D world-space drawing (geometry in Blender coords) + +--- + +## Property Naming Conventions + +| Type | Prefix | Example | +|------|--------|---------| +| Scene PropertyGroup | `Civil*Properties` | `CivilAlignmentProperties` | +| Object PropertyGroup | `CivilObjectProperties` | - | +| Panel class | `CIVIL_PT_*` | `CIVIL_PT_alignments` | +| Operator class | `CIVIL_OT_*` | `CIVIL_OT_add_alignment` | +| UIList class | `CIVIL_UL_*` | `CIVIL_UL_alignments` | + +**Note:** Use `Civil*` prefix, NOT `BIM*` or `BC_*` to avoid conflicts with Bonsai. + +--- + +## IFC 4.3 Compliance + +### Supported Entities +- `IfcAlignment`, `IfcAlignmentHorizontal`, `IfcAlignmentVertical` +- `IfcAlignmentSegment`, `IfcAlignmentHorizontalSegment`, `IfcAlignmentVerticalSegment` +- `IfcRoad`, `IfcSite` +- Segment types: LINE, CIRCULARARC, CLOTHOID, CONSTANTGRADIENT, PARABOLICARC + +### Spatial Hierarchy +``` +IfcProject +└── IfcSite + └── IfcRoad + └── IfcAlignment + ├── IfcAlignmentHorizontal + │ └── IfcAlignmentSegment(s) + └── IfcAlignmentVertical + └── IfcAlignmentSegment(s) +``` + +### Key API +```python +import ifcopenshell.api.alignment as align_api + +# Create alignment using PI method +alignment = align_api.create_by_pi_method( + ifc_file, + name="Main Road", + hpoints=[(0, 0), (100, 0), (200, 50)], + radii=[0, 150, 0], + start_station=0.0 +) + +# Get horizontal layout from alignment +h_layout = align_api.get_horizontal_layout(alignment) +``` + +--- + +## Development Setup + +### Prerequisites +- Blender 5.0+ +- Python 3.11 +- Git + +### Symlink for Development +```powershell +# Remove installed Bonsai extension +Remove-Item -Recurse "C:\Users\{USER}\AppData\Roaming\Blender Foundation\Blender\5.0\extensions\blender_org\bonsai" + +# Create symlink to source +New-Item -ItemType SymbolicLink -Path "C:\Users\{USER}\AppData\Roaming\Blender Foundation\Blender\5.0\extensions\blender_org\bonsai" -Target "C:\GitHub\IfcOpenShell\src\bonsai\bonsai" +``` + +### Required Setup for Symlink +1. Update `blender_manifest.toml`: Change `platforms = ["os-arch"]` to `platforms = ["windows-x64"]` +2. Extract all wheels to site-packages (see Development Notes) +3. Mark manifest as skip-worktree: `git update-index --skip-worktree src/bonsai/bonsai/blender_manifest.toml` + +--- + +## Development Progress + +### Completed Features +- Horizontal alignment creation (PI method) +- All segment type visualization (LINE, CIRCULARARC, CLOTHOID, spirals via IfcOpenShell geometry engine) +- Individual segment selection (each segment is a selectable Blender curve) +- Alignment hierarchy in Blender (parent/child relationships) +- Stationing referents +- Zero-length terminator segment handling (invisible to users) +- Georeference/coordinate transformation support +- PI picker rubber band visualization (GPU decorator for visual feedback during modal picking) + +### In Progress +- Vertical alignment support +- Corridor generation +- Cross-section profiles + +### Planned +- Earthwork calculations +- Drainage design + +--- + +## Testing + +```python +# In Blender Python console: + +# Test IFC access +import bonsai.tool as tool +ifc = tool.Ifc.get() +print(f"IFC loaded: {ifc is not None}") +print(f"Schema: {ifc.schema if ifc else 'N/A'}") + +# Test alignments +if ifc: + alignments = ifc.by_type("IfcAlignment") + print(f"Alignments: {len(alignments)}") +``` + +--- + +## Resources + +### Documentation +- IFC 4.3 Spec: https://ifc43-docs.standards.buildingsmart.org/ +- IfcOpenShell: https://docs.ifcopenshell.org/ +- Bonsai Docs: https://docs.bonsaibim.org/ +- Bonsai Wiki: https://wiki.osarch.org/ + +### Community +- OSArch Forum: https://community.osarch.org/ +- buildingSMART: https://www.buildingsmart.org/ + +--- + +## Contact & Ownership + +**Primary Developer:** Michael Yoder (Desert Springs Civil Engineering PLLC) +**Project:** Part of IfcOpenShell/Bonsai (open-source, community-driven) +**License:** GPL v3 + +--- + +*Last Updated: February 2026* +*Saikei - Civil Engineering for Bonsai* + +--- + +## Architecture Clarification Note + +**Source:** Dion Moult (Bonsai founder), January 2026 + +The three-layer architecture follows this strict separation: + +| Layer | Contains | Simple Rule | +|-------|----------|-------------| +| **Core** | Business logic ONLY | "Decides WHAT happens" | +| **Tool** | Math, algorithms, IFC, Blender | "Implements HOW it happens" | +| **UI** | User interaction | "Handles WHO interacts" | + +**Key insight:** If code contains a math formula or creates an IFC entity, it belongs in **Tool**, not **Core**. Core only orchestrates and validates - it never calculates. + +--- + +## Geometry Pipeline (Alignment Visualization) + +This section documents how alignment segments are converted from IFC entities to visible Blender curves. + +### Entity Hierarchy + +``` +IFC Semantic Layer (Business Logic) +├── IfcAlignment +│ └── IfcAlignmentHorizontal (nested via IfcRelNests) +│ └── IfcAlignmentSegment (nested via IfcRelNests) +│ └── DesignParameters: IfcAlignmentHorizontalSegment +│ • StartPoint, StartDirection +│ • SegmentLength, PredefinedType (LINE, CIRCULARARC, CLOTHOID, etc.) +│ • StartRadiusOfCurvature, EndRadiusOfCurvature + +IFC Geometric Layer (Representation) +├── IfcCompositeCurve (for horizontal) +│ └── IfcCurveSegment (one per alignment segment) +│ • Placement: IfcAxis2Placement2D +│ • ParentCurve: IfcLine, IfcCircle, IfcClothoid, etc. +│ • SegmentStart, SegmentLength +``` + +### Geometry Flow + +``` +IfcAlignmentSegment + │ + ▼ +┌───────────────────────────────┐ +│ align_api.get_mapped_segments │ (IfcOpenShell API) +│ Maps semantic → geometric │ +└───────────────────────────────┘ + │ + ▼ +IfcCurveSegment (geometric representation) + │ + ▼ +┌───────────────────────────────┐ +│ align_util.evaluate_segment │ (IfcOpenShell geometry engine) +│ Returns 4x4 transform matrix │ +│ at distance along segment │ +└───────────────────────────────┘ + │ + ▼ +Transform Matrix (4x4, TRANSPOSED) +┌ ┐ +│ Xx Xy Xz 0 │ Row 0: X-axis direction +│ Yx Yy Yz 0 │ Row 1: Y-axis direction +│ Zx Zy Zz 0 │ Row 2: Z-axis direction +│ Tx Ty Tz 1 │ Row 3: Translation (position) +└ ┘ + │ + ▼ +Blender CURVE object with vertices +``` + +### Key Implementation: `get_segment_vertices()` + +Location: `src/bonsai/bonsai/tool/alignment.py` + +This method extracts vertices from an IFC alignment segment using IfcOpenShell's geometry engine: + +```python +@classmethod +def get_segment_vertices( + cls, segment: "ifcopenshell.entity_instance", distance_interval: float = 1.0 +) -> Optional[List[Tuple[float, float, float]]]: + """Get vertices for a single alignment segment using IfcOpenShell's geometry engine.""" + import ifcopenshell.api.alignment as align_api + from ifcopenshell.api.alignment import util as align_util + + # Get the mapped curve segment(s) for this alignment segment + mapped_segments = align_api.get_mapped_segments(segment) + # mapped_segments is tuple: (main_segment, spiral_in, spiral_out) + + for curve_segment in [s for s in mapped_segments if s is not None]: + segment_length = curve_segment.SegmentLength.wrappedValue + num_points = max(2, int(segment_length / distance_interval) + 1) + + for i in range(num_points): + dist_along = (i / (num_points - 1)) * segment_length + + # Use IfcOpenShell geometry engine to evaluate position + transform_matrix = align_util.evaluate_segment(curve_segment, dist_along) + + # CRITICAL: Matrix is transposed - position is in ROW 3, not column 3 + x = float(transform_matrix[3, 0]) / unit_scale + y = float(transform_matrix[3, 1]) / unit_scale + z = float(transform_matrix[3, 2]) / unit_scale + + vertices.append((x, y, z)) + + return vertices +``` + +**Critical Note:** The transform matrix returned by `evaluate_segment()` is transposed compared to standard OpenGL/Blender conventions. Position values are in **row 3** (indices `[3, 0]`, `[3, 1]`, `[3, 2]`), not column 3. + +### Key Implementation: `_create_segment_curve()` + +Location: `src/bonsai/bonsai/tool/alignment.py` + +Creates a Blender CURVE object from segment vertices: + +```python +@classmethod +def _create_segment_curve( + cls, + segment: "ifcopenshell.entity_instance", + segment_number: int, + parent_obj: "bpy.types.Object", +) -> Optional["bpy.types.Object"]: + """Create a Blender curve object for an alignment segment.""" + + # Skip zero-length terminator segments + if cls.is_zero_length_segment(segment): + return None + + # Get vertices using IfcOpenShell geometry engine + vertices = cls.get_segment_vertices(segment) + if not vertices: + return None + + # Create Blender curve + curve_data = bpy.data.curves.new(name=curve_name, type='CURVE') + curve_data.dimensions = '3D' + spline = curve_data.splines.new('POLY') + spline.points.add(len(vertices) - 1) + + for i, (x, y, z) in enumerate(vertices): + spline.points[i].co = (x, y, z, 1.0) # w=1.0 for 3D + + obj = bpy.data.objects.new(curve_name, curve_data) + obj.parent = parent_obj + + # Link to IFC element + tool.Ifc.link(segment, obj) + + return obj +``` + +### Zero-Length Segment Handling + +IFC alignments require a zero-length terminator segment at the end. These are handled invisibly: + +```python +@classmethod +def is_zero_length_segment(cls, segment: "ifcopenshell.entity_instance") -> bool: + """Check if segment is a zero-length terminator.""" + design_params = segment.DesignParameters + if design_params.is_a("IfcAlignmentHorizontalSegment"): + return design_params.SegmentLength == 0.0 + elif design_params.is_a("IfcAlignmentVerticalSegment"): + return design_params.HorizontalLength == 0.0 + return False + +@classmethod +def layout_has_real_segments(cls, layout: "ifcopenshell.entity_instance") -> bool: + """Check if layout has segments beyond just the zero-length terminator.""" + for rel in layout.IsNestedBy: + for segment in rel.RelatedObjects: + if not cls.is_zero_length_segment(segment): + return True + return False +``` + +**Behavior:** +- Zero-length segments are never visualized (no Blender object created) +- Empty layouts (only zero-length segment) skip geometry generation silently +- Warnings are suppressed for expected empty-layout scenarios + +### IfcOpenShell Alignment API Functions Used + +| Function | Location | Purpose | +|----------|----------|---------| +| `get_mapped_segments(segment)` | `ifcopenshell.api.alignment` | Maps IfcAlignmentSegment → IfcCurveSegment(s) | +| `evaluate_segment(curve_seg, dist)` | `ifcopenshell.api.alignment.util` | Returns 4x4 transform matrix at distance | +| `get_horizontal_layout(alignment)` | `ifcopenshell.api.alignment` | Gets IfcAlignmentHorizontal from alignment | +| `get_curve(alignment)` | `ifcopenshell.api.alignment` | Gets geometric representation curve | +| `has_zero_length_segment(layout)` | `ifcopenshell.api.alignment` | Checks for terminator segment | + +### Coordinate Transformation + +When georeference data exists, coordinates are transformed: + +```python +# In tool/alignment.py +georeference = tool.Georeference.get_georeference() +if georeference: + vertices = [ + tool.Georeference.xyz2local(v, georeference) + for v in vertices + ] +``` + +This ensures alignment geometry displays correctly in Blender's coordinate system when the IFC file uses real-world coordinates. + +--- + +## Troubleshooting & Debugging + +### Common Issues and Solutions + +| Symptom | Likely Cause | Solution | +|---------|--------------|----------| +| All vertices at (0,0,0) | Matrix index wrong (column vs row) | Use `matrix[3, 0]` not `matrix[0, 3]` for position | +| No visualization | Zero-length segment only | Check `layout_has_real_segments()` before generating | +| Segment not selectable | Created as EMPTY, not CURVE | Ensure `_create_segment_curve()` creates curve data | +| Wrong position in viewport | Missing georeference transform | Apply `tool.Georeference.xyz2local()` to vertices | +| `generate_vertices failed` | Empty IfcCompositeCurve | Skip geometry generation for empty layouts | + +### Debugging with Blender MCP + +Use Blender MCP to execute Python in Blender for live debugging: + +```python +# Check if segments exist +import bonsai.tool as tool +ifc = tool.Ifc.get() +alignments = ifc.by_type("IfcAlignment") +for a in alignments: + for rel in a.IsNestedBy: + for child in rel.RelatedObjects: + if child.is_a("IfcAlignmentHorizontal"): + for rel2 in child.IsNestedBy: + print(f"Segments: {len(rel2.RelatedObjects)}") + +# Test geometry evaluation +import ifcopenshell.api.alignment as align_api +from ifcopenshell.api.alignment import util as align_util +segment = ifc.by_type("IfcAlignmentSegment")[0] +mapped = align_api.get_mapped_segments(segment) +if mapped[0]: + matrix = align_util.evaluate_segment(mapped[0], 0.0) + print(f"Start position: ({matrix[3,0]}, {matrix[3,1]}, {matrix[3,2]})") +``` + +### Key Files to Check When Debugging + +1. **`tool/alignment.py`** - Main implementation (geometry, Blender objects) +2. **`ifcopenshell/api/alignment/util.py`** - Geometry engine functions +3. **`ifcopenshell/api/alignment/get_mapped_segments.py`** - Semantic to geometric mapping + +### Rebuilding After Breaking Changes + +If the alignment visualization breaks: + +1. **Verify IFC structure**: Check that IfcAlignmentSegment has DesignParameters and is nested correctly +2. **Verify geometry mapping**: Confirm `get_mapped_segments()` returns valid IfcCurveSegments +3. **Verify matrix extraction**: Remember matrix is TRANSPOSED - position in row 3 +4. **Verify unit scaling**: Apply `calculate_unit_scale(file)` to coordinates +5. **Verify Blender object creation**: Ensure curve has spline points and is linked to IFC element