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.
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.
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
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
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
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.
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.
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.
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.
Phase 3a of the solver server integration: add dict/JSON serialization
to all KCSolve pybind11 types so SolveContext and SolveResult can be
transported as JSON between the Create client, Silo server, and solver
runners.
Implementation:
- Constexpr enum string mapping tables for all 5 enums (BaseJointKind,
SolveStatus, DiagnosticKind, MotionKind, LimitKind) with template
bidirectional lookup helpers
- File-local to_dict/from_dict conversion functions for all 10 types
(Transform, Part, Constraint::Limit, Constraint, MotionDef,
SimulationParams, SolveContext, ConstraintDiagnostic,
SolveResult::PartResult, SolveResult)
- .def("to_dict") and .def_static("from_dict") on every py::class_<>
binding chain
Serialization details per SOLVER.md §3:
- SolveContext.to_dict() includes api_version field
- SolveContext.from_dict() validates api_version, raises ValueError on
mismatch
- Enums serialize as strings matching pybind11 .value() names
- Transform: {position: [x,y,z], quaternion: [w,x,y,z]}
- Optional simulation serializes as None/null
- Pure pybind11 py::dict construction, no new dependencies
Tests: 16 new tests in TestKCSolveSerialization covering round-trips for
all types, all 24 BaseJointKind values, all 4 SolveStatus values,
json.dumps/loads stdlib round-trip, and error cases (missing key,
invalid enum, bad array length, wrong api_version).
Add the kcsolve pybind11 module exposing the KCSolve C++ API to Python:
- PyIKCSolver trampoline enabling Python IKCSolver subclasses
- Bindings for all 5 enums, 10 structs, IKCSolver, and OndselAdapter
- Module functions wrapping SolverRegistry (available, load, joints_for,
set_default, get_default, register_solver)
- PySolverHolder class forwarding virtual calls with GIL acquisition
- register_solver() for runtime Python solver registration
IKCSolver constructor moved from protected to public for pybind11
trampoline access (class remains abstract via 3 pure virtuals).
Includes 16 Python tests covering module import, type bindings, enum
values, registry functions, Python solver subclassing, and full
register/load/solve round-trip.
Closes#288
Rewire AssemblyObject to call through KCSolve::IKCSolver instead of
directly manipulating OndselSolver ASMT types.
Key changes:
- Remove all 30+ #include <OndselSolver/*> from AssemblyObject.cpp
- Remove MbDPartData, objectPartMap, mbdAssembly members
- Add solver_ (IKCSolver), lastResult_ (SolveResult), partIdToObjs_ maps
- New buildSolveContext() builds SolveContext from FreeCAD document objects
with JointType/DistanceType -> BaseJointKind decomposition
- New computeMarkerTransform() replaces handleOneSideOfJoint()
- New computeRackPinionMarkers() replaces getRackPinionMarkers()
- Rewrite solve/preDrag/doDragStep/postDrag/generateSimulation to call
through IKCSolver interface
- Rewrite setNewPlacements/validateNewPlacements to use SolveResult
- Rewrite updateSolveStatus to use ConstraintDiagnostic
- Add export_native() to IKCSolver for ASMT export support
- Register OndselAdapter at Assembly module init in AppAssembly.cpp
- Remove OndselSolver from Assembly_LIBS (linked transitively via KCSolve)
Assembly module now has zero OndselSolver includes. All solver coupling
is confined to KCSolve/OndselAdapter.cpp.
Phase 1b of the pluggable solver system. Converts KCSolve from a
header-only INTERFACE target to a SHARED library and implements
the SolverRegistry with dynamic plugin discovery.
Changes:
- Add KCSolveGlobal.h export macro header (KCSolveExport)
- Move SolverRegistry method bodies from header to SolverRegistry.cpp
- Implement scan() with dlopen/LoadLibrary plugin loading
- Add scan_default_paths() for KCSOLVE_PLUGIN_PATH + system paths
- Plugin entry points: kcsolve_api_version() + kcsolve_create()
- API version checking (major version compatibility)
- Convert CMakeLists.txt from INTERFACE to SHARED library
- Link FreeCADBase (PRIVATE) for Console logging
- Link dl on POSIX for dynamic loading
- Fix -Wmissing-field-initializers warnings in IKCSolver.h defaults
The registry discovers plugins by scanning directories for shared
libraries that export the kcsolve C entry points. Plugins are
validated for API version compatibility before registration.
Manual registration via register_solver() remains available for
built-in solvers (e.g. OndselAdapter in Phase 1c).
Add the pluggable solver API as header-only files under
src/Mod/Assembly/Solver/. This is Phase 1a of the pluggable solver
system (INTER_SOLVER.md).
New files:
- Types.h: BaseJointKind enum (24 decomposed constraint types),
Transform, Part, Constraint, SolveContext, SolveResult, and
supporting types. Uses standalone types (no FreeCAD dependency)
for future server worker compatibility.
- IKCSolver.h: Abstract solver interface with solve(), drag protocol
(pre_drag/drag_step/post_drag), kinematic simulation
(run_kinematic/num_frames/update_for_frame), and diagnostics.
Only solve(), name(), and supported_joints() are pure virtual;
all other methods have default implementations.
- SolverRegistry.h: Thread-safe singleton registry for solver
backends with factory-based registration and default solver
selection.
- CMakeLists.txt: INTERFACE library target (header-only for now).
Modified:
- Assembly/CMakeLists.txt: add_subdirectory(Solver)
- Assembly/App/CMakeLists.txt: link KCSolve INTERFACE target
- Add Spreadsheet color preferences to KindredCreate.cfg using FCText
entries (TextColor, AliasedCellBackgroundColor, PositiveNumberColor,
NegativeNumberColor) matching the C++ GetASCII() reader in SheetModel.cpp
- Remove CatppuccinMocha install directive from CMakeLists.txt
- Update ztools submodule: theme.py deleted, CatppuccinMocha preference
pack removed, package.xml cleaned up
The previous apply_spreadsheet_colors() in ztools was a no-op: it called
SetUnsigned() but the Spreadsheet C++ reads GetASCII() — different param
types in FreeCAD's parameter system. Now properly fixed via preference pack.
Closes#278
Documentation updates:
- KNOWN_ISSUES.md: correct #6 (datum handling), resolve#10
(delete_bom_entry) and #11 (silo icons), fix stale formatDate
reference, mark completed next steps, add new next steps.
- INTEGRATION_PLAN.md: correct ztools SDK migration claim.
kc_format.py (#277):
- New _manifest_enrich_hook: populates part_uuid from SiloItemId and
silo_instance from Silo API URL on every .kc save.
- New update_manifest_fields(): public API to update manifest fields
in an already-saved .kc ZIP (used for post-upload revision_hash).
mods/silo submodule (#276):
- New bom_sync.py extraction engine.
- Post-commit hooks for BOM sync and manifest revision update.
- SSE bom_merged signal + Activity pane handler.
- merge_bom_json client method (forward-looking).
Refs: #276, #277
Wire live data fetching, SSE subscriptions, and server write-back into
the History, Metadata, and Dependency viewer widgets.
Changes:
- Add server integration helpers (_init_server, _is_online,
_get_part_number, offline banner) with lazy silo_commands import
- SiloHistoryViewer: Refresh button fetches live revisions via
SiloClient.get_revisions(); SSE revision_created auto-refreshes
- SiloMetadataEditor: Save pushes to server (update_metadata,
patch_lifecycle, patch_tags); SSE item_updated refreshes form
when no local edits pending; offline banner
- SiloDependencyTable: Server-side UUID resolution via
resolve_dependencies(); Download button for unresolved items;
Refresh re-checks status; three-state icons (resolved/
downloadable/missing)
- All viewers show 'Offline — showing cached data' banner when
disconnected and disable server-dependent controls
Bump silo submodule to track new silo-client API methods:
get_metadata, update_metadata, patch_lifecycle, patch_tags,
resolve_dependencies (silo-client PR #19)
Closeskindred/silo-mod#43
Add 10 SVG placeholder icons copied from existing silo action icons.
These provide immediate visual feedback in the document tree while
proper Catppuccin-themed icons are designed later.
Icons: silo-group, silo-manifest, silo-metadata, silo-history,
silo-approvals, silo-dependencies, silo-job, silo-macro,
silo-jobs-group, silo-macros-group
Also adds install(DIRECTORY) rule for resources/icons/ in CMakeLists.
Closes#42
Add four viewer widgets for the remaining Silo tree node types:
- SiloApprovalsViewer: ECO approval status cards with colored status
icons, state badge, and Open in Silo Web UI button
- SiloDependencyTable: QTableView with resolution status (checks open
documents for matching part_uuid)
- SiloJobViewer: YAML source editor with Edit/Lock toggle, monospace
font, dirty tracking, and unsaved-changes guard
- SiloMacroEditor: Python source editor with Run Now (exec in FreeCAD
context), Save button, and dirty tracking
Also extends the viewer factory with prefix-based routing for
silo/jobs/*.yaml and silo/macros/*.py entries.
Closes#41
Add SiloHistoryViewer widget that opens in an MDI subwindow when the
user double-clicks the History node in the Silo tree. Displays revision
cards newest-first with revision number, Catppuccin-themed lifecycle
status badges, author, timestamp, and commit comment.
Changes:
- silo_viewers.py: SiloHistoryViewer with revision card layout,
status badge QSS, scroll area, empty-history placeholder
Closes#40
Add SiloMetadataEditor widget that opens in an MDI subwindow when the
user double-clicks the Metadata node in the Silo tree. Supports editing
lifecycle state, tags (add/remove chips), and schema-defined fields with
type-inferred widgets (QCheckBox, QDoubleSpinBox, QLineEdit).
Changes:
- silo_viewers.py: SiloMetadataEditor with dirty tracking, Save/Reset
buttons, unsaved-changes close guard, and tag chip management
- silo_objects.py: mark_dirty()/is_dirty()/clear_dirty() on proxy
- kc_format.py: fix entries=None before hooks; _metadata_save_hook
writes dirty RawContent back to silo/ cache on document save
Closes#39
Add SiloManifestViewer widget that opens in an MDI subwindow when the
user double-clicks the Manifest node in the Silo tree. Displays all
manifest.json fields in a read-only QFormLayout with copy buttons for
Part UUID and Silo Instance.
New files:
- silo_viewers.py: SiloManifestViewer widget + create_viewer_widget()
factory with _VIEWER_REGISTRY for future viewer classes
Modified files:
- silo_viewproviders.py: doubleClicked() wired to open MDI subwindow
with deduplication via widget objectName()
- CMakeLists.txt: add silo_viewers.py to install list
Closes#38
Add document tree infrastructure that creates Silo metadata nodes when
a .kc file is opened. Nodes appear under a "Silo" group and represent
the silo/ ZIP directory entries (manifest, metadata, history, etc.).
New files:
- silo_objects.py: SiloViewerObject proxy with Transient properties
- silo_viewproviders.py: SiloViewerViewProvider with icon stubs
- silo_tree.py: SiloTreeBuilder with conditional node creation
- silo_document.py: SiloDocumentObserver singleton + registration
Modified files:
- kc_format.py: pre_reinject hook system for silo/ entry mutation
- InitGui.py: 600ms timer registration for document observer
- CMakeLists.txt: install list for 4 new Python files
Closes#37
Route platform API calls through kindred_sdk wrappers:
ZTools:
- Replace hardcoded MOCHA dict with kindred_sdk.get_theme_tokens()
- Add sdk dependency to package.xml
Silo:
- Replace FreeCADGui.registerEditingOverlay() with kindred_sdk.register_overlay()
- Replace FreeCADGui.addOrigin()/removeOrigin() with kindred_sdk wrappers
- Replace hardcoded _MOCHA palette subset with kindred_sdk.get_theme_tokens()
- Add sdk dependency to package.xml
Create module:
- Replace dock panel boilerplate with kindred_sdk.register_dock_panel()
Behavior is identical before and after — this is a refactor only.
Closes#250
Add mods/sdk/ with the kindred_sdk Python package providing a stable
API layer for addon integration with Kindred Create platform features.
Modules:
- context: editing context/overlay registration wrappers
- theme: YAML-driven palette system (Catppuccin Mocha)
- origin: FileOrigin registration helpers
- dock: deferred dock panel registration
- compat: version detection utilities
The SDK loads at priority 0 (before all other addons) via the existing
manifest-driven loader. Theme colors are defined in a single YAML
palette file instead of hardcoded Python dicts, enabling future theme
support and eliminating color duplication across addons.
Closes#249
Add FreeCAD.getAddonRegistry() function for runtime addon introspection.
Changes to addon_loader.py:
- Add contexts field to AddonManifest for tracking context IDs
- Add register_context() method for addons to declare contexts at runtime
- Add contexts() method returning {context_id: [addon_names]} mapping
- Parse <contexts> element from <kindred> in package.xml
- Add getAddonRegistry() function returning the registry singleton
Changes to Init.py:
- Expose getAddonRegistry as FreeCAD.getAddonRegistry after loading
Usage:
registry = FreeCAD.getAddonRegistry()
registry.get('ztools') # AddonManifest for ztools
registry.loaded() # list of loaded addons
registry.is_loaded('silo') # True/False
registry.contexts() # {context_id: [addon_names]}
Closes#253
Add <kindred> elements to ztools (priority=50) and silo (priority=60)
package.xml files declaring min_create_version, load_priority,
pure_python, and context metadata.
Fix addon_loader.py topological sort to use level-by-level processing
with (priority, name) sorting within each level, replacing
static_order() which did not guarantee deterministic ordering.
Add docs/src/development/package-xml-schema.md documenting the full
field reference, schema example, and backward compatibility notes.
Closes#252
Add addon_loader.py implementing a six-stage pipeline: scan mods/ for
package.xml files, parse standard fields and optional <kindred>
extensions via ElementTree, validate version compatibility, resolve
load order via graphlib.TopologicalSorter (with legacy fallback),
exec() Init.py/InitGui.py, and populate a runtime AddonRegistry
exposed as FreeCAD.KindredAddons.
Replace hard-coded addon lists in Init.py and InitGui.py with calls
to addon_loader.load_addons(). All QTimer-based Silo integration code
in InitGui.py is unchanged.
Backward compatible: addons without <kindred> elements load with no
constraints using the existing ztools-then-silo order.
Closes#248
During document restore, PropertyLinkList::Restore sets the Group property
on AssemblyObject, triggering onChanged → updateSolveStatus → solve →
validateNewPlacements. At this point joints reference objects that haven't
been restored yet, causing a null pointer dereference (SIGSEGV).
Add an isRestoring() guard to skip solver invocation during restore,
matching the existing pattern in AssemblyLink::onChanged().
The Create module (src/Mod/Create/) was lost from src/Mod/CMakeLists.txt
during the rebase onto upstream FreeCAD 1.2.0-dev. Without it:
- Mod/Create/ is never installed, so the bootstrap that loads ztools
and silo never runs
- mods/ztools/ and mods/silo/ are never installed
- Startup shows 'Ignoring unknown SiloWorkbench/ZToolsWorkbench'
Also adds kc_format.py to the Create install list (was missing).
Use resizeDocks() to give Database Activity 80% of the vertical space
in the right dock area, keeping Database Auth compact at 120px.
Closes#190, Closes#191
Adds push_dag() and get_dag() to SiloClient:
- push_dag: PUT /api/items/{pn}/dag with nodes + edges
- get_dag: GET /api/items/{pn}/dag with optional revision filter
Closes#215
When the silo workbench is loaded, Insert Component now opens the
Silo part browser (OpenItemWidget) instead of the OS file dialog.
Users can search both the Silo database and local files to find
components to insert.
Falls back to the OS file dialog if silo-mod is not installed.
Also adds .kc file support to the fallback file dialog filter.
Closes#202
The Create module (src/Mod/Create/) was never included in the build
because src/Mod/CMakeLists.txt did not call add_subdirectory(Create).
This meant:
- Mod/Create/InitGui.py was never installed, so the workbench loader
(setup_kindred_workbenches) that loads silo and ztools never ran
- The install rules for mods/silo/freecad/ and mods/silo/silo-client/
never executed, leaving the silo workbench files missing
- kc_format.py was also not listed in the Create install rule
Fixes the 'Ignoring unknown SiloWorkbench' error on startup.
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
Remove the closeStart preference check so the start page MDI tab
always closes when a document is opened or created. Re-enable
QListView updates before closing to prevent Qt hang during widget
destruction.
Add hideEvent override as a safety net to re-enable disabled
QListView updates whenever the start page is hidden.
Add #include <QApplication> (for qApp macro), #include <QEvent> (for
QEvent::LanguageChange), and #include <Gui/Application.h> (for
Gui::Application::Instance) which were transitively available in
FreeCAD 1.0 but not in 1.2.