Library Phase A.2 (Symbol/Object-Import) + Oberleiste-Pill-Restyle
Library Phase A.2: - import_symbol/import_object via File3dm.Read + InstanceDefinitions.Add - Stabile Block-Namen 'dossier_lib_<libraryId>' fuer Dedupe - Seed-Manifest erweitert um Nordpfeil (symbol) + Laubbaum (object) - ItemCard rendert type-spezifische Preview (Color-Swatch fuer material, Material-Icon fuer symbol/object) Oberleiste-Pill-Restyle: - OberleisteApp: Version unter DOSSIER-Logo, Settings-Icons vertikal gestapelt - ProjectSettingsDialog: Pill-Tabs, BarToggle-Footer, MaterialRow mit Hover-Highlight, Header entfernt (Eto.Form hat eigenen) - LibraryBrowser: BarButton-Reload, Pill-Typ-Filter, MaterialCard mit BarToggle-Pill, Header entfernt - Globaler select-Stil: bg-input statt bg-item (dunkler im Dark-Mode, konsistent zu Oberleiste-BarCombo) Routing: - OberleisteBridge delegiert OPEN_PROJECT_SETTINGS + OPEN_LIBRARY an EbenenBridge (sticky ebenen_bridge_ref) — vorher kamen die Messages an der falschen Bridge an und wurden verschluckt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+154
-5
@@ -70,7 +70,9 @@ def ensure_library():
|
||||
|
||||
|
||||
def _write_seed_manifest(path):
|
||||
"""Bootstrappt 4 typische Architektur-Materialien als Start."""
|
||||
"""Bootstrappt 4 Materialien + 2 Symbol/Object-Beispiel-Eintraege als
|
||||
Start. Die Symbol/Object-Files muss der User selber ablegen unter
|
||||
library/assets/ — sonst schlaegt der Import fehl (graceful)."""
|
||||
seed = {
|
||||
"schemaVersion": _SCHEMA_VERSION,
|
||||
"name": "Dossier-Library (lokal)",
|
||||
@@ -103,6 +105,20 @@ def _write_seed_manifest(path):
|
||||
"tags": ["holz", "ausbau"],
|
||||
"data": {"color": "#c8a06a", "hatch": "Solid", "scale": 1.0},
|
||||
},
|
||||
{
|
||||
"id": "sym-nordpfeil-01",
|
||||
"type": "symbol", "version": 1,
|
||||
"name": "Nordpfeil",
|
||||
"tags": ["plan", "nordpfeil"],
|
||||
"files": ["assets/sym-nordpfeil-01.3dm"],
|
||||
},
|
||||
{
|
||||
"id": "obj-baum-laubbaum-01",
|
||||
"type": "object", "version": 1,
|
||||
"name": "Baum — Laubbaum",
|
||||
"tags": ["aussen", "vegetation"],
|
||||
"files": ["assets/obj-baum-laubbaum-01.3dm"],
|
||||
},
|
||||
],
|
||||
}
|
||||
try:
|
||||
@@ -148,6 +164,9 @@ def _normalize_item(it):
|
||||
"tags": list(it.get("tags") or []),
|
||||
"preview": it.get("preview"),
|
||||
"data": it.get("data") or {},
|
||||
# files: relative Pfade (zur library_root()) auf .3dm-Fragmente.
|
||||
# Symbol/Object-Import liest die ueber File3dm.Read.
|
||||
"files": list(it.get("files") or []),
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -191,13 +210,143 @@ def import_material(doc, item):
|
||||
return True, "Material importiert"
|
||||
|
||||
|
||||
# --- Symbol/Object-Import via File3dm + InstanceDefinitions ----------------
|
||||
|
||||
def _lib_asset_path(rel_path):
|
||||
"""Absoluter Pfad zu einer Library-Asset-Datei (Eintrag aus item.files)."""
|
||||
if not rel_path: return None
|
||||
return os.path.join(library_root(), rel_path)
|
||||
|
||||
|
||||
def _block_name_for(item):
|
||||
"""Stabiler Block-Name fuer InstanceDefinition. Format:
|
||||
'dossier_lib_<libraryId>' — Dedup ueber Lib-ID, nicht Item-Name (sonst
|
||||
Konflikt bei Umbenennen)."""
|
||||
lid = item.get("id") or ""
|
||||
safe = "".join(c if (c.isalnum() or c in "-_") else "_" for c in lid)
|
||||
return "dossier_lib_" + safe
|
||||
|
||||
|
||||
def _read_3dm_geometry(abs_path):
|
||||
"""Liest alle Top-Level-Objekte aus einer .3dm-Datei. Returns Liste von
|
||||
(GeometryBase, ObjectAttributes). Bei Fehler leere Liste."""
|
||||
if not abs_path or not os.path.isfile(abs_path):
|
||||
print("[LIBRARY] _read_3dm: Datei nicht gefunden:", abs_path)
|
||||
return []
|
||||
try:
|
||||
from Rhino.FileIO import File3dm
|
||||
f3 = File3dm.Read(abs_path)
|
||||
if f3 is None:
|
||||
print("[LIBRARY] _read_3dm: File3dm.Read returned None:", abs_path)
|
||||
return []
|
||||
out = []
|
||||
for obj in f3.Objects:
|
||||
try:
|
||||
g = obj.Geometry
|
||||
a = obj.Attributes.Duplicate() if obj.Attributes else None
|
||||
if g is not None:
|
||||
out.append((g.Duplicate(), a))
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] _read_3dm obj:", ex)
|
||||
return out
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] _read_3dm:", ex)
|
||||
return []
|
||||
|
||||
|
||||
def _ensure_block_definition(doc, item, geometry_attrs):
|
||||
"""Erstellt InstanceDefinition fuer dieses Library-Item wenn noch nicht
|
||||
da. Returns (idx, was_created). idx<0 bei Fehler."""
|
||||
if Rhino is None: return -1, False
|
||||
name = _block_name_for(item)
|
||||
try:
|
||||
existing = doc.InstanceDefinitions.Find(name)
|
||||
except Exception:
|
||||
existing = None
|
||||
if existing is not None:
|
||||
return existing.Index, False
|
||||
# Geometry + Attributes separat sammeln
|
||||
geoms = [g for g, _ in geometry_attrs if g is not None]
|
||||
attrs = [a for _, a in geometry_attrs]
|
||||
if not geoms:
|
||||
return -1, False
|
||||
base_pt = Rhino.Geometry.Point3d(0, 0, 0)
|
||||
desc = "Dossier-Library: " + (item.get("name") or "")
|
||||
try:
|
||||
idx = doc.InstanceDefinitions.Add(name, desc, base_pt, geoms, attrs)
|
||||
return idx, True
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] InstanceDef Add:", ex)
|
||||
return -1, False
|
||||
|
||||
|
||||
def import_symbol(doc, item):
|
||||
"""Importiert ein Symbol-Item (= 2D-Block) in das Doc. Liest die
|
||||
.3dm-Datei(en) aus item.files, erstellt eine InstanceDefinition mit
|
||||
stabilem Namen, fuegt eine Instanz am Ursprung ein.
|
||||
Returns (ok, message)."""
|
||||
return _import_block_like(doc, item, kind="symbol")
|
||||
|
||||
|
||||
def import_object(doc, item):
|
||||
"""Importiert ein Object-Item (= 3D-Block, BIM-Element) in das Doc.
|
||||
Gleiche Pipeline wie import_symbol — Symbol/Object unterscheiden sich
|
||||
nur in der UI-Kategorisierung."""
|
||||
return _import_block_like(doc, item, kind="object")
|
||||
|
||||
|
||||
def _import_block_like(doc, item, kind):
|
||||
if doc is None: return False, "Kein aktives Dokument"
|
||||
files = item.get("files") or []
|
||||
if not files:
|
||||
return False, "Item hat keine .3dm-Files: " + str(item.get("id"))
|
||||
# Alle Files zusammen in eine Block-Definition packen.
|
||||
all_geom = []
|
||||
for f in files:
|
||||
abs_p = _lib_asset_path(f)
|
||||
all_geom.extend(_read_3dm_geometry(abs_p))
|
||||
if not all_geom:
|
||||
return False, "Keine importierbare Geometrie in {}".format(files)
|
||||
idx, created = _ensure_block_definition(doc, item, all_geom)
|
||||
if idx < 0:
|
||||
return False, "InstanceDefinition konnte nicht erstellt werden"
|
||||
# Instanz am Ursprung einfuegen — User kann danach verschieben.
|
||||
try:
|
||||
xform = Rhino.Geometry.Transform.Identity
|
||||
inst_id = doc.Objects.AddInstanceObject(idx, xform)
|
||||
if inst_id == System_Guid_Empty():
|
||||
return False, "AddInstanceObject fehlgeschlagen"
|
||||
try: doc.Views.Redraw()
|
||||
except Exception: pass
|
||||
msg = ("Block '{}' importiert + am Ursprung eingefuegt".format(
|
||||
item.get("name") or "")
|
||||
if created else
|
||||
"Block bereits vorhanden — neue Instanz eingefuegt")
|
||||
return True, msg
|
||||
except Exception as ex:
|
||||
print("[LIBRARY] AddInstanceObject:", ex)
|
||||
return False, str(ex)
|
||||
|
||||
|
||||
def System_Guid_Empty():
|
||||
"""Helper — System.Guid.Empty Vergleichswert."""
|
||||
try:
|
||||
import System
|
||||
return System.Guid.Empty
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def import_item(doc, item_id):
|
||||
"""Type-dispatching Import. Phase A: nur material. symbol/object kommen
|
||||
spaeter via File3dm.Read + InstanceDefinition."""
|
||||
"""Type-dispatching Import. material → Project-Settings-Liste.
|
||||
symbol/object → InstanceDefinition im Doc via File3dm.Read."""
|
||||
item = find_item(item_id)
|
||||
if item is None: return False, "Item nicht gefunden: " + str(item_id)
|
||||
t = item.get("type")
|
||||
if t == "material":
|
||||
return import_material(doc, item)
|
||||
# Phase A: symbol/object noch nicht
|
||||
return False, "Typ '{}' wird erst in Phase A.2 unterstuetzt".format(t)
|
||||
if t == "symbol":
|
||||
return import_symbol(doc, item)
|
||||
if t == "object":
|
||||
return import_object(doc, item)
|
||||
return False, "Unbekannter Typ: '{}'".format(t)
|
||||
|
||||
@@ -1066,6 +1066,22 @@ class OberleisteBridge(panel_base.BaseBridge):
|
||||
masse_settings.open_as_window()
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] open masse:", ex)
|
||||
elif t == "OPEN_PROJECT_SETTINGS":
|
||||
# Delegiert an EBENEN-Bridge (sie haelt die Satellite-Logik fuer
|
||||
# Projekt-Settings + Library).
|
||||
try:
|
||||
eb = sc.sticky.get("ebenen_bridge_ref")
|
||||
if eb is not None: eb._open_project_settings()
|
||||
else: print("[OBERLEISTE] open project-settings: ebenen_bridge_ref nicht da")
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] open project-settings:", ex)
|
||||
elif t == "OPEN_LIBRARY":
|
||||
try:
|
||||
eb = sc.sticky.get("ebenen_bridge_ref")
|
||||
if eb is not None: eb._open_library()
|
||||
else: print("[OBERLEISTE] open library: ebenen_bridge_ref nicht da")
|
||||
except Exception as ex:
|
||||
print("[OBERLEISTE] open library:", ex)
|
||||
|
||||
# --- Darstellung (SIA-400 LoD globaler Override) -----------------
|
||||
elif t == "SET_DARSTELLUNG":
|
||||
|
||||
Reference in New Issue
Block a user