diff --git a/rhino/library.py b/rhino/library.py index f17ad95..e0388ec 100644 --- a/rhino/library.py +++ b/rhino/library.py @@ -210,6 +210,137 @@ def add_item(item): return ok, load_manifest() +def convert_to_3dm_via_import(src_path, target_name): + """Konvertiert eine beliebige CAD-Datei (.dwg/.obj/.fbx/.dae/.stl/...) + nach .3dm. Strategie: Rhinos _-Import in den aktiven Doc, dann die + NEU hinzugekommenen Objekte als File3dm in library/assets/.3dm + speichern + aus dem Doc wieder loeschen. Returns relativer Pfad oder + None. + + WARNUNG: User-Doc wird kurz veraendert (Objects in/out) — wir + delete'n alles wieder am Ende. Setzt einen sticky-Flag damit unsere + eigenen Listener nichts cascaden.""" + if not src_path or not os.path.isfile(src_path): return None + if Rhino is None: return None + import scriptcontext as sc + doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return None + ensure_library() + assets_dir = os.path.join(library_root(), "assets") + if not os.path.isdir(assets_dir): + try: os.makedirs(assets_dir) + except Exception: pass + safe = "".join(c if (c.isalnum() or c in "-_") else "_" for c in target_name) + if not safe.endswith(".3dm"): safe += ".3dm" + target = os.path.join(assets_dir, safe) + if os.path.isfile(target): + stem, ext = os.path.splitext(safe) + n = 2 + while os.path.isfile(os.path.join(assets_dir, "{}_{}{}".format(stem, n, ext))): + n += 1 + target = os.path.join(assets_dir, "{}_{}{}".format(stem, n, ext)) + # Listener stilllegen: unsere Add-/Delete-Cascade soll bei diesem + # temporaeren Import nicht greifen (Objekte haben keine DOSSIER- + # UserStrings, kommen aber trotzdem durch unsere Schnellfilter). + sc.sticky["dossier_library_import_busy"] = True + sc.sticky["dossier_swisstopo_busy"] = True # blockt schon viele Listener + # Snapshot der existierenden Object-IDs + before_ids = set() + try: + for o in doc.Objects: + try: + if not o.IsDeleted: before_ids.add(str(o.Id)) + except Exception: pass + except Exception: pass + # User-Selection sichern damit wir sie am Ende restoren + sel_before_ids = [] + try: + for o in doc.Objects.GetSelectedObjects(False, False): + sel_before_ids.append(o.Id) + except Exception: pass + new_objs = [] + try: + try: doc.Objects.UnselectAll() + except Exception: pass + # _-Import dash-prefix = scripted, kein UI-Dialog. Pfad in Quotes + # damit Spaces nicht splitten. _Enter beendet die Optionen. + cmd = '_-Import "' + src_path + '" _Enter _Enter' + try: + Rhino.RhinoApp.RunScript(cmd, False) + except Exception as ex: + print("[LIBRARY] convert_to_3dm RunScript:", ex) + # Sammle die NEU hinzugekommenen Objekte + try: + for o in doc.Objects: + try: + if o.IsDeleted: continue + if str(o.Id) not in before_ids: + new_objs.append(o) + except Exception: pass + except Exception: pass + if not new_objs: + print("[LIBRARY] convert_to_3dm: Import lieferte keine Objekte") + return None + # In File3dm packen + try: + from Rhino.FileIO import File3dm + import Rhino.Geometry as rg + bbox = rg.BoundingBox.Empty + geoms_attrs = [] + for o in new_objs: + g = o.Geometry + if g is None: continue + try: + bb = g.GetBoundingBox(True) + if bb.IsValid: bbox.Union(bb) + except Exception: pass + geoms_attrs.append((g, o.Attributes)) + if bbox.IsValid: + offset = rg.Transform.Translation( + -bbox.Min.X, -bbox.Min.Y, -bbox.Min.Z) + else: + offset = rg.Transform.Identity + f3 = File3dm() + for g, a in geoms_attrs: + try: + g2 = g.Duplicate() + try: g2.Transform(offset) + except Exception: pass + try: f3.Objects.Add(g2, a) + except Exception: + if isinstance(g2, rg.Brep): f3.Objects.AddBrep(g2) + elif isinstance(g2, rg.Curve): f3.Objects.AddCurve(g2) + elif isinstance(g2, rg.Mesh): f3.Objects.AddMesh(g2) + except Exception as ex: + print("[LIBRARY] convert add geom:", ex) + try: f3.Write(target, 8) + except Exception as ex: + print("[LIBRARY] convert_to_3dm Write:", ex) + return None + except Exception as ex: + print("[LIBRARY] convert_to_3dm File3dm:", ex) + return None + finally: + # Cleanup: importierte Objekte wieder loeschen + for o in new_objs: + try: doc.Objects.Delete(o.Id, True) + except Exception: pass + # Restore Selection + try: + for gid in sel_before_ids: + try: doc.Objects.Select(gid, True) + except Exception: pass + except Exception: pass + rel = os.path.relpath(target, library_root()) + print("[LIBRARY] convert_to_3dm OK: {} → {}".format(src_path, rel)) + return rel + finally: + sc.sticky["dossier_library_import_busy"] = False + sc.sticky["dossier_swisstopo_busy"] = False + try: doc.Views.Redraw() + except Exception: pass + + def save_selection_to_asset(doc, target_name): """Speichert die aktuelle Selection aus dem Doc als eigene .3dm-Datei in library/assets/.3dm. Returns relativer Pfad oder None. diff --git a/rhino/rhinopanel.py b/rhino/rhinopanel.py index 2c8cb9c..21055b5 100644 --- a/rhino/rhinopanel.py +++ b/rhino/rhinopanel.py @@ -1224,20 +1224,28 @@ class EbenenBridge(panel_base.BaseBridge): path = dlg.FileName or "" if not path: return ext = (path.split(".")[-1] if "." in path else "").lower() - if ext != "3dm": - # TODO: konvertieren via temporaerer RhinoDoc-Import - # Phase 1: nur .3dm direkt unterstuetzt - print("[PROJECT-SETTINGS] Format '.{}' wird in dieser " - "Version noch nicht konvertiert — bitte in Rhino " - "oeffnen + als .3dm speichern".format(ext)) + import os + base = os.path.basename(path) + stem = os.path.splitext(base)[0] + rel = None + if ext == "3dm": + # Direkt kopieren + rel = library.copy_to_assets(path) + elif ext in ("dwg", "dxf", "obj", "fbx", "dae", + "stl", "3ds", "skp", "iges", "igs", + "step", "stp", "ply"): + # Konvertieren via Rhino-Import + rel = library.convert_to_3dm_via_import( + path, stem + "_" + variant) + else: self.send("LIBRARY_ERROR", { - "msg": "Format .{} noch nicht unterstuetzt. " - "Konvertiere in Rhino zu .3dm.".format(ext), + "msg": "Format .{} wird nicht unterstuetzt.".format(ext), }) return - rel = library.copy_to_assets(path) if not rel: - print("[PROJECT-SETTINGS] copy_to_assets failed") + print("[PROJECT-SETTINGS] add file failed") + self.send("LIBRARY_ERROR", { + "msg": "Konnte Datei nicht importieren — siehe Log."}) return if target_id: # Bestehendes Item updaten @@ -1250,10 +1258,7 @@ class EbenenBridge(panel_base.BaseBridge): break library.save_manifest(m) else: - # Neues Item - import os - base = os.path.basename(path) - stem = os.path.splitext(base)[0] + # Neues Item — stem aus dem Pfad oben bereits berechnet import uuid as _uuid new_id = "obj-" + _uuid.uuid4().hex[:10] item = {