├── .gitignore ├── Fusion 360 Total Export.manifest ├── README.md └── Fusion 360 Total Export.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | -------------------------------------------------------------------------------- /Fusion 360 Total Export.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "autodeskProduct": "Fusion360", 3 | "type": "script", 4 | "author": "Justin Nesselrotte", 5 | "description": { 6 | "": "A convenient way to export all of your designs and projects in the event you suddenly find yourself in need of something like that." 7 | }, 8 | "supportedOS": "windows|mac", 9 | "editEnabled": true 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > **NOTE** 2 | > 3 | > This is only a fork to add DXF export of sketches. The [original version by @jnesselr](https://github.com/Jnesselr/fusion-360-total-exporter) is probably going to swoosh past this fork within days if not even hours, so star and spread that one and also all credit, kudos and love to @jnesselr! 4 | 5 | # Fusion 360 Total Exporter 6 | Need to export all of your fusion data in a bunch of formats? We've got you covered. This will export your designs across all hubs, projects, files, and components. It exports them in STL, STEP, and IGES format. It also exports the sketches as DXF. It doesn't ask what you want written out, it just writes out the latest version of everything. 7 | 8 | ## How do I use this? 9 | 1. Clone this repo somewhere on your computer 10 | 2. Open Fusion 360 11 | 3. Click on "Tools" then "Scripts/Add-ins" 12 | 4. Click the + button and select the cloned directory 13 | 5. Double click on the "Fusion 360 Total Export" script 14 | 6. Acknowledge that this might take a while (There's a menu, but you should probably internalize that) 15 | 7. Select where you want the output to go 16 | 8. Go do something else for a while or enjoy a walk down memory lane as every single design you have is opened, exported, then closed again. 17 | 18 | ## Why did you make this? 19 | Autodesk just announced that they were limiting features in their free tier to a level that made people a wee bit upset. I pay for Fusion 360, but I get that it's too much of an expense for some people. I had experience with exporting STL files for BotQueue (shhh spoilers) and figured that if I wrote a plugin, no one would have to do manual exports. Yay! Automation reigns supreme! 20 | 21 | ## What happens if I don't like what this plugin does? 22 | No warrenty is implied, etc. etc. Go blame Autodesk for changing the free tier. If you want to blame me for anything, blame me and my sense of ethics for feeling like I need to write this program in the first place. 23 | 24 | ## What if I find a bug? 25 | If an exception occurs, it should tell you what file it happened on in a message box and THEN another message box will pop up with the exception itself. Take screenshots of both. Submit an issue. Please and thank you! 26 | 27 | Also, if you can share the file that it failed on, that may help me, but it depends on what the exception actually shows. 28 | 29 | ## I don't like your style of writing 30 | That wasn't a question. But yeah... me too some days. 31 | -------------------------------------------------------------------------------- /Fusion 360 Total Export.py: -------------------------------------------------------------------------------- 1 | #Author-Justin Nesselrotte 2 | #Description-A convenient way to export all of your designs and projects in the event you suddenly find yourself in need of something like that. 3 | from __future__ import with_statement 4 | 5 | import adsk.core, adsk.fusion, adsk.cam, traceback 6 | from threading import Thread 7 | import time 8 | import os 9 | 10 | 11 | 12 | class TotalExport(object): 13 | def __init__(self, app): 14 | self.app = app 15 | self.ui = self.app.userInterface 16 | self.data = self.app.data 17 | self.documents = self.app.documents 18 | 19 | def __enter__(self): 20 | return self 21 | 22 | def __exit__(self, exc_type, exc_val, exc_tb): 23 | pass 24 | 25 | def run(self, context): 26 | self.ui.messageBox( 27 | "Searching for and exporting files will take a while, depending on how many files you have.\n\n" \ 28 | "You won't be able to do anything else. It has to do everything in the main thread and open and close every file.\n\n" \ 29 | "Take an early lunch." 30 | ) 31 | 32 | folder_dialog = self.ui.createFolderDialog() 33 | folder_dialog.title = "Where should we store this export?" 34 | dialog_result = folder_dialog.showDialog() 35 | if dialog_result != adsk.core.DialogResults.DialogOK: 36 | return 37 | output_path = folder_dialog.folder 38 | 39 | progress_dialog = self.ui.createProgressDialog() 40 | progress_dialog.show("Writing data!", "Writing %v of %m", 0, 1, 1) 41 | 42 | all_hubs = self.data.dataHubs 43 | for hub_index in range(all_hubs.count): 44 | hub = all_hubs.item(hub_index) 45 | 46 | all_projects = hub.dataProjects 47 | for project_index in range(all_projects.count): 48 | files = [] 49 | project = all_projects.item(project_index) 50 | folder = project.rootFolder 51 | 52 | files.extend(self._get_files_for(folder)) 53 | 54 | progress_dialog.message = "Hub: {} of {}\nProject: {} of {}\nWriting design %v of %m".format( 55 | hub_index + 1, 56 | all_hubs.count, 57 | project_index + 1, 58 | all_projects.count 59 | ) 60 | progress_dialog.maximumValue = len(files) 61 | progress_dialog.reset() 62 | 63 | for file_index in range(len(files)): 64 | if progress_dialog.wasCancelled: 65 | return 66 | file = files[file_index] 67 | progress_dialog.progressValue = file_index + 1 68 | self._write_data_file(output_path, file) 69 | 70 | def _get_files_for(self, folder): 71 | files = [] 72 | for file in folder.dataFiles: 73 | files.append(file) 74 | 75 | for sub_folder in folder.dataFolders: 76 | files.extend(self._get_files_for(sub_folder)) 77 | 78 | return files 79 | 80 | def _write_data_file(self, root_folder, file: adsk.core.DataFile): 81 | if file.fileExtension != "f3d" and file.fileExtension != "f3z": 82 | return 83 | 84 | try: 85 | document = self.documents.open(file) 86 | except BaseException as ex: 87 | self.ui.messageBox("Opening {} failed!".format(file.name)) 88 | return 89 | 90 | if document is None: 91 | return 92 | 93 | try: 94 | file_folder = file.parentFolder 95 | file_folder_path = self._name(file_folder.name) 96 | while file_folder.parentFolder is not None: 97 | file_folder = file_folder.parentFolder 98 | file_folder_path = os.path.join(self._name(file_folder.name), file_folder_path) 99 | 100 | parent_project = file_folder.parentProject 101 | parent_hub = parent_project.parentHub 102 | 103 | file_folder_path = self._take( 104 | root_folder, 105 | "Hub {}".format(self._name(parent_hub.name)), 106 | "Project {}".format(self._name(parent_project.name)), 107 | file_folder_path, 108 | self._name(file.name) + "." + file.fileExtension 109 | ) 110 | 111 | if not os.path.exists(file_folder_path): 112 | self.ui.messageBox("Couldn't make root folder {}".format(file_folder_path)) 113 | return 114 | 115 | fusion_document: adsk.fusion.FusionDocument = adsk.fusion.FusionDocument.cast(document) 116 | design: adsk.fusion.Design = fusion_document.design 117 | export_manager: adsk.fusion.ExportManager = design.exportManager 118 | 119 | file_export_path = os.path.join(file_folder_path, self._name(file.name)) 120 | # Write f3d/f3z file 121 | options = export_manager.createFusionArchiveExportOptions(file_export_path) 122 | export_manager.execute(options) 123 | 124 | self._write_component(file_folder_path, design.rootComponent) 125 | except BaseException: 126 | self.ui.messageBox("Failed while working on {}".format(file_folder_path)) 127 | raise 128 | finally: 129 | document.close(False) 130 | 131 | def _write_component(self, component_base_path, component: adsk.fusion.Component): 132 | design = component.parentDesign 133 | 134 | output_path = os.path.join(component_base_path, self._name(component.name)) 135 | 136 | self._write_step(output_path, component) 137 | self._write_stl(output_path, component) 138 | self._write_iges(output_path, component) 139 | 140 | sketches = component.sketches 141 | for sketch_index in range(sketches.count): 142 | sketch = sketches.item(sketch_index) 143 | self._write_dxf(os.path.join(output_path, sketch.name) + '.dxf', sketch) 144 | 145 | occurrences = component.occurrences 146 | for occurrence_index in range(occurrences.count): 147 | occurrence = occurrences.item(occurrence_index) 148 | sub_component = occurrence.component 149 | sub_path = self._take(component_base_path, self._name(component.name)) 150 | self._write_component(sub_path, sub_component) 151 | 152 | def _write_step(self, output_path, component: adsk.fusion.Component): 153 | export_manager = component.parentDesign.exportManager 154 | 155 | options = export_manager.createSTEPExportOptions(output_path, component) 156 | export_manager.execute(options) 157 | 158 | def _write_stl(self, output_path, component: adsk.fusion.Component): 159 | export_manager = component.parentDesign.exportManager 160 | 161 | try: 162 | options = export_manager.createSTLExportOptions(component, output_path) 163 | export_manager.execute(options) 164 | except BaseException: 165 | # Probably an empty model, ignore it 166 | pass 167 | 168 | bRepBodies = component.bRepBodies 169 | meshBodies = component.meshBodies 170 | 171 | if (bRepBodies.count + meshBodies.count) > 0: 172 | self._take(output_path) 173 | for index in range(bRepBodies.count): 174 | body = bRepBodies.item(index) 175 | self._write_stl_body(os.path.join(output_path, body.name), body) 176 | 177 | for index in range(meshBodies.count): 178 | body = meshBodies.item(index) 179 | self._write_stl_body(os.path.join(output_path, body.name), body) 180 | 181 | def _write_stl_body(self, output_path, body): 182 | export_manager = body.parentComponent.parentDesign.exportManager 183 | 184 | try: 185 | options = export_manager.createSTLExportOptions(body, output_path) 186 | export_manager.execute(options) 187 | except BaseException: 188 | # Probably an empty model, ignore it 189 | pass 190 | 191 | def _write_iges(self, output_path, component: adsk.fusion.Component): 192 | export_manager = component.parentDesign.exportManager 193 | 194 | options = export_manager.createIGESExportOptions(output_path, component) 195 | export_manager.execute(options) 196 | 197 | def _write_dxf(self, output_path, sketch: adsk.fusion.Sketch): 198 | sketch.saveAsDXF(output_path) 199 | 200 | def _take(self, *path): 201 | out_path = os.path.join(*path) 202 | os.makedirs(out_path, exist_ok=True) 203 | return out_path 204 | 205 | def _name(self, name): 206 | return name.replace('/', ' ').replace('"', ' ').replace('%', ' ') 207 | 208 | 209 | 210 | def run(context): 211 | ui = None 212 | try: 213 | app = adsk.core.Application.get() 214 | 215 | with TotalExport(app) as total_export: 216 | total_export.run(context) 217 | 218 | except: 219 | ui = app.userInterface 220 | ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) 221 | --------------------------------------------------------------------------------