Script harness & run
The ScriptHarness library — ScriptContext, ManifestMetadata, Colors — is the primary author API for writing parametric CAD scripts. The run verb hosts those scripts headlessly, caching an SPM workspace and building/executing them on demand.
Entries
ScriptContext · add(Shape) · add(Wire) · add(Edge) · addCompound · addGraph · addGraphsForAllShapes · emit · ManifestMetadata · Colors · run
ScriptContext init
Initialize a script context; accumulates geometry and writes BREP + STEP files to the output directory on emit().
Signature
ScriptContext(exportSTEP: Bool = true, metadata: ManifestMetadata? = nil)
Parameters
| name | type | default | description |
|---|---|---|---|
exportSTEP | Bool | true | Whether to write a combined output.step file (disable to skip STEP for speed) |
metadata | ManifestMetadata? | nil | Optional project metadata (name, revision, tags, notes) written into manifest.json |
What it does — Creates a new context, cleans the output directory (iCloud Drive ~/Library/Mobile Documents/.../OCCTSwiftScripts/output/ if available, else ~/.occtswift-scripts/output/), and prepares to accumulate geometry.
Example
import OCCTSwift
import ScriptHarness
let ctx = ScriptContext(
exportSTEP: true,
metadata: ManifestMetadata(
name: "Parametric Bracket",
revision: "1.0",
tags: ["fastener", "cast-aluminum"]
)
)
let profile = Wire.rectangle(width: 20, height: 10)!
try ctx.add(profile, id: "sketch", color: ScriptContext.Colors.yellow)
Notes — ScriptContext is Sendable (thread-safe via internal NSLock). Output directory is cleaned on init; if you call ScriptContext() multiple times in one script, only the final emit() persists.
add(Shape)
Add a solid, shell, compound, or face to the output; writes BREP immediately.
Signature
try ctx.add(
_ shape: Shape,
id: String? = nil,
color: [Float]? = nil,
name: String? = nil,
roughness: Float? = nil,
metallic: Float? = nil
) throws
Parameters
| name | type | required | description |
|---|---|---|---|
shape | Shape | yes | Any OCCTSwift shape (solid, shell, compound, face, etc.) |
id | String? | no | Body identifier; default "body-N" where N is the add order |
color | [Float]? | no | RGBA as [r, g, b, a] with values in 0–1 range |
name | String? | no | Display name shown in the viewport |
roughness | Float? | no | PBR roughness (reserved for future use) |
metallic | Float? | no | PBR metallic value (reserved for future use) |
What it does — Writes the shape to body-N.brep immediately, records metadata in the manifest, and appends to the internal shape list for later graph export or compound operations.
Example
let solid = Shape.box(width: 10, height: 10, depth: 10)!
let filleted = solid.filleted(radius: 1.0) ?? solid
try ctx.add(filleted, id: "bracket", color: [0.7, 0.7, 0.75, 1.0], name: "Main Bracket")
Returns — Throws ScriptError on BREP write failure.
Notes — Wire and Edge shapes should use the overloaded add(_:Wire) or add(_:Edge) methods instead, which convert internally. BREP write is fast (~1 ms); STEP (optional) is slower (~50 ms).
add(Wire)
Add a wire profile or sketch to the output; displayed as wireframe.
Signature
try ctx.add(
_ wire: Wire,
id: String? = nil,
color: [Float]? = nil,
name: String? = nil
) throws
Parameters — Same as add(Shape), minus roughness/metallic.
What it does — Converts the wire to a Shape via Shape.fromWire(_:) and adds it as a wireframe body. Useful for sketches, sweep paths, or construction geometry.
Example
let profilePath = Wire.circle(radius: 5.0, center: SIMD3(0, 0, 0))!
try ctx.add(profilePath, id: "sweep-path", color: ScriptContext.Colors.cyan)
Notes — The wire’s topology is preserved in BREP format, displayed as edges (no fill).
add(Edge)
Add a single edge or curve to the output; displayed as wireframe.
Signature
try ctx.add(
_ edge: Edge,
id: String? = nil,
color: [Float]? = nil,
name: String? = nil
) throws
Parameters — Same as add(Shape), minus roughness/metallic.
What it does — Converts the edge to a Shape via Shape.fromEdge(_:) and adds it as a wireframe body. Useful for construction axes or curve references.
Example
let axis = Edge.line(from: SIMD3(0, 0, 0), to: SIMD3(0, 0, 100))!
try ctx.add(axis, id: "rotation-axis", color: ScriptContext.Colors.red)
addCompound
Add multiple shapes as a single compound body.
Signature
try ctx.addCompound(
_ shapes: [Shape],
id: String? = nil,
color: [Float]? = nil,
name: String? = nil
) throws
Parameters — Same as add(Shape), minus roughness/metallic.
What it does — Compounds the input shapes and writes as a single BREP body. Useful for assemblies or multi-part results.
Example
let part1 = Shape.box(width: 10, height: 10, depth: 5)!
let part2 = Shape.cylinder(radius: 3, height: 20)!.translated(by: SIMD3(5, 0, 0))!
try ctx.addCompound([part1, part2], id: "assembly", color: ScriptContext.Colors.gray)
addGraph
Export a topology graph as JSON and optionally SQLite.
Signature
try ctx.addGraph(
_ graph: TopologyGraph,
id: String? = nil,
sourceBodyId: String? = nil,
sqlite: Bool = true
) throws
Parameters
| name | type | required | description |
|---|---|---|---|
graph | TopologyGraph | yes | A topology graph built from a shape |
id | String? | no | Graph identifier; default "graph-N" |
sourceBodyId | String? | no | Body ID this graph was derived from (for reference) |
sqlite | Bool | no | Also write a SQLite database (default true) |
What it does — Writes the graph as graph-N.json (BREPGraph v1 schema) and optionally graph-N.sqlite for indexing and queries. Adds graph metadata to the manifest.
Example
let shape = Shape.box(width: 10, height: 10, depth: 10)!
try ctx.add(shape, id: "part")
if let graph = TopologyGraph(shape: shape) {
try ctx.addGraph(graph, sourceBodyId: "part", sqlite: true)
}
addGraphsForAllShapes
Build and export topology graphs for all shapes added so far.
Signature
try ctx.addGraphsForAllShapes(sqlite: Bool = true) throws
Parameters
| name | type | required | description |
|---|---|---|---|
sqlite | Bool | no | Also write SQLite databases (default true) |
What it does — Convenience method that iterates all accumulated shapes, builds a TopologyGraph for each, and exports each to JSON + optional SQLite. Skips shapes that fail to build a graph. Each graph is linked to its source body ID in the manifest.
Example
try ctx.add(shape1, id: "bracket")
try ctx.add(shape2, id: "fastener")
try ctx.addGraphsForAllShapes(sqlite: false) // JSON only, skip SQLite for speed
try ctx.emit(description: "Parts with topology data")
emit
Write manifest.json (trigger file) and optional combined output.step.
Signature
try ctx.emit(description: String? = nil) throws
Parameters
| name | type | required | description |
|---|---|---|---|
description | String? | no | Short description of the script output |
What it does — If exportSTEP is true, writes a single output.step file combining all added shapes (for external tool interop). Writes manifest.json last — this is the trigger file that the OCCTSwiftViewport file watcher listens to, so geometry is only visible after emit() completes successfully.
Returns — Throws ScriptError on manifest or STEP write failure.
Output files (in order of creation)
body-0.brep,body-1.brep, … (written by eachadd()call)graph-0.json,graph-0.sqlite, … (written byaddGraph()calls)output.step(optional, written byemit()ifexportSTEP: true)manifest.json(written byemit()last; watches file for changes)
Example
import OCCTSwift
import ScriptHarness
let ctx = ScriptContext()
let C = ScriptContext.Colors.self
let profile = Wire.rectangle(width: 20, height: 10)!
let solid = Shape.extrude(profile: profile, direction: SIMD3(0, 0, 1), length: 5)!
let final = solid.filleted(radius: 1.0) ?? solid
try ctx.add(profile, id: "sketch", color: C.yellow)
try ctx.add(final, id: "part", color: C.steel)
try ctx.emit(description: "Filleted rectangular extrusion")
Notes — Call emit() last after all geometry is added. Partial output (missing manifest.json) will not trigger the viewport watcher, so failed scripts leave the previous frame visible. Output directory is ~/.occtswift-scripts/output/ or iCloud Drive equivalent (resolved at ScriptContext init time).
ManifestMetadata
Project or part metadata carried through the manifest.
Fields
struct ManifestMetadata: Codable, Sendable {
public let name: String // required
public let revision: String? // version or revision identifier
public let dateCreated: Date? // ISO 8601 timestamp
public let dateModified: Date? // ISO 8601 timestamp
public let source: String? // URL, file path, or reference
public let tags: [String]? // keywords (e.g., ["fastener", "stainless-steel"])
public let notes: String? // freeform description
}
Parameters
| name | type | required | description |
|---|---|---|---|
name | String | yes | Project or part name |
revision | String? | no | Version, revision, or build ID |
dateCreated | Date? | no | Creation timestamp (encoded as ISO 8601) |
dateModified | Date? | no | Last modification timestamp |
source | String? | no | URL, file path, or source reference |
tags | [String]? | no | Searchable keywords or categories |
notes | String? | no | Long-form notes or description |
Example
let meta = ManifestMetadata(
name: "Mounting Bracket Assembly",
revision: "2.1",
dateCreated: Date(timeIntervalSince1970: 0),
source: "https://github.com/myorg/cad-designs",
tags: ["fastener", "aluminum", "cast"],
notes: "Mounting bracket for the secondary encoder. Cast aluminum with drilled holes."
)
let ctx = ScriptContext(metadata: meta)
Colors
Predefined RGBA color constants for quick styling.
Enum values
ScriptContext.Colors.red // [0.9, 0.2, 0.2, 1.0]
ScriptContext.Colors.green // [0.2, 0.8, 0.3, 1.0]
ScriptContext.Colors.blue // [0.3, 0.5, 0.9, 1.0]
ScriptContext.Colors.yellow // [1.0, 0.9, 0.2, 1.0]
ScriptContext.Colors.orange // [0.9, 0.5, 0.2, 1.0]
ScriptContext.Colors.purple // [0.6, 0.3, 0.8, 1.0]
ScriptContext.Colors.cyan // [0.2, 0.8, 0.9, 1.0]
ScriptContext.Colors.white // [0.9, 0.9, 0.9, 1.0]
ScriptContext.Colors.gray // [0.5, 0.5, 0.5, 1.0]
ScriptContext.Colors.steel // [0.7, 0.7, 0.75, 1.0] (PBR-friendly)
ScriptContext.Colors.brass // [0.8, 0.7, 0.3, 1.0] (warm metallic)
ScriptContext.Colors.copper // [0.8, 0.5, 0.3, 1.0] (warm metallic)
Example
let C = ScriptContext.Colors.self
try ctx.add(solid, id: "chassis", color: C.steel)
try ctx.add(fastener, id: "bolt", color: C.copper)
Notes — All colors are RGBA with alpha = 1.0. Use custom [Float] arrays for custom colors: [r, g, b, a] with values in 0–1 range.
run
Host a user Swift script headlessly via a cached SPM workspace.
Input — Flag form or --serve JSONL mode.
Signature
occtkit run <script.swift> [options]
Parameters
| name | type | required | description |
|---|---|---|---|
<script.swift> | path | yes | Path to a Swift source file to execute |
--format | list | no | Output formats: brep,step,graph-json,graph-sqlite (default: brep,step) |
--output, -o | path | no | Copy output directory to this path after run |
--serve | flag | no | Read JSONL requests on stdin, emit JSONL envelopes on stdout |
What it does — Creates or updates a cached SPM workspace under ~/.occtswift-scripts/runner-cache/workspace/, copies the user script to Sources/Script/main.swift, rewrites imports/settings as needed, runs swift build && swift run Script, and copies the output directory on completion.
Format control — The --format list controls what ScriptContext writes:
brep— individual body BREP files (always written)step— combinedoutput.step(rewritesScriptContext()toScriptContext(exportSTEP: false))graph-json— topology graphs asgraph-N.json(injectsctx.addGraphsForAllShapes(sqlite: false)before emit)graph-sqlite— topology graphs asgraph-N.sqlite(injectsctx.addGraphsForAllShapes(sqlite: true)before emit)
Workspace resolution — ScriptHarness dependency is auto-detected in order:
$OCCTKIT_SCRIPTS_PATHenvironment variable (if set and containsPackage.swift)- Auto-detect from running binary’s
argv[0](works forswift run occtkit ...) - Remote fallback:
from: "0.2.0"tag from GitHub
Example
# Run a script with default output (BREP + STEP)
occtkit run my_design.swift
# Run with graphs, output to a specific directory
occtkit run my_design.swift --format brep,step,graph-json --output /tmp/results
# Service mode: read JSONL requests
printf '{"args":["test.swift"]}\n' | occtkit run --serve
Example result
{
"ok": true,
"exit": 0,
"stdout": "Script output: 2 bodies written to ~/.occtswift-scripts/output\n STEP: output.step\n Graphs: 1 topology graph(s)"
}
Error handling — Build or runtime errors are reported in stderr and exit code is non-zero.
{
"ok": false,
"exit": 1,
"stderr": "Build failed:\nerror: ...",
"error": "Build failed:\nerror: ..."
}
Notes — First run is ~30 s (full SPM build of OCCTSwift + dependencies); subsequent runs are ~1–2 s incremental. Output directory is resolved the same way as ScriptContext: iCloud Drive ~/Library/Mobile Documents/.../OCCTSwiftScripts/output/ if available, else ~/.occtswift-scripts/output/. With --serve, the subcommand’s stdout/stderr and any child process output are captured into the envelope, not leaked to occtkit’s own stdout.