Skip to content

Csg2g - #232

Open
erikg wants to merge 12 commits into
mainfrom
csg2g
Open

Csg2g#232
erikg wants to merge 12 commits into
mainfrom
csg2g

Conversation

@erikg

@erikg erikg commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Bidirectional OpenSCAD converters that preserve CSG structure:

  • csg-g — imports OpenSCAD's evaluated .csg format into BRL-CAD .g files
  • g-scad — exports BRL-CAD .g objects to .scad files

Both converters map primitives and boolean operations directly to their equivalents rather than tessellating to mesh, producing clean analytic geometry wherever possible.

csg-g (OpenSCAD → BRL-CAD)

.csg input BRL-CAD output
cube() RPP
sphere() SPH
cylinder() TGC
polyhedron() ARB4/5/6/7/8 if convex match, else BOT
rotate_extrude(circle()) TOR
linear_extrude(square()) RPP
linear_extrude(circle()) TGC
linear_extrude(polygon()) sketch + extrusion
import("file.stl") BOT (STL inlined during conversion)
difference/union/intersection/group boolean combinations
multmatrix() transformation matrix
color() color/alpha attributes
render() pass-through

Polyhedron import detects all ARB types (4–8) with a convexity check — non-convex polyhedra fall back to BOT. Tetrahedra skip the convexity check since they're always convex.

g-scad (BRL-CAD → OpenSCAD)

BRL-CAD input .scad output
SPH/ELL sphere() + multmatrix()
TGC/REC cylinder() + multmatrix()
ARB4–8 polyhedron() (degenerate faces stripped)
BOT polyhedron()
TOR rotate_extrude() circle()
PARTICLE hull() { sphere(); sphere(); }
PIPE union() of cylinders + joint spheres
REVOLVE rotate_extrude(angle) polygon()
EXTRUDE linear_extrude() polygon()
HALF multmatrix() cube(2e10)
combinations difference/union/intersection
combination colors color()
everything else tessellated via ft_tessellatepolyhedron()

Tessellation uses a fresh rt_db_get_internal copy to avoid heap corruption from ft_tessellate modifying the caller's internal.

Usage

# OpenSCAD to BRL-CAD (two-step via .csg)
openscad -o model.csg model.scad
csg-g model.csg model.g

# BRL-CAD to OpenSCAD
g-scad -o model.scad model.g object_name

Testing

Round-trip tested across 59 BRL-CAD sample models (.g → .scad → .csg → .g):

  • 55 of 59 models completed the full circuit successfully
  • 43 models had zero warnings in both directions — lossless round-trip
  • 4 timeouts on large BREP/DSP tessellation (bishop, terra, 2 NIST models)
  • All round-tripped .scad files verified to render in OpenSCAD
  • regress-repository passes (API wrapper compliance)
  • Full ninja test suite passes (1093/1095, 2 pre-existing non-issues)
  • CI passes on Ubuntu GCC, MSVC Ninja, MSVC Standard Tools, macOS Clang

Solid count mismatches in round-trip are structural, not data loss:

  • PIPE primitives expand to multiple cylinders+spheres in OpenSCAD (e.g., toyjeep 321→475)
  • One hull() node (from PARTICLE) is not parsed back by csg-g

Known Limitations

  • hull() and minkowski() nodes in .csg are skipped (no CSG equivalent without shelling out to OpenSCAD)
  • linear_extrude with twist parameter has no BRL-CAD equivalent
  • Binary STL in import() not yet handled (ASCII only)
  • Named hierarchy and region IDs are lost through the .csg format (OpenSCAD doesn't preserve them)
  • Transforms get baked into primitives by OpenSCAD's .csg export — geometry is equivalent but representation differs

erikg added 12 commits April 8, 2026 09:12
Import OpenSCAD's evaluated CSG tree format (.csg) into BRL-CAD .g
files.  The .csg format is produced by: openscad -o file.csg input.scad

Supports cube, sphere, cylinder primitives, boolean operations
(difference, union, intersection, group), multmatrix transforms,
and color nodes.  Warns and skips unsupported nodes (linear_extrude,
rotate_extrude, hull, minkowski).
Parse OpenSCAD polyhedron() nodes, attempting to match ARB primitives
before falling back to BOT (bag of triangles).  Detection order:
arb8 (8 verts, 6 quad faces), arb5 (5 verts, quad base + apex),
arb4 (4 verts, 4 tri faces).  Non-matching polyhedra are
fan-triangulated into BOT solids.
Non-convex polyhedra go straight to BOT since ARB primitives require
convexity for correct ray tracing.  The check verifies that for each
face, all other vertices lie on the same side of the face plane.
Tetrahedra (arb4) skip the check since they are always convex.
Parse color([r, g, b, a]) and store as BRL-CAD attributes:
  color = R/G/B (0-255 range)
  alpha = 0.NNN (only if < 1.0)
Walks the CSG tree preserving boolean structure (union, difference,
intersection), transformation matrices, and primitive geometry.
Direct mappings: SPH/ELL to sphere(), TGC/REC to cylinder(), ARB to
polyhedron(), BOT to polyhedron(), TOR to rotate_extrude(circle()).
Unsupported primitives are tessellated via ft_tessellate/nmg_bot.
Combination colors are emitted as color() wrappers.
rotate_extrude(circle()) maps to mk_tor (torus).  This recovers all
1145 previously-skipped solids across the test suite.

linear_extrude(square()) maps to mk_rpp (box).
linear_extrude(circle()) maps to mk_tgc (cylinder/cone).

Unsupported extrude children (polygon, etc.) warn and skip.
csg-g: linear_extrude(polygon()) creates a BRL-CAD sketch from the
polygon vertices/paths, then extrudes it with mk_extrusion.  Also
handles rotate_extrude(circle()) as torus and linear_extrude with
circle/square children as cylinder/box.

g-scad: ID_EXTRUDE primitives are exported as linear_extrude(polygon())
by reading the referenced sketch's vertices and line segments.
ID_SKETCH primitives encountered standalone are silently skipped.
ft_tessellate can corrupt the rt_db_internal structure, causing
heap corruption when rt_db_free_internal runs later.  Fix by
re-reading the primitive from the database for a fresh copy
before tessellating.

Note: EHY tessellation (rt_ehy_tess) has a pre-existing heap
corruption bug in BRL-CAD itself — not addressed here.
csg-g: import(file="foo.stl") now reads the referenced STL file
relative to the .csg file's directory and inlines it as a BOT.
Also adds render() as a pass-through wrapper.

g-scad: ID_HALF (halfspace) is exported as a 2e10mm cube positioned
on the correct side of the plane via multmatrix.  Not mathematically
exact but sufficient for any practical CSG subtraction.
ID_PARTICLE: exported as hull() of two spheres at endpoints.
ID_PIPE: exported as union of cylinders for straight segments
  with spheres at joints for smooth connections.
ID_REVOLVE: exported as rotate_extrude(polygon()) by reading
  the referenced sketch's vertices and line segments.
ARB6 (triangular prism): 6 vertices, 5 faces (2 triangles + 3 quads).
Identifies opposing triangle faces and pairs vertices via shared
side quads.

ARB7 (arb8 with one collapsed corner): 7 vertices, 6 faces
(2 triangles + 4 quads).  Finds the bottom quad, identifies top
vertices, and pairs via face adjacency.

Previously these fell through to BOT.
@starseeker

Copy link
Copy Markdown
Member

@erikg Is this ready to merge yet, or are you still working on it?

@erikg

erikg commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@erikg Is this ready to merge yet, or are you still working on it?

ready, just wanted review/confirmation since this is a new capability

@starseeker

Copy link
Copy Markdown
Member

@erikg we're about to release, so we'll review it and plan to merge next week

@brlcad

brlcad commented Jul 16, 2026

Copy link
Copy Markdown
Member

Pretty sweet. Just fyi arbs can be planar concave now (it turned them into bots). We're also moving them all into gcv but that can happen later. Would be good to get the ones that failed attached as an issue with detail on what it needs like Minkowski or whatever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants