app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
"""Offline native-adapter fault tests; no Fusion, graphics display or host access."""
import copy
import importlib
import threading
from types import SimpleNamespace as NS
import unittest
from unittest.mock import patch
from test_mechanical import c, Entity, Collection, MeshCollection
from test_mechanical_exploded_view import fixture as assembly_fixture, pose
p = importlib.import_module('mechanical_test_commands.mechanical.exploded_preview_prototype')
plan = importlib.import_module('mechanical_test_commands.mechanical.exploded_view')
real_revision = c.revision
def enrich_blank(document, design, root):
"""Native-like blank data for the actual core aggregate and presentation witness."""
document.attributes = Collection()
document.name = 'Untitled'
document.creationId = 'native-presentation'
design.attributes = Collection()
design.designType = design.designIntent = 0
design.isConfiguredDesign = design.isConfiguration = False
design.configurationRowId = ''
design.timeline.markerPosition = 0
design.snapshots = Collection()
design.snapshots.hasPendingSnapshot = False
for name in ('allOccurrences', 'joints', 'asBuiltJoints', 'jointOrigins', 'rigidGroups', 'motionLinks',
'constructionAxes', 'constructionPlanes', 'constructionPoints', 'modelParameters', 'canvases', 'decals'):
if not hasattr(root, name):
setattr(root, name, Collection())
for name in ('isBodiesFolderLightBulbOn', 'isConstructionFolderLightBulbOn', 'isOriginFolderLightBulbOn',
'isSketchFolderLightBulbOn', 'isJointsFolderLightBulbOn', 'isJointOriginsFolderLightBulbOn',
'isCanvasFolderLightBulbOn', 'isDecalFolderLightBulbOn'):
setattr(root, name, True)
root.attributes = Collection()
root.name = 'Presentation'
root.partNumber = root.description = ''
root.material = None
for field, kind, vector in (
('xConstructionAxis', 'ConstructionAxis', (1., 0., 0.)),
('yConstructionAxis', 'ConstructionAxis', (0., 1., 0.)),
('zConstructionAxis', 'ConstructionAxis', (0., 0., 1.)),
('xYConstructionPlane', 'ConstructionPlane', (0., 0., 1.)),
('xZConstructionPlane', 'ConstructionPlane', (0., 1., 0.)),
('yZConstructionPlane', 'ConstructionPlane', (1., 0., 0.)),
('originConstructionPoint', 'ConstructionPoint', None)):
xyz = NS(x=0., y=0., z=0.)
geometry = xyz if vector is None else NS(origin=xyz, direction=NS(x=vector[0], y=vector[1], z=vector[2]), normal=NS(x=vector[0], y=vector[1], z=vector[2]))
setattr(root, field, Entity(objectType='adsk::fusion::' + kind, name=field, component=root, entityToken='origin-' + field,
nativeObject=None, isDeletable=False, isLightBulbOn=False, isVisible=False,
geometry=geometry, attributes=Collection()))
class PresentationDesign(Entity):
@property
def rev(self):
return self.revision_override if self.revision_override is not None else real_revision(self)
@rev.setter
def rev(self, value):
self.revision_override = None if value == 'blank' else value
class Matrix:
def __init__(self, values=None): self.values = list(values or p.IDENTITY)
def setWithArray(self, values): self.values = list(values); return True
def asArray(self): return list(self.values)
def body(component=None, transient=False):
points = [(0.,0.,0.), (1.,0.,0.), (0.,1.,0.), (0.,0.,1.)]
result = Entity(objectType='adsk::fusion::BRepBody', isTransient=transient, isSolid=True,
parentComponent=component, nativeObject=None, isVisible=True, volume=1/6,
boundingBox=NS(minPoint=NS(x=0.,y=0.,z=0.), maxPoint=NS(x=1.,y=1.,z=1.)),
faces=Collection([object()]*4), edges=Collection([object()]*6),
vertices=Collection([Entity(objectType="adsk::fusion::BRepVertex",geometry=NS(x=x,y=y,z=z)) for x,y,z in points]))
def proxy(occ):
return Entity(objectType='adsk::fusion::BRepBody', nativeObject=result, assemblyContext=occ,
isVisible=getattr(occ,'bodyVisible',True))
result.createForAssemblyContext=proxy
return result
class Graphics(Collection):
def __init__(self, parent=None, root_collection=None):
super().__init__(); self.parent=parent; self.root_collection=root_collection
self.isValid=True; self.id=''; self.isVisible=True; self.isSelectable=True; self.isChildrenSelectable=True
self.transform=Matrix(); self.objectType='adsk::fusion::CustomGraphicsGroup'
self.fail_transform=False; self.delete_ack=True; self.ignore_delete=False; self.null_body=False
self.ignore_transform=False; self._transform=Matrix()
@property
def transform(self): return self._transform
@transform.setter
def transform(self, value):
if getattr(self,'fail_transform',False):raise RuntimeError('native transform refused')
if not getattr(self,'ignore_transform',False):self._transform=Matrix(value.asArray())
self.effect()
def effect(self):
if self.root_collection is not None:
if self.root_collection.on_effect is not None:self.root_collection.on_effect()
elif isinstance(self.parent, Graphics):self.parent.effect()
def addGroup(self):
group=Graphics(self);self.values.append(group);self.effect();return group
def addBRepBody(self,native):
if self.null_body:return None
copied=copy.deepcopy(native);copied.isTransient=True;copied.parentComponent=None
graphic=Entity(objectType='adsk::fusion::CustomGraphicsBRepBody',parent=self,isVisible=True,
transform=Matrix(),bRepBody=copied)
self.values.append(graphic);self.effect();return graphic
def deleteMe(self):
if self.delete_ack and not self.ignore_delete:
self.root_collection.values.remove(self);self.isValid=False;self.effect()
return self.delete_ack
class Roots(Collection):
def __init__(self,component):super().__init__();self.component=component;self.fail=False;self.on_effect=None
def add(self):
if self.fail:return None
value=Graphics(self.component,self);self.values.append(value);value.effect();return value
class PreviewTests(unittest.TestCase):
def setUp(self):
c._documents.clear();c._entities.clear();self.f=assembly_fixture()
for comp in (self.f.root,self.f.shared,self.f.pin):
comp.meshBodies=Collection();comp.bRepBodies=Collection([body(comp)])
for occ in self.f.root.allOccurrences.values:
occ.isVisible=True;occ.timelineObject=NS(isSuppressed=False)
self.f.design.parentDocument=self.f.doc
self.f.design.rev='source-rev'
self.f.doc.doc_id='source';self.f.doc.objectType='adsk::core::Document'
self.f.value={'documentId':'source','revision':'source-rev'}
presentation_doc=Entity(objectType='adsk::core::Document',isSaved=False,isModified=False,doc_id='presentation')
root=Entity(objectType='adsk::fusion::Component',bRepBodies=Collection(),meshBodies=MeshCollection(),
occurrences=Collection(),sketches=Collection(),features=Collection(),revisionId='blank')
root.customGraphicsGroups=Roots(root)
design=PresentationDesign(parentDocument=presentation_doc,rootComponent=root,allComponents=Collection([root]),
allParameters=Collection(),timeline=NS(count=0),revision_override=None)
root.parentDesign=design
enrich_blank(presentation_doc, design, root)
self.doc,self.design,self.root=presentation_doc,design,root
self.app=NS(documents=Collection([self.f.doc,self.doc]),activeDocument=self.f.doc)
def activate(doc):self.app.activeDocument=doc;return True
self.f.doc.activate=lambda:activate(self.f.doc);self.doc.activate=lambda:activate(self.doc)
patches=[patch.object(p,'document_identity',side_effect=lambda d:d.doc_id),
patch.object(p,'revision',side_effect=lambda d:d.rev),
patch.object(p,'target',return_value=(self.f.doc,self.f.design,self.f.value)),
patch.object(plan,'target',return_value=(self.f.doc,self.f.design,self.f.value)),
patch.object(plan,'configuration_snapshot',return_value={'configured':False,'isConfiguration':False,'configurationRowId':'','table':None,'complete':True}),
patch.object(p.adsk.core,'Matrix3D',NS(create=Matrix),create=True)]
for item in patches:item.start();self.addCleanup(item.stop)
self.args={'op':'exploded_view_plan','sessionId':p.SESSION_ID,'target':self.f.value,'unit':'mm',
'sampleTimes':[.5], 'steps':[{'durationSeconds':1,'moves':[
{'occurrence':c.ref(self.f.a,self.f.value),'direction':[1,0,0],'distance':10,'frame':'local'},
{'occurrence':c.ref(self.f.ap,self.f.value),'direction':[1,0,0],'distance':5,'frame':'parent'}]}]}
self.generated=plan.exploded_view_plan(self.app,self.args)
self.receipt={'success':True,'ownerId':'native-owned-run','sessionId':p.SESSION_ID,'documentId':'presentation'}
def prepared(self):return p.prepare(self.app,self.args,self.generated['planId'])
def lease(self):return p.OwnedPresentationLease(self.app,self.doc,self.design,'native-owned-run',lambda:copy.deepcopy(self.receipt))
def preview(self):return p.Preview(self.prepared(),self.lease())
def enable_revision_effects(self):
effects = []
def changed():
effects.append(1)
self.root.revisionId = 'owned-graphics-' + str(len(effects))
self.root.customGraphicsGroups.on_effect = changed
return effects
def test_owned_effects_advance_only_private_presentation_revision(self):
effects = self.enable_revision_effects()
preview = self.preview()
initial = copy.deepcopy(preview.lease.verified_state)
preview.create()
for sequence, index in enumerate((0, 2, 0), 2):
before = preview.lease.model_revision
preview.sample(index)
self.assertNotEqual(preview.lease.model_revision, before)
self.assertEqual(preview.lease.model_revision, real_revision(self.design))
self.assertEqual(preview.lease.sequence, sequence)
self.assertEqual(preview.lease.verified_state['witnessDigest'], initial['witnessDigest'])
before_show = preview.lease.model_revision
preview.activate()
self.assertEqual(preview.lease.model_revision, before_show)
preview.dispose()
self.assertEqual(preview.lease.sequence, 5)
self.assertEqual(preview.lease.model_revision, real_revision(self.design))
self.assertNotEqual(preview.lease.model_revision, initial['revision'])
self.assertEqual(self.f.design.rev, 'source-rev')
self.assertGreater(len(effects), 5)
def test_a_moved_scope_plan_of_a_big_design_is_refused_before_any_copy(self):
with patch.object(plan, 'MAX_INSTANCES', 0):
generated = plan.exploded_view_plan(self.app, self.args)
self.assertEqual(generated['plan']['instanceScope'], 'moved')
with self.assertRaises(c.MechanicalError) as error:
p.prepare(self.app, self.args, generated['planId'])
self.assertEqual(error.exception.code, 'preview_budget')
self.assertIn('exploded_view_stills', str(error.exception))
self.assertEqual(self.root.customGraphicsGroups.count, 0)
def test_native_top_group_parent_shape_does_not_replace_inventory_proof(self):
preview = self.preview()
preview.create()
preview.root.parent = Graphics() # Measured Fusion top-level getter shape.
preview.sample(2)
exact = preview.root
foreign = Graphics(self.root, self.root.customGraphicsGroups)
self.root.customGraphicsGroups.values[0] = foreign
with self.assertRaises(c.MechanicalError):
preview.dispose()
self.assertTrue(exact.isValid)
self.assertTrue(foreign.isValid)
def test_unbracketed_opaque_revision_change_is_never_adopted(self):
preview = self.preview(); preview.create()
verified = copy.deepcopy(preview.lease.verified_state)
self.root.revisionId = 'unrelated-edit'
self.assertEqual(p.presentation_observation(self.doc, self.design)['witnessDigest'], verified['witnessDigest'])
for operation in (lambda: preview.sample(0), preview.activate, preview.dispose):
with self.assertRaises(c.MechanicalError):operation()
self.assertEqual(preview.lease.verified_state, verified)
self.assertFalse(preview._delete_issued)
def test_same_count_external_attributes_and_origin_changes_refuse(self):
preview = self.preview(); preview.create()
for owner in (self.doc, self.design, self.root, self.root.originConstructionPoint):
with self.subTest(owner=type(owner).__name__):
before = real_revision(self.design)
owner.attributes.values.append(Entity(objectType='adsk::core::Attribute', groupName='AnotherOwner', name='note', value='changed'))
if owner is not self.root:self.assertEqual(real_revision(self.design), before)
with self.assertRaises(c.MechanicalError):preview.sample(0)
owner.attributes.values.clear()
self.root.xConstructionAxis.geometry.direction.x = .5
with self.assertRaises(c.MechanicalError):preview.dispose()
self.assertFalse(preview._delete_issued)
def test_mid_effect_metadata_change_cannot_be_rebased_or_cleaned_up(self):
self.enable_revision_effects()
preview = self.preview(); preview.create()
previous = copy.deepcopy(preview.lease.verified_state)
original = Graphics.transform.fset
def corrupt(group, matrix):
original(group, matrix)
self.doc.attributes.values[:] = [Entity(objectType='adsk::core::Attribute', groupName='user', name='note', value='unrelated change')]
with patch.object(Graphics, 'transform', property(Graphics.transform.fget, corrupt)), self.assertRaises(c.MechanicalError):
preview.sample(2)
self.assertEqual(preview.lease.verified_state, previous)
self.assertIsNone(preview.lease.cleanup_state)
self.assertFalse(preview.lease.failure_observation['cleanupOnly'])
with self.assertRaises(c.MechanicalError):preview.dispose()
self.assertFalse(preview._delete_issued)
def test_partial_effect_revision_is_cleanup_only_and_never_ready(self):
self.enable_revision_effects()
preview = self.preview(); preview.create(); preview.sample(1)
last_good = copy.deepcopy(preview.lease.verified_state)
preview.nodes[2]['group'].fail_transform = True
with self.assertRaises(RuntimeError):preview.sample(2)
self.assertEqual(preview.lease.verified_state, last_good)
self.assertNotEqual(preview.lease.cleanup_state['revision'], last_good['revision'])
self.assertTrue(preview.lease.failure_observation['cleanupOnly'])
with self.assertRaises(c.MechanicalError):preview.lease.check()
self.assertEqual(preview.lease.check(cleanup=True), preview.lease.cleanup_state)
self.f.doc.isValid = False
self.app.documents.values.remove(self.f.doc)
self.assertEqual(preview.dispose()['state'], 'disposed')
self.assertEqual(preview.lease.model_revision, real_revision(self.design))
def test_changed_flags_during_owned_effect_are_observed_not_reset(self):
preview = self.preview(); preview.create()
before = preview.lease.model_revision
self.root.customGraphicsGroups.on_effect = lambda: setattr(self.doc, 'isModified', True)
with self.assertRaises(c.MechanicalError) as caught:preview.sample(2)
self.assertEqual(caught.exception.details['presentationAfter']['flags'], [False, True])
self.assertEqual(caught.exception.details['presentationBefore']['flags'], [False, False])
self.assertFalse(caught.exception.details['rollbackClaimed'])
self.assertTrue(self.doc.isModified)
self.assertEqual(preview.lease.model_revision, before)
with self.assertRaises(c.MechanicalError):preview.dispose()
def test_throwing_witness_getter_retains_primary_error_and_flag_change(self):
class ThrowingAttributes:
@property
def count(inner):
self.doc.isModified = True
raise RuntimeError('attribute inventory failed after dirtying')
self.doc.attributes = ThrowingAttributes()
with self.assertRaises(c.MechanicalError) as caught:p.presentation_observation(self.doc, self.design)
self.assertIn('attribute inventory failed', str(caught.exception))
self.assertEqual(caught.exception.details['presentationObservation']['flagEffect'], 'changed')
self.assertFalse(caught.exception.details['rollbackClaimed'])
def test_bounded_full_aggregate_and_strict_blank_witness(self):
original = p.presentation_observation(self.doc, self.design)
self.design.rev = 'different-aggregate'
with self.assertRaises(c.MechanicalError) as caught:p.presentation_observation(self.doc, self.design)
self.assertEqual(caught.exception.code, 'preview_revision_unavailable')
self.design.rev = 'blank'
for owner, name, value in ((self.doc, 'isModified', 0), (self.root, 'constructionPoints', Collection([object()])),
(self.design, 'isConfiguredDesign', True), (self.doc, 'attributes', NS(count=257)),
(self.root, 'isOriginFolderLightBulbOn', 1)):
previous = getattr(owner, name)
setattr(owner, name, value)
with self.subTest(name=name), self.assertRaises(c.MechanicalError):p.presentation_observation(self.doc, self.design)
setattr(owner, name, previous)
self.assertEqual(p.presentation_observation(self.doc, self.design), original)
def test_flat_definition_copies_and_world_translation_applied_once(self):
preview=self.preview();self.app.activeDocument=self.doc
receipt=preview.create()
self.assertIs(self.app.activeDocument,self.f.doc)
self.assertEqual(receipt['bodyCopyCount'],5)
self.assertFalse(preview.root.isSelectable);self.assertFalse(preview.root.isChildrenSelectable)
self.assertEqual(len(preview.root.values),5)
by_key={r['instanceId']:r for r in self.generated['plan']['instances']}
for node in preview.nodes:
self.assertIs(node['group'].parent,preview.root)
self.assertEqual(p.matrix_values(node['graphics'][0]),p.IDENTITY)
self.assertTrue(node['graphics'][0].bRepBody.isTransient)
if node['instanceId'] is not None:
expected=list(by_key[node['instanceId']]['homeMatrix'])
for i in (3,7,11):expected[i]/=10
self.assertEqual(p.matrix_values(node['group']),expected)
self.assertEqual(self.f.doc.isModified,True);self.assertEqual(self.f.design.rev,'source-rev')
def test_sample_uses_server_owned_matrices_with_preview_active(self):
preview=self.preview();preview.create();preview.activate()
self.args['steps'][0]['moves'][0]['distance']=99999
self.generated['samples'][-1]['poses'][0]['matrix'][3]=999999
receipt=preview.sample(2)
self.assertIs(self.app.activeDocument,self.doc);self.assertEqual(receipt['sampleIndex'],2)
pin=next(r for r in self.generated['plan']['instances'] if r['path']=='Assembly:1+Pin:1')
group=next(n['group'] for n in preview.nodes if n['instanceId']==pin['instanceId'])
self.assertEqual([p.matrix_values(group)[i] for i in (3,7,11)],[10.,2.5,0.])
preview.sample(0)
self.assertEqual([p.matrix_values(group)[i] for i in (3,7,11)],[10.,1.,0.])
for index in (True,-1,3,1.,{'matrix':p.IDENTITY}):
with self.assertRaises(c.MechanicalError):preview.sample(index)
def test_copy_is_independent_and_no_transient_visibility_read(self):
preview=self.preview();preview.create()
copied=preview.nodes[0]['graphics'][0].bRepBody
del copied.isVisible
preview.sample(1) # Effective visibility is checked only on source/native proxies.
self.f.root.bRepBodies.item(0).volume=2
self.assertEqual(copied.volume,1/6)
with self.assertRaises(c.MechanicalError):preview.sample(2)
self.assertEqual(preview.state,'ready')
self.assertEqual(preview.dispose()['state'],'disposed') # Stale source must not trap owned cleanup.
self.assertTrue(self.doc.isValid) # No document close.
def test_mesh_hidden_suppressed_surface_and_context_mismatch_refuse_before_graphics(self):
cases=[(self.f.pin,'meshBodies',Collection([object()])),(self.f.ap,'isVisible',False),
(self.f.ap,'bodyVisible',False),(self.f.ap,'timelineObject',NS(isSuppressed=True)),
(self.f.pin.bRepBodies.item(0),'isSolid',False),
(self.f.pin.bRepBodies.item(0),'nativeObject',object())]
for obj,key,value in cases:
with self.subTest(key=key):
had=hasattr(obj,key);old=getattr(obj,key,None);setattr(obj,key,value)
with self.assertRaises(c.MechanicalError):self.prepared()
setattr(obj,key,old) if had else delattr(obj,key)
self.assertEqual(self.root.customGraphicsGroups.count,0)
native=self.f.pin.bRepBodies.item(0);native.createForAssemblyContext=lambda o:Entity(objectType='adsk::fusion::BRepBody',isVisible=True,nativeObject=native,assemblyContext=self.f.b)
with self.assertRaises(c.MechanicalError):self.prepared()
def test_ownership_semantic_error_foreign_id_saved_or_nonblank_refuses(self):
for change in ({'success':False},{'documentId':'other'},{'sessionId':'old'}, {'data':{'success':False}}):
old=copy.deepcopy(self.receipt);self.receipt.update(change)
with self.assertRaises(c.MechanicalError):self.lease()
self.receipt=old
self.doc.isSaved=True
with self.assertRaises(c.MechanicalError):self.lease()
self.doc.isSaved=False;self.root.bRepBodies.values.append(body())
with self.assertRaises(c.MechanicalError):self.lease()
def test_worker_guard_precedes_native_getters_at_all_entries(self):
class Trap:
def __getattribute__(self,key):raise AssertionError('native getter '+key)
errors=[]
def run():
calls=[lambda:p.prepare(Trap(),{},'x'),lambda:p.OwnedPresentationLease(Trap(),Trap(),Trap(),'x',lambda:{}),
lambda:p.Preview(Trap(),Trap()),lambda:p.Preview.create(Trap()),lambda:p.Preview.sample(Trap(),0),
lambda:p.Preview.activate(Trap()),lambda:p.Preview.dispose(Trap())]
for call in calls:
try:call()
except Exception as exc:errors.append(exc)
thread=threading.Thread(target=run);thread.start();thread.join()
self.assertEqual(len(errors),7)
self.assertTrue(all(isinstance(e,c.MechanicalError) and e.code=='wrong_thread' for e in errors))
def test_wrong_plan_stale_revision_or_changed_flags_refuses_before_copy(self):
with self.assertRaises(c.MechanicalError):p.prepare(self.app,self.args,'foreign-plan')
prepared=self.prepared();self.f.design.rev='changed'
with self.assertRaises(c.MechanicalError):p.Preview(prepared,self.lease()).create()
self.f.design.rev='source-rev';self.f.doc.isModified=False
with self.assertRaises(c.MechanicalError):p.Preview(prepared,self.lease()).create()
self.assertEqual(self.root.customGraphicsGroups.count,0)
def test_partial_sample_retained_then_exact_dispose_without_source(self):
preview=self.preview();preview.create();preview.nodes[2]['group'].fail_transform=True
with self.assertRaises(RuntimeError):preview.sample(2)
self.assertEqual(preview.state,'retained_failure')
self.assertNotEqual(p.matrix_values(preview.nodes[1]['group']),p.IDENTITY)
with self.assertRaises(c.MechanicalError):preview.sample(2)
self.f.doc.isValid=False;self.app.documents.values.remove(self.f.doc)
self.assertEqual(preview.dispose()['state'],'disposed')
self.assertEqual(self.root.customGraphicsGroups.count,0)
def test_ignored_setter_and_corrupt_copy_fail_readback(self):
preview=self.preview();preview.create();preview.nodes[1]['group'].ignore_transform=True
with self.assertRaises(c.MechanicalError):preview.sample(2)
self.assertEqual(preview.state,'retained_failure');preview.dispose()
preview=self.preview();preview.create();preview.nodes[1]['graphics'][0].bRepBody.volume=99
with self.assertRaises(c.MechanicalError):preview.sample(1)
preview.dispose()
def test_partial_creation_keeps_exact_root_for_disposal(self):
preview=self.preview()
original=Graphics.addBRepBody
counter=[]
def throwing(group,native):
counter.append(1)
if len(counter)==2:raise RuntimeError('copy refused')
return original(group,native)
with patch.object(Graphics,'addBRepBody',throwing),self.assertRaises(RuntimeError):preview.create()
self.assertIsNotNone(preview.root);self.assertEqual(preview.state,'retained_failure')
self.assertEqual(preview.dispose()['state'],'disposed')
def test_unacknowledged_added_child_prevents_blind_cleanup(self):
preview=self.preview()
def null_after_add(group):
group.values.append(Graphics(group));return None
with patch.object(Graphics,'addGroup',null_after_add),self.assertRaises(c.MechanicalError):preview.create()
with self.assertRaises(c.MechanicalError):preview.dispose()
self.assertEqual(self.root.customGraphicsGroups.count,1)
def test_foreign_graphics_or_lease_prevents_deletion(self):
preview=self.preview();preview.create();preview.root.values.append(Graphics(preview.root))
with self.assertRaises(c.MechanicalError):preview.dispose()
preview.root.values.pop();self.receipt['success']=False
with self.assertRaises(c.MechanicalError):preview.dispose()
self.assertTrue(preview.root.isValid)
def test_false_delete_or_missing_absence_proof_retains_exact_refs(self):
for ignored in (False,True):
preview=self.preview();preview.create()
preview.root.delete_ack=ignored;preview.root.ignore_delete=ignored
with self.assertRaises(c.MechanicalError):preview.dispose()
self.assertEqual(preview.state,'retained_failure');self.assertTrue(preview.root.isValid)
preview.root.delete_ack=True;preview.root.ignore_delete=False
with self.assertRaises(c.MechanicalError):preview.dispose()
self.assertTrue(preview.root.isValid)
self.root.customGraphicsGroups.values.clear() # Isolated fake reset; no second native deletion.
def test_foreign_active_document_is_never_activated_away(self):
preview=self.preview();preview.create();foreign=Entity();self.app.activeDocument=foreign
with self.assertRaises(c.MechanicalError):preview.activate()
self.assertIs(self.app.activeDocument,foreign)
preview.dispose()
preview=self.preview()
with self.assertRaises(c.MechanicalError):preview.create()
self.assertIs(self.app.activeDocument,foreign)
preview.dispose()
def test_bounds_and_nonfinite_source_refuse_without_native_graphics(self):
self.f.pin.bRepBodies.item(0).boundingBox.maxPoint.x=float('nan')
with self.assertRaises(c.MechanicalError):self.prepared()
self.f.pin.bRepBodies.item(0).boundingBox.maxPoint.x=1.
with patch.object(p,'MAX_BODIES',2),self.assertRaises(c.MechanicalError):self.prepared()
with patch.object(p,'MAX_VERTICES',3),self.assertRaises(c.MechanicalError):self.prepared()
self.assertEqual(self.root.customGraphicsGroups.count,0)
def test_acknowledged_but_corrupt_native_matrix_factory_cannot_become_expectation(self):
preview=self.preview();preview.create()
def corrupt(matrix, values):
matrix.values=list(values)
for i in (3,7,11):matrix.values[i]=0.
# Also corrupt caller input: expectations must not alias this list.
for i in (3,7,11):values[i]=0.
return True
with patch.object(Matrix,'setWithArray',corrupt),self.assertRaises(c.MechanicalError) as error:
preview.sample(2)
self.assertEqual(error.exception.code,'preview_transform_refused')
self.assertIsNone(preview.sample_index);self.assertEqual(preview.state,'retained_failure')
preview.dispose()
preview=self.preview()
with patch.object(Matrix,'setWithArray',corrupt),self.assertRaises(c.MechanicalError):preview.create()
self.assertEqual(preview.state,'retained_failure');preview.dispose()
def test_nonnull_child_and_body_handles_retained_before_throwing_validity_getter(self):
class Once:
def __init__(self, wrapped):object.__setattr__(self,'wrapped',wrapped);object.__setattr__(self,'failed',False)
def __getattr__(self,key):
if key=='isValid' and not self.failed:
object.__setattr__(self,'failed',True);raise RuntimeError('validity getter failed once')
return getattr(self.wrapped,key)
def __setattr__(self,key,value):setattr(self.wrapped,key,value)
for method in ('addGroup','addBRepBody'):
with self.subTest(method=method):
original=getattr(Graphics,method)
def wrapped(group,*args):
value=Once(original(group,*args));group.values[-1]=value;return value
preview=self.preview()
with patch.object(Graphics,method,wrapped),self.assertRaises(RuntimeError):preview.create()
self.assertEqual(preview.state,'retained_failure')
self.assertEqual(len(preview.nodes),1)
if method=='addBRepBody':self.assertEqual(len(preview.nodes[0]['graphics']),1)
self.assertEqual(preview.dispose()['state'],'disposed')
self.assertEqual(self.root.customGraphicsGroups.count,0)
def test_not_registered_as_public_or_durable_operation(self):
from test_mechanical import m
self.assertTrue(p.PROTOTYPE_ONLY)
self.assertFalse(any(getattr(handler, '__module__', None) == p.__name__ for handler in m.OPERATIONS.values()))
self.assertFalse(hasattr(p,'READS'));self.assertFalse(hasattr(p,'WRITES'))
if __name__=='__main__':unittest.main()