A complete pipeline for authoring 3D building components in Blender, exporting them as GLB + JSON metadata, and assembling them in a configurator engine with placement validation, rendering presets, and live pricing.
Built for the Tiny House Titans CabinPlanner configurator.
BlendaModula solves a specific problem: you want to design tiny houses by snapping together pre-authored 3D components (windows, doors, roof trim, corner boards) onto a shell, and have the system automatically validate placement rules, compute materials costs, and render the result with production-quality lighting.
The pipeline has three layers:
-
Blender authoring — Model components in Blender following naming conventions. Each component has anchor points ("ports") that define how it attaches to the building shell.
-
Python tooling — Validate and export
.blendfiles to GLB format. Batch process an entire library. Generate consistent preview thumbnails. -
TypeScript configurator engine — Place modules on walls/roofs/corners with rule validation, detect collisions, compute BOM and pricing, apply render presets.
- Blender 4.0+ (for authoring and export tooling)
- Node.js 18+ (for the configurator engine)
- Python 3.10+ (for standalone JSON validation; Blender scripts use Blender's built-in Python)
cd modules_3d/configurator
npm install# TypeScript engine tests (60 tests)
cd modules_3d/configurator
npm test
# Type-check
npm run build
# Validate all module JSON metadata (no Blender needed)
cd /path/to/BlendaModula
python3 modules_3d/tools/blender/batch_export.py -- \
--library-root modules_3d/library \
--json-only \
--output-report batch_report.jsonBlendaModula/
CLAUDE.md # Build spec and project status
README.md # This file
modules_3d/
library/ # Module library (10 components)
windows/ # 3 window types
doors/ # 3 door types
roof_edges/ # 2 roof edge kits
corners_trims/ # 2 trim kits
furniture/ # Placeholder for future modules
tools/
blender/ # Blender Python automation scripts
schema/ # JSON Schema for module.json
configurator/ # TypeScript engine
types.ts # Shared type definitions
placement/ # Port math, host rules, collision, resolver
render/ # PBR setup, HDRI, shadows/tone mapping
pricing/ # BOM aggregation and pricing computation
templates/ # Tiny house preset configurations
docs/ # Technical documentation
MODULE_STANDARD.md # Blender authoring guide
PORTS_AND_HOSTING.md # Port system reference
RENDERING_GUIDE.md # PBR and lighting guide
PRICING_BOM_GUIDE.md # BOM and pricing formula
Each module lives in its own folder with these files:
| File | Purpose | Required |
|---|---|---|
module.blend |
Blender source file | Yes* |
module.glb |
Exported binary glTF | Yes* |
module.json |
Metadata, ports, rules, BOM | Yes |
preview.png |
512x512 thumbnail | Yes* |
*The .blend, .glb, and .png files are generated during authoring. The V1 build includes complete module.json stubs for all 10 components — Blender geometry authoring is the next step.
These were chosen to maximize visual impact on tiny house exteriors:
Windows
- WIN_AWNING_24x36_v1 — 24"x36" top-hinged awning window. Common in bathrooms and kitchens.
- WIN_CASEMENT_30x48_v1 — 30"x48" side-hinged casement. Egress-capable. Most common operable window in tiny houses.
- WIN_PICTURE_48x48_v1 — 48"x48" fixed picture window. Biggest visual impact on elevations.
Doors
- DOR_ENTRY_36_v1 — Standard 36" entry door with lever handle. Multiple panel styles.
- DOR_SLIDER_72_v1 — 72" two-panel sliding glass door. Major feature element.
- DOR_FRENCH_60_v1 — 60" double-leaf French door with glass lites. Premium feel.
Roof Edge Kits (1.0m tileable segments)
- ROOF_EAVE_KIT_v1 — Fascia board + soffit panel + optional frieze. Tiles along eave edges.
- ROOF_RAKE_KIT_v1 — Rake/barge board + optional boxed return. Runs along gable edges.
Corner and Trim Kits (1.0m tileable segments)
- CORNER_TRIM_OUTSIDE_v1 — Two intersecting boards for outside corners. Huge visual upgrade.
- OPENING_CASING_KIT_v1 — Parametric casing for any window or door opening. Highest impact-per-effort module.
Follow the naming conventions:
| Prefix | Purpose | Example |
|---|---|---|
MESH_ |
Visible geometry | MESH_frame |
PORT_ |
Anchor empties (ports) | PORT_HOST_WALL_PLANE |
COLL_ |
Collision proxy (optional) | COLL_bounds |
HELP_ |
Helper empty (optional) | HELP_center_ref |
Ports are Blender empties (Plain Axes) that define attachment points. Each module type has required ports — for example, a window needs PORT_HOST_WALL_PLANE, PORT_OPENING_CENTER, PORT_LEFT_JAMB, PORT_RIGHT_JAMB, PORT_SILL_LINE, and PORT_HEADER.
Port orientation: +Y = outward, +X = right, +Z = up.
All objects must have scale (1,1,1) and rotation (0,0,0). In Blender: Ctrl+A > All Transforms.
blender -b module.blend -P validate_module.py -- \
--json-path ./module.json \
--report ./validation_report.jsonThe validator checks naming, transforms, ports, materials, bounding box, and JSON Schema compliance. Fix all errors before exporting.
blender -b module.blend -P export_module.py -- \
--out ./module.glb \
--embed-textures \
--update-bbox \
--json-path ./module.jsonblender -b module.blend -P bake_previews.py -- \
--out ./preview.png \
--resolution 512blender -b -P batch_export.py -- \
--library-root ./library \
--output-report ./batch_report.jsonSee modules_3d/tools/blender/README.md for full CLI documentation.
The TypeScript engine handles everything after export.
The placement system resolves where modules go and validates the result:
import { registerModule, resolve } from "./placement/resolver.js";
import { canHost } from "./placement/host_rules.js";
import { findConflicts } from "./placement/collision.js";
// Register module metadata (loaded from module.json)
registerModule(windowMetadata);
// Resolve a placement
const result = resolve({
moduleId: "WIN_CASEMENT_30x48_v1",
hostId: "front_wall",
insertionParam: 0.5, // halfway along wall
heightOffset: 0.914, // sill height
parameters: { flipX: false },
}, scene);
if (result.errors.length === 0) {
// result.transform — 4x4 matrix to position the module
// result.bomLineItems — cost items generated by this placement
// result.hostModifications — e.g., "cut opening" record
}Validation checks performed automatically:
- Module type compatible with host type
- Wall type in module's allowed list
- Minimum distance from wall corners
- Minimum spacing between openings
- Opening doesn't exceed 70% of wall length
- No bounding box overlaps with existing modules
All transforms use 4x4 matrices in column-major order (matching glTF/WebGL):
import {
alignPlaneToPlane, // Snap module port to host wall surface
alignPointToPoint, // Translate module to host point
applyFlip, // Mirror for flipX parameter
interpolateEdge, // Position along edge (t=0..1)
getPortTransform, // Extract port from GLB scene graph
} from "./placement/port_math.js";Apply production-quality rendering settings:
import { getRenderConfig, listRenderPresets } from "./render/shadows.js";
import { getHDRIConfig } from "./render/hdri.js";
// Get a complete render config
const config = getRenderConfig("production_exterior");
// Returns: { preset, shadow, ssao, toneMapping, sunLight }
// Available presets:
// "production_exterior" — Default. Bright overcast, soft shadows
// "golden_hour_exterior" — Warm sun, long shadows, dramatic
// "studio_clean" — Neutral studio, even lighting
// "night_mood" — Dark ambient, no sunEvery placed module contributes line items to the bill of materials:
import { aggregateBom, exportBom } from "./pricing/bom.js";
import { computePricing, formatPricingSummary } from "./pricing/pricing.js";
// Collect BOM from all placed modules
const bom = aggregateBom(scene);
// Compute pricing
const pricing = computePricing(bom, {
marginPercent: 0.25, // 25% margin
shippingFlat: 500.00, // Flat shipping
taxRate: 0.07, // 7% tax
laborRate: 65.00, // $/hr override for labor items
});
console.log(formatPricingSummary(pricing));
// === Pricing Summary ===
// Materials: $2,450.00
// Labor: $487.50
// Hardware: $205.00
// Subtotal: $3,142.50
// Margin (25%): $785.63
// Shipping: $500.00
// Tax (7.0%): $309.97
// ──────────────────
// TOTAL: $4,738.09
// Export as CSV or JSON
const csv = exportBom(bom, "csv");
const json = exportBom(bom, "json");Load a preset tiny house configuration:
import { listTemplates, loadTemplate, computeSceneBom } from "./templates/template_loader.js";
// See available presets
const templates = listTemplates();
// 10 presets: Classic Gable 16'/20'/24'/28', Modern Shed 16'/20'/24',
// Modern Flat 20'/24', A-Frame 20'
// Load a template — generates shell geometry and resolves all module placements
const scene = loadTemplate("TMPL_CLASSIC_GABLE_20");
// Get the BOM for the loaded scene
const bom = computeSceneBom(scene);Each template defines:
- Shell geometry — length, width, wall height, roof type/pitch, wall types
- Module placements — which modules go on which walls/edges, at what position
- Render preset — which lighting configuration to apply
Every module.json is validated against modules_3d/tools/schema/module.schema.json (JSON Schema Draft 2020-12).
The schema enforces:
- Required fields for all modules (id, type, category, version, bbox, ports, hosts, bom, tags)
- ID pattern:
^[A-Z][A-Za-z0-9_]+_v[0-9]+$ - Semantic version format for
versionfield - Port name pattern:
^[A-Z][A-Z0-9_]+$withPORT_prefix on object names - Conditional port requirements — window modules must have HOST_WALL_PLANE + OPENING_CENTER + LEFT_JAMB + RIGHT_JAMB + SILL_LINE + HEADER; door modules must have THRESHOLD instead of SILL_LINE; etc.
- BOM SKU pattern:
^[A-Z]+-[A-Z]+-[A-Z0-9]+$ - Tag format: lowercase kebab-case
cd modules_3d/configurator
# Run all tests
npm test
# Results:
# placement/port_math.test.ts — 33 tests (matrix ops, vector math, alignment, flip, interpolation)
# placement/collision.test.ts — 11 tests (AABB overlap, world bbox, fits-within, conflicts)
# placement/host_rules.test.ts — 7 tests (host compatibility, spacing, structural limits)
# pricing/pricing.test.ts — 9 tests (BOM aggregation, grouping, export, pricing computation)
# Total: 60 tests, all passingModule JSON validation (Python):
# Validate all 10 module stubs
python3 modules_3d/tools/blender/batch_export.py -- \
--library-root modules_3d/library \
--json-only \
--output-report batch_report.json
# Result: 10 passed, 0 failedDetailed guides are in modules_3d/docs/:
| Document | Audience | Contents |
|---|---|---|
MODULE_STANDARD.md |
Blender artists | Naming conventions, transforms, ports, materials |
PORTS_AND_HOSTING.md |
Blender artists + devs | Port reference, orientation, hosting rules |
RENDERING_GUIDE.md |
Engine developers | PBR standards, HDRI presets, shadow/SSAO config |
PRICING_BOM_GUIDE.md |
Engine developers | BOM structure, pricing formula, default config |
Why GLB + JSON (not just GLB)? GLB carries geometry and materials. JSON carries behavior — placement rules, constraints, parameters, pricing. Keeping them separate means you can update pricing without re-exporting geometry, and validate metadata without loading 3D files.
Why ports instead of bounding-box snapping?
Ports give precise, named attachment points with orientation. A window's HOST_WALL_PLANE port defines exactly where it sits on the wall surface with the correct facing direction. This is more reliable than inferring attachment from geometry bounds.
Why tileable segments for roof/corner kits? A 1-meter segment that tiles along an edge means one Blender asset covers any roof length. The engine repeats/trims to fit. This avoids needing separate assets for every roof size.
Why V1 opening strategy (visual overlap)? Boolean CSG on wall meshes is complex and fragile. For exterior-only visualization, simply placing windows/doors on the wall surface looks correct. CSG can be added in V2 when interior views are needed.
Why column-major matrices? Matches glTF and WebGL conventions. No conversion needed when reading transforms from loaded GLB scenes.
The V1 framework is complete. To reach a working configurator:
- Author Blender geometry for all 10 modules (
.blendfiles with actual meshes, textures, and port empties) - Integrate with a 3D renderer (Three.js or Babylon.js) to load GLBs and apply placement transforms
- Build a frontend UI for selecting templates, placing modules, and seeing live pricing
- Add furniture modules (kitchen pack, bath pack)
- Implement V2 opening cuts (CSG on wall geometry for interior views)