[Ri...] Posted October 29 Share Posted October 29 There are times, where I have to convert a lot of STLs to STEP via the AutoSurfacing tool. It is tedious, and repetitive, so I created an app to do it for me. At the moment, it will just prompt you to enter the directory where the STL files are located. I'm working on a better solution. # -*- coding: utf-8 -*- import gom, os, glob # --- CONFIG --- RECURSIVE = False OUT_SUBFOLDER = "STEP" NUM_QUADS = 100_000 def _find_stls(folder: str, recursive: bool) -> list[str]: pattern = os.path.join(folder, "**", "*.stl") if recursive else os.path.join(folder, "*.stl") return sorted(glob.glob(pattern, recursive=recursive)) def _safe_delete_part_by_name(name: str): """Delete an existing Part by name, if present; ignore if absent.""" try: part_obj = gom.app.project.parts[name] except Exception: return try: gom.script.cad.delete_element( elements=[part_obj], with_measuring_principle=True ) except Exception: # If your build needs a different command, record one delete and swap it in here. pass def main(): # --- inline embedded dialog (1-line input) --- try: RES = gom.script.sys.execute_user_defined_dialog(dialog={ "content": [[{ "columns": 1, "name": "src_folder", "password": False, "read_only": False, "rows": 1, "tooltip": {"id": "", "text": "", "translatable": True}, "type": "input::string", "value": "" }]], "control": {"id": "OkCancel"}, "embedding": "always_toplevel", "position": "", "size": {"height": 124, "width": 420}, "sizemode": "automatic", "style": "", "title": {"id": "", "text": "Source folder path", "translatable": True} }) except gom.RequestError: print("Dialog canceled.") return # Safely fetch the folder path raw_path = None try: raw_path = getattr(RES, "src_folder", None) except Exception: pass if raw_path is None: try: raw_path = RES["src_folder"] except Exception: pass if not raw_path: print("No folder provided (empty value or object-name mismatch).") return # Normalize & validate SRC_FOLDER = os.path.normpath(str(raw_path).strip('" ')) if not os.path.isdir(SRC_FOLDER): print(f"Not a valid folder: {SRC_FOLDER!r}") return stls = _find_stls(SRC_FOLDER, RECURSIVE) if not stls: print(f"ℹ️ No STL files found in: {SRC_FOLDER}") return out_folder = os.path.join(SRC_FOLDER, OUT_SUBFOLDER) os.makedirs(out_folder, exist_ok=True) print(f"🗂 Found {len(stls)} STL(s). Exporting STEP to: {out_folder}") prev_part = None for i, file_path in enumerate(stls, start=1): base = os.path.splitext(os.path.basename(file_path))[0] part_name = base # unique part per file step_path = os.path.join(out_folder, f"{base}.step") print(f"\n[{i}/{len(stls)}] Processing: {file_path}") try: # 1) Clear previous Part (do NOT close project or app) if prev_part: _safe_delete_part_by_name(prev_part) prev_part = None # 2) Import STL into clipboard gom.script.sys.import_stl( bgr_coding=True, files=[file_path], geometry_based_refining=False, import_mode='clipboard', length_unit='mm', stl_color_bit_set=False, target_type='mesh' ) # 3) Create new Part for THIS file & move actuals into it gom.script.part.create_new_part(name=part_name) actuals = list(gom.app.project.clipboard.actual_elements) if not actuals: raise RuntimeError("No actual elements found after STL import.") gom.script.part.add_elements_to_part( delete_invisible_elements=True, elements=actuals, import_mode='new_elements', part=gom.app.project.parts[part_name] ) # 4) Build CAD from THIS part's mesh (explicit target) gom.script.sys.switch_to_mesh_editing_workspace() cad_group = gom.script.mesh.automatic_cad_surface_from_mesh( merge=True, mesh=gom.app.project.parts[part_name].actual, # <- unambiguous mesh name='CAD group 1', number_of_quads=NUM_QUADS, preview_type=1 ) # 5) Export ONLY the CAD group we just created gom.script.sys.export_step( elements=[cad_group], file=step_path ) print(f"✅ STEP exported: {step_path}") # 6) Optional: remove that temporary CAD group (keeps project lean) try: gom.script.cad.delete_element( elements=[cad_group], with_measuring_principle=True ) except Exception: pass # remember for next loop's deletion prev_part = part_name except Exception as e: print(f"❌ Error on '{file_path}': {e} (continuing)") # Optional: delete last part too if prev_part: _safe_delete_part_by_name(prev_part) print("\n🎉 All done.") if __name__ == "__main__": main() Auto CAD.addon 1 Link to comment Share on other sites More sharing options...
[Ri...] Posted October 29 Author Share Posted October 29 To follow-up on this. There is File System Browser widget that is part of the documentation, that while I can add it to the script, and "use it". I am unable to actually access any information from it. https://zeissiqs.github.io/zeiss-inspect-addon-api/2025/howtos/python_api_introduction/user_defined_dialogs.html#file-system-browser-widget It even shows an example of print(DIALOG.filesystemWidget.selected) but it doesn't work for me. If someone is able to figure this out for me, I'd greatly appreciate it. Link to comment Share on other sites More sharing options...
[Ma...] Posted October 29 Share Posted October 29 There is no problem with that widget. You can filter filetypes, you can force user with ROOT folder, you can use multiselect. All needed info about files is from ".selected" and it's a list of strings with full path print(DIALOG.filesystem.selected) -> ['C:/Install.log', 'C:/csb.log', 'C:/appverifUI.dll'] Link to comment Share on other sites More sharing options...
[Ri...] Posted October 29 Author Share Posted October 29 Yeah, it doesn't work for me. Can you post a sample snippet with the dialog as well? I'm uncertain why I can't get it work. Link to comment Share on other sites More sharing options...
[Ri...] Posted Wednesday at 09:47 PM Author Share Posted Wednesday at 09:47 PM Clarification - I can get the dialog itself to work, but to be able to pull the value from the file or folder selected does not work for me. Link to comment Share on other sites More sharing options...
[Ma...] Posted Wednesday at 09:51 PM Share Posted Wednesday at 09:51 PM Well I am using different approach for dialogs. I am using file with gdlg extension. test.gdlg: { "content": [ [ { "columns": 1, "name": "filesystem", "root": "", "rows": 1, "show_date": true, "show_size": true, "show_type": true, "tooltip": { "id": "", "text": "", "translatable": true }, "type": "special::filesystem", "use_multiselection": true } ] ], "control": { "id": "OkCancel" }, "embedding": "always_toplevel", "position": "center", "size": { "height": 284, "width": 284 }, "sizemode": "automatic", "style": "Standard", "title": { "id": "", "text": "Test", "translatable": true } } test.py: DIALOG = gom.script.sys.create_user_defined_dialog(file='test.gdlg') gom.script.sys.show_user_defined_dialog(dialog=DIALOG) print(DIALOG.filesystem.selected) This way I can access each widget Link to comment Share on other sites More sharing options...
[Ma...] Posted Wednesday at 09:54 PM Share Posted Wednesday at 09:54 PM But i think you can use your current definition of dialog ( in py file ) but first make a dialog as "gom.script.sys.create_user_defined_dialog" to be able to access values Link to comment Share on other sites More sharing options...
[Ri...] Posted Wednesday at 09:55 PM Author Share Posted Wednesday at 09:55 PM Well that's frustrating. It works perfectly if it lives outside of the Python script. hahaha. Thanks, Martin. Link to comment Share on other sites More sharing options...
[Ri...] Posted Wednesday at 10:14 PM Author Share Posted Wednesday at 10:14 PM # -*- coding: utf-8 -*- import gom, os, glob from typing import List, Optional # --- CONFIG --- RECURSIVE = False OUT_SUBFOLDER = "STEP" NUM_QUADS = 100_000 def _find_stls(folder: str, recursive: bool) -> List[str]: pattern = os.path.join(folder, "**", "*.stl") if recursive else os.path.join(folder, "*.stl") return sorted(glob.glob(pattern, recursive=recursive)) def _safe_delete_part_by_name(name: str) -> None: """Delete an existing Part by name, if present; ignore if absent.""" try: part_obj = gom.app.project.parts[name] except Exception: return try: gom.script.cad.delete_element( elements=[part_obj], with_measuring_principle=True ) except Exception: pass def _pick_src_folder_via_gdlg(file_path='dialog.gdlg'): """ Uses your .gdlg with a File System Browser widget named 'filesystem' and returns a normalized folder path, or None if canceled/invalid. """ DIALOG = gom.script.sys.create_user_defined_dialog(file=file_path) # Show dialog; on Cancel it raises gom.RequestError → return None try: gom.script.sys.show_user_defined_dialog(dialog=DIALOG) except gom.RequestError: return None # Read selection from the widget handle (not from a Result map) try: sel = DIALOG.filesystem.selected except Exception: sel = None if not sel: return None # If multiselect is on, take the first item if isinstance(sel, (list, tuple)): sel = sel[0] path = str(sel) # If a file was selected, use its parent folder folder = os.path.dirname(path) if os.path.isfile(path) else path folder = os.path.normpath(folder) return folder if os.path.isdir(folder) else None def main() -> None: # ---- Get SRC_FOLDER from .gdlg filesystem picker ---- SRC_FOLDER = _pick_src_folder_via_gdlg('dialog.gdlg') if SRC_FOLDER is None: print("No valid folder selected. Cancelled.") return # --------- batch process ---------- stls = _find_stls(SRC_FOLDER, RECURSIVE) if not stls: print(f"ℹ️ No STL files found in: {SRC_FOLDER}") return out_folder = os.path.join(SRC_FOLDER, OUT_SUBFOLDER) os.makedirs(out_folder, exist_ok=True) print(f"🗂 Found {len(stls)} STL(s). Exporting STEP to: {out_folder}") prev_part = None for i, file_path in enumerate(stls, start=1): base = os.path.splitext(os.path.basename(file_path))[0] part_name = base # unique part per file step_path = os.path.join(out_folder, f"{base}.step") print(f"\n[{i}/{len(stls)}] Processing: {file_path}") try: # 1) Clear previous Part if prev_part: _safe_delete_part_by_name(prev_part) prev_part = None # 2) Import STL into clipboard gom.script.sys.import_stl( bgr_coding=True, files=[file_path], geometry_based_refining=False, import_mode='clipboard', length_unit='mm', stl_color_bit_set=False, target_type='mesh' ) # 3) Create new Part for this file & move actuals into it gom.script.part.create_new_part(name=part_name) actuals = list(gom.app.project.clipboard.actual_elements) if not actuals: raise RuntimeError("No actual elements found after STL import.") gom.script.part.add_elements_to_part( delete_invisible_elements=True, elements=actuals, import_mode='new_elements', part=gom.app.project.parts[part_name] ) # 4) Build CAD from this part's mesh (explicit target) gom.script.sys.switch_to_mesh_editing_workspace() cad_group = gom.script.mesh.automatic_cad_surface_from_mesh( merge=True, mesh=gom.app.project.parts[part_name].actual, name='CAD group 1', number_of_quads=NUM_QUADS, preview_type=1 ) # 5) Export only the CAD group we just created gom.script.sys.export_step( elements=[cad_group], file=step_path ) print(f"✅ STEP exported: {step_path}") # 6) Optional: remove that temporary CAD group (keeps project lean) try: gom.script.cad.delete_element( elements=[cad_group], with_measuring_principle=True ) except Exception: pass # remember for next loop's deletion prev_part = part_name except Exception as e: print(f"❌ Error on '{file_path}': {e} (continuing)") # Optional: delete last part too if prev_part: _safe_delete_part_by_name(prev_part) print("\n🎉 All done.") if __name__ == "__main__": main() Updated with the help from Martin. Now you can directly select the folder instead of entering the string. Auto CAD.addon 1 Link to comment Share on other sites More sharing options...
Recommended Posts
Please sign in to comment
You will be able to leave a comment after signing in