name: DM WorkPlane Architecture description: Reference for the three parallel workplane systems and how they interact. Required reading before touching workplane placement, projection, or the WorkPlaneCreator tool.
DM WorkPlane Architecture
There are three separate workplane concepts in this codebase. They overlap but have no shared interface.
The Three Systems
1. WorkPlaneManager — Transient Coin3D Grid
File: core/work_plane.py
- Created per-tool, not persisted to the document
- Renders a live preview grid that follows the mouse (surface-snapping or camera-facing fallback)
- Exposes
get_placement()→FreeCAD.Placement - Shows/hides via
show()/hide() - Calls
projector.get_geometry_info(event_dict)to detect geometry hits and snap its normal - Destroyed when the tool exits
2. DMWorkPlane — Persistent Document Object
File: core/dm_workplane.py
- A
Part::FeaturePythonobject stored in the FreeCAD document - Has
Placement,Length,Width,GridSpacingproperties ViewProviderDMWorkPlane.attach()builds its own separate Coin3D grid (duplicatesWorkPlaneManager's visual code)- Retrieved via
FreeCADGui.Selectionor by scanningdoc.ObjectsforProxy.__class__.__name__ == "DMWorkPlane" - Survives save/load
3. DMBase.working_plane — Per-Tool Placement
File: tools/dm_base.py
- A plain
FreeCAD.Placementstored on each tool instance - Set in
_detect_selected_workplane()from a selectedDMWorkPlane, or fromWorkPlaneManager.get_placement()during the tool session - Used by
ViewProjector.get_mouse_plane_pt()andget_projected_point()to constrain mouse clicks to the plane - Ephemeral — lost when tool exits
How They Interact (Data Flow)
User selects a DMWorkPlane in scene
→ DMBase._detect_selected_workplane()
→ reads obj.Placement
→ sets self.working_plane = obj.Placement
Mouse moves during tool
→ WorkPlaneManager.update(event_dict)
→ ViewProjector.get_geometry_info() → surface snap
→ DMInputManager.get_view_transform() → camera-facing fallback
→ updates self.current_placement
User clicks
→ ViewProjector.get_mouse_plane_pt(event_dict)
→ queries all visible DMWorkPlane objects (doc.Objects scan)
→ ray-casts against each plane
→ returns closest hit OR camera-plane fallback
→ tool uses self.working_plane for local↔global transform
Problem: working_plane (step 1) and WorkPlaneManager.current_placement (step 2) and get_mouse_plane_pt() (step 3) may all disagree if the user hasn't explicitly selected a workplane.
Key Methods
| Method | File | What it does |
|---|---|---|
WorkPlaneManager.update(event_dict) |
core/work_plane.py:98 |
Raycasts to update live grid visual |
WorkPlaneManager.get_placement() |
core/work_plane.py:169 |
Returns current transient placement |
DMBase._detect_selected_workplane() |
tools/dm_base.py:78 |
Reads selection → sets self.working_plane |
ViewProjector.get_mouse_plane_pt() |
core/view_projector.py |
Finds point on nearest visible DMWorkPlane |
ViewProjector.get_geometry_info() |
core/view_projector.py |
Ray-casts to find surface hit + normal |
Grid Visual Duplication
Both WorkPlaneManager and ViewProviderDMWorkPlane build the same Coin3D grid structure independently:
WorkPlaneManager._setup_grid(size, steps)
coin.SoSeparator → SoMaterial + SoCoordinate3 + SoLineSet + SoFaceSet
ViewProviderDMWorkPlane._setup_grid(length, width, spacing)
coin.SoSeparator → SoMaterial + SoCoordinate3 + SoLineSet + SoFaceSet + center cross
The persistent workplane adds a red center cross (center_sep); the transient one does not.
Refactor Goal
Replace all three systems with a single ActiveWorkPlane class:
class ActiveWorkPlane:
placement: FreeCAD.Placement
def to_local(self, world_pt: FreeCAD.Vector) -> FreeCAD.Vector: ...
def to_global(self, local_pt: FreeCAD.Vector) -> FreeCAD.Vector: ...
def project_ray(self, ray_origin: FreeCAD.Vector, ray_dir: FreeCAD.Vector) -> FreeCAD.Vector: ...
def normal(self) -> FreeCAD.Vector: ...
WorkPlaneManagerbecomes a factory/cache that returnsActiveWorkPlaneinstancesDMBase.working_planebecomesDMBase.active_workplane: ActiveWorkPlane- All coordinate math calls
self.active_workplane.project_ray(...)instead of inline math
Files to Read Before Editing
core/work_plane.py— transient managercore/dm_workplane.py— persistent object + view providertools/dm_base.py— per-tool placement +_detect_selected_workplane()tools/work_plane_tool.py— workplane creation tool (uses all three systems)core/view_projector.py—get_mouse_plane_pt(),get_geometry_info()