chore: remove ZTools addon from build and loader (#344)
All checks were successful
Build and Test / build (pull_request) Successful in 29m28s
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).
This commit is contained in:
@@ -47,7 +47,6 @@ body:
|
||||
description: Which part of Kindred Create is affected?
|
||||
options:
|
||||
- General / Core
|
||||
- ztools Workbench
|
||||
- Silo (Parts Database)
|
||||
- Theme / QSS
|
||||
- Assembly
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -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
|
||||
|
||||
Submodule mods/ztools deleted from 08e439b9ca
@@ -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
|
||||
|
||||
@@ -134,7 +134,7 @@ _registry: Optional[AddonRegistry] = None
|
||||
|
||||
# Legacy load order for backward compatibility when no <kindred> elements exist.
|
||||
# Once addons declare <kindred> 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user