Auswahl als Library-Item speichern (Step 3)
User selektiert in Rhino → klickt im Symbole-Tab 'Aus Auswahl' → Selection wird in eine .3dm-Datei verpackt + Library-Item entsteht (oder bestehendes wird aktualisiert). Backend (library.py): - save_selection_to_asset(doc, target_name): erstellt File3dm aus der aktuellen Selection, packt Geometry relativ zu BoundingBox.Min (Block- Origin am Ursprung), schreibt nach library/assets/ Backend (ProjectSettingsBridge): - SAVE_SELECTION_AS_LIBRARY-Handler: holt Selection, fragt bei neuen Items via Rhino-GetString nach Name, schreibt .3dm, fuegt Item zum Manifest oder updated bestehendes (variant '2d'/'3d') Frontend (Symbole-Tab): - List-Footer: 'Aus Datei' + 'Aus Auswahl' Pills (neues Item) - Pro 2D/3D-Slot im Detail: 'Datei wählen' + 'Aus Auswahl' Pills (Variante eines bestehenden Items befüllen) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -210,6 +210,91 @@ def add_item(item):
|
|||||||
return ok, load_manifest()
|
return ok, load_manifest()
|
||||||
|
|
||||||
|
|
||||||
|
def save_selection_to_asset(doc, target_name):
|
||||||
|
"""Speichert die aktuelle Selection aus dem Doc als eigene .3dm-Datei
|
||||||
|
in library/assets/<target_name>.3dm. Returns relativer Pfad oder None.
|
||||||
|
Geometry wird relativ zum BoundingBox-Min platziert damit der Block-
|
||||||
|
Origin am Ursprung sitzt — sinnvoll fuer Symbol-Insert."""
|
||||||
|
if doc is None or not target_name: return None
|
||||||
|
try:
|
||||||
|
sel = list(doc.Objects.GetSelectedObjects(False, False))
|
||||||
|
except Exception:
|
||||||
|
sel = []
|
||||||
|
if not sel:
|
||||||
|
print("[LIBRARY] save_selection: keine Auswahl")
|
||||||
|
return None
|
||||||
|
if Rhino 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 filename
|
||||||
|
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))
|
||||||
|
# File3dm aufbauen + Selection rein
|
||||||
|
try:
|
||||||
|
from Rhino.FileIO import File3dm, File3dmWriteOptions
|
||||||
|
import Rhino.Geometry as rg
|
||||||
|
# BoundingBox sammeln um geometrie an Ursprung zu verschieben
|
||||||
|
bbox = rg.BoundingBox.Empty
|
||||||
|
geoms = []
|
||||||
|
for o in sel:
|
||||||
|
g = o.Geometry
|
||||||
|
if g is None: continue
|
||||||
|
try:
|
||||||
|
bb = g.GetBoundingBox(True)
|
||||||
|
if bb.IsValid: bbox.Union(bb)
|
||||||
|
except Exception: pass
|
||||||
|
geoms.append((g, o.Attributes))
|
||||||
|
if not geoms:
|
||||||
|
return None
|
||||||
|
if not bbox.IsValid:
|
||||||
|
origin = rg.Point3d(0, 0, 0)
|
||||||
|
else:
|
||||||
|
origin = bbox.Min
|
||||||
|
offset = rg.Transform.Translation(-origin.X, -origin.Y, -origin.Z)
|
||||||
|
f3 = File3dm()
|
||||||
|
for g, a in geoms:
|
||||||
|
try:
|
||||||
|
g2 = g.Duplicate()
|
||||||
|
try: g2.Transform(offset)
|
||||||
|
except Exception: pass
|
||||||
|
# Generischer Add fuer alle GeometryBase
|
||||||
|
try: f3.Objects.Add(g2, a)
|
||||||
|
except Exception:
|
||||||
|
# Fallback: typ-spezifisch
|
||||||
|
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)
|
||||||
|
elif isinstance(g2, rg.Point): f3.Objects.AddPoint(g2.Location)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[LIBRARY] save_selection add:", ex)
|
||||||
|
# Write
|
||||||
|
try:
|
||||||
|
opts = File3dmWriteOptions()
|
||||||
|
opts.Version = 8
|
||||||
|
f3.Write(target, opts)
|
||||||
|
except Exception:
|
||||||
|
try: f3.Write(target, 8)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[LIBRARY] save_selection write:", ex)
|
||||||
|
return None
|
||||||
|
rel = os.path.relpath(target, library_root())
|
||||||
|
print("[LIBRARY] save_selection: {} objs → {}".format(len(geoms), rel))
|
||||||
|
return rel
|
||||||
|
except Exception as ex:
|
||||||
|
print("[LIBRARY] save_selection:", ex)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def copy_to_assets(src_path, target_name=None):
|
def copy_to_assets(src_path, target_name=None):
|
||||||
"""Kopiert eine Datei in library/assets/. target_name optional (sonst
|
"""Kopiert eine Datei in library/assets/. target_name optional (sonst
|
||||||
Original-Name). Returns relativer Pfad ('assets/foo.3dm') oder None."""
|
Original-Name). Returns relativer Pfad ('assets/foo.3dm') oder None."""
|
||||||
|
|||||||
@@ -1012,6 +1012,8 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
self._update_library_item(p)
|
self._update_library_item(p)
|
||||||
elif t == "DELETE_LIBRARY_ITEM":
|
elif t == "DELETE_LIBRARY_ITEM":
|
||||||
self._delete_library_item(p)
|
self._delete_library_item(p)
|
||||||
|
elif t == "SAVE_SELECTION_AS_LIBRARY":
|
||||||
|
self._save_selection_as_library(p)
|
||||||
def _pick_texture(self, payload):
|
def _pick_texture(self, payload):
|
||||||
slot = payload.get("slot") or "diffuse"
|
slot = payload.get("slot") or "diffuse"
|
||||||
try:
|
try:
|
||||||
@@ -1288,6 +1290,71 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
self._send_library()
|
self._send_library()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[PROJECT-SETTINGS] _delete_library_item:", ex)
|
print("[PROJECT-SETTINGS] _delete_library_item:", ex)
|
||||||
|
|
||||||
|
def _save_selection_as_library(self, payload):
|
||||||
|
"""Aktuelle Selection im Doc als Library-Item speichern.
|
||||||
|
payload: {variant: '2d'|'3d', targetId?: str, itemType?: str}.
|
||||||
|
Wenn targetId gesetzt: bestehendes Item updaten. Sonst:
|
||||||
|
Rhino-Prompt nach Name + neues Item anlegen."""
|
||||||
|
d = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if d is None: return
|
||||||
|
try:
|
||||||
|
sel = list(d.Objects.GetSelectedObjects(False, False))
|
||||||
|
except Exception: sel = []
|
||||||
|
if not sel:
|
||||||
|
self.send("LIBRARY_ERROR", {
|
||||||
|
"msg": "Bitte erst Objekte in Rhino selektieren."})
|
||||||
|
return
|
||||||
|
import library
|
||||||
|
variant = (payload.get("variant") or "2d").lower()
|
||||||
|
if variant not in ("2d", "3d"): variant = "2d"
|
||||||
|
target_id = (payload.get("targetId") or "").strip() or None
|
||||||
|
item_type = payload.get("itemType") or "object"
|
||||||
|
if item_type not in ("symbol", "object"): item_type = "object"
|
||||||
|
# Name: bei bestehendem Item den Item-Name nehmen, sonst
|
||||||
|
# Rhino-Prompt
|
||||||
|
name = ""
|
||||||
|
if target_id:
|
||||||
|
m = library.load_manifest()
|
||||||
|
for it in m.get("items", []):
|
||||||
|
if it.get("id") == target_id:
|
||||||
|
name = it.get("name") or "item"
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
import Rhino.Input.Custom as ric
|
||||||
|
from Rhino.Input import GetResult
|
||||||
|
gs = ric.GetString()
|
||||||
|
gs.SetCommandPrompt("Name fuer neues Library-Item")
|
||||||
|
gs.AcceptNothing(True)
|
||||||
|
if gs.Get() == GetResult.String:
|
||||||
|
name = (gs.StringResult() or "").strip()
|
||||||
|
except Exception as ex:
|
||||||
|
print("[PROJECT-SETTINGS] save_selection prompt:", ex)
|
||||||
|
if not name: name = "Neues_Item"
|
||||||
|
# Speichern
|
||||||
|
rel = library.save_selection_to_asset(d, name + "_" + variant)
|
||||||
|
if not rel:
|
||||||
|
self.send("LIBRARY_ERROR", {
|
||||||
|
"msg": "Konnte Selection nicht speichern."})
|
||||||
|
return
|
||||||
|
if target_id:
|
||||||
|
library.update_item(target_id, {
|
||||||
|
("files" + variant): [rel],
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
import uuid as _uuid
|
||||||
|
new_id = "obj-" + _uuid.uuid4().hex[:10]
|
||||||
|
item = {
|
||||||
|
"id": new_id,
|
||||||
|
"type": item_type,
|
||||||
|
"name": name,
|
||||||
|
"version": 1,
|
||||||
|
"tags": [],
|
||||||
|
("files" + variant): [rel],
|
||||||
|
}
|
||||||
|
library.add_item(item)
|
||||||
|
self._send_library()
|
||||||
b = _ProjectSettingsBridge()
|
b = _ProjectSettingsBridge()
|
||||||
bridge_holder["form"] = panel_base.open_satellite_window(
|
bridge_holder["form"] = panel_base.open_satellite_window(
|
||||||
"project_settings",
|
"project_settings",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
renameLinetype, deleteLinetype, loadLinetypeDefaults, importLinetypeFile,
|
renameLinetype, deleteLinetype, loadLinetypeDefaults, importLinetypeFile,
|
||||||
renameHatch, deleteHatch, importHatchFile,
|
renameHatch, deleteHatch, importHatchFile,
|
||||||
listLibraryItems, addLibraryFile, updateLibraryItem, deleteLibraryItem,
|
listLibraryItems, addLibraryFile, updateLibraryItem, deleteLibraryItem,
|
||||||
|
saveSelectionAsLibrary,
|
||||||
} from '../lib/rhinoBridge'
|
} from '../lib/rhinoBridge'
|
||||||
|
|
||||||
/* Field — Stack-Layout fuer komplexe Inputs (mehrere Felder nebeneinander) */
|
/* Field — Stack-Layout fuer komplexe Inputs (mehrere Felder nebeneinander) */
|
||||||
@@ -1150,12 +1151,15 @@ export default function ProjectSettingsDialog({
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 4,
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4,
|
||||||
padding: '6px 8px',
|
padding: '6px 8px',
|
||||||
borderTop: '1px solid var(--border-light)' }}>
|
borderTop: '1px solid var(--border-light)' }}>
|
||||||
<BarToggle icon="add" label="Neues Objekt"
|
<BarToggle icon="upload_file" label="Aus Datei"
|
||||||
onClick={() => addLibraryFile('2d', null, 'object')}
|
onClick={() => addLibraryFile('2d', null, 'object')}
|
||||||
title="Datei waehlen, kopiert in Library + neues Item" />
|
title=".3dm-Datei waehlen → kopiert in Library + neues Item" />
|
||||||
|
<BarToggle icon="add_to_photos" label="Aus Auswahl"
|
||||||
|
onClick={() => saveSelectionAsLibrary('2d', null, 'object')}
|
||||||
|
title="Selektierte Objekte in Rhino als Library-Item speichern" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1221,8 +1225,13 @@ export default function ProjectSettingsDialog({
|
|||||||
{(it.files2d || []).join(', ')}
|
{(it.files2d || []).join(', ')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
<BarToggle icon="upload_file" label="Datei wählen"
|
<BarToggle icon="upload_file" label="Datei wählen"
|
||||||
onClick={() => addLibraryFile('2d', it.id, it.type)} />
|
onClick={() => addLibraryFile('2d', it.id, it.type)} />
|
||||||
|
<BarToggle icon="add_to_photos" label="Aus Auswahl"
|
||||||
|
onClick={() => saveSelectionAsLibrary('2d', it.id, it.type)}
|
||||||
|
title="Selektierte Objekte als 2D-Variante speichern" />
|
||||||
|
</div>
|
||||||
</DetailSection>
|
</DetailSection>
|
||||||
|
|
||||||
<DetailSection title="3D-Darstellung (Perspektive)">
|
<DetailSection title="3D-Darstellung (Perspektive)">
|
||||||
@@ -1240,8 +1249,13 @@ export default function ProjectSettingsDialog({
|
|||||||
{(it.files3d || []).join(', ')}
|
{(it.files3d || []).join(', ')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
<BarToggle icon="upload_file" label="Datei wählen"
|
<BarToggle icon="upload_file" label="Datei wählen"
|
||||||
onClick={() => addLibraryFile('3d', it.id, it.type)} />
|
onClick={() => addLibraryFile('3d', it.id, it.type)} />
|
||||||
|
<BarToggle icon="add_to_photos" label="Aus Auswahl"
|
||||||
|
onClick={() => saveSelectionAsLibrary('3d', it.id, it.type)}
|
||||||
|
title="Selektierte Objekte als 3D-Variante speichern" />
|
||||||
|
</div>
|
||||||
</DetailSection>
|
</DetailSection>
|
||||||
|
|
||||||
<DetailSection title="Tags">
|
<DetailSection title="Tags">
|
||||||
|
|||||||
@@ -217,6 +217,16 @@ export function updateLibraryItem(id, patch) {
|
|||||||
send('UPDATE_LIBRARY_ITEM', { id, patch })
|
send('UPDATE_LIBRARY_ITEM', { id, patch })
|
||||||
}
|
}
|
||||||
export function deleteLibraryItem(id) { send('DELETE_LIBRARY_ITEM', { id }) }
|
export function deleteLibraryItem(id) { send('DELETE_LIBRARY_ITEM', { id }) }
|
||||||
|
// Aktuelle Rhino-Selection als Library-Item speichern. variant='2d'|'3d'.
|
||||||
|
// Wenn targetId gesetzt: bestehendes Item aktualisieren. Sonst neues
|
||||||
|
// Item (Backend fragt nach Namen via Rhino-Prompt).
|
||||||
|
export function saveSelectionAsLibrary(variant, targetId, itemType) {
|
||||||
|
send('SAVE_SELECTION_AS_LIBRARY', {
|
||||||
|
variant: variant || '2d',
|
||||||
|
targetId: targetId || null,
|
||||||
|
itemType: itemType || 'object',
|
||||||
|
})
|
||||||
|
}
|
||||||
// Schnitt/Ansicht — interaktiver 2-Punkt-Pick im Rhino-Viewport. Erzeugt
|
// Schnitt/Ansicht — interaktiver 2-Punkt-Pick im Rhino-Viewport. Erzeugt
|
||||||
// eine neue Zeichnungsebene type=schnitt + 2D-Plan-Symbol + aktiviert sie.
|
// eine neue Zeichnungsebene type=schnitt + 2D-Plan-Symbol + aktiviert sie.
|
||||||
// opts: { cutAtLine: bool, depthBack: m, heightMin: m, heightMax: m, namePrefix }
|
// opts: { cutAtLine: bool, depthBack: m, heightMin: m, heightMax: m, namePrefix }
|
||||||
|
|||||||
Reference in New Issue
Block a user