20 KiB
CLAUDE.md - Saikei Civil Context for IfcOpenShell Contributions
This document provides context for Claude Code when working on Saikei Civil contributions to IfcOpenShell, specifically horizontal alignment visualization.
Project Overview
Saikei Civil (formerly BlenderCivil) is an open-source Blender extension for native IFC 4.3 infrastructure design. The project aims to democratize professional civil engineering tools by providing free alternatives to expensive commercial software like Civil 3D ($2,500/year) and OpenRoads ($4,000/year).
Mission
- Bonsai BIM = Buildings (vertical construction)
- Saikei Civil = Infrastructure (horizontal construction: roads, earthwork, drainage)
"While Bonsai crafts the buildings, Saikei shapes the world around them."
Key Differentiator
Native IFC Philosophy: IFC files serve as the primary database rather than export targets. We're not converting TO IFC - we ARE IFC from the start.
Architecture Principles
Three-Layer Architecture (Matches Bonsai)
Saikei follows Bonsai's proven architecture with core/ and tool/ at the package root level:
saikei/
├── core/ # Layer 1: Pure Python - NO bpy imports
│ └── alignment.py # Business logic, math, validation
├── tool/ # Layer 2: Blender implementations - HAS bpy
│ └── alignment.py # Blender object creation, linking
└── civil/ # Layer 3: UI (like Bonsai's bim/)
└── module/
└── alignment/
├── __init__.py # Registration
├── operator.py # Blender operators
├── ui.py # UI panels
├── prop.py # PropertyGroups
└── data.py # UI data caching
Layer Responsibilities:
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: civil/module/{name}/ (UI Layer) │
│ - operator.py: User actions (bpy.types.Operator) │
│ - ui.py: Interface panels (bpy.types.Panel) │
│ - prop.py: UI state (bpy.types.PropertyGroup) │
│ - Calls core functions, passing tool implementations │
└─────────────────────────────────────────────────────────────┘
│ calls
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: core/ (Pure Python - NO bpy imports) │
│ - All business logic, algorithms, mathematics │
│ - Receives tool classes as parameters (dependency inject) │
│ - MUST be testable outside Blender │
└─────────────────────────────────────────────────────────────┘
│ receives as parameters
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: tool/ (Blender implementations) │
│ - Concrete implementations with bpy │
│ - Blender object creation, scene manipulation │
│ - Wraps Bonsai's tool.Ifc, tool.Collector, etc. │
└─────────────────────────────────────────────────────────────┘
Bonsai's Dependency Injection Pattern
Core functions receive tool classes as parameters, enabling testability:
# In core/alignment.py (NO bpy imports)
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import ifcopenshell
import saikei.tool as tool
def create_alignment_visualization(
ifc: type[tool.Ifc],
alignment_tool: type[tool.Alignment],
alignment: ifcopenshell.entity_instance,
) -> list:
"""Pure business logic - tools passed as parameters."""
segments = get_layout_segments(alignment)
objects = []
for segment in segments:
obj = alignment_tool.create_segment_object(segment)
ifc.link(obj, segment)
objects.append(obj)
return objects
# In civil/module/alignment/operator.py (HAS bpy)
import bpy
import saikei.tool as tool
import saikei.core.alignment as core
class SAIKEI_OT_visualize_alignment(bpy.types.Operator):
def execute(self, context):
alignment = self.get_active_alignment()
# Call core logic, passing tool implementations
core.create_alignment_visualization(
tool.Ifc, tool.Alignment, alignment
)
return {'FINISHED'}
Golden Pattern for Native IFC
# 1. GET IFC FILE
ifc = NativeIfcManager.get_file()
# 2. CREATE/MODIFY IFC ENTITY FIRST
entity = ifc.create_entity("IfcAlignment", ...)
# 3. CREATE BLENDER VISUALIZATION SECOND
obj = create_blender_object(...)
# 4. LINK THEM (minimal storage in Blender)
NativeIfcManager.link_object(obj, entity)
# obj["ifc_definition_id"] = entity.id()
# obj["ifc_class"] = entity.is_a()
# obj["GlobalId"] = entity.GlobalId
# 5. SAVE = WRITE IFC FILE
NativeIfcManager.save_file("project.ifc")
Key Principles
- IFC-First Design: ALL civil engineering data lives in the IFC file
- Minimal Blender Storage: Only 3 properties stored in Blender objects
- Separation of Concerns: Core logic has NO bpy imports
- IfcStore Pattern: Following Bonsai's proven pattern for IFC file management
IFC 4.3 Alignment Structure
Entity Hierarchy
IfcProject
└── IfcSite
└── IfcRoad (via IfcRelAggregates)
└── IfcAlignment (via IfcRelContainedInSpatialStructure)
├── IfcAlignmentHorizontal (via IfcRelNests)
│ └── [IfcAlignmentSegment → IfcAlignmentHorizontalSegment]*
├── IfcAlignmentVertical (via IfcRelNests)
│ └── [IfcAlignmentSegment → IfcAlignmentVerticalSegment]*
└── IfcAlignmentCant (via IfcRelNests) [Future]
└── [IfcAlignmentSegment → IfcAlignmentCantSegment]*
Critical IFC Rules
- Zero-Length Terminal Segments: The last
IfcAlignmentSegment.DesignParametersin each layout MUST be zero length to provide the end point - Ordered Collections:
IfcRelNests.RelatedObjectsmaintains segment order (start to end) - Nesting Order: Horizontal precedes Vertical precedes Cant in
IfcRelNests
Semantic vs Geometric Representation
The IFC 4.3 alignment model has TWO parallel structures:
Semantic (Business Logic):
IfcAlignmentHorizontalSegmentwithPredefinedType(LINE, CIRCULARARC, CLOTHOID, etc.)- Contains design parameters (radius, length, direction)
Geometric (Shape):
IfcCurveSegment→ParentCurve(IfcLine, IfcCircle, IfcClothoid)- Combined into
IfcCompositeCurve(horizontal) orIfcGradientCurve(vertical)
Mapping Table:
| Business Logic (Semantic) | Geometric ParentCurve |
|---|---|
| LINE | IfcLine |
| CIRCULARARC | IfcCircle |
| CLOTHOID | IfcClothoid |
| CUBIC | IfcPolynomialCurve |
| PARABOLICARC | IfcPolynomialCurve |
| CONSTANTGRADIENT | IfcLine |
The 2.5D Layered Model
IFC alignments use a "2.5D" approach - combining multiple 2D curves:
- Layer 1 - Horizontal:
IfcCompositeCurvein X-Y (Easting-Northing) plane - Layer 2 - Vertical:
IfcGradientCurvein "Distance Along, Elevation" coordinate systemBaseCurveattribute points to horizontalIfcCompositeCurve
- Layer 3 - Cant:
IfcSegmentedReferenceCurvefor superelevationBaseCurvetypically points toIfcGradientCurve
Horizontal Alignment Implementation
PI-Driven Design Approach
Saikei Civil uses the Point of Intersection (PI) method, matching professional workflows in Civil 3D and OpenRoads:
PI● is just an intersection point
╲
╲ Tangent
BC (Begin Curve)
╲ ╱
● Curve (R=150m)
╱ ╲
EC (End Curve)
╱ Tangent
╱
Key Distinction:
- PIs are just intersection points (NO radius property)
- Curves are separate entities inserted between tangents (Curves HAVE radius)
Core Classes
class HorizontalAlignmentManager:
"""Core engine for horizontal alignment"""
def add_pi(self, x: float, y: float) -> PI
def insert_curve(self, pi_index: int, radius: float) -> Curve
def generate_segments(self) -> List[Segment]
class PI:
"""Point of Intersection - just a position"""
position: Tuple[float, float]
# NO radius property
class Curve:
"""Curve inserted at a PI"""
radius: float
bc: Tuple[float, float] # Begin Curve point
ec: Tuple[float, float] # End Curve point
at_pi: int # Which PI this curve is at
Civil Engineering Mathematics
Deflection Angle:
Δ = arccos(t₁ · t₂)
Where t₁ and t₂ are normalized incoming/outgoing tangent vectors.
Tangent Length:
T = R × tan(Δ/2)
Arc Length:
L = R × Δ (radians)
BC/EC Points:
BC = PI - t₁ × T
EC = PI + t₂ × T
Segment Generation
def regenerate_segments(self):
"""Auto-generate tangent and curve segments from PIs"""
segments = []
for i, pi in enumerate(self.pis[:-1]):
next_pi = self.pis[i + 1]
# Check if curve at this PI
curve = self.get_curve_at_pi(i)
if curve:
# Add incoming tangent (trimmed to BC)
segments.append(create_line_segment(prev_end, curve.bc))
# Add curve
segments.append(create_arc_segment(curve))
prev_end = curve.ec
else:
# Add full tangent
segments.append(create_line_segment(prev_end, next_pi.position))
prev_end = next_pi.position
return segments
Visualization Layer
AlignmentVisualizer Class
class AlignmentVisualizer:
"""Create Blender visualization of IFC alignment"""
def __init__(self, native_alignment):
self.alignment = native_alignment
self.collection = None
self.pi_objects = []
self.segment_objects = []
def visualize_all(self):
"""Generate complete visualization"""
self.setup_collection()
self.create_pi_markers()
self.create_segment_curves()
Color Coding Convention
PI Markers (Empties):
- 🟢 Green = Tangent points (no curve)
- 🟠 Orange = Curve PIs (curve inserted)
Segment Objects (Curves):
- 🔵 Blue = Tangent segments (LINE)
- 🔴 Red = Circular arcs (CIRCULARARC)
Object Linking Pattern
# Every Blender object stores only 3 properties
obj["ifc_definition_id"] = entity.id()
obj["ifc_class"] = entity.is_a()
obj["GlobalId"] = entity.GlobalId
# All other data comes from IFC
entity = NativeIfcManager.get_entity(obj)
params = entity.DesignParameters # Real data from IFC!
Rick Brice's IfcOpenShell Alignment API
Rick Brice's alignment API was merged into IfcOpenShell v0.8.0 (PR #6234, March 14, 2025). This API provides comprehensive Python functions for IFC alignment creation.
Key Functions
Creation:
import ifcopenshell.api.alignment as align_api
# Create alignment with PI method
alignment = align_api.create_by_pi_method(
ifc_file,
name='Main Alignment',
hpoints=[(0,0), (100,50), (200,100)], # Horizontal PI coordinates
radii=[0, 150, 200], # Curve radii at PIs
start_station=0.0
)
# Create alignment structure
alignment = align_api.create(
ifc_file,
name="Highway 101",
horizontal_layout=horiz,
vertical_layout=vert # Optional
)
Layout Functions:
# Add to existing horizontal
align_api.layout_horizontal_alignment_by_pi_method(
ifc_file, horiz_alignment, hpoints, radii
)
# Add vertical layout
align_api.add_vertical_layout(alignment, vertical_layout)
# Add zero-length terminator (required!)
align_api.add_zero_length_segment(layout)
Getters:
horiz = align_api.get_horizontal_layout(alignment)
vert = align_api.get_vertical_layout(alignment)
segments = align_api.get_layout_segments(layout)
curve = align_api.get_layout_curve(layout) # IfcCompositeCurve/IfcGradientCurve
Integration Strategy
Recommended architecture for Saikei contributions:
| Layer | Rick's API Responsibility | Saikei's Responsibility |
|---|---|---|
| IFC Backend | All IFC entity creation, relationships, geometric representations | None - use Rick's API |
| Business Logic | Alignment math, segment generation, stationing | Design validation, AASHTO rules |
| UI Layer | None (Python API only) | Full Blender panels, operators |
| Visualization | None | Real-time 3D preview, PI markers |
Example Integration Pattern
# Saikei Civil operator using Rick's API backend
import ifcopenshell.api.alignment as align_api
class SAIKEI_OT_create_alignment(bpy.types.Operator):
def execute(self, context):
# Get PI data from Blender UI
hpoints = [(pi.x, pi.y) for pi in context.scene.saikei_pis]
radii = [pi.radius for pi in context.scene.saikei_pis]
# Use Rick's API for IFC creation
alignment = align_api.create_by_pi_method(
self.ifc_file,
name='Main Alignment',
hpoints=hpoints,
radii=radii,
start_station=context.scene.saikei_start_station
)
# Saikei handles visualization
self.visualizer.update_from_ifc(alignment)
return {'FINISHED'}
Validation and Compliance
buildingSMART Validation
Saikei Civil has undergone extensive validation testing with buildingSMART International. Key compliance requirements:
- Zero-Length Terminal Segments: Every layout must end with a zero-length segment
- Spatial Hierarchy: Proper
IfcRelAggregatesandIfcRelContainedInSpatialStructure - Segment Continuity: End of segment N must match start of segment N+1 (< 0.001m tolerance)
- Ordered Nesting: Segments in correct order via
IfcRelNests.RelatedObjects
Validation Code Pattern
def validate_alignment(alignment):
"""Validate IFC alignment structure"""
errors = []
warnings = []
# Check basic structure
if not alignment:
errors.append("No IfcAlignment entity")
return errors, warnings
# Get horizontal layout
horiz = get_horizontal_layout(alignment)
if not horiz:
errors.append("No IfcAlignmentHorizontal")
return errors, warnings
# Check segments
segments = get_layout_segments(horiz)
if len(segments) < 2:
warnings.append("Need at least 2 segments")
# Check zero-length terminator
last_seg = segments[-1].DesignParameters
if last_seg.SegmentLength > 0.0001:
errors.append("Missing zero-length terminal segment")
# Check continuity
for i in range(len(segments) - 1):
gap = calculate_gap(segments[i], segments[i+1])
if gap > 0.001:
errors.append(f"Gap of {gap}m between segments {i} and {i+1}")
return errors, warnings
PR Focus: Horizontal Alignment Visualization
For your first PR focused on horizontal alignment visualization, focus on:
Core Requirements
- Read IFC alignment data using Rick's API getters
- Generate Blender visualization objects (curves/empties)
- Link objects to IFC entities with minimal storage
- Color-code by segment type (LINE=blue, CIRCULARARC=red)
- Support real-time updates via depsgraph handlers
Key Files to Create/Modify
Following Bonsai's architecture with core/ and tool/ at package root:
saikei/
├── core/ # NEW - Pure Python (NO bpy)
│ ├── __init__.py
│ └── alignment.py # Business logic, math, validation
├── tool/ # NEW - Blender implementations
│ ├── __init__.py
│ └── alignment.py # Blender object creation, linking
└── civil/ # EXISTING - UI layer
└── module/
└── alignment/
├── __init__.py # Registration
├── operator.py # Operators call core.*, passing tool.*
├── ui.py # UI panels
├── prop.py # PropertyGroups
└── data.py # UI data caching
Minimal Visualization Implementation
def visualize_horizontal_alignment(alignment, collection):
"""Create Blender visualization from IFC alignment"""
# Get segments from IFC
horiz = align_api.get_horizontal_layout(alignment)
segments = align_api.get_layout_segments(horiz)
objects = []
for i, segment in enumerate(segments):
params = segment.DesignParameters
if params.PredefinedType == "LINE":
obj = create_line_curve(params, name=f"Tangent_{i}")
set_material_color(obj, BLUE)
elif params.PredefinedType == "CIRCULARARC":
obj = create_arc_curve(params, name=f"Curve_{i}")
set_material_color(obj, RED)
# Link to IFC
obj["ifc_definition_id"] = segment.id()
obj["ifc_class"] = segment.is_a()
obj["GlobalId"] = segment.GlobalId
collection.objects.link(obj)
objects.append(obj)
return objects
Development Guidelines
Code Style
- Follow PEP 8
- Type annotations for all public functions
- Docstrings with examples
- No
bpyimports incore/modules
Testing
- Unit tests for all core logic (outside Blender)
- Integration tests with sample IFC files
- Validation against buildingSMART checker
Commit Messages
feat(alignment): Add horizontal alignment visualization
- Create AlignmentVisualizer class for Blender curve generation
- Support LINE and CIRCULARARC segment types
- Implement IFC entity linking pattern
- Add color coding by segment type
Refs: #123
Resources
Documentation
- IFC 4.3 Specification: https://ifc43-docs.standards.buildingsmart.org/
- IfcOpenShell Docs: https://docs.ifcopenshell.org/
- Bonsai Wiki: https://wiki.osarch.org/
Community
- OSArch Forum: https://community.osarch.org/
- IfcOpenShell GitHub: https://github.com/IfcOpenShell/IfcOpenShell
- buildingSMART Forums: https://forums.buildingsmart.org/
Key Contacts
- Rick Brice (WSDOT) - IfcOpenShell alignment API author
- Dion Moult - Bonsai BIM founder
- Will Sharp (HDR) - buildingSMART committee co-chair
Quick Reference
Entity Creation Pattern
# Always create IFC first, then Blender, then link
entity = ifc.create_entity("IfcAlignmentSegment", ...)
obj = bpy.data.objects.new("Segment", curve_data)
obj["ifc_definition_id"] = entity.id()
Segment Types
LINE→ IfcLine (blue visualization)CIRCULARARC→ IfcCircle (red visualization)CLOTHOID→ IfcClothoid (future)CONSTANTGRADIENT→ IfcLine (vertical)PARABOLICARC→ IfcPolynomialCurve (vertical)
Required Terminal Segment
# Last segment MUST be zero-length
align_api.add_zero_length_segment(layout)
Minimal Object Storage
# ONLY these 3 properties in Blender
obj["ifc_definition_id"] = entity.id()
obj["ifc_class"] = entity.is_a()
obj["GlobalId"] = entity.GlobalId
Document Version: 1.1 Updated: January 2026 Changes: Updated architecture to match Bonsai's actual pattern (core/ and tool/ at package root) For: Saikei Civil IfcOpenShell PR - Horizontal Alignment Visualization