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
HwndHostcreates that window inBuildWindowCore. - Avalonia: a
NativeControlHostcreates it inCreateNativeControlCore, 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.