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.
← Commit history
Add post-command document save and scratch cleanup hints without automatic mutations
4 files changed
+117−1
addin/AdomBridge/commands/__init__.py+5−1@@ -154,4 +154,8 @@ def dispatch_command(app: adsk.core.Application, command: str, args: dict) -> di "error": f"Unknown add-in command: {command}", "_hint": f"Valid add-in commands: {', '.join(sorted(COMMAND_HANDLERS.keys()))}.", }- return handler(app, args)+ result = handler(app, args)+ if command != "activity_banner":+ from .document_hygiene import attach_document_hygiene+ result = attach_document_hygiene(app, result)+ return result
addin/AdomBridge/commands/document_hygiene.pyadded+60@@ -0,0 +1,60 @@+"""Read-only save reminders. Called on Fusion's main thread after command completion.++Exports are checkpoints, not proof that Fusion cleared its modified/recovery state.+This module never saves, closes, infers ownership from names, or deletes recovery data.+"""+++def _property(obj, name):+ try:+ return getattr(obj, name)+ except Exception:+ return None+++def inspect_documents(app):+ documents = []+ try:+ for i in range(app.documents.count):+ doc = app.documents.item(i)+ saved = _property(doc, 'isSaved')+ modified = _property(doc, 'isModified')+ documents.append({+ 'name': _property(doc, 'name'),+ 'isSaved': saved, 'isModified': modified,+ 'needsSave': modified is True or (saved is False and modified is not False),+ 'saveStateKnown': saved is not None and modified is not None,+ })+ except Exception as exc:+ return {'inspectionComplete': False, 'documents': documents, 'error': str(exc)}+ pending = [d for d in documents if d['needsSave']]+ return {'inspectionComplete': True, 'openDocumentCount': len(documents),+ 'needsSaveCount': len(pending), 'documents': documents,+ 'recoveryHealth': 'notInspected', 'ownership': 'notInferred'}+++def attach_document_hygiene(app, result):+ if not isinstance(result, dict):+ return result+ state = inspect_documents(app)+ result['documentHygiene'] = state+ count = state.get('needsSaveCount', 0)+ if count or state.get('openDocumentCount', 0) > 3:+ names = [d['name'] for d in state['documents'] if d['needsSave']]+ hint = (+ 'Document checkpoint: %s open, %s need saving%s. '+ 'Save edited work at milestones and before ending the task. A source export '+ 'or Document.CopyToDesktop is a separate checkpoint and may leave Fusion '+ 'isSaved=false/isModified=true; verify the artifact and re-read save state. '+ 'Close only this task\'s completed scratch documents by exact identity with '+ 'fusion_close_document after verifying a current native checkpoint; never '+ 'bulk-discard other work. If Fusion reports recovery-file failure, checkpoint '+ 'immediately, then save or close that document; do not just dismiss recovery. '+ 'Use a task subfolder when saving to cloud.'+ ) % (state.get('openDocumentCount', '?'), count,+ (' (' + ', '.join(str(n) for n in names[:6]) + (', ...' if len(names)>6 else '') + ')') if names else '')+ result['_hintDocumentHygiene'] = hint+ result['_hint'] = (result.get('_hint', '') + ' ' + hint).strip()+ elif not state['inspectionComplete']:+ result['_hintDocumentHygiene'] = 'Save-state inspection incomplete; inspect documents before cleanup.'+ return result
skills/fusion-no-modal-dialogs/SKILL.md+24@@ -178,3 +178,27 @@ is what reached Fusion and what appeared on screen, not what the docstring asser 4. Is there a dismissal sweep doing load-bearing work? If yes, that is the bug. 5. Does any guard swallow its own failure? Make it shout. 6. Test by recording the commands that actually reach Fusion.++## Save checkpoints and close completed scratch documents++After edits, inspect `documentHygiene` in bridge responses. `isSaved` and `isModified`+are separate: a previously saved document can have new edits; a pristine blank Untitled+is not a dirty board. Unknown properties are not proof of a clean state.++At each completed routing/DRC milestone and before ending a task:++1. Save the working document, or export a fresh native checkpoint to a durable task folder.+2. Verify the file exists, has content, and can be reopened; log its path and hash with the+ exact document identity. Exporting/`Document.CopyToDesktop` may leave the live tab+ `isSaved=false, isModified=true`; do not report that as a Fusion save.+3. Close completed documents opened by this task, one exact identity at a time, after+ checkpoint verification. Do not infer ownership from a name prefix, close all tabs,+ delete recovery data, or discard edits in a pre-existing user document.+4. Re-enumerate documents. Leave only ongoing work and pre-existing user documents;+ Fusion may retain one pristine Untitled tab.++If Fusion reports that recovery-file creation failed, checkpoint immediately and then+save or close the affected task document. The warning alone does not establish why+recovery failed; inspect disk/path/permissions if it recurs in a clean session. Never+silence the warning by disabling recovery. The advisory response does not inspect+recovery health and does not automatically save or close documents.
tests/test_document_hygiene.pyadded+28@@ -0,0 +1,28 @@+import importlib.util+from pathlib import Path+from types import SimpleNamespace as NS+import unittest+spec=importlib.util.spec_from_file_location('hygiene',Path(__file__).parents[1]/'addin/AdomBridge/commands/document_hygiene.py')+m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)+def app(*docs): return NS(documents=NS(count=len(docs),item=lambda i:docs[i]))+def doc(saved,modified,name='board'): return NS(name=name,isSaved=saved,isModified=modified)+class Hygiene(unittest.TestCase):+ def test_export_does_not_clear_dirty_state(self):+ r=m.attach_document_hygiene(app(doc(False,True)),{'success':True,'_hint':'Existing hint'})+ self.assertEqual(r['documentHygiene']['needsSaveCount'],1)+ self.assertTrue(r['_hint'].startswith('Existing hint'))+ self.assertIn('separate checkpoint',r['_hint'])+ def test_previously_saved_but_modified(self):+ self.assertEqual(m.inspect_documents(app(doc(True,True)))['needsSaveCount'],1)+ def test_blank_untouched_is_not_dirty(self):+ self.assertEqual(m.inspect_documents(app(doc(False,False,'Untitled')))['needsSaveCount'],0)+ def test_unknown_is_not_reported_clean(self):+ r=m.inspect_documents(app(NS(name='unsupported')))+ self.assertFalse(r['documents'][0]['saveStateKnown'])+ def test_errors_and_original_hint_preserved(self):+ r=m.attach_document_hygiene(app(doc(False,True)),{'success':False,'error':'actual failure','_hint':'original'})+ self.assertFalse(r['success']);self.assertEqual(r['error'],'actual failure')+ def test_enumeration_failure_does_not_replace_result(self):+ r=m.attach_document_hygiene(NS(),{'success':True})+ self.assertTrue(r['success']);self.assertFalse(r['documentHygiene']['inspectionComplete'])+if __name__=='__main__':unittest.main()