Skip to content

Latest commit

 

History

History
305 lines (253 loc) · 13.5 KB

File metadata and controls

305 lines (253 loc) · 13.5 KB
title CADFileLoader
parent API Reference

CADFileLoader

The bridge between CAD files and renderable geometry. CADFileLoader wraps OCCTSwiftIO's headless ShapeLoader and converts the resulting shapes into ViewportBody + CADBodyMetadata for renderable, pickable consumers. It also exposes the standalone Shape → body bridge (shapeToBodyAndMetadata) and two mesh-quality presets. For headless, shape-only work with no Viewport dependency, use OCCTSwiftIO.ShapeLoader directly instead.

Topics


CADFileLoader.load(...)

Loads a CAD file and returns viewport bodies with selection metadata. STL and IGES loads that fail the primary bridge are transparently re-loaded via the robust sewing/healing variants (Shape.loadSTLRobust / Shape.loadIGESRobust); STEP / OBJ / BREP have no fallback.

When the robust reload returns a multibody result — since OCCTSwift v1.11.3 a sewn multibody STL comes back as a compound of solids (OCCTSwift#302) — it is split into one ViewportBody per body rather than one lumped body, matching the STEP path. Note the primary STL path meshes loose faces as a single body: per-body identity for a mesh file only arises once sewing has recovered solids, i.e. on the robust reload.

public static func load(
    from url: URL,
    format: CADFileFormat,
    progress: ImportProgress? = nil,
    includeIdentity: Bool = false
) async throws -> CADLoadResult
  • Parameters:
    • url: file URL to load.
    • format: the CADFileFormat (.step, .iges, .stl, .obj, .brep, …).
    • progress: optional progress + cancellation observer. Honoured by .step and .iges only (STL / OBJ / BREP are single-call upstream). If progress.shouldCancel() returns true, the import throws OCCTSwift.ImportError.cancelled.
    • includeIdentity: populate CADLoadResult.identity with a ShapeIdentity per body: the shape it was tessellated from, a BRepGraph for it, and the three ordinal-to-identity tables SubShapePickResolver reads. Off by default: each body costs a BRepGraph, measured at roughly half the cost of meshing that body, and a consumer that loads geometry to render or reproject it never picks. Turn it on for anything that does. This is the only supported way to get identity out of a multi-body file load.
  • Returns: a CADLoadResult with bridged bodies, per-body metadata, raw shapes, any PMI (dimensions / tolerances / datums), and, when asked, per-body identity.
  • Example:
    let result = try await CADFileLoader.load(
        from: URL(fileURLWithPath: "/path/bracket.step"),
        format: .step
    )
    
    // Picking as well as rendering: ask for identity, then read it by body id.
    let pickable = try await CADFileLoader.load(
        from: URL(fileURLWithPath: "/path/assembly.step"),
        format: .step,
        includeIdentity: true
    )
    let uid = pickable.identity["step-0"]?.faces.uid(forOrdinal: 3)

CADFileLoader.loadFromManifest(...)

Loads bodies from a script manifest (manifest.json plus its referenced BREP files), applying each body's recorded colour. Synchronous.

public static func loadFromManifest(
    at url: URL,
    includeIdentity: Bool = false
) throws -> CADLoadResult
  • Parameters:
    • url: file URL of the manifest.json.
    • includeIdentity: as on load(from:format:progress:includeIdentity:).
  • Returns: a CADLoadResult whose bodies use ids of the form "script-<descriptor.id>" and fall back to grey (0.7, 0.7, 0.7, 1) where no colour was recorded.
  • Example:
    let result = try CADFileLoader.loadFromManifest(
        at: URL(fileURLWithPath: "/path/output/manifest.json")
    )

CADFileLoader.shapeToBodyAndMetadata(...)

Converts an OCCTSwift Shape to a ViewportBody and optional CADBodyMetadata. Meshes the shape (using the high-quality preset by default), applies crease-aware normal smoothing, extracts wireframe edge polylines, and gathers source-shape vertices for picking. If meshing fails it still returns an edge-only body when edge polylines could be extracted.

public static func shapeToBodyAndMetadata(
    _ shape: Shape,
    id bodyID: String,
    color rgba: SIMD4<Float>,
    stl: Bool = false,
    deflection customDeflection: Double? = nil,
    gpuTessellation: Bool = false,
    edgeDeflection: Double = defaultEdgeDeflection,
    maxPointsPerEdge: Int = defaultMaxPointsPerEdge,
    includeMeasurements: Bool = false
) -> (ViewportBody?, CADBodyMetadata?)
  • Parameters:
    • shape — the OCCTSwift shape to bridge.
    • id — body identifier.
    • color — RGBA body colour.
    • stl — if true, uses a coarser deflection (1.0) suitable for pre-tessellated STL data.
    • deflection — custom linear deflection override; lower = smoother.
    • gpuTessellation — if true, uses the coarser tessellationMeshParams preset (GPU PN triangles refine it).
    • edgeDeflection — linear deflection for the wireframe edge polylines (independent of the triangle deflection). Defaults to defaultEdgeDeflection (0.005).
    • maxPointsPerEdge — hard cap on points per edge polyline. Defaults to defaultMaxPointsPerEdge (1000).
    • includeMeasurements — if true, populates metadata.measurements with per-face areas and per-edge lengths (via Shape.measure). Off by default — O(faces).
  • Returns: a tuple (ViewportBody?, CADBodyMetadata?). Both elements are nil when meshing fails and no edge polylines could be extracted.
  • Example:
    let (body, meta) = CADFileLoader.shapeToBodyAndMetadata(
        Shape.box(width: 10, height: 5, depth: 3)!,
        id: "box",
        color: SIMD4<Float>(0.6, 0.6, 0.65, 1),
        includeMeasurements: true
    )

CADFileLoader.shapeToBodyMetadataAndIdentity(...)

Overload of shapeToBodyAndMetadata that also emits a FaceIdentityTable mapping every ordinal in the returned metadata's faceIndices back to the Shape it was tessellated from, and, when graph is supplied, to the durable GraphUID minted from that graph. See FaceIdentityTable for why this exists: a face ordinal resolved via shape.subShapes(ofType: .face)[ordinal] silently misaligns once a face is shared between two shells, and this captures the correspondence directly instead.

public static func shapeToBodyMetadataAndIdentity(
    _ shape: Shape,
    id bodyID: String,
    color rgba: SIMD4<Float>,
    stl: Bool = false,
    deflection customDeflection: Double? = nil,
    gpuTessellation: Bool = false,
    edgeDeflection: Double = defaultEdgeDeflection,
    maxPointsPerEdge: Int = defaultMaxPointsPerEdge,
    includeMeasurements: Bool = false,
    directMesh useDirectMesh: Bool = false,
    graph: BRepGraph? = nil
) -> (ViewportBody?, CADBodyMetadata?, FaceIdentityTable?)
  • Parameters: same as shapeToBodyAndMetadata, plus graph, an optional BRepGraph built from this same shape. When supplied, it populates FaceIdentityTable.uids, minted via graph.findNode(for:) on each ordinal's face so IsSame semantics hold. Without a graph, only FaceIdentityTable.shapes is populated.
  • Returns: a tuple (ViewportBody?, CADBodyMetadata?, FaceIdentityTable?). shapeToBodyAndMetadata itself is unchanged; this is a separate, additive overload.
  • Example:
    let box = Shape.box(width: 10, height: 5, depth: 3)!
    let graph = BRepGraph(shape: box)!
    let (body, meta, faceTable) = CADFileLoader.shapeToBodyMetadataAndIdentity(
        box, id: "box", color: SIMD4<Float>(0.6, 0.6, 0.65, 1), graph: graph
    )

CADFileLoader.shapeToBodyMetadataAndIdentities(...)

Overload of shapeToBodyAndMetadata that emits identity tables for all three pickable sub-shape kinds: FaceIdentityTable, EdgeIdentityTable, VertexIdentityTable. Each maps the render-path ordinal stored in the corresponding ViewportBody array (faceIndices / edgeIndices / vertexIndices) back to the Shape it was extracted from, and, when graph is supplied, to the durable GraphUID minted from that graph.

public static func shapeToBodyMetadataAndIdentities(
    _ shape: Shape,
    id bodyID: String,
    color rgba: SIMD4<Float>,
    stl: Bool = false,
    deflection customDeflection: Double? = nil,
    gpuTessellation: Bool = false,
    edgeDeflection: Double = defaultEdgeDeflection,
    maxPointsPerEdge: Int = defaultMaxPointsPerEdge,
    includeMeasurements: Bool = false,
    directMesh useDirectMesh: Bool = false,
    graph: BRepGraph? = nil
) -> (ViewportBody?, CADBodyMetadata?, FaceIdentityTable?, EdgeIdentityTable?, VertexIdentityTable?)
  • Parameters: same as shapeToBodyMetadataAndIdentity. Pass a BRepGraph built from this same shape to populate every table's uids; without one, only shapes is populated on each table.
  • Returns: a five-element tuple. shapeToBodyAndMetadata and shapeToBodyMetadataAndIdentity are both unchanged; this is a separate, additive overload rather than an extension of either existing return shape.
  • Example:
    let box = Shape.box(width: 10, height: 5, depth: 3)!
    let graph = BRepGraph(shape: box)!
    let (body, meta, faceTable, edgeTable, vertexTable) = CADFileLoader.shapeToBodyMetadataAndIdentities(
        box, id: "box", color: SIMD4<Float>(0.6, 0.6, 0.65, 1), graph: graph
    )

Mesh presets

Two MeshParameters presets steer shapeToBodyAndMetadata. They are public so you can inspect or reuse them.

public static let highQualityMeshParams: MeshParameters
public static let tessellationMeshParams: MeshParameters
  • highQualityMeshParams — fine CPU mesh for smooth curved surfaces without GPU tessellation (deflection 0.03, ~11° angle, controlled surface deflection, parallel). The default used by shapeToBodyAndMetadata.
  • tessellationMeshParams — moderate CPU mesh for GPU PN-triangle tessellation (deflection 0.1, ~20° angle). Selected by gpuTessellation: true.

Static defaults

public static let defaultEdgeDeflection: Double = 0.005
public static let defaultMaxPointsPerEdge: Int = 1000
  • defaultEdgeDeflection — default linear deflection for wireframe edge polyline extraction.
  • defaultMaxPointsPerEdge — default per-edge point cap for wireframe polyline extraction.

Note: WireConverter defines its own constants of the same names with a different cap (10000) — see WireConverter.


CADLoadResult

The value type returned by every CADFileLoader load. @unchecked Sendable.

public struct CADLoadResult: @unchecked Sendable {
    public var bodies: [ViewportBody]
    public var metadata: [String: CADBodyMetadata]
    public var shapes: [Shape]
    public var dimensions: [DimensionInfo]
    public var geomTolerances: [GeomToleranceInfo]
    public var datums: [DatumInfo]
    public var identity: [String: ShapeIdentity]

    public init(
        bodies: [ViewportBody] = [],
        metadata: [String: CADBodyMetadata] = [:],
        shapes: [Shape] = [],
        dimensions: [DimensionInfo] = [],
        geomTolerances: [GeomToleranceInfo] = [],
        datums: [DatumInfo] = [],
        identity: [String: ShapeIdentity] = [:]
    )
}
  • bodies: bridged, renderable bodies.
  • metadata: per-body selection metadata, keyed by body id.
  • shapes: the raw OCCTSwift shapes that were loaded. Do not pair positionally with bodies, see below.
  • dimensions / geomTolerances / datums: PMI (product manufacturing information) surfaced by formats that carry it. The types come from OCCTSwiftIO.
  • identity: a ShapeIdentity per loaded body, keyed by ViewportBody.id. Empty unless the load was asked for it (includeIdentity: true).

Why identity is keyed by body id, and shapes is not safe to pair

shapes and bodies line up positionally on the primary bridge, where both arrays are appended together only on tessellation success. They do not line up after the STL/IGES robust reload (reloadRobustAndBridge), which appends a shape for every input even when that input produced no body: every later pairing shifts by one, and a body silently gets another body's geometry.

From outside the loader the only symptom is a count mismatch (shapes.count > bodies.count), which is why consumers used to guard on it and drop identity wholesale. identity is built inside the loader, in the same branch that creates each body, so nothing pairs positionally and there is nothing left to guard (OCCTSwiftInteraction#7).

let result = try await CADFileLoader.load(from: url, format: .step, includeIdentity: true)
for body in result.bodies {
    guard let identity = result.identity[body.id],
          let meta = result.metadata[body.id] else { continue }
    let ref = SubShapePickResolver.resolveFace(
        triangleIndex: pick.triangleIndex,
        faceIndices: meta.faceIndices,
        identity: identity.faces,
        shape: identity.shape)
}