| title | InteractiveContext |
|---|---|
| parent | API Reference |
The per-scene interactive state object, one InteractiveContext to one ViewportController. It owns
the array of ViewportBodys rendered by MetalViewportView, the current selection / hover, the
presentation styles, and the dimension registry. @MainActor, ObservableObject.
@MainActor
public final class InteractiveContext: ObservableObject {
public init(viewport: ViewportController)
}Bind it via MetalViewportView(controller: ctx.viewport, bodies: $ctx.bodies) when ctx is a
@StateObject.
- Published properties · display(_:style:) · update(_:to:absorbing:operationName:) · remove(_:) · removeAll() · Selection mutation · displaysBody(withID:) · Selection filters · Area selection · setStyle(_:for:) · setHighlightStyle(_:) · add(_:) · remove(_:)-dimension · dimensions · refreshDimensionMeasurement(_:) · remap(_:using:rebindingTo:) · isDeleted(_:in:)
public let viewport: ViewportController
@Published public var bodies: [ViewportBody]
@Published public var selectionMode: Set<SelectionMode> // default [.body]
@Published public private(set) var selection: Selection
@Published public private(set) var hover: SubShape?
public var highlightStyle: HighlightStyle // default .defaultbodies: the bodies fed toMetalViewportView; bind via$bodies.selectionMode: what kinds of pick produce a selection. Changing it clears the current selection.selection: the current selection (read-only; mutate viaselect/deselect/clearSelectionor a pick). Observable.hover: the currently hovered sub-shape (body granularity today), ornil.- Example:
ais.selectionMode = [.face]
// SwiftUI:
// .onChange(of: ais.selection) { _, sel in ... }Display a shape with topology-aware selection enabled. Tessellates the Shape, appends a
ViewportBody, and registers a selectable InteractiveObject.
@discardableResult
public func display(_ shape: Shape, style: PresentationStyle = .default) -> InteractiveObject- Parameters:
shape: the OCCTSwiftShape;style: initial presentation style. - Returns: the
InteractiveObjectscene handle. - Example:
let part = ais.display(Shape.box(width: 10, height: 5, depth: 3)!,
style: .highlighted)display also builds a BRepGraph from shape and retains it for the object's lifetime, see
update(_:to:absorbing:operationName:), below, for what that's for.
Update a displayed object after a modelling operation that rebuilds its shape, a boolean, a fillet, a
chamfer, anything produced via one of OCCTSwift's *WithFullHistory methods run against object.shape.
Absorbs the operation's history into the object's living BRepGraph (built once in display,
retained across every subsequent update call, the input and result share one graph instance, so
every SubShapeRef.uid already held stays resolvable), rebuilds the displayed mesh, and remaps any
current selection / hover sub-shapes referencing object forward via remap(_:using:rebindingTo:).
@discardableResult
public func update(
_ object: InteractiveObject,
to newShape: Shape,
absorbing history: ShapeHistoryRef,
operationName: String
) -> InteractiveObject?- Parameters:
object: the currently-displayed object being mutated;newShape: the operation's result;history: the handle returned alongside it by any*WithFullHistorymethod;operationName: a label recorded on every emitted history record. - Returns: the updated
InteractiveObject(sameid, newshape), ornilifobjectisn't displayed, has no living graph (construction failed atdisplaytime), or the absorb fails, in any of those cases,removeanddisplayfresh, accepting that the selection doesn't survive. - Example:
let (result, history) = part.shape.subtractedWithFullHistory(tool)!
if let updated = ais.update(part, to: result, absorbing: history, operationName: "cut") {
part = updated
}Remove a displayed object, its body, and any selection / hover entries that referenced it.
public func remove(_ object: InteractiveObject)- Example:
ais.remove(part)Clear every body, selection, hover, dimension, and viewport measurement in one go.
public func removeAll()- Example:
ais.removeAll()Add, remove, or clear sub-shapes. select / deselect use Set semantics (idempotent).
public func select(_ subshape: SubShape) // == scheme: .add
public func select(_ subshape: SubShape, scheme: SelectionScheme)
public func deselect(_ subshape: SubShape) // == scheme: .remove
public func clearSelection()-
scheme:.replaceassigns,.addinserts if absent,.removedrops it,.xortoggles it. The sameSelectionSchemesemanticsselectRectangle/selectPolygonuse over a whole match set. -
No default value on
scheme, deliberately. Defaulting it to.replacewould silently retune every existingselect(x)call site from add to replace;select(_:)keeps its original meaning and forwards to.add. -
Example:
let face0 = part.shape.subShape(type: .face, index: 0)!
let face2 = part.shape.subShape(type: .face, index: 2)!
ais.select(.face(part, ref: SubShapeRef(shape: face0, ordinal: 0))) // additive
ais.select(.face(part, ref: SubShapeRef(shape: face2, ordinal: 2)))
ais.deselect(.face(part, ref: SubShapeRef(shape: face0, ordinal: 0)))
ais.select(.face(part, ref: SubShapeRef(shape: face2, ordinal: 2)), scheme: .replace)
ais.clearSelection()In practice most selections come from a pick, handlePick mints the SubShapeRef (uid included)
for you.
This is the package's only selection store. OCCTSwiftCADKit.CADViewportService drives it
rather than keeping one of its own (OCCTSwiftInteraction#3): its selection is this one
projected into PickedEntity values, and its selectionModes is selectionMode. Mutating
either side is visible from the other.
public func displaysBody(withID bodyID: String) -> BoolWhether bodyID names a body this context displays as a selectable InteractiveObject, that is,
one added via display(_:style:). False for internal bodies (manipulator handles, dimensions),
which are not selectable objects, and for bodies a host composited into bodies itself.
For a host that shares this context's selection and clears it on an unresolved pick, this is the check that stops it from wiping a selection it never owned.
Restrict what handlePick / handleHover accept, beyond selectionMode. See
Selection Filters for the filter types themselves.
@Published public private(set) var filters: [any SelectionFilter]
public func addFilter(_ filter: any SelectionFilter)
public func removeFilter(_ filter: any SelectionFilter) // by reference identity
public func removeAllFilters()- Installed filters combine with AND (a deliberate departure from OCCT's OR, see
Selection Filters for the rationale). Never gates programmatic
select(_:). - Example:
ais.addFilter(SurfaceTypeFilter([.cylinder]))
ais.removeAllFilters()Rectangle and lasso selection over a screen-space region, honours selectionMode and installed
filters exactly like a point pick. See Area Selection for AreaSelectionMode,
SelectionScheme, and the SwiftUI gesture integration (AreaSelectionController,
.attachAreaSelection(_:)).
public func selectRectangle(from: CGPoint, to: CGPoint,
mode: AreaSelectionMode = .enclosed,
scheme: SelectionScheme = .replace,
viewportSize: CGSize)
public func selectPolygon(_ points: [CGPoint],
mode: AreaSelectionMode = .enclosed,
scheme: SelectionScheme = .replace,
viewportSize: CGSize)- Example:
ais.selectRectangle(from: CGPoint(x: 100, y: 100), to: CGPoint(x: 400, y: 300),
viewportSize: CGSize(width: 800, height: 600))Restyle a displayed object in place, updates the underlying ViewportBody's color and visibility.
public func setStyle(_ style: PresentationStyle, for object: InteractiveObject)- Example:
ais.setStyle(.ghosted, for: part)Set the colors used by the highlight overlay and refresh the current selection visuals immediately.
public func setHighlightStyle(_ style: HighlightStyle)- Example:
ais.setHighlightStyle(HighlightStyle(selectionColor: SIMD3<Float>(1, 0.65, 0)))Add a dimension to the scene. Pushes its viewportMeasurement to viewport.measurements, where the
renderer's overlay picks it up. Idempotent for the same instance (re-adding refreshes its anchors).
A dimension whose anchors do not resolve (anchorPoints is [], see
Dimensions) is registered but draws nothing, rather than being drawn at
the world origin. Call refreshDimensionMeasurement(_:) once its anchors can resolve to make it
appear.
@discardableResult
public func add<D: Dimension>(_ dimension: D) -> D- Returns: the same dimension, for chaining.
- Example:
let v0 = SubShapeRef(shape: part.shape.subShape(type: .vertex, index: 0)!, ordinal: 0)
let v6 = SubShapeRef(shape: part.shape.subShape(type: .vertex, index: 6)!, ordinal: 6)
let lin = ais.add(LinearDimension(from: .vertex(part, ref: v0), to: .vertex(part, ref: v6)))Remove a previously-added dimension.
public func remove(_ dimension: any Dimension)- Example:
ais.remove(lin)All dimensions currently displayed in this context.
public var dimensions: [any Dimension] { get }- Example:
print(ais.dimensions.count)Re-fetch a dimension's viewportMeasurement and replace it in place in viewport.measurements. Call
after the underlying anchors moved (e.g. a target Shape mutated).
Anchors that stopped resolving drop the measurement from the overlay, and anchors that started resolving add it back.
public func refreshDimensionMeasurement(_ dimension: any Dimension)- Example:
ais.refreshDimensionMeasurement(lin)Remap a Selection whose sub-shapes were captured against an earlier shape state into a new
Selection against newObject, using history absorbed into graph via
BRepGraph.add(_:absorbing:inputRoots:operationName:). This is the lower-level primitive
update(_:to:absorbing:operationName:) calls internally, reach for it directly only if you're
managing the BRepGraph yourself rather than going through update.
public func remap(
_ selection: Selection,
using graph: BRepGraph,
rebindingTo newObject: InteractiveObject
) -> Selection- Parameters:
selection: the pre-mutation selection;graph: theBRepGraphthat absorbed the operation's history (input and result must share this one instance);newObject: the post-mutation scene object the result references. - Returns: a
SelectionagainstnewObject, resolved through each sub-shape'sSubShapeRef.uid, never a stored index.1 → 1(modified in place) keeps the same node re-resolved to a fresh uid;1 → N(e.g. a face split by a cut) expands into N entries;1 → 0(deleted) is dropped, seeisDeleted(_:in:). A sub-shape with nouidis dropped: there's nothing durable to resolve it by..body(_)always rebinds tonewObject. - Example:
let remapped = ais.remap(oldSelection, using: graph, rebindingTo: newObj)
for sub in remapped.subshapes { ais.select(sub) }Whether a sub-shape's durable node was explicitly consumed by history absorbed into graph: as
opposed to simply never being mentioned by any recorded operation. remap's silent drop can't tell
these apart on its own; both look like "absent from the result."
public func isDeleted(_ subshape: SubShape, in graph: BRepGraph) -> Bool- Returns:
falsefor.bodysub-shapes, and for any sub-shape with nouidor whoseuidisn'tgraph's own, there's no node ingraphto ask about. - Example:
if ais.isDeleted(pickedFace, in: graph) {
print("the cut removed that face entirely")
}