Table of Contents

3D views

OCCT draws with OpenGL into a native window: an HWND on Windows, an X11 window on Linux, an NSView on macOS. The UI framework provides the window, NetOcc's Aspect_Window.FromNativeHandle wraps it for the view.

flowchart TD
    display[Aspect_DisplayConnection] --> driver["OpenGl_GraphicDriver<br/><i>one per display</i>"]
    driver --> viewer["V3d_Viewer<br/><i>lights, the shared scene</i>"]
    viewer --> context["AIS_InteractiveContext<br/><i>display, highlight, select</i>"]
    viewer --> view["V3d_View<br/><i>camera, one per window</i>"]
    window["native window<br/>HWND, X11 Window, NSView"] -->|Aspect_Window.FromNativeHandle| view
    context --> shape["AIS_Shape"]
    context --> document["XCAFPrs_AISObject"]
    controller["AIS_ViewController<br/><i>mouse to camera and selection</i>"] -.->|"FlushViewEvents(context, view)"| view

The viewer

public sealed class ShapeView : IDisposable
{
  public ShapeView(IntPtr window)
  {
    // one driver per display connection, one viewer per driver, views of the viewer per window
    Display = new Aspect_DisplayConnection();
    Driver = new OpenGl_GraphicDriver(Display);
    Viewer = new V3d_Viewer(Driver);
    Viewer.SetDefaultLights();
    Viewer.SetLightOn();

    // the interactive context shows and selects presentations
    Context = new AIS_InteractiveContext(Viewer);
    Context.SetDisplayMode((int)AIS_DisplayMode.AIS_Shaded, false);

    View = Viewer.CreateView();
    View.SetWindow(Aspect_Window.FromNativeHandle(window, Display));
    View.SetBackgroundColor(new Quantity_Color(Quantity_NameOfColor.Quantity_NOC_GRAY20));
    View.TriedronDisplay(Aspect_TypeOfTriedronPosition.Aspect_TOTP_LEFT_LOWER,
                         new Quantity_Color(Quantity_NameOfColor.Quantity_NOC_WHITE), 0.1,
                         V3d_TypeOfVisualization.V3d_ZBUFFER);
    View.MustBeResized();
  }

  public Aspect_DisplayConnection Display { get; }
  public OpenGl_GraphicDriver Driver { get; }
  public V3d_Viewer Viewer { get; }
  public AIS_InteractiveContext Context { get; }
  public V3d_View View { get; }

  public void Show(TopoDS_Shape shape)
  {
    Context.Display(new AIS_Shape(shape), true);
    View.FitAll(0.01, true);
  }

  // on the UI thread, before the window goes: the finalizer thread has no GL context
  public void Dispose()
  {
    Context.RemoveAll(false);
    View.Remove();
    Context.Dispose();
    View.Dispose();
    Viewer.Dispose();
    Driver.Dispose();
    Display.Dispose();
  }
}

After that, the window's events drive the view: on a resize View.MustBeResized(), on a paint View.Redraw(). Dispose it on the UI thread before the window is destroyed, see Object lifetimes.

Hosting the window

The view needs a child window of its own, which also receives the mouse:

  • Win32: register a window class with CS_OWNDC (OpenGL needs its own device context) and a window procedure that passes size, paint and mouse messages on to the view.
  • WPF: an HwndHost creates that window in BuildWindowCore.
  • Avalonia: a NativeControlHost creates it in CreateNativeControlCore, once the control is shown.

The demos do all of this in a WPF and an Avalonia 12 viewer that open STEP files; NetOcc.Viewer has the shared part.

Presentations

What the context displays are presentations: AIS_Shape for a shape, XCAFPrs_AISObject for a document label with its colors. They hold the display attributes:

var presentation = new AIS_Shape(new BRepPrimAPI_MakeBox(10, 20, 30).Shape());
presentation.SetColor(new Quantity_Color(0.2, 0.5, 0.8,
                                         Quantity_TypeOfColor.Quantity_TOC_sRGB));
presentation.SetTransparency(0.3);
presentation.SetMaterial(
  new Graphic3d_MaterialAspect(Graphic3d_NameOfMaterial.Graphic3d_NameOfMaterial_Aluminum));
presentation.SetDisplayMode((int)AIS_DisplayMode.AIS_Shaded);

// edges drawn over the shading
presentation.Attributes().SetFaceBoundaryDraw(true);
Display mode Shows
AIS_WireFrame (0) edges and isolines
AIS_Shaded (1) shaded faces

The view triangulates shapes when it shows them, with a deflection relative to their size; mesh them yourself first for control over it.

Documents

public static void ShowDocument(AIS_InteractiveContext context, TDF_LabelSequence roots)
{
  // XCAFPrs_AISObject shows a label with the colors the document gives its parts
  foreach (var root in roots)
  {
    context.Display(new XCAFPrs_AISObject(root), (int)AIS_DisplayMode.AIS_Shaded, 0, false);
  }

  context.UpdateCurrentViewer();
}

Camera

public static void Look(V3d_View view)
{
  view.SetProj(V3d_TypeOfOrientation.V3d_TypeOfOrientation_Zup_AxoRight); // isometric
  view.FitAll(0.01, false);
  view.SetZoom(1.5, true);
  view.Redraw();

  view.Dump("view.png"); // what the view shows, as an image
}

V3d_View.Camera() gives the Graphic3d_Camera for full control: eye, center, up, projection, field of view.

Mouse input

AIS_ViewController turns input into rotation (left button), panning (middle), zoom (right button, wheel) and selection (click). Positions are the window's pixels. Buttons and modifier keys are OCCT bit masks OCCT declares in anonymous enums, so C# repeats them: buttons left 1 << 13, middle 1 << 14, right 1 << 15; keys shift 1 << 8, control 1 << 9, alt 1 << 10.

sequenceDiagram
    participant UI as window procedure
    participant C as AIS_ViewController
    participant V as V3d_View
    UI->>C: UpdateMousePosition(point, buttons, keys)
    C-->>UI: true: something changed
    UI->>C: FlushViewEvents(context, view)
    C->>V: rotate, pan, zoom the camera
    C->>V: redraw
/// <summary>
/// OCCT's mouse buttons (<c>Aspect_VKeyMouse</c>), which C# doesn't get since OCCT declares them
/// in an anonymous enum.
/// </summary>
[Flags]
public enum Buttons : uint
{
  None = 0,
  Left = 1 << 13,
  Middle = 1 << 14,
  Right = 1 << 15,
}

public static void MouseMove(AIS_ViewController controller, AIS_InteractiveContext context,
                             V3d_View view, int x, int y, Buttons buttons)
{
  // the controller turns input into rotation, panning, zoom and selection, then FlushViewEvents
  // applies it
  using var point = new BVH_Vec2i(x, y);
  if (controller.UpdateMousePosition(point, (uint)buttons, 0, false))
  {
    controller.FlushViewEvents(context, view, true);
  }
}

public static void MouseWheel(AIS_ViewController controller, AIS_InteractiveContext context,
                              V3d_View view, int x, int y, double notches)
{
  using var point = new BVH_Vec2i(x, y);
  using var delta = new Aspect_ScrollDelta(point, notches);
  if (controller.UpdateZoom(delta))
  {
    controller.FlushViewEvents(context, view, true);
  }
}

UpdateMouseButtons takes button presses and releases the same way.

Selection

The context highlights what's under the mouse and selects on click; selection modes choose what can be picked:

public static List<TopoDS_Shape> PickFaces(AIS_InteractiveContext context, V3d_View view,
                                           AIS_Shape presentation, int x, int y)
{
  // select faces instead of the whole shape
  context.Deactivate(presentation);
  context.Activate(presentation, AIS_Shape.SelectionMode(TopAbs_ShapeEnum.TopAbs_FACE), false);

  // highlight what's under the pixel, then select it
  context.MoveTo(x, y, view, true);
  context.SelectDetected(AIS_SelectionScheme.AIS_SelectionScheme_Replace);
  context.UpdateCurrentViewer();

  var faces = new List<TopoDS_Shape>();
  for (context.InitSelected(); context.MoreSelected(); context.NextSelected())
  {
    faces.Add(context.SelectedShape());
  }

  return faces;
}
Selection mode Picks
0 the whole presentation
AIS_Shape.SelectionMode(TopAbs_VERTEX), EDGE, WIRE, FACE, SOLID sub-shapes of that type

Threads and disposal

Views, the driver and presentations belong to the thread that made them, the UI thread: create, use and dispose them there. Dispose in reverse order of creation (view, viewer, driver), see Object lifetimes.