Compare commits

..

13 Commits

Author SHA1 Message Date
forbes
b3a58a6d92 docs: Create module bootstrap sequence (#148)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Document the two-phase bootstrap: console-phase addon loading via
exec(), GUI-phase workbench registration, six deferred QTimer
callbacks (kc_format, silo origin, auth panel, first-start check,
activity panel, update checker), and the Gitea releases API polling
with skip/interval logic.
2026-02-14 13:30:52 -06:00
2c564bea8a Merge pull request 'docs: Datum Creator system reference' (#212) from docs/datum-creator-system into main
Some checks failed
Deploy Docs / build-and-deploy (push) Successful in 54s
Build and Test / build (push) Has been cancelled
Reviewed-on: #212
2026-02-14 19:28:48 +00:00
forbes
e443538548 docs: Datum Creator system reference (#141)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Document the ZTools Datum Creator: SelectionItem geometry classification,
16 creation modes (7 plane, 4 axis, 5 point), auto-detection scoring
algorithm, task panel UI layout, parameter sections, dispatch to
datums/core.py, and core implementation details.
2026-02-14 13:22:43 -06:00
be0f8128ec Merge pull request 'fix: standardize panel heading styles and prevent clipping' (#211) from fix/panel-heading-styles into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #211
2026-02-14 19:17:03 +00:00
forbes
48f5cc9311 fix: standardize panel heading styles and prevent clipping (#198, #199)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
- QDockWidget::title: add font-weight bold and text-align left to
  match ActionGroup header style
- ActionLabel header: add min-width 0 to prevent title clipping
  in narrow panels

Applied to both src/Gui/Stylesheets and resources/preferences copies.
2026-02-14 13:15:33 -06:00
cb4b3363ec Merge pull request 'fix: status bar text contrast — use Text color instead of Subtext0' (#210) from fix/status-bar-contrast into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #210
2026-02-14 19:11:10 +00:00
4334c9a494 Merge pull request 'fix: viewport background gradient — Catppuccin Mocha Overlay0/Mantle' (#209) from fix/viewport-background-gradient into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #209
2026-02-14 19:10:58 +00:00
abc53e6b93 Merge pull request 'fix: file dialog defaults to home dir instead of /etc/ssl/certs' (#208) from fix/file-dialog-default-path into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #208
2026-02-14 19:10:38 +00:00
forbes
332e14f1f7 fix: status bar text contrast — use Text color instead of Subtext0 (#195)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Change status bar text from #bac2de (Subtext0) to #cdd6f4 (Text)
for better readability against the #181825 Mantle background.
2026-02-14 13:08:02 -06:00
forbes
06475d5291 fix: viewport background gradient — Catppuccin Mocha Overlay0/Mantle (#188)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Replace 2-color Base→Crust gradient with 3-color Overlay0→Mantle→Overlay0
gradient in both KindredCreate preference pack and CatppuccinMocha theme.
Enables UseBackgroundColorMid for the 3-color effect.
2026-02-14 13:03:42 -06:00
forbes
172f5cddee fix: update silo submodule — cert browser default path (#203)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
2026-02-14 12:50:01 -06:00
f06bb96fdb Merge pull request 'feat: .kc Layer 1 — manifest auto-creation and platform file associations' (#204) from feat/kc-layer1-finish into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #204
2026-02-14 18:47:56 +00:00
forbes
04835c3629 feat: .kc Layer 1 — manifest auto-creation and platform file associations
Some checks failed
Build and Test / build (pull_request) Has been cancelled
kc_format.py:
- Auto-create silo/manifest.json with default fields (UUID, timestamps,
  username) when saving a .kc file that lacks one
- Update modified_at timestamp on each save
- KC_VERSION = 1.0

Platform integration:
- kindred-create.desktop: add application/x-kindred-create MIME type
- kindred-create.xml: register .kc glob patterns with dedicated MIME type
  (application/x-kindred-create), separate from .fcstd type
2026-02-14 12:47:01 -06:00
9 changed files with 262 additions and 21 deletions

View File

@@ -0,0 +1,177 @@
# 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.

View File

@@ -7,7 +7,7 @@ Icon=kindred-create
Terminal=false
Type=Application
Categories=Graphics;Science;Engineering;
MimeType=application/x-extension-fcstd;x-scheme-handler/kindred;
MimeType=application/x-extension-fcstd;application/x-kindred-create;x-scheme-handler/kindred;
Keywords=CAD;3D;modeling;engineering;design;parametric;
StartupNotify=true
StartupWMClass=KindredCreate

View File

@@ -1,11 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-extension-fcstd">
<comment>Kindred Create Document</comment>
<comment xml:lang="en">Kindred Create Document</comment>
<comment>FreeCAD Document</comment>
<comment xml:lang="en">FreeCAD Document</comment>
<icon name="kindred-create"/>
<glob pattern="*.FCStd"/>
<glob pattern="*.fcstd"/>
<glob pattern="*.FCSTD"/>
</mime-type>
<mime-type type="application/x-kindred-create">
<comment>Kindred Create Document</comment>
<comment xml:lang="en">Kindred Create Document</comment>
<icon name="kindred-create"/>
<glob pattern="*.kc"/>
<glob pattern="*.KC"/>
</mime-type>
</mime-info>

View File

@@ -247,6 +247,8 @@ QDockWidget {
QDockWidget::title {
background-color: #181825;
color: #cdd6f4;
font-weight: bold;
text-align: left;
padding: 8px 6px;
border-bottom: 1px solid #313244;
min-height: 18px;
@@ -1129,6 +1131,11 @@ QSint--ActionGroup QFrame {
border-radius: 4px;
}
QSint--ActionGroup QSint--ActionLabel[class="header"] {
color: #cdd6f4;
min-width: 0;
}
/* Input Field */
Gui--InputField {
background-color: #313244;

View File

@@ -31,13 +31,13 @@
<FCBool Name="checkShowReportViewOnWarning" Value="1" />
</FCParamGroup>
<FCParamGroup Name="View">
<FCUInt Name="BackgroundColor" Value="505294591" />
<FCUInt Name="BackgroundColor2" Value="286333951" />
<FCUInt Name="BackgroundColor3" Value="404235775" />
<FCUInt Name="BackgroundColor4" Value="825378047" />
<FCUInt Name="BackgroundColor" Value="404235775" />
<FCUInt Name="BackgroundColor2" Value="1819313919" />
<FCUInt Name="BackgroundColor3" Value="1819313919" />
<FCUInt Name="BackgroundColor4" Value="404235775" />
<FCBool Name="Simple" Value="0" />
<FCBool Name="Gradient" Value="1" />
<FCBool Name="UseBackgroundColorMid" Value="0" />
<FCBool Name="UseBackgroundColorMid" Value="1" />
<FCUInt Name="HighlightColor" Value="3416717311" />
<FCUInt Name="SelectionColor" Value="3032415999" />
<FCUInt Name="PreselectColor" Value="2497893887" />

View File

@@ -247,6 +247,8 @@ QDockWidget {
QDockWidget::title {
background-color: #181825;
color: #cdd6f4;
font-weight: bold;
text-align: left;
padding: 8px 6px;
border-bottom: 1px solid #313244;
min-height: 18px;
@@ -976,7 +978,7 @@ QSplitter::handle:hover {
QStatusBar {
background-color: #181825;
color: #bac2de;
color: #cdd6f4;
border-top: 1px solid #313244;
}
@@ -1244,6 +1246,11 @@ QSint--ActionGroup QFrame {
border-radius: 4px;
}
QSint--ActionGroup QSint--ActionLabel[class="header"] {
color: #cdd6f4;
min-width: 0;
}
/* Input Field */
Gui--InputField {
background-color: #313244;

View File

@@ -1,18 +1,40 @@
"""
kc_format.py — .kc file format round-trip preservation.
kc_format.py — .kc file format support.
Caches silo/ ZIP entries before FreeCAD's C++ save rewrites the ZIP
from scratch, then re-injects them after save completes.
Handles two responsibilities:
1. Round-trip preservation: caches silo/ ZIP entries before FreeCAD's C++
save rewrites the ZIP from scratch, then re-injects them after save.
2. Manifest auto-creation: ensures every .kc file has silo/manifest.json.
"""
import json
import os
import uuid
import zipfile
from datetime import datetime, timezone
import FreeCAD
# Cache: filepath -> {entry_name: bytes}
_silo_cache = {}
KC_VERSION = "1.0"
def _default_manifest():
"""Generate a default silo/manifest.json for new .kc files."""
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
username = os.environ.get("USER", os.environ.get("USERNAME", "unknown"))
return {
"kc_version": KC_VERSION,
"silo_instance": None,
"part_uuid": str(uuid.uuid4()),
"revision_hash": None,
"created_at": now,
"modified_at": now,
"created_by": username,
}
class _KcFormatObserver:
"""Document observer that preserves silo/ entries across saves."""
@@ -35,22 +57,43 @@ class _KcFormatObserver:
pass
def slotFinishSaveDocument(self, doc, filename):
"""After save: re-inject cached silo/ entries into the .kc ZIP."""
"""After save: re-inject cached silo/ entries and ensure manifest."""
if not filename.lower().endswith(".kc"):
_silo_cache.pop(filename, None)
return
entries = _silo_cache.pop(filename, None)
if not entries:
return
try:
with zipfile.ZipFile(filename, "a") as zf:
existing = set(zf.namelist())
for name, data in entries.items():
if name not in existing:
zf.writestr(name, data)
# Re-inject cached silo/ entries
if entries:
for name, data in entries.items():
if name not in existing:
zf.writestr(name, data)
existing.add(name)
# Ensure silo/manifest.json exists
if "silo/manifest.json" not in existing:
manifest = _default_manifest()
zf.writestr(
"silo/manifest.json",
json.dumps(manifest, indent=2) + "\n",
)
else:
# Update modified_at timestamp
raw = zf.read("silo/manifest.json")
manifest = json.loads(raw)
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
if manifest.get("modified_at") != now:
manifest["modified_at"] = now
# ZipFile append mode can't overwrite; write new entry
# (last duplicate wins in most ZIP readers)
zf.writestr(
"silo/manifest.json",
json.dumps(manifest, indent=2) + "\n",
)
except Exception as e:
FreeCAD.Console.PrintWarning(
f"kc_format: failed to preserve silo/ entries: {e}\n"
f"kc_format: failed to update .kc silo/ entries: {e}\n"
)