Library: Format-Konvertierung beim Import (.dwg/.obj/.fbx/.dae/.stl/...)

User kann jetzt im Symbole-Tab Dateien in vielen Formaten waehlen — Rhino
konvertiert automatisch nach .3dm via _-Import + File3dm.Write.

library.py — convert_to_3dm_via_import():
- Snapshot der existierenden Object-IDs im aktiven Doc
- User-Selection sichern
- _-Import scripted ausfuehren → neue Objekte in Doc
- Diff -> neue Objekte sammeln
- BoundingBox.Min als Origin → in File3dm packen
- f3.Write nach library/assets/<name>.3dm
- Neue Objekte aus Doc wieder loeschen + User-Selection restoren
- sticky 'dossier_library_import_busy' + 'dossier_swisstopo_busy' damit
  unsere Listener nichts cascaden waehrend Doc kurzzeitig die Importe haelt

rhinopanel.py — _add_library_file():
- .3dm: copy_to_assets (wie bisher)
- .dwg/.dxf/.obj/.fbx/.dae/.stl/.3ds/.skp/.iges/.step/.ply: konvertieren
- Sonst LIBRARY_ERROR

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 18:45:58 +02:00
parent 68b9d14453
commit 827bd8d4d7
2 changed files with 150 additions and 14 deletions
+131
View File
@@ -210,6 +210,137 @@ def add_item(item):
return ok, load_manifest() 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/<name>.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): def save_selection_to_asset(doc, target_name):
"""Speichert die aktuelle Selection aus dem Doc als eigene .3dm-Datei """Speichert die aktuelle Selection aus dem Doc als eigene .3dm-Datei
in library/assets/<target_name>.3dm. Returns relativer Pfad oder None. in library/assets/<target_name>.3dm. Returns relativer Pfad oder None.
+19 -14
View File
@@ -1224,20 +1224,28 @@ class EbenenBridge(panel_base.BaseBridge):
path = dlg.FileName or "" path = dlg.FileName or ""
if not path: return if not path: return
ext = (path.split(".")[-1] if "." in path else "").lower() ext = (path.split(".")[-1] if "." in path else "").lower()
if ext != "3dm": import os
# TODO: konvertieren via temporaerer RhinoDoc-Import base = os.path.basename(path)
# Phase 1: nur .3dm direkt unterstuetzt stem = os.path.splitext(base)[0]
print("[PROJECT-SETTINGS] Format '.{}' wird in dieser " rel = None
"Version noch nicht konvertiert — bitte in Rhino " if ext == "3dm":
"oeffnen + als .3dm speichern".format(ext)) # 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", { self.send("LIBRARY_ERROR", {
"msg": "Format .{} noch nicht unterstuetzt. " "msg": "Format .{} wird nicht unterstuetzt.".format(ext),
"Konvertiere in Rhino zu .3dm.".format(ext),
}) })
return return
rel = library.copy_to_assets(path)
if not rel: 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 return
if target_id: if target_id:
# Bestehendes Item updaten # Bestehendes Item updaten
@@ -1250,10 +1258,7 @@ class EbenenBridge(panel_base.BaseBridge):
break break
library.save_manifest(m) library.save_manifest(m)
else: else:
# Neues Item # Neues Item — stem aus dem Pfad oben bereits berechnet
import os
base = os.path.basename(path)
stem = os.path.splitext(base)[0]
import uuid as _uuid import uuid as _uuid
new_id = "obj-" + _uuid.uuid4().hex[:10] new_id = "obj-" + _uuid.uuid4().hex[:10]
item = { item = {