# Datum Creator System The ZTools Datum Creator (`ZTools_DatumCreator`) creates parametric datum planes, axes, and points. It auto-detects the datum type from selected geometry and provides a unified task panel with 16 creation modes. **Source files:** - `mods/ztools/ztools/ztools/commands/datum_commands.py` -- UI, selection handling, mode detection - `mods/ztools/ztools/ztools/datums/core.py` -- geometry computation and FreeCAD object creation ## Geometry Classification The `SelectionItem` class wraps a selected geometry element and classifies it into one of seven types: | Type | Detection | Icon | |------|-----------|------| | face | Planar `Part.Face` or object with "Plane" in TypeId | ▢ | | plane | Datum plane objects or planar faces | ▣ | | cylinder | `Part.Face` with `Surface.Cylinder` | ◎ | | edge | `Part.Edge` with `Part.Line` curve | ― | | circle | `Part.Edge` with `Part.Circle` or `Part.ArcOfCircle` | ○ | | vertex | `Part.Vertex` | • | | unknown | Unclassified geometry | ? | Classification flow: 1. Extract shape from object using the sub-element name (Face#, Edge#, Vertex#) 2. Check shape type (Face, Edge, Vertex) 3. For faces: check `surface.isPlanar()` or `isinstance(surface, Part.Cylinder)` 4. For edges: check curve type (Line, Circle, ArcOfCircle) 5. Fall back to `unknown` ## Creation Modes ### Planes (7 modes) | Mode | Required Selection | Parameters | Description | |------|--------------------|------------|-------------| | `offset_face` | 1 face | distance (mm) | Plane parallel to face at offset | | `offset_plane` | 1 plane | distance (mm) | Plane parallel to datum plane at offset | | `midplane` | 2 faces | -- | Plane halfway between two parallel faces | | `3_points` | 3 vertices | -- | Plane through three non-collinear points | | `normal_edge` | 1 edge | position (0--1) | Plane perpendicular to edge at parameter | | `angled` | 1 face + 1 edge | angle (deg) | Plane at angle to face, rotating about edge | | `tangent_cyl` | 1 cylinder | angle (deg) | Plane tangent to cylinder at angular position | ### Axes (4 modes) | Mode | Required Selection | Parameters | Description | |------|--------------------|------------|-------------| | `axis_2pt` | 2 vertices | -- | Axis through two points | | `axis_edge` | 1 edge | -- | Axis along linear edge | | `axis_cyl` | 1 cylinder | -- | Axis along cylinder centerline | | `axis_intersect` | 2 planes | -- | Axis at intersection of two planes | ### Points (5 modes) | Mode | Required Selection | Parameters | Description | |------|--------------------|------------|-------------| | `point_vertex` | 1 vertex | -- | Point at vertex location | | `point_xyz` | (none) | x, y, z (mm) | Point at explicit coordinates | | `point_edge` | 1 edge | position (0--1) | Point at parameter location on edge | | `point_face` | 1 face | -- | Point at center of mass of face | | `point_circle` | 1 circle | -- | Point at center of circular edge | ## Auto-Detection Algorithm The `_match_score()` method scores how well the current selection matches each mode's required types. **Scoring:** 1. If the selection has fewer items than required, score is **0** (no match). 2. For each required type, find a matching selected type using fuzzy matching: - `face` requirement matches `face` or `cylinder` - `edge` requirement matches `edge` or `circle` - All other types require exact match 3. Score calculation: - **Exact cardinality** (selection count == required count): score = `100 + matched_count` - **Over-selected** (more items than required): score = `matched_count` - **No match**: score = `0` The mode with the highest score wins. On each selection change, `update_mode_from_selection()` re-evaluates all 16 modes and activates the best match. The detected mode is displayed with a category color: - Planes: Mauve (#cba6f7) - Axes: Teal (#94e2d5) - Points: Yellow (#f9e2af) ## Task Panel UI ``` +-------------------------------------+ | ZTools Datum Creator | +-------------------------------------+ | Selection | | [icon] Element Name [Remove] | | [Add Selected] [Remove] [Clear] | +-------------------------------------+ | Datum Type | | DETECTED MODE (colored) | | Override: [dropdown] | +-------------------------------------+ | Parameters (dynamic per mode) | | Offset: [spinner] mm | | Angle: [spinner] deg | | Position: [spinner] 0-1 | | X/Y/Z: [spinner] mm | +-------------------------------------+ | Options | | [ ] Link to Spreadsheet | | [x] Add to Active Body | | [ ] Custom Name: [text] | +-------------------------------------+ | [OK] [Cancel] | +-------------------------------------+ ``` ### Selection table Three columns: type icon (28px), element name (stretch), remove button (28px). Duplicates are rejected. A FreeCAD `SelectionObserver` keeps the "Add Selected" button state synchronized with the active 3D selection. ### Mode override The dropdown lists all 16 modes prefixed by category (`[P]` plane, `[A]` axis, `[Pt]` point). Selecting a mode disables auto-detection until "(Auto-detect)" is re-selected. ### Parameters section Rebuilt dynamically when the mode changes. Only the parameter widgets relevant to the active mode are shown. Modes with no parameters hide the section entirely. ### Options - **Link to Spreadsheet**: creates a spreadsheet alias and expression-links the datum parameter (offset, angle, coordinates) for parametric control. - **Add to Active Body**: when checked, creates a `PartDesign::Plane/Line/Point` inside the active body. When unchecked, creates a document-level `Part::Plane/Line/Vertex`. - **Custom Name**: overrides the auto-generated name (format: `ZPlane_Offset_001`). ## Dispatch to core.py `DatumCreatorTaskPanel.accept()` calls `create_datum()`, which: 1. Reads the current mode, body, name, and parameters from the UI 2. Extracts `SelectionItem` objects by type 3. Dispatches to the corresponding `core.*` function: | Mode | Core function | |------|---------------| | `offset_face` | `core.plane_offset_from_face(face, distance, ...)` | | `offset_plane` | `core.plane_offset_from_plane(plane, distance, ...)` | | `midplane` | `core.plane_midplane(face1, face2, ...)` | | `3_points` | `core.plane_from_3_points(p1, p2, p3, ...)` | | `normal_edge` | `core.plane_normal_to_edge(edge, parameter, ...)` | | `angled` | `core.plane_angled(face, edge, angle, ...)` | | `tangent_cyl` | `core.plane_tangent_to_cylinder(face, angle, ...)` | | `axis_2pt` | `core.axis_from_2_points(p1, p2, ...)` | | `axis_edge` | `core.axis_from_edge(edge, ...)` | | `axis_cyl` | `core.axis_cylinder_center(face, ...)` | | `axis_intersect` | `core.axis_intersection_planes(plane1, plane2, ...)` | | `point_vertex` | `core.point_at_vertex(vertex, ...)` | | `point_xyz` | `core.point_at_coordinates(x, y, z, ...)` | | `point_edge` | `core.point_on_edge(edge, parameter, ...)` | | `point_face` | `core.point_center_of_face(face, ...)` | | `point_circle` | `core.point_center_of_circle(edge, ...)` | ## Core Implementation Each `core.*` function: 1. Creates the FreeCAD object (`PartDesign::Plane` in a body, `Part::Plane` at document level) 2. Computes the placement from the source geometry 3. Configures attachment (MapMode, AttachmentSupport, offset) for parametric updates 4. Stores ZTools metadata in custom properties: - `ZTools_Type` -- creation method identifier (e.g. `"offset_from_face"`) - `ZTools_Params` -- parameters as JSON (e.g. `{"distance": 10}`) - `ZTools_SourceRefs` -- source geometry references as JSON 5. Applies Catppuccin Mocha styling (mauve at 70% transparency for planes) 6. Optionally links parameters to a spreadsheet alias Auto-naming uses the format `ZPlane_Offset_001`, incrementing the index for each new datum of the same type.