Skip to content

Latest commit

 

History

History
415 lines (301 loc) · 13.2 KB

File metadata and controls

415 lines (301 loc) · 13.2 KB
title InteractiveContext
parent API Reference

InteractiveContext

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.

Topics


Published properties

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 .default
  • bodies: the bodies fed to MetalViewportView; bind via $bodies.
  • selectionMode: what kinds of pick produce a selection. Changing it clears the current selection.
  • selection: the current selection (read-only; mutate via select / deselect / clearSelection or a pick). Observable.
  • hover: the currently hovered sub-shape (body granularity today), or nil.
  • Example:
ais.selectionMode = [.face]
// SwiftUI:
// .onChange(of: ais.selection) { _, sel in ... }

display(_:style:)

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 OCCTSwift Shape; style: initial presentation style.
  • Returns: the InteractiveObject scene 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(_:to:absorbing:operationName:)

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 *WithFullHistory method; operationName: a label recorded on every emitted history record.
  • Returns: the updated InteractiveObject (same id, new shape), or nil if object isn't displayed, has no living graph (construction failed at display time), or the absorb fails, in any of those cases, remove and display fresh, 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(_:)

Remove a displayed object, its body, and any selection / hover entries that referenced it.

public func remove(_ object: InteractiveObject)
  • Example:
ais.remove(part)

removeAll()

Clear every body, selection, hover, dimension, and viewport measurement in one go.

public func removeAll()
  • Example:
ais.removeAll()

Selection mutation

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: .replace assigns, .add inserts if absent, .remove drops it, .xor toggles it. The same SelectionScheme semantics selectRectangle / selectPolygon use over a whole match set.

  • No default value on scheme, deliberately. Defaulting it to .replace would silently retune every existing select(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.


displaysBody(withID:)

public func displaysBody(withID bodyID: String) -> Bool

Whether 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.


Selection filters

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()

Area selection

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))

setStyle(_:for:)

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)

setHighlightStyle(_:)

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(_:)

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(_:)-dimension

Remove a previously-added dimension.

public func remove(_ dimension: any Dimension)
  • Example:
ais.remove(lin)

dimensions

All dimensions currently displayed in this context.

public var dimensions: [any Dimension] { get }
  • Example:
print(ais.dimensions.count)

refreshDimensionMeasurement(_:)

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(_:using:rebindingTo:)

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: the BRepGraph that absorbed the operation's history (input and result must share this one instance); newObject: the post-mutation scene object the result references.
  • Returns: a Selection against newObject, resolved through each sub-shape's SubShapeRef.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, see isDeleted(_:in:). A sub-shape with no uid is dropped: there's nothing durable to resolve it by. .body(_) always rebinds to newObject.
  • Example:
let remapped = ais.remap(oldSelection, using: graph, rebindingTo: newObj)
for sub in remapped.subshapes { ais.select(sub) }

isDeleted(_:in:)

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: false for .body sub-shapes, and for any sub-shape with no uid or whose uid isn't graph's own, there's no node in graph to ask about.
  • Example:
if ais.isDeleted(pickedFace, in: graph) {
    print("the cut removed that face entirely")
}