From 485f69f257192c2f3c9c0bea3541422335c48e62 Mon Sep 17 00:00:00 2001 From: forbes Date: Fri, 27 Feb 2026 12:46:32 -0600 Subject: [PATCH] chore: remove ZTools addon from build and loader (#344) - 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). --- .gitea/ISSUE_TEMPLATE/bug_report.yml | 1 - .gitea/ISSUE_TEMPLATE/documentation.yml | 2 +- .gitea/ISSUE_TEMPLATE/feature_request.yml | 1 - .gitmodules | 4 - mods/ztools | 1 - src/Mod/Create/CMakeLists.txt | 16 +- src/Mod/Create/addon_loader.py | 8 +- tests/test_kindred_pure.py | 227 +--------------------- 8 files changed, 12 insertions(+), 248 deletions(-) delete mode 160000 mods/ztools diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.yml b/.gitea/ISSUE_TEMPLATE/bug_report.yml index e6e35199bb..7de39a11b5 100644 --- a/.gitea/ISSUE_TEMPLATE/bug_report.yml +++ b/.gitea/ISSUE_TEMPLATE/bug_report.yml @@ -47,7 +47,6 @@ body: description: Which part of Kindred Create is affected? options: - General / Core - - ztools Workbench - Silo (Parts Database) - Theme / QSS - Assembly diff --git a/.gitea/ISSUE_TEMPLATE/documentation.yml b/.gitea/ISSUE_TEMPLATE/documentation.yml index 4264ce97db..11baeb2160 100644 --- a/.gitea/ISSUE_TEMPLATE/documentation.yml +++ b/.gitea/ISSUE_TEMPLATE/documentation.yml @@ -29,7 +29,7 @@ body: attributes: label: Location description: Where should this documentation live? Link to existing pages if applicable. - placeholder: e.g. docs/src/guide/ztools.md, or "new page under Reference" + placeholder: e.g. docs/src/guide/assembly.md, or "new page under Reference" validations: required: false diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.yml b/.gitea/ISSUE_TEMPLATE/feature_request.yml index fec67320ce..4ef5313147 100644 --- a/.gitea/ISSUE_TEMPLATE/feature_request.yml +++ b/.gitea/ISSUE_TEMPLATE/feature_request.yml @@ -27,7 +27,6 @@ body: description: Which part of Kindred Create does this relate to? options: - General / Core - - ztools Workbench - Silo (Parts Database) - Theme / QSS - Assembly diff --git a/.gitmodules b/.gitmodules index 0bd5614cad..f3d4807919 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,10 +10,6 @@ [submodule "src/Mod/AddonManager"] path = src/Mod/AddonManager url = https://github.com/FreeCAD/AddonManager.git -[submodule "mods/ztools"] - path = mods/ztools - url = https://git.kindred-systems.com/forbes/ztools.git - branch = main [submodule "mods/silo"] path = mods/silo url = https://git.kindred-systems.com/kindred/silo-mod.git diff --git a/mods/ztools b/mods/ztools deleted file mode 160000 index 08e439b9ca..0000000000 --- a/mods/ztools +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 08e439b9ca403b67be5910ad1ef483bcec780c02 diff --git a/src/Mod/Create/CMakeLists.txt b/src/Mod/Create/CMakeLists.txt index ec342f8702..021bef1bba 100644 --- a/src/Mod/Create/CMakeLists.txt +++ b/src/Mod/Create/CMakeLists.txt @@ -1,5 +1,5 @@ # Kindred Create core module -# Handles auto-loading of ztools and Silo addons +# Handles auto-loading of Silo and other addons # C++ module targets add_subdirectory(App) @@ -41,20 +41,6 @@ install( Mod/Create/resources/icons ) -# Install ztools addon -install( - DIRECTORY - ${CMAKE_SOURCE_DIR}/mods/ztools/ztools - DESTINATION - mods/ztools -) -install( - FILES - ${CMAKE_SOURCE_DIR}/mods/ztools/package.xml - DESTINATION - mods/ztools -) - # Install Silo addon install( DIRECTORY diff --git a/src/Mod/Create/addon_loader.py b/src/Mod/Create/addon_loader.py index a0983a20fe..10417de2df 100644 --- a/src/Mod/Create/addon_loader.py +++ b/src/Mod/Create/addon_loader.py @@ -134,7 +134,7 @@ _registry: Optional[AddonRegistry] = None # Legacy load order for backward compatibility when no elements exist. # Once addons declare in their package.xml (issue #252), this is ignored. -_LEGACY_ORDER = ["ztools", "silo"] +_LEGACY_ORDER = ["silo"] # --------------------------------------------------------------------------- @@ -387,7 +387,11 @@ def resolve_load_order( while ts.is_active(): ready = list(ts.get_ready()) # Sort each level by (priority, name) for determinism - ready.sort(key=lambda n: (by_name[n].load_priority, n) if n in by_name else (999, n)) + ready.sort( + key=lambda n: ( + (by_name[n].load_priority, n) if n in by_name else (999, n) + ) + ) for name in ready: ts.done(name) order.extend(ready) diff --git a/tests/test_kindred_pure.py b/tests/test_kindred_pure.py index 93dbcef390..b65f70a766 100644 --- a/tests/test_kindred_pure.py +++ b/tests/test_kindred_pure.py @@ -1,8 +1,8 @@ """Tier 1 — Pure-logic tests for Kindred Create addons. -These tests exercise standalone functions from update_checker, datum_commands, -spreadsheet_commands, silo_commands, silo_start, and silo_origin WITHOUT -requiring a FreeCAD binary, running GUI, or Silo server. +These tests exercise standalone functions from update_checker, +silo_commands, silo_start, and silo_origin WITHOUT requiring a +FreeCAD binary, running GUI, or Silo server. Run directly: python tests/test_kindred_pure.py Via runner: python tests/run_kindred_tests.py @@ -103,7 +103,7 @@ sys.modules["silo_client._ssl"] = mock.MagicMock() # Add addon source paths sys.path.insert(0, str(_REPO_ROOT / "src" / "Mod" / "Create")) sys.path.insert(0, str(_REPO_ROOT / "mods" / "sdk")) -sys.path.insert(0, str(_REPO_ROOT / "mods" / "ztools" / "ztools")) + sys.path.insert(0, str(_REPO_ROOT / "mods" / "silo" / "freecad")) @@ -116,14 +116,6 @@ import silo_start # noqa: E402 from silo_commands import _safe_float # noqa: E402 from update_checker import _parse_version, _should_check # noqa: E402 -# For datum_commands, the module registers Gui.addCommand at import time. -# We need Gui.addCommand to be a no-op mock (already is via MagicMock). -from ztools.commands.datum_commands import ( # noqa: E402 - DatumCreatorTaskPanel, - SelectionItem, -) -from ztools.commands.spreadsheet_commands import column_to_index # noqa: E402 - # =================================================================== # Test: update_checker._parse_version # =================================================================== @@ -229,179 +221,6 @@ class TestShouldCheck(unittest.TestCase): self.assertTrue(_should_check(p)) -# =================================================================== -# Test: datum_commands._match_score and _type_matches -# =================================================================== - - -class _StubPanel: - """Minimal stub to access DatumCreatorTaskPanel methods without GUI.""" - - _match_score = DatumCreatorTaskPanel._match_score - _type_matches = DatumCreatorTaskPanel._type_matches - - -class TestTypeMatches(unittest.TestCase): - """Tests for DatumCreatorTaskPanel._type_matches.""" - - def setUp(self): - self.p = _StubPanel() - - def test_exact_face(self): - self.assertTrue(self.p._type_matches("face", "face")) - - def test_exact_edge(self): - self.assertTrue(self.p._type_matches("edge", "edge")) - - def test_exact_vertex(self): - self.assertTrue(self.p._type_matches("vertex", "vertex")) - - def test_cylinder_matches_face(self): - self.assertTrue(self.p._type_matches("cylinder", "face")) - - def test_circle_matches_edge(self): - self.assertTrue(self.p._type_matches("circle", "edge")) - - def test_face_does_not_match_edge(self): - self.assertFalse(self.p._type_matches("face", "edge")) - - def test_vertex_does_not_match_face(self): - self.assertFalse(self.p._type_matches("vertex", "face")) - - def test_face_does_not_match_cylinder(self): - # face is NOT a cylinder (cylinder IS a face, not reverse) - self.assertFalse(self.p._type_matches("face", "cylinder")) - - def test_edge_does_not_match_circle(self): - self.assertFalse(self.p._type_matches("edge", "circle")) - - def test_unknown_matches_nothing(self): - self.assertFalse(self.p._type_matches("unknown", "face")) - self.assertFalse(self.p._type_matches("unknown", "edge")) - - -class TestMatchScore(unittest.TestCase): - """Tests for DatumCreatorTaskPanel._match_score.""" - - def setUp(self): - self.p = _StubPanel() - - def test_exact_single_face(self): - score = self.p._match_score(("face",), ("face",)) - self.assertEqual(score, 101) # 100 + 1 matched, exact count - - def test_exact_two_faces(self): - score = self.p._match_score(("face", "face"), ("face", "face")) - self.assertEqual(score, 102) # 100 + 2 - - def test_exact_three_vertices(self): - score = self.p._match_score( - ("vertex", "vertex", "vertex"), - ("vertex", "vertex", "vertex"), - ) - self.assertEqual(score, 103) - - def test_surplus_selection_lower_score(self): - exact = self.p._match_score(("face",), ("face",)) - surplus = self.p._match_score(("face", "edge"), ("face",)) - self.assertGreater(exact, surplus) - self.assertGreater(surplus, 0) - - def test_not_enough_items_zero(self): - score = self.p._match_score(("face",), ("face", "face")) - self.assertEqual(score, 0) - - def test_wrong_type_zero(self): - score = self.p._match_score(("vertex",), ("face",)) - self.assertEqual(score, 0) - - def test_empty_selection_zero(self): - score = self.p._match_score((), ("face",)) - self.assertEqual(score, 0) - - def test_cylinder_satisfies_face(self): - score = self.p._match_score(("cylinder",), ("face",)) - self.assertEqual(score, 101) - - def test_face_and_edge(self): - score = self.p._match_score(("face", "edge"), ("face", "edge")) - self.assertEqual(score, 102) - - def test_order_independence(self): - # edge,face should match face,edge requirement - score = self.p._match_score(("edge", "face"), ("face", "edge")) - self.assertEqual(score, 102) - - -# =================================================================== -# Test: datum_commands.SelectionItem properties -# =================================================================== - - -class TestSelectionItemProperties(unittest.TestCase): - """Tests for SelectionItem.display_name and type_icon.""" - - def _make_item(self, label, subname, geo_type): - obj = mock.MagicMock() - obj.Label = label - item = SelectionItem.__new__(SelectionItem) - item.obj = obj - item.subname = subname - item.shape = None - item.geo_type = geo_type - return item - - def test_display_name_with_subname(self): - item = self._make_item("Box", "Face1", "face") - self.assertEqual(item.display_name, "Box.Face1") - - def test_display_name_without_subname(self): - item = self._make_item("DatumPlane", "", "plane") - self.assertEqual(item.display_name, "DatumPlane") - - def test_type_icon_face(self): - item = self._make_item("X", "Face1", "face") - self.assertEqual(item.type_icon, "▢") - - def test_type_icon_vertex(self): - item = self._make_item("X", "Vertex1", "vertex") - self.assertEqual(item.type_icon, "•") - - def test_type_icon_unknown(self): - item = self._make_item("X", "", "unknown") - self.assertEqual(item.type_icon, "?") - - -# =================================================================== -# Test: spreadsheet_commands.column_to_index -# =================================================================== - - -class TestColumnToIndex(unittest.TestCase): - """Tests for spreadsheet column_to_index conversion.""" - - def test_a(self): - self.assertEqual(column_to_index("A"), 0) - - def test_b(self): - self.assertEqual(column_to_index("B"), 1) - - def test_z(self): - self.assertEqual(column_to_index("Z"), 25) - - def test_aa(self): - self.assertEqual(column_to_index("AA"), 26) - - def test_ab(self): - self.assertEqual(column_to_index("AB"), 27) - - def test_az(self): - self.assertEqual(column_to_index("AZ"), 51) - - def test_ba(self): - self.assertEqual(column_to_index("BA"), 52) - - # =================================================================== # Test: silo_commands._safe_float # =================================================================== @@ -516,44 +335,6 @@ class TestSiloOriginCapabilities(unittest.TestCase): self.assertEqual(origin.nickname(), "Production") -# =================================================================== -# Test: DatumCreatorTaskPanel.MODES integrity -# =================================================================== - - -class TestDatumModes(unittest.TestCase): - """Verify the MODES table is internally consistent.""" - - def test_all_modes_have_four_fields(self): - for mode in DatumCreatorTaskPanel.MODES: - self.assertEqual(len(mode), 4, f"Mode tuple wrong length: {mode}") - - def test_mode_ids_unique(self): - ids = [m[1] for m in DatumCreatorTaskPanel.MODES] - self.assertEqual(len(ids), len(set(ids)), "Duplicate mode IDs found") - - def test_categories_valid(self): - valid = {"plane", "axis", "point"} - for _, mode_id, _, category in DatumCreatorTaskPanel.MODES: - self.assertIn(category, valid, f"Invalid category for {mode_id}") - - def test_required_types_are_tuples(self): - for _, mode_id, req, _ in DatumCreatorTaskPanel.MODES: - self.assertIsInstance(req, tuple, f"required_types not a tuple: {mode_id}") - - def test_plane_modes_count(self): - planes = [m for m in DatumCreatorTaskPanel.MODES if m[3] == "plane"] - self.assertEqual(len(planes), 7) - - def test_axis_modes_count(self): - axes = [m for m in DatumCreatorTaskPanel.MODES if m[3] == "axis"] - self.assertEqual(len(axes), 4) - - def test_point_modes_count(self): - points = [m for m in DatumCreatorTaskPanel.MODES if m[3] == "point"] - self.assertEqual(len(points), 5) - - # =================================================================== -- 2.49.1