← Commit history

Merge PR #344: Mechanical BOM that reads a large real assembly right: views, make/buy reasons, kits, libraries, wires, cut and order lists (feature/bom-v2 -> master) merge

Colby Knox ·eb0f18e930 ·13d ago ·parents 4031ef5, a166704

Showing changes introduced by the merge (against its first parent).

2 files changed +101
tests/test_mechanical.py+86
@@ -264,4 +264,90 @@ class Readbacks(unittest.TestCase):             scoped = purchased.material_list(app, {'query': 'steel', 'libraryId': 'lib-fusion'})['materials']             self.assertEqual([r['name'] for r in scoped], ['Steel']) ++class ReferencedOccurrences(unittest.TestCase):+    def test_suppression_inside_a_referenced_design_is_decided_in_its_source(self):+        class Proxy(Entity):+            @property+            def timelineObject(self):+                raise RuntimeError('2 : InternalValidationError : dcInstance')+        ref_root = Entity(isReferencedComponent=True, assemblyContext=None)+        inner = Proxy(isReferencedComponent=False, assemblyContext=ref_root)+        self.assertIsNone(c.occurrence_suppression(inner))+        stray = Proxy(isReferencedComponent=False, assemblyContext=None)+        with self.assertRaises(RuntimeError):+            c.occurrence_suppression(stray)+        self.assertTrue(c.occurrence_suppression(Entity(timelineObject=NS(isSuppressed=True))))+++class NativeProperties(unittest.TestCase):+    """partNumber and description are cloud reads (~0.6 s each per component on a cloud document)."""+    def setUp(self):+        c._entities.clear()+        self.astate = importlib.import_module('mechanical_test_commands.mechanical.assembly_state')+        self.astate._native_properties.clear()++    def component(self, reads):+        class Comp(Entity):+            @property+            def partNumber(self):+                reads.append('pn'); return 'ADM-1'+            @property+            def description(self):+                reads.append('desc'); return 'Bracket'+        return Comp(objectType='adsk::fusion::Component', name='Bracket', revisionId='r1', attributes=Collection(), material=None, entityToken='tok-bracket')++    def test_the_revision_witness_never_reads_them(self):+        reads = []+        comp = self.component(reads)+        row = self.astate.metadata(comp, native=False)+        self.assertEqual((row['partNumber'], row['description'], reads), (None, None, []))++    def test_they_are_read_once_per_component_revision(self):+        reads = []+        comp = self.component(reads)+        self.assertEqual(self.astate.metadata(comp)['partNumber'], 'ADM-1')+        self.astate.metadata(comp)+        self.assertEqual(reads, ['pn', 'desc'])+        comp.revisionId = 'r2'+        self.astate.metadata(comp)+        self.assertEqual(reads, ['pn', 'desc', 'pn', 'desc'])+++class IdentityRegistry(unittest.TestCase):+    """2026-10-28: every lookup compared against every identity in the session, so the revision of a+    1,249-component assembly ran for many minutes. Lookups now compare a few candidates."""+    def setUp(self):+        c._entities.clear()++    def entity(self, name, token, owner="Owner"):+        return Entity(objectType='adsk::fusion::BRepBody', name=name, entityToken=token, parentComponent=NS(name=owner))++    def test_same_object_same_identity_and_same_names_stay_distinct(self):+        a, b = self.entity('Body1', 't-a'), self.entity('Body1', 't-b')+        self.assertEqual(c.identity(a), c.identity(a))+        self.assertNotEqual(c.identity(a), c.identity(b))++    def test_a_renamed_entity_is_found_by_its_token(self):+        a = self.entity('Body1', 't-a')+        key = c.identity(a)+        a.name = 'Renamed'+        self.assertEqual(c.identity(a), key)++    def test_a_reset_registry_forgets_its_indexes(self):+        a = self.entity('Body1', 't-a')+        key = c.identity(a)+        c._entities.clear()+        self.assertNotEqual(c.identity(a), key)++    def test_a_lookup_compares_a_few_candidates_not_the_session(self):+        for i in range(3000):+            c.identity(self.entity('Body%d' % i, 't-%d' % i, owner='C%d' % (i % 50)))+        probe = self.entity('Body1500', 't-new', owner='C0')+        calls = []+        real = c.same+        with patch.object(c, 'same', side_effect=lambda x, y: calls.append(1) or real(x, y)):+            c.identity(probe)+        self.assertLess(len(calls), 10)+ if __name__=='__main__':unittest.main()
tests/test_mechanical_revision.py+15
@@ -178,6 +178,21 @@ class RevisionInitialization(unittest.TestCase):         self.assertEqual(error.exception.code, 'revision_unavailable')         self.assertEqual(self.events, []) +    def test_a_component_inserted_by_reference_enters_by_its_source_version(self):+        # 2026-10-28: a production cell inserts 23 designs by reference; every operation refused+        source = Entity(objectType='adsk::core::Document', name='Referenced', dataFile=NS(id='urn:ref', versionNumber=7))+        self.part.parentDesign = Entity(parentDocument=source)+        first = c.revision(self.design)+        self.assertNotIn('Part', {row[1] for row in self.events if row[0] == 'mesh'})+        self.assertEqual(c.revision(self.design), first)+        source.dataFile = NS(id='urn:ref', versionNumber=8)+        self.assertNotEqual(c.revision(self.design), first)+        # a foreign component from an unsaved document is not a reference: still refused+        self.part.parentDesign = Entity(parentDocument=Entity(dataFile=None))+        with self.assertRaises(c.MechanicalError) as error:+            c.revision(self.design)+        self.assertEqual(error.exception.code, 'revision_unavailable')+     def test_invalid_or_malformed_mesh_collection_never_counts_as_empty(self):         for meshes in (None, NS(count=0, isValid=False, objectType='adsk::fusion::MeshBodies'),                        NS(count=0, isValid=True, objectType='adsk::fusion::BRepBodies'), NS(count=0)):