| name | DM Additive/Subtractive Primitive Pattern |
| description | How primitives store and render their additive/subtractive mode. Required reading before implementing any additive/subtractive toggle or per-field color rendering. |
DM Additive/Subtractive Primitive Pattern
Every SDF primitive is either additive (default) or subtractive. Additive primitives render orange (1.0, 0.5, 0.0). Subtractive primitives render blue (0.3, 0.5, 1.0). The mode is stored as a FreeCAD property on the object and passed to the GPU shader as a per-field uniform.
Object Property
DMObjectProxy.__init__ adds this property for shape_type == "sdf":
if not hasattr(obj, "IsSubtractive"):
obj.addProperty("App::PropertyBool", "IsSubtractive", "Sdf",
"If True, this primitive subtracts material")
obj.IsSubtractive = False
Shader Uniform
The scene ray march renderer (DMSceneRayMarchRenderer) passes a per-field int array:
uniform int u_is_subtractive[8];
In _rebuild(), for each visible field:
obj = ...
is_sub = getattr(obj, "IsSubtractive", False)
self._u[f"u_is_subtractive[{fi}]"].value.setValue(1 if is_sub else 0)
In the fragment shader, after computing hit_field:
vec3 base_color = (u_is_subtractive[hit_field] == 1)
? vec3(0.3, 0.5, 1.0) // blue = subtractive
: vec3(1.0, 0.5, 0.0); // orange = additive
vec3 color = base_color * (0.15 + 0.75 * diff) + vec3(0.4) * spec;
Ctrl Toggle During Drag
During primitive placement (in PrimitiveCreatorBase subclasses), the Ctrl key toggles the preview object between additive and subtractive:
im = DMInputManager.get_instance()
if self._preview_obj and hasattr(self._preview_obj, "IsSubtractive"):
self._preview_obj.IsSubtractive = im.is_ctrl_down()
The user holds Ctrl during drag to switch to subtractive. Releasing Ctrl switches back. The final value at commit time is persisted.
Label Mapping for Per-Field Object Lookup
DMSceneRayMarchRenderer._fields stores label -> (field, visible). To look up the FreeCAD object for a label during _rebuild():
doc = FreeCAD.activeDocument()
obj = doc.getObject(label) if doc else None
This works because create_dm_object names the FreeCAD object with the same label used in update_field(label, field).
Double-Click to Re-Edit
When no tool is active, DMInputManager.eventFilter handles QEvent.MouseButtonDblClick with Qt.LeftButton:
- Call
DMSelectionManager.try_sdf_selection(qt_pos) to select the SDF object under the cursor.
- If an SDF sdf object is now selected, launch
SdfEditTool and call activate().
This reuses the existing SdfEditTool which reads the selected object's SdfField and allows corner-drag editing.
Files to Read Before Editing
core/dm_object.py:167 — DMObjectProxy.__init__ (property registration for sdf)
core/dm_scene_ray_march_renderer.py:73 — _setup_nodes (uniform registration)
core/dm_scene_ray_march_renderer.py:480 — _rebuild (per-field uniform upload)
tools/primitive_tool.py:51 — PrimitiveCreatorBase.update_preview (preview update loop)
core/input_manager.py:202 — is_ctrl_down() accessor
core/input_manager.py:30 — eventFilter (where to add double-click handler)
tools/edit_tool.py:483 — SdfEditTool (existing edit tool for sdf objects)