From f9f0f1915f2153cdc66ed0936764f24554504767 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 13 Feb 2026 09:14:32 +1100 Subject: [PATCH] Remove claude --- CLAUDE.md | 844 ------------------------------------------------------ 1 file changed, 844 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index b9e5109d73..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,844 +0,0 @@ -# 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` - -### Git Workflow for Local-Only Files - -This file (CLAUDE.md) is committed locally but should **never be pushed to origin**. A pre-push hook automatically enforces this. - -**How it works:** -- CLAUDE.md is tracked in git (committed normally) -- The pre-push hook at `.git/hooks/pre-push` detects commits containing local-only files -- When you try to `git push`, it blocks and shows the exact command to push only feature commits - -**To push your feature commits:** -```bash -# Option 1: Use the alias -git push-features - -# Option 2: Manual command (shown by pre-push hook) -git push origin HEAD~N:branch_name # N = number of local-only commits at HEAD -``` - -**Workflow example:** -```bash -# Normal development -git add src/bonsai/... -git commit -m "Add new feature" - -# Update CLAUDE.md (keeps it at tip of branch) -git add -f CLAUDE.md -git commit -m "Local: Update CLAUDE.md" - -# Push feature commits only -git push-features # Pushes everything except CLAUDE.md commit -``` - -**If you need to set this up on a new machine:** -1. The pre-push hook is in `.git/hooks/pre-push` (not tracked by git) -2. Create the git alias: `git config alias.push-features "push origin HEAD~1:saikei"` -3. Force-add CLAUDE.md: `git add -f CLAUDE.md` - -**Local-only files pattern:** -The hook checks for files listed in `LOCAL_ONLY_FILES` variable (currently just `CLAUDE.md`). Add more files there if needed. - ---- - -## 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