Commit Graph

42839 Commits

Author SHA1 Message Date
forbes
f6fa2f0d2c fix(editing-context): trust parentId chain without re-checking ancestor match()
Parent match() functions are designed for flat priority resolution and
may not return true when a more specific child is active (e.g.
assembly.idle checks getActivePartObject() which may not return the
assembly during edit mode). The parentId declaration is the author's
assertion of structural containment — the leaf already matched, so
we walk the full chain unconditionally.
2026-03-04 16:16:05 -06:00
forbes
74b0073327 feat(editing-context): hierarchical context system with stack, guards, and breadcrumb injection (#385, #386, #387)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Replaces the flat context model with a tree-structured hierarchy:

- ContextDefinition gains parentId field for declaring parent-child
  relationships between contexts
- Resolver builds a context stack by walking parentId links from
  leaf to root, verifying each ancestor matches current state
- Breadcrumb is now auto-built from the stack — each level
  contributes its expanded label and color, replacing all hardcoded
  special cases
- EditingContext gains stack field (QStringList, root to leaf)

Transition guards (#386):
- addTransitionGuard() / removeTransitionGuard() on resolver
- Guards run synchronously before applyContext(); first rejection
  cancels the transition and emits contextTransitionBlocked signal
- Full SDK/pybind11/Python bindings

Breadcrumb injection (#387):
- injectBreadcrumb() / removeBreadcrumbInjection() on resolver
- Addons can append segments to any context's breadcrumb display
- Active only when the target context is in the current stack
- Full SDK/pybind11/Python bindings

Built-in parent assignments:
- partdesign.body → partdesign.workbench
- partdesign.feature → partdesign.body
- partdesign.in_assembly → assembly.edit
- sketcher.edit → partdesign.body
- assembly.idle → assembly.workbench
- assembly.edit → assembly.idle
- Workbench-level and root contexts have no parent

SDK surface:
- Types.h: parentId on ContextDef, stack on ContextSnapshot
- SDKRegistry: guard/injection delegation, snapshotFromGui helper
- kcsdk_py: parent_id param, context_stack(), guard/injection bindings
- kindred_sdk: context_stack(), add/remove_transition_guard(),
  inject/remove_breadcrumb_injection(), parent_id on register_context()

Closes #385, closes #386, closes #387
2026-03-04 14:23:45 -06:00
forbes
2127d2c904 feat(sdk): addon load timing diagnostics — addon_diagnostics() (#390)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Add kindred_sdk.addon_diagnostics() returning per-addon load state,
timing, and error info as a list of dicts. Reads name, state,
load_time_ms, and error from AddonManifest via AddonRegistry.

Add _print_load_summary() to addon_loader.py that prints a formatted
summary table to the console after each load phase (Init.py and
InitGui.py), replacing interleaved individual log lines with a
consolidated view.

Closes #390
2026-03-04 13:50:37 -06:00
forbes
519d5b4436 feat(sdk): add context introspection — available_contexts and context_history (#383)
All checks were successful
Build and Test / build (pull_request) Successful in 25m9s
C++ layer:
- EditingContextResolver::registeredContexts() returns static metadata
  (id, labelTemplate, color, priority) for all registered contexts
- SDKRegistry::registeredContexts() wraps with Qt-to-std conversion
- kcsdk.available_contexts() pybind11 binding returns list of dicts

Python layer:
- kindred_sdk.available_contexts() wraps kcsdk binding
- kindred_sdk.context_history(limit=10) returns recent transitions
  from a 50-entry ring buffer tracked in lifecycle.py, with timestamps

Closes #383
2026-03-04 13:42:34 -06:00
forbes
d2ec22f5ff feat(sdk): add context lifecycle callbacks (#381)
All checks were successful
Build and Test / build (pull_request) Successful in 29m7s
Expose EditingContextResolver::contextChanged to Python addons via
two-layer design:

C++ layer:
- SDKRegistry::onContextChanged() stores callbacks and lazily connects
  to the Qt signal on first registration
- pybind11 binding kcsdk.on_context_changed() wraps Python callables
  with GIL-safe invocation

Python layer:
- kindred_sdk.on_context_enter(context_id, callback) subscribes to
  context activation ("*" wildcard supported)
- kindred_sdk.on_context_exit(context_id, callback) subscribes to
  context deactivation
- Internal tracking of previous context derives enter/exit transitions
- Emits context.enter / context.exit on the SDK event bus

Closes #381
2026-03-04 10:01:00 -06:00
forbes
4d94857883 chore(silo): add root package.xml and migrate setup to silo addon (#373)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Update silo submodule to include root-level package.xml, Init.py, and
InitGui.py following the standard addon layout used by sdk, gears,
datums, and solver.

Move five Silo-specific deferred setup functions from
src/Mod/Create/InitGui.py into the silo addon's own InitGui.py:
- Document observer registration (600ms)
- Auth dock panel via kindred_sdk.register_dock_panel() (2000ms)
- First-start settings check (3000ms)
- Activity dock panel via kindred_sdk.register_dock_panel() (4000ms)
- Remove duplicate origin registration (was 1500ms, already done in
  SiloWorkbench.Initialize())

Update CMake install targets to include root-level silo files.

Create core InitGui.py now only handles kc_format (500ms) and
update_checker (10000ms).
2026-03-03 08:24:45 -06:00
10dd66fc29 Merge pull request 'feat(datums): add unified datum creator addon' (#370) from feat/unified-datum-creator into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #370
2026-03-02 19:04:08 +00:00
470e6f3cf6 Merge remote-tracking branch 'origin/main' into feat/kcsdk-finalize
All checks were successful
Build and Test / build (pull_request) Successful in 29m7s
# Conflicts:
#	mods/sdk/kindred_sdk/context.py
#	mods/sdk/kindred_sdk/dock.py
#	mods/sdk/kindred_sdk/menu.py
#	mods/sdk/kindred_sdk/origin.py
#	mods/sdk/kindred_sdk/toolbar.py
2026-03-02 12:32:48 -06:00
e3de2c0e71 feat(datums): add unified datum creator addon
All checks were successful
Build and Test / build (pull_request) Successful in 29m22s
Introduces mods/datums/ — a standalone addon that replaces the three
stock PartDesign datum commands (Plane, Line, Point) with a single
unified Create_DatumCreator command.

Features:
- 16 smart creation modes (7 plane, 4 axis, 5 point)
- Auto-detection engine: selects best mode from geometry selection
- Mode override combo for manual selection
- Dynamic parameter UI (offset, angle, position, XYZ)
- Datums_Type/Params/SourceRefs metadata for edit panel
- Edit panel with real-time parameter updates via AttachExtension
- Catppuccin Mocha themed plane styling via SDK theme tokens
- Injected into partdesign.body and partdesign.feature contexts

Also adds CMake install targets for gears and datums addons.

Ported from archived ztools with key changes:
- Property prefix: ZTools_ -> Datums_
- No document-level datums (Body-only)
- PySide -> PySide6
- SDK integration (register_command, inject_commands, get_theme_tokens)
2026-03-02 12:15:18 -06:00
b0621f9731 Merge pull request 'chore: native repository conversion — remove FreeCAD/GitHub artifacts (#367)' (#368) from chore/repo-cleanup into main
All checks were successful
Build and Test / build (push) Successful in 28m57s
Reviewed-on: #368
2026-03-02 14:08:39 +00:00
forbes
1b01762a1c chore: rebrand XDGData and icon installs to Kindred Create (#367)
All checks were successful
Build and Test / build (pull_request) Successful in 29m26s
- Replace org.freecad.FreeCAD.desktop with kindred-create.desktop
- Replace org.freecad.FreeCAD.metainfo.xml.in with Kindred Create
  metainfo (new description, Kindred URLs, kindred-create app ID)
- Replace org.freecad.FreeCAD.xml with kindred-create.xml (adds .kc
  MIME type alongside .fcstd)
- Replace FreeCAD.thumbnailer.in with kindred-create.thumbnailer.in
- Update XDGData/CMakeLists.txt for new filenames
- Update src/Gui/CMakeLists.txt icon installs: use kindred-create
  icons from resources/ (all sizes 16-512 + SVG) instead of renaming
  freecad-icon-*.png to org.freecad.FreeCAD.png
- Update freecad-thumbnailer.in default icon to kindred-create.png
- Update AppImage create_bundle.sh to use kindred-create desktop/icon

Remaining org.freecad.FreeCAD references are macOS bundle IDs
(MainWindow.cpp, MacAppBundle/) and packaging fallbacks — separate
rebranding tasks.
2026-03-02 08:06:36 -06:00
forbes
79f69e2856 feat(sdk): remove fallbacks, add deprecation warnings, bump v1.0.0 (#357)
All checks were successful
Build and Test / build (pull_request) Successful in 28m54s
- Bump SDK_VERSION to 1.0.0 in version.py and package.xml
- Remove <pure_python> tag from package.xml (kcsdk is C++)
- Remove all FreeCADGui.* fallback paths in context.py, dock.py,
  toolbar.py, menu.py; require kcsdk module
- Remove query fallbacks in origin.py (keep register/unregister
  which still need FreeCADGui.addOrigin/removeOrigin)
- Add deprecation warnings to 11 superseded FreeCADGui methods
  in ApplicationPy.cpp (not addOrigin/removeOrigin)
- All 39 Tier 1 tests pass
2026-03-01 14:42:00 -06:00
forbes
a6a6cefc16 feat(sdk): add status bar widget wrapper and origin query bindings (#356)
register_status_widget(): pure Python wrapper that adds a live widget
to the main window status bar with context menu discoverability.

Origin query bindings (kcsdk.list_origins, active_origin, get_origin,
set_active_origin): thin C++ forwarding to OriginManager with Python
wrappers using kcsdk-first routing.

IOriginProvider and IStatusBarProvider C++ interfaces dropped — existing
FileOrigin stack is already complete, and status bar widgets don't need
C++ lifecycle management.
2026-03-01 14:42:00 -06:00
forbes
747c458e23 feat(sdk): add IMenuProvider interface and register_command wrapper (#355)
IMenuProvider: declarative menu placement with optional context awareness.
C++ interface with pybind11 bindings + GIL-safe holder. SDKMenuManipulator
(shared WorkbenchManipulator) injects menu items on workbench switch,
filtered by editing context when context_ids() is non-empty.

register_command(): thin Python wrapper around FreeCADGui.addCommand()
that standardizes the calling convention within the SDK contract.

Python wrappers (kindred_sdk.register_menu, kindred_sdk.register_command)
use kcsdk-first routing with FreeCADGui fallback.
2026-03-01 14:42:00 -06:00
forbes
4eb643a26f feat(sdk): add IToolbarProvider interface to kcsdk (#354) 2026-03-01 14:42:00 -06:00
forbes
5f8557fc83 fix(editing-context): resolve initial context on construction
EditingContextResolver constructor did not call refresh(), leaving
d->current as a default empty EditingContext. When BreadcrumbToolBar
queried currentContext() on creation, it received an empty context
with no breadcrumb segments, causing the navbar to appear blank.

Add refresh() at end of constructor so the initial state is resolved
before any View3DInventor queries it.
2026-03-01 14:42:00 -06:00
forbes
64644eb623 feat(sdk): add panel provider and theme engine to kcsdk (#352, #353)
- IPanelProvider: abstract interface for dock panels with PySide widget bridging
- PyIPanelProvider/PyProviderHolder: pybind11 trampoline + GIL-safe holder
- WidgetBridge: PySide QWidget → C++ QWidget* conversion via Shiboken
- SDKRegistry: panel registration, creation, and lifecycle management
- ThemeEngine: C++ singleton with minimal YAML parser, palette cache,
  getColor/allTokens/formatQss matching Python Palette API
- kcsdk bindings: DockArea, PanelPersistence enums, panel functions,
  theme_color, theme_tokens, format_qss, load_palette
- dock.py: kcsdk delegation with FreeCADGui fallback
- theme.py: kcsdk delegation with Python YAML fallback
2026-03-01 14:42:00 -06:00
forbes
60c0489d73 feat(sdk): migrate editing context API to kcsdk (#351)
Add context/overlay registration, injection, query, and refresh to the
KCSDK C++ library and kcsdk pybind11 module.

New files:
- src/Gui/SDK/Types.h — ContextDef, OverlayDef, ContextSnapshot structs
  (plain C++, no Qt in public API)

Modified:
- src/Gui/SDK/SDKRegistry.h/.cpp — register_context/overlay, unregister,
  inject_commands, current_context, refresh (delegates to
  EditingContextResolver with std↔Qt conversion)
- src/Gui/SDK/CMakeLists.txt — add Types.h, link FreeCADGui
- src/Gui/SDK/bindings/kcsdk_py.cpp — bind all context functions with
  GIL-safe match callable wrapping and dict-based snapshot return
- mods/sdk/kindred_sdk/context.py — try kcsdk first, fall back to
  FreeCADGui for backwards compatibility
2026-03-01 14:42:00 -06:00
forbes
63e591a626 feat(sdk): scaffold KCSDK library + kcsdk pybind11 module (#350)
Add the KCSDK C++ shared library and kcsdk pybind11 module, establishing
the build infrastructure for the C++-backed addon SDK.

New files:
- src/Gui/SDK/KCSDKGlobal.h — DLL export macros
- src/Gui/SDK/SDKRegistry.h/.cpp — empty singleton with API_VERSION_MAJOR=1
- src/Gui/SDK/CMakeLists.txt — builds KCSDK shared library
- src/Gui/SDK/bindings/kcsdk_py.cpp — pybind11 module exposing version + available()
- src/Gui/SDK/bindings/CMakeLists.txt — builds kcsdk pybind11 module

Modified:
- src/Gui/CMakeLists.txt — add_subdirectory(SDK)

Verified: import kcsdk; kcsdk.API_VERSION_MAJOR == 1; kcsdk.available() == []
2026-03-01 14:42:00 -06:00
forbes
ab8519c272 feat(sdk): add status bar widget wrapper and origin query bindings (#356)
All checks were successful
Build and Test / build (pull_request) Successful in 29m13s
register_status_widget(): pure Python wrapper that adds a live widget
to the main window status bar with context menu discoverability.

Origin query bindings (kcsdk.list_origins, active_origin, get_origin,
set_active_origin): thin C++ forwarding to OriginManager with Python
wrappers using kcsdk-first routing.

IOriginProvider and IStatusBarProvider C++ interfaces dropped — existing
FileOrigin stack is already complete, and status bar widgets don't need
C++ lifecycle management.
2026-03-01 14:13:31 -06:00
forbes
2bf083609d feat(sdk): add IMenuProvider interface and register_command wrapper (#355)
All checks were successful
Build and Test / build (pull_request) Successful in 28m41s
IMenuProvider: declarative menu placement with optional context awareness.
C++ interface with pybind11 bindings + GIL-safe holder. SDKMenuManipulator
(shared WorkbenchManipulator) injects menu items on workbench switch,
filtered by editing context when context_ids() is non-empty.

register_command(): thin Python wrapper around FreeCADGui.addCommand()
that standardizes the calling convention within the SDK contract.

Python wrappers (kindred_sdk.register_menu, kindred_sdk.register_command)
use kcsdk-first routing with FreeCADGui fallback.
2026-03-01 13:13:44 -06:00
forbes
76e448d0d7 feat(sdk): add IToolbarProvider interface to kcsdk (#354)
All checks were successful
Build and Test / build (pull_request) Successful in 30m28s
2026-03-01 09:32:25 -06:00
forbes
1b38d7b24b fix(editing-context): resolve initial context on construction
EditingContextResolver constructor did not call refresh(), leaving
d->current as a default empty EditingContext. When BreadcrumbToolBar
queried currentContext() on creation, it received an empty context
with no breadcrumb segments, causing the navbar to appear blank.

Add refresh() at end of constructor so the initial state is resolved
before any View3DInventor queries it.
2026-02-28 14:53:44 -06:00
forbes
18532e3bd7 feat(sdk): add panel provider and theme engine to kcsdk (#352, #353)
- IPanelProvider: abstract interface for dock panels with PySide widget bridging
- PyIPanelProvider/PyProviderHolder: pybind11 trampoline + GIL-safe holder
- WidgetBridge: PySide QWidget → C++ QWidget* conversion via Shiboken
- SDKRegistry: panel registration, creation, and lifecycle management
- ThemeEngine: C++ singleton with minimal YAML parser, palette cache,
  getColor/allTokens/formatQss matching Python Palette API
- kcsdk bindings: DockArea, PanelPersistence enums, panel functions,
  theme_color, theme_tokens, format_qss, load_palette
- dock.py: kcsdk delegation with FreeCADGui fallback
- theme.py: kcsdk delegation with Python YAML fallback
2026-02-28 14:53:38 -06:00
forbes
f44aba7388 feat(sdk): migrate editing context API to kcsdk (#351)
All checks were successful
Build and Test / build (pull_request) Successful in 29m14s
Add context/overlay registration, injection, query, and refresh to the
KCSDK C++ library and kcsdk pybind11 module.

New files:
- src/Gui/SDK/Types.h — ContextDef, OverlayDef, ContextSnapshot structs
  (plain C++, no Qt in public API)

Modified:
- src/Gui/SDK/SDKRegistry.h/.cpp — register_context/overlay, unregister,
  inject_commands, current_context, refresh (delegates to
  EditingContextResolver with std↔Qt conversion)
- src/Gui/SDK/CMakeLists.txt — add Types.h, link FreeCADGui
- src/Gui/SDK/bindings/kcsdk_py.cpp — bind all context functions with
  GIL-safe match callable wrapping and dict-based snapshot return
- mods/sdk/kindred_sdk/context.py — try kcsdk first, fall back to
  FreeCADGui for backwards compatibility
2026-02-27 14:05:07 -06:00
forbes
9b28feb8ca feat(sdk): scaffold KCSDK library + kcsdk pybind11 module (#350)
All checks were successful
Build and Test / build (pull_request) Successful in 29m2s
Add the KCSDK C++ shared library and kcsdk pybind11 module, establishing
the build infrastructure for the C++-backed addon SDK.

New files:
- src/Gui/SDK/KCSDKGlobal.h — DLL export macros
- src/Gui/SDK/SDKRegistry.h/.cpp — empty singleton with API_VERSION_MAJOR=1
- src/Gui/SDK/CMakeLists.txt — builds KCSDK shared library
- src/Gui/SDK/bindings/kcsdk_py.cpp — pybind11 module exposing version + available()
- src/Gui/SDK/bindings/CMakeLists.txt — builds kcsdk pybind11 module

Modified:
- src/Gui/CMakeLists.txt — add_subdirectory(SDK)

Verified: import kcsdk; kcsdk.API_VERSION_MAJOR == 1; kcsdk.available() == []
2026-02-27 13:36:48 -06:00
forbes
485f69f257 chore: remove ZTools addon from build and loader (#344)
All checks were successful
Build and Test / build (pull_request) Successful in 29m28s
- Remove ZTools install block from src/Mod/Create/CMakeLists.txt
- Remove mods/ztools submodule entry from .gitmodules
- Remove 'ztools' from legacy fallback order in addon_loader.py
- Remove ztools imports and test classes from test_kindred_pure.py
  (TestTypeMatches, TestMatchScore, TestSelectionItemProperties,
   TestColumnToIndex, TestDatumModes)
- Remove 'ztools Workbench' from issue template component lists
- Remove mods/ztools submodule from git tracking

ZTools will be archived to a reference folder in a separate step (#345).
This is part of the UI/UX rework epic (#346).
2026-02-27 12:46:32 -06:00
forbes
d47c94af8d chore: remove QuickNav addon from build and loader (#343)
All checks were successful
Build and Test / build (pull_request) Successful in 30m57s
- Remove QuickNav install block from src/Mod/Create/CMakeLists.txt
- Remove mods/quicknav submodule entry from .gitmodules
- Remove quicknav imports and TestWorkbenchMap tests from test_kindred_pure.py
- Remove mods/quicknav submodule from git tracking

QuickNav will be archived to a reference folder in a separate step (#345).
This is part of the UI/UX rework epic (#346).
2026-02-27 12:27:33 -06:00
forbes
d174ef7a8d fix(assembly): use short-arc angle in validateNewPlacements (#338)
All checks were successful
Build and Test / build (pull_request) Successful in 30m5s
Rotation::evaluateVector() computes angle = 2*acos(w) which gives
values in [0, 2*pi].  When the relative quaternion has w < 0 (opposite
hemisphere), the angle exceeds pi even though q and -q represent the
same rotation.  This caused the validator to report ~350 degree 'flips'
and reject valid solver output.

Fix: map the angle to [0, pi] before comparing against the 91-degree
threshold.  This is the short-arc equivalent — the minimum rotation
angle between two orientations regardless of quaternion sign convention.
2026-02-27 10:38:37 -06:00
69ccdbf742 Merge pull request 'fix(assembly): use instance suffixes for duplicate part labels' (#335) from fix/assembly-part-number-suffix into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #335
2026-02-26 16:34:34 +00:00
7c85b2ad93 fix(assembly): use instance suffixes for duplicate part labels
All checks were successful
Build and Test / build (pull_request) Successful in 29m11s
When parts with structured part numbers (e.g., P03-0001) are inserted
into an assembly multiple times, UniqueNameManager::decomposeName()
treats the trailing digits as an auto-generated suffix and increments
them (P03-0002, P03-0003), corrupting the part number.

Add a makeInstanceLabel() helper in AssemblyLink.cpp that appends -N
instance suffixes instead (P03-0001-1, P03-0001-2). All instances get
a suffix starting at -1 so the original part number is never modified.

Applied at all three Label.setValue() sites in
synchronizeComponents() (AssemblyLink, link group, and regular link
creation paths).

Also add a UniqueNameManager test documenting the trailing-digit
decomposition behavior for structured part numbers.

Closes #327
2026-02-26 09:00:13 -06:00
311d911cfa fix(theme): prevent panel element headings from being clipped
All checks were successful
Build and Test / build (pull_request) Successful in 29m23s
Three QSS issues caused headings to render with only the top ~60%
visible:

- QGroupBox: margin-top 12px was insufficient for the title rendered
  in subcontrol-origin: margin. Increased to 16px and added 2px
  vertical padding to the title.
- QDockWidget::title: min-height 18px conflicted with padding 8px 6px,
  constraining the content area. Removed min-height to let Qt auto-size
  from padding + font metrics.
- QSint--ActionGroup QToolButton: min-height 18px forced a height that
  was then clipped by the C++ setFixedHeight(headerSize) calculation.
  Set min-height to 0px so the C++ layout controls sizing.

Closes #325
2026-02-26 08:48:01 -06:00
e73c5fc750 feat: add QuickNav addon — Phase 1 core infrastructure (#320)
All checks were successful
Build and Test / build (pull_request) Successful in 29m58s
Add quicknav submodule and create-side integration for keyboard-driven
command navigation.

Submodule: mods/quicknav (https://git.kindred-systems.com/kindred/quicknav)

Create-side changes:
- CMakeLists.txt: add quicknav install rules
- test_kindred_pure.py: add 16 workbench_map validation tests
- docs/src/quicknav/SPEC.md: QuickNav specification

QuickNav provides numbered-key access to workbenches (Ctrl+1-5),
command groupings (Shift+1-9), and individual commands (1-9), with
a navigation bar toolbar and input-widget safety guards.
2026-02-23 14:12:02 -06:00
forbes
14ee8c673f fix(assembly): classify datum planes from all class hierarchies in Distance joints
Some checks failed
Build and Test / build (pull_request) Has been cancelled
The datum plane detection in getDistanceType() only checked for
App::Plane (origin planes). This missed two other class hierarchies:

  - PartDesign::Plane (inherits Part::Datum, NOT App::Plane)
  - Part::Plane primitive referenced bare (no Face element)

Both produce empty element types (sub-name ends with ".") but failed
the isDerivedFrom<App::Plane>() check, falling through to
DistanceType::Other and the Planar fallback. This caused incorrect
constraint geometry, leading to conflicting/unsatisfiable constraints
and solver failures.

Add shape-based isDatumPlane/Line/Point helpers that cover all three
hierarchies by inspecting the actual OCCT geometry rather than relying
on class identity alone. Extend getDistanceType() to use these helpers
for all datum-vs-datum and datum-vs-element combinations.

Adds TestDatumClassification.py with tests for PartDesign::Plane,
Part::Plane (bare ref), and cross-hierarchy datum combinations.
2026-02-22 21:18:34 -06:00
forbes
962b521f5c fix(assembly): classify datum planes from all class hierarchies in Distance joints
All checks were successful
Build and Test / build (pull_request) Successful in 30m13s
The datum plane detection in getDistanceType() only checked for
App::Plane (origin planes). This missed two other class hierarchies:

  - PartDesign::Plane (inherits Part::Datum, NOT App::Plane)
  - Part::Plane primitive referenced bare (no Face element)

Both produce empty element types (sub-name ends with ".") but failed
the isDerivedFrom<App::Plane>() check, falling through to
DistanceType::Other and the Planar fallback. This caused incorrect
constraint geometry, leading to conflicting/unsatisfiable constraints
and solver failures.

Add shape-based isDatumPlane/Line/Point helpers that cover all three
hierarchies by inspecting the actual OCCT geometry rather than relying
on class identity alone. Extend getDistanceType() to use these helpers
for all datum-vs-datum and datum-vs-element combinations.

Adds TestDatumClassification.py with tests for PartDesign::Plane,
Part::Plane (bare ref), and cross-hierarchy datum combinations.
2026-02-22 18:55:39 -06:00
forbes
b4835e1b05 fix(assembly): classify datum plane references in Distance joints
Some checks failed
Build and Test / build (pull_request) Has been cancelled
When a Distance joint references a datum plane (XY_Plane, XZ_Plane,
YZ_Plane), getDistanceType() failed to recognize it because datum
plane sub-names yield an empty element type. This caused the fallback
to DistanceType::Other → BaseJointKind::Planar, which adds spurious
parallel-normal residuals that overconstrain the system.

For example, three vertex-to-datum-plane Distance joints produced
10 residuals (3×Planar) with 6 mutually contradictory orientation
constraints, causing the solver to find garbage least-squares
solutions.

Add early detection of App::Plane datum objects before the main
geometry classification chain. Datum planes are now correctly mapped:
- Vertex + DatumPlane → PointPlane → PointInPlane (1 residual)
- Edge + DatumPlane → LinePlane → LineInPlane
- Face/DatumPlane + DatumPlane → PlanePlane → Planar
2026-02-22 14:19:11 -06:00
forbes
e5b07449d7 fix(assembly): classify datum plane references in Distance joints
Some checks failed
Build and Test / build (pull_request) Has been cancelled
When a Distance joint references a datum plane (XY_Plane, XZ_Plane,
YZ_Plane), getDistanceType() failed to recognize it because datum
plane sub-names yield an empty element type. This caused the fallback
to DistanceType::Other → BaseJointKind::Planar, which adds spurious
parallel-normal residuals that overconstrain the system.

For example, three vertex-to-datum-plane Distance joints produced
10 residuals (3×Planar) with 6 mutually contradictory orientation
constraints, causing the solver to find garbage least-squares
solutions.

Add early detection of App::Plane datum objects before the main
geometry classification chain. Datum planes are now correctly mapped:
- Vertex + DatumPlane → PointPlane → PointInPlane (1 residual)
- Edge + DatumPlane → LinePlane → LineInPlane
- Face/DatumPlane + DatumPlane → PlanePlane → Planar
2026-02-22 12:24:44 -06:00
forbes
a10b9d9a9f fix(assembly): classify datum plane references in Distance joints
All checks were successful
Build and Test / build (pull_request) Successful in 41m13s
When a Distance joint references a datum plane (XY_Plane, XZ_Plane,
YZ_Plane), getDistanceType() failed to recognize it because datum
plane sub-names yield an empty element type. This caused the fallback
to DistanceType::Other → BaseJointKind::Planar, which adds spurious
parallel-normal residuals that overconstrain the system.

For example, three vertex-to-datum-plane Distance joints produced
10 residuals (3×Planar) with 6 mutually contradictory orientation
constraints, causing the solver to find garbage least-squares
solutions.

Add early detection of App::Plane datum objects before the main
geometry classification chain. Datum planes are now correctly mapped:
- Vertex + DatumPlane → PointPlane → PointInPlane (1 residual)
- Edge + DatumPlane → LinePlane → LineInPlane
- Face/DatumPlane + DatumPlane → PlanePlane → Planar
2026-02-21 22:04:18 -06:00
441cf9e826 Merge pull request 'feat(assembly): add diagnostic logging to solver and assembly' (#313) from feat/solver-diagnostic-logging into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #313
2026-02-21 16:11:34 +00:00
forbes
c682c5d153 feat(assembly): add diagnostic logging to solver and assembly
Some checks failed
Build and Test / build (pull_request) Has been cancelled
C++ (AssemblyObject):
- getOrCreateSolver: log which solver backend was loaded
- solve: log assembly name, grounded/joint counts, context size,
  result status with DOF and placement count, per-constraint
  diagnostics on failure
- preDrag/doDragStep/postDrag: log drag part count, per-step
  validation failures, and summary (total steps / rejected count)
- buildSolveContext: log grounded/free part counts, constraint count,
  limits count, and bundle_fixed flag

Python (kindred_solver submodule):
- solver.py: log solve entry/exit with timing, system build stats,
  decomposition decisions, Newton/BFGS fallback events, drag lifecycle
- decompose.py: log cluster stats and per-cluster convergence
- Init.py: FreeCAD log handler routing Python logging to Console
2026-02-21 10:08:51 -06:00
f65a4a5e2b Merge pull request 'fix(assembly): update flip-detection baseline during drag steps' (#312) from fix/assembly-drag-flip-detection into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #312
2026-02-21 15:59:55 +00:00
forbes
5d55f091d0 fix(assembly): update flip-detection baseline during drag steps
Some checks failed
Build and Test / build (pull_request) Has been cancelled
During drag operations, validateNewPlacements() compared each solver
result against the pre-drag positions saved once in preDrag().  As the
user dragged further, the cumulative rotation from that fixed baseline
easily exceeded the 91-degree threshold, causing valid intermediate
results to be rejected with 'flipped orientation' warnings and making
parts appear to explode.

Fix: call savePlacementsForUndo() after each accepted drag step so
that the flip check compares against the last accepted state rather
than the original pre-drag origin.
2026-02-21 09:59:04 -06:00
a445275fd2 Merge pull request 'fix(kc_format): eliminate duplicate silo/manifest.json in .kc files' (#311) from fix/kc-duplicate-manifest into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #311
2026-02-21 15:50:58 +00:00
forbes
88efa2a6ae fix(kc_format): eliminate duplicate silo/manifest.json entries in .kc files
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Two code paths were appending silo/manifest.json to the ZIP without
removing the previous entry, causing Python's zipfile module to warn
about duplicate names:

1. slotFinishSaveDocument() re-injected the cached manifest from
   entries, then the modified_at update branch wrote a second copy.

2. update_manifest_fields() opened the ZIP in append mode and wrote
   an updated manifest without removing the old one.

Fix slotFinishSaveDocument() by preparing the final manifest (with
updated modified_at) in the entries dict before writing, so only one
copy is written to the ZIP.

Fix update_manifest_fields() by rewriting the ZIP atomically via a
temp file, deduplicating any pre-existing duplicate entries in the
process.
2026-02-21 09:49:36 -06:00
62f077a267 Merge pull request 'fix(assembly): prevent segfault when all joints are removed' (#310) from fix/assembly-empty-joints-segfault into main
Some checks failed
Build and Test / build (push) Has been cancelled
Reviewed-on: #310
2026-02-21 15:47:59 +00:00
forbes
b6b0ebb4dc fix(assembly): prevent segfault when all joints are removed
Some checks failed
Build and Test / build (pull_request) Has been cancelled
updateSolveStatus() calls solve() when lastResult_.placements is empty,
but solve() calls updateSolveStatus() at the end. When an assembly has
zero constraints (all joints removed), the solver returns zero
placements, causing infinite recursion until stack overflow (segfault).

Add a static re-entrancy guard so the recursive solve() call is skipped
if updateSolveStatus() is already on the call stack.
2026-02-21 09:47:15 -06:00
forbes
5883ac8a0d fix(gui): resolve PartDesign toolbars and breadcrumb when editing body inside assembly
Some checks failed
Build and Test / build (pull_request) Has been cancelled
When double-clicking a PartDesign Body inside an Assembly, the editing
context resolver failed to show PartDesign toolbars or a proper
breadcrumb. Three root causes:

1. Priority preemption: assembly.edit (priority 90) always matched
   because the Assembly VP remained in edit mode, blocking all
   PartDesign contexts (priorities 30-40).

2. Wrong active-object key: PartDesign matchers only queried the
   "part" active object, but ViewProviderBody::toggleActiveBody()
   sets "part" to the containing App::Part (which is the Assembly
   itself). The Body is set under "pdbody", which was never queried.

3. Missing refresh trigger: ActiveObjectList::setObject() fires
   Document::signalActivatedViewProvider, but EditingContextResolver
   had no connection to it, so setActiveObject("pdbody", body) never
   triggered a context re-resolve.

Changes:
- Forward signalActivatedViewProvider from Gui::Document to
  Gui::Application (same pattern as signalInEdit/signalResetEdit)
- Connect EditingContextResolver to the new application-level signal
- Add getActivePdBodyObject() helper querying the "pdbody" key
- Add partdesign.in_assembly context (priority 95) that matches when
  a Body is active pdbody while an Assembly is in edit
- Update partdesign.body and partdesign.feature matchers to check
  pdbody before falling back to the part key
- Add Assembly > Body breadcrumb with Blue > Mauve coloring
- Update label resolution to prefer pdbody name
2026-02-21 09:43:51 -06:00
0f8fa0be86 Merge pull request 'feat(assembly): fixed reference planes (Top/Front/Right) + solver docs' (#307) from feat/assembly-origin-planes into main
Some checks failed
Deploy Docs / build-and-deploy (push) Successful in 51s
Build and Test / build (push) Has been cancelled
Reviewed-on: #307
2026-02-21 15:09:55 +00:00
forbes
acc255972d feat(assembly): fixed reference planes + solver docs
Some checks failed
Build and Test / build (pull_request) Failing after 7m52s
Assembly Origin Planes:
- AssemblyObject::setupObject() relabels origin planes to
  Top (XY), Front (XZ), Right (YZ) on assembly creation
- CommandCreateAssembly.py makes origin planes visible by default
- AssemblyUtils.cpp getObjFromRef() resolves LocalCoordinateSystem
  to child datum elements for joint references to origin planes
- TestAssemblyOriginPlanes.py: 9 integration tests covering
  structure, labels, grounding, reference resolution, solver,
  and save/load round-trip

Solver Documentation:
- docs/src/solver/: 7 new pages covering architecture overview,
  expression DAG, constraints, solving algorithms, diagnostics,
  assembly integration, and writing custom solvers
- docs/src/SUMMARY.md: added Kindred Solver section
2026-02-21 09:09:16 -06:00
forbes
6e7d2b582e fix(assembly): move resetSolver() out-of-line to fix incomplete type error
Some checks failed
Build and Test / build (pull_request) Has been cancelled
unique_ptr::reset() requires the complete type for its deleter, but
IKCSolver is only forward-declared in AssemblyObject.h. Move the
definition to AssemblyObject.cpp where the full header is included.
2026-02-21 07:08:59 -06:00