ScriptHarness library reference
ScriptHarness is the SPM library product behind the script workflow. Its core type, ScriptContext, accumulates geometry built with the full OCCTSwift API, writes each body as a BREP file, optionally writes a combined STEP, and emits a manifest.json that OCCTSwiftViewport’s ScriptWatcher live-reloads.
.product(name: "ScriptHarness", package: "OCCTSwiftScripts"),
Source: Sources/ScriptHarness/.
ScriptContext
public final class ScriptContext: Sendable {
public let exportSTEP: Bool
public let metadata: ManifestMetadata?
public init(exportSTEP: Bool = true, metadata: ManifestMetadata? = nil)
}
The init resolves the output directory (and cleans it):
- iCloud Drive —
~/Library/Mobile Documents/com~apple~CloudDocs/OCCTSwiftScripts/output/if present, else - Local —
~/.occtswift-scripts/output/.
(On iOS/sandboxed platforms it writes into the app’s Documents directory.)
Minimal lifecycle
A script always: make a context → build geometry → add each body → emit last.
import OCCTSwift
import ScriptHarness
let ctx = ScriptContext()
let C = ScriptContext.Colors.self
// Sketch a profile
let profile = Wire.rectangle(width: 20, height: 10)!
try ctx.add(profile, id: "sketch", color: C.yellow)
// Extrude → fillet
let solid = Shape.extrude(profile: profile, direction: SIMD3(0, 0, 1), length: 5)!
let filleted = solid.filleted(radius: 1.0)!
try ctx.add(filleted, id: "body", color: C.blue)
// Boolean cut
let hole = Shape.cylinder(radius: 3, height: 10)!.translated(by: SIMD3(5, 0, -1))!
let result = filleted.subtracting(hole)!
try ctx.add(result, id: "final", color: C.steel)
try ctx.emit(description: "Filleted plate with hole")
Run it from a checkout with swift run Script, or run any .swift file headlessly with occtkit run <file>.swift.
Adding geometry
ScriptContext.add is overloaded for Shape, Wire, and Edge. Wire/edge inputs are converted to a Shape internally (BREP preserves the topology; the viewport draws them as wireframe). Each add writes body-N.brep immediately.
// Shape (solids, shells, compounds, faces)
public func add(_ shape: Shape, id: String? = nil, color: [Float]? = nil,
name: String? = nil, roughness: Float? = nil, metallic: Float? = nil) throws
// Wire (profiles, sketches, sweep paths)
public func add(_ wire: Wire, id: String? = nil, color: [Float]? = nil, name: String? = nil) throws
// Edge
public func add(_ edge: Edge, id: String? = nil, color: [Float]? = nil, name: String? = nil) throws
// Multiple shapes → one compound (assembly results)
public func addCompound(_ shapes: [Shape], id: String? = nil,
color: [Float]? = nil, name: String? = nil) throws
| Parameter | Type | Notes |
|---|---|---|
id | String? | Body identifier (default "body-N") |
color | [Float]? | RGBA [r,g,b,a], 0–1 |
name | String? | Display name |
roughness / metallic | Float? | PBR (reserved; Shape overload only) |
try ctx.add(shape, id: "part", color: C.steel, name: "Bracket") // solid
try ctx.add(wire, id: "sketch", color: C.yellow) // wireframe
try ctx.addCompound([a, b], id: "assembly", color: C.gray) // compound
Topology graphs
ScriptContext can also export topology graphs (JSON + optional SQLite) alongside bodies. These are consumed by occtkit graph-query and the viewport’s graph tooling.
public func addGraph(_ graph: TopologyGraph, id: String? = nil,
sourceBodyId: String? = nil, sqlite: Bool = true) throws
/// Build + export a TopologyGraph for every shape added so far.
public func addGraphsForAllShapes(sqlite: Bool = true) throws
try ctx.add(part, id: "part")
try ctx.addGraphsForAllShapes(sqlite: true) // → graph-0.json + graph-0.sqlite
try ctx.emit(description: "part + topology graph")
occtkit run <file>.swift --format graph-json,graph-sqliteinjectsaddGraphsForAllShapes(...)for you, so plain scripts need not call it.
Emit
public func emit(description: String? = nil) throws
Call last. It writes (in order): a combined output.step (only when exportSTEP and there are shapes), then manifest.json last — so a partial failure leaves the prior frame intact and the watcher only triggers on a complete frame.
let ctx = ScriptContext(exportSTEP: false) // BREP only, faster (no STEP)
// ... add bodies ...
try ctx.emit(description: "My parametric design")
Predefined colors
ScriptContext.Colors exposes [Float] RGBA constants:
let C = ScriptContext.Colors.self
// .red .green .blue .yellow .orange .purple .cyan .white .gray .steel .brass .copper
try ctx.add(shape, color: C.steel)
Manifest types
emit serialises a ScriptManifest. These Codable/Sendable types are public, so in-process consumers can read or build manifests directly.
public struct ScriptManifest: Codable, Sendable {
public let version: Int // default 1
public let timestamp: Date
public let description: String?
public let bodies: [BodyDescriptor]
public let graphs: [GraphDescriptor]?
public let metadata: ManifestMetadata?
}
public struct BodyDescriptor: Codable, Sendable {
public let id: String?
public let file: String // e.g. "body-0.brep"
public let format: String // "brep"
public let name: String?
public let color: [Float]? // RGBA
public let roughness: Float?
public let metallic: Float?
}
public struct GraphDescriptor: Codable, Sendable {
public let id: String
public let file: String // e.g. "graph-0.json"
public let sourceBodyId: String?
public let stats: GraphStats? // faces/edges/vertices/shells/solids
}
public struct ManifestMetadata: Codable, Sendable {
public let name: String
public let revision: String?
public let dateCreated: Date?
public let dateModified: Date?
public let source: String?
public let tags: [String]?
public let notes: String?
}
Attach part metadata at construction time:
let ctx = ScriptContext(metadata: ManifestMetadata(
name: "L-Bracket", revision: "A", source: "parametric script", tags: ["bracket", "demo"]
))
Errors
public enum ScriptError: Error, LocalizedError {
case conversionFailed(String) // e.g. Wire → Shape failed
case message(String)
}
Fallible OCCTSwift factories return optionals — unwrap with guard let/if let in real code, or a trailing ! only in throwaway scripts. ScriptContext.add throws ScriptError.conversionFailed when a Wire/Edge/compound conversion fails.