Symbole-Tab in Project-Settings (Library-Item-Management)
Project-Settings hat jetzt 5 Tabs. Neuer 'Symbole'-Tab managt die Dossier-Library: List/Detail wie Materialien, mit 2D + 3D Slot pro Item. Backend (library.py): - save_manifest, update_item, delete_item, add_item — full CRUD aufs library.json - copy_to_assets: kopiert User-Dateien in library/assets/ mit Konflikt-Resolution (auto-suffix) Backend (rhinopanel.py / ProjectSettingsBridge): - _send_library: aktuelle Items + libraryRoot an Frontend - _add_library_file: File-Picker (.3dm direkt; .dwg/.obj/etc. zeigt Hinweis fuer kuenftige Konvertierung), kopiert + appended ans Item (variant 2d/3d) oder erstellt neues Item - _update_library_item: patch by id - _delete_library_item: entfernt Eintrag aus Manifest - LIBRARY_ITEMS + LIBRARY_ERROR Messages ans Frontend Frontend: - Neuer 'Symbole'-Tab mit List/Detail - Liste: Name, Type-Icon, '2D'/'3D' Status-Badge - Detail rechts: Name-Edit (live persist on blur), Type-Toggle (Symbol/Objekt), 2D/3D-File-Slots mit Datei-Picker, Tags-Editor - 'Neues Objekt' Button im Listen-Footer Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -148,6 +148,97 @@ def load_manifest():
|
|||||||
return _empty_manifest()
|
return _empty_manifest()
|
||||||
|
|
||||||
|
|
||||||
|
def save_manifest(manifest):
|
||||||
|
"""Schreibt das Manifest zurueck zur library.json. Items werden
|
||||||
|
normalisiert. Returns True/False."""
|
||||||
|
ensure_library()
|
||||||
|
path = os.path.join(library_root(), _MANIFEST_FN)
|
||||||
|
try:
|
||||||
|
if not isinstance(manifest, dict): manifest = _empty_manifest()
|
||||||
|
manifest.setdefault("schemaVersion", _SCHEMA_VERSION)
|
||||||
|
manifest.setdefault("name", "Dossier-Library")
|
||||||
|
items = manifest.get("items") or []
|
||||||
|
manifest["items"] = [_normalize_item(x) for x in items
|
||||||
|
if _normalize_item(x) is not None]
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(manifest, f, indent=2, ensure_ascii=False)
|
||||||
|
return True
|
||||||
|
except Exception as ex:
|
||||||
|
print("[LIBRARY] save_manifest:", ex)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def update_item(item_id, patch):
|
||||||
|
"""Patcht ein Item im Manifest. Returns (ok, new_manifest)."""
|
||||||
|
m = load_manifest()
|
||||||
|
items = m.get("items", [])
|
||||||
|
found = False
|
||||||
|
for it in items:
|
||||||
|
if it.get("id") == item_id:
|
||||||
|
for k, v in (patch or {}).items():
|
||||||
|
it[k] = v
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found: return False, m
|
||||||
|
ok = save_manifest(m)
|
||||||
|
return ok, load_manifest()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_item(item_id):
|
||||||
|
"""Loescht ein Item aus dem Manifest. Asset-Files bleiben auf Disk
|
||||||
|
(User koennte sie noch wollen)."""
|
||||||
|
m = load_manifest()
|
||||||
|
items = m.get("items", [])
|
||||||
|
new_items = [it for it in items if it.get("id") != item_id]
|
||||||
|
if len(new_items) == len(items): return False, m
|
||||||
|
m["items"] = new_items
|
||||||
|
ok = save_manifest(m)
|
||||||
|
return ok, load_manifest()
|
||||||
|
|
||||||
|
|
||||||
|
def add_item(item):
|
||||||
|
"""Fuegt ein neues Item zum Manifest hinzu. Returns (ok, new_manifest)."""
|
||||||
|
norm = _normalize_item(item)
|
||||||
|
if norm is None: return False, load_manifest()
|
||||||
|
m = load_manifest()
|
||||||
|
items = m.get("items", [])
|
||||||
|
# Dedupe per id
|
||||||
|
items = [it for it in items if it.get("id") != norm["id"]]
|
||||||
|
items.append(norm)
|
||||||
|
m["items"] = items
|
||||||
|
ok = save_manifest(m)
|
||||||
|
return ok, load_manifest()
|
||||||
|
|
||||||
|
|
||||||
|
def copy_to_assets(src_path, target_name=None):
|
||||||
|
"""Kopiert eine Datei in library/assets/. target_name optional (sonst
|
||||||
|
Original-Name). Returns relativer Pfad ('assets/foo.3dm') oder None."""
|
||||||
|
if not src_path or not os.path.isfile(src_path):
|
||||||
|
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
|
||||||
|
base = target_name or os.path.basename(src_path)
|
||||||
|
target = os.path.join(assets_dir, base)
|
||||||
|
# Konflikt-Resolution: bei doppeltem Namen Nummer dran
|
||||||
|
if os.path.isfile(target):
|
||||||
|
stem, ext = os.path.splitext(base)
|
||||||
|
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))
|
||||||
|
try:
|
||||||
|
import shutil
|
||||||
|
shutil.copy2(src_path, target)
|
||||||
|
rel = os.path.relpath(target, library_root())
|
||||||
|
return rel
|
||||||
|
except Exception as ex:
|
||||||
|
print("[LIBRARY] copy_to_assets:", ex)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _empty_manifest():
|
def _empty_manifest():
|
||||||
return {"schemaVersion": _SCHEMA_VERSION,
|
return {"schemaVersion": _SCHEMA_VERSION,
|
||||||
"name": "Dossier-Library", "items": []}
|
"name": "Dossier-Library", "items": []}
|
||||||
|
|||||||
@@ -888,13 +888,25 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
})
|
})
|
||||||
except Exception:
|
except Exception:
|
||||||
built_in = []
|
built_in = []
|
||||||
|
# Library-Items initial laden (Symbole/Objekte fuer den
|
||||||
|
# Symbole-Tab; Materialien sind separat).
|
||||||
|
try:
|
||||||
|
import library
|
||||||
|
lib_manifest = library.load_manifest()
|
||||||
|
lib_items = lib_manifest.get("items", [])
|
||||||
|
lib_root = library.library_root()
|
||||||
|
except Exception:
|
||||||
|
lib_items = []; lib_root = ""
|
||||||
params = {
|
params = {
|
||||||
"defaults": current.get("defaults", {}),
|
"defaults": current.get("defaults", {}),
|
||||||
|
"project": current.get("project", {}),
|
||||||
"materials": current.get("materials", []),
|
"materials": current.get("materials", []),
|
||||||
"builtinMaterials": built_in,
|
"builtinMaterials": built_in,
|
||||||
"hatchPatterns": _hatch_pattern_names(doc),
|
"hatchPatterns": _hatch_pattern_names(doc),
|
||||||
"hatchPatternsFull": _list_hatch_patterns_full(doc),
|
"hatchPatternsFull": _list_hatch_patterns_full(doc),
|
||||||
"linetypes": _list_linetypes_full(doc),
|
"linetypes": _list_linetypes_full(doc),
|
||||||
|
"libraryItems": lib_items,
|
||||||
|
"libraryRoot": lib_root,
|
||||||
}
|
}
|
||||||
def on_save(updated):
|
def on_save(updated):
|
||||||
doc2 = Rhino.RhinoDoc.ActiveDoc
|
doc2 = Rhino.RhinoDoc.ActiveDoc
|
||||||
@@ -992,6 +1004,14 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
self._delete_hatch(p)
|
self._delete_hatch(p)
|
||||||
elif t == "IMPORT_HATCH_FILE":
|
elif t == "IMPORT_HATCH_FILE":
|
||||||
self._import_hatch_file()
|
self._import_hatch_file()
|
||||||
|
elif t == "LIST_LIBRARY_ITEMS":
|
||||||
|
self._send_library()
|
||||||
|
elif t == "ADD_LIBRARY_FILE":
|
||||||
|
self._add_library_file(p)
|
||||||
|
elif t == "UPDATE_LIBRARY_ITEM":
|
||||||
|
self._update_library_item(p)
|
||||||
|
elif t == "DELETE_LIBRARY_ITEM":
|
||||||
|
self._delete_library_item(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:
|
||||||
@@ -1157,6 +1177,117 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
"linetypes": _list_linetypes_full(d),
|
"linetypes": _list_linetypes_full(d),
|
||||||
"hatchPatternsFull": _list_hatch_patterns_full(d),
|
"hatchPatternsFull": _list_hatch_patterns_full(d),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# ---- Library-Item CRUD ----
|
||||||
|
def _send_library(self):
|
||||||
|
"""Sendet aktuelle Library-Items ans Frontend
|
||||||
|
(LIBRARY_ITEMS-Message)."""
|
||||||
|
try:
|
||||||
|
import library
|
||||||
|
m = library.load_manifest()
|
||||||
|
self.send("LIBRARY_ITEMS", {
|
||||||
|
"items": m.get("items", []),
|
||||||
|
"libraryRoot": library.library_root(),
|
||||||
|
})
|
||||||
|
except Exception as ex:
|
||||||
|
print("[PROJECT-SETTINGS] _send_library:", ex)
|
||||||
|
|
||||||
|
def _add_library_file(self, payload):
|
||||||
|
"""Datei-Picker → kopieren in library/assets/ → Item zum
|
||||||
|
Manifest hinzufuegen. Payload: {variant: '2d'|'3d',
|
||||||
|
targetId: ?, itemType: 'symbol'|'object'}.
|
||||||
|
Wenn targetId gesetzt: bestehendes Item updaten (Datei
|
||||||
|
wird seinem files2d/3d zugewiesen). Sonst: neues Item."""
|
||||||
|
try:
|
||||||
|
import library
|
||||||
|
import Eto.Forms as forms
|
||||||
|
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"
|
||||||
|
dlg = forms.OpenFileDialog()
|
||||||
|
dlg.Title = ("Datei waehlen ({})".format(variant.upper()))
|
||||||
|
dlg.MultiSelect = False
|
||||||
|
dlg.Filters.Add(forms.FileFilter(
|
||||||
|
"Rhino 3DM", ".3dm"))
|
||||||
|
dlg.Filters.Add(forms.FileFilter(
|
||||||
|
"Andere CAD-Formate", ".dwg", ".obj", ".fbx", ".dae", ".stl"))
|
||||||
|
dlg.Filters.Add(forms.FileFilter("Alle", ".*"))
|
||||||
|
parent = bridge_holder.get("form")
|
||||||
|
res = dlg.ShowDialog(parent) if parent else dlg.ShowDialog(None)
|
||||||
|
if str(res) != "Ok":
|
||||||
|
return
|
||||||
|
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))
|
||||||
|
self.send("LIBRARY_ERROR", {
|
||||||
|
"msg": "Format .{} noch nicht unterstuetzt. "
|
||||||
|
"Konvertiere in Rhino zu .3dm.".format(ext),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
rel = library.copy_to_assets(path)
|
||||||
|
if not rel:
|
||||||
|
print("[PROJECT-SETTINGS] copy_to_assets failed")
|
||||||
|
return
|
||||||
|
if target_id:
|
||||||
|
# Bestehendes Item updaten
|
||||||
|
m = library.load_manifest()
|
||||||
|
for it in m.get("items", []):
|
||||||
|
if it.get("id") == target_id:
|
||||||
|
key = "files" + variant
|
||||||
|
it[key] = [rel]
|
||||||
|
if not it.get("type"): it["type"] = item_type
|
||||||
|
break
|
||||||
|
library.save_manifest(m)
|
||||||
|
else:
|
||||||
|
# Neues Item
|
||||||
|
import os
|
||||||
|
base = os.path.basename(path)
|
||||||
|
stem = os.path.splitext(base)[0]
|
||||||
|
import uuid as _uuid
|
||||||
|
new_id = "obj-" + _uuid.uuid4().hex[:10]
|
||||||
|
item = {
|
||||||
|
"id": new_id,
|
||||||
|
"type": item_type,
|
||||||
|
"name": stem,
|
||||||
|
"version": 1,
|
||||||
|
"tags": [],
|
||||||
|
"files" + variant: [rel],
|
||||||
|
}
|
||||||
|
library.add_item(item)
|
||||||
|
self._send_library()
|
||||||
|
except Exception as ex:
|
||||||
|
print("[PROJECT-SETTINGS] _add_library_file:", ex)
|
||||||
|
|
||||||
|
def _update_library_item(self, payload):
|
||||||
|
"""Patcht ein Item per id. payload: {id, patch: {...}}"""
|
||||||
|
try:
|
||||||
|
import library
|
||||||
|
item_id = (payload.get("id") or "").strip()
|
||||||
|
patch = payload.get("patch") or {}
|
||||||
|
if not item_id or not isinstance(patch, dict): return
|
||||||
|
library.update_item(item_id, patch)
|
||||||
|
self._send_library()
|
||||||
|
except Exception as ex:
|
||||||
|
print("[PROJECT-SETTINGS] _update_library_item:", ex)
|
||||||
|
|
||||||
|
def _delete_library_item(self, payload):
|
||||||
|
try:
|
||||||
|
import library
|
||||||
|
item_id = (payload.get("id") or "").strip()
|
||||||
|
if not item_id: return
|
||||||
|
library.delete_item(item_id)
|
||||||
|
self._send_library()
|
||||||
|
except Exception as ex:
|
||||||
|
print("[PROJECT-SETTINGS] _delete_library_item:", ex)
|
||||||
b = _ProjectSettingsBridge()
|
b = _ProjectSettingsBridge()
|
||||||
bridge_holder["form"] = panel_base.open_satellite_window(
|
bridge_holder["form"] = panel_base.open_satellite_window(
|
||||||
"project_settings",
|
"project_settings",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
openLibrary, pickTextureFile, onMessage,
|
openLibrary, pickTextureFile, onMessage,
|
||||||
renameLinetype, deleteLinetype, loadLinetypeDefaults, importLinetypeFile,
|
renameLinetype, deleteLinetype, loadLinetypeDefaults, importLinetypeFile,
|
||||||
renameHatch, deleteHatch, importHatchFile,
|
renameHatch, deleteHatch, importHatchFile,
|
||||||
|
listLibraryItems, addLibraryFile, updateLibraryItem, deleteLibraryItem,
|
||||||
} from '../lib/rhinoBridge'
|
} from '../lib/rhinoBridge'
|
||||||
|
|
||||||
/* Field — Stack-Layout fuer komplexe Inputs (mehrere Felder nebeneinander) */
|
/* Field — Stack-Layout fuer komplexe Inputs (mehrere Felder nebeneinander) */
|
||||||
@@ -638,6 +639,9 @@ export default function ProjectSettingsDialog({
|
|||||||
const [hatches, setHatches] = useState(initial.hatchPatternsFull || [])
|
const [hatches, setHatches] = useState(initial.hatchPatternsFull || [])
|
||||||
const [selLt, setSelLt] = useState(null)
|
const [selLt, setSelLt] = useState(null)
|
||||||
const [selHt, setSelHt] = useState(null)
|
const [selHt, setSelHt] = useState(null)
|
||||||
|
const [libItems, setLibItems] = useState(initial.libraryItems || [])
|
||||||
|
const [libRoot, setLibRoot] = useState(initial.libraryRoot || '')
|
||||||
|
const [selLib, setSelLib] = useState(null)
|
||||||
const builtin = initial.builtinMaterials || []
|
const builtin = initial.builtinMaterials || []
|
||||||
|
|
||||||
// Aktuell ausgewaehltes Material aus Selection ableiten
|
// Aktuell ausgewaehltes Material aus Selection ableiten
|
||||||
@@ -668,6 +672,13 @@ export default function ProjectSettingsDialog({
|
|||||||
if (lts) setLinetypes(lts)
|
if (lts) setLinetypes(lts)
|
||||||
if (hps) setHatches(hps)
|
if (hps) setHatches(hps)
|
||||||
})
|
})
|
||||||
|
onMessage('LIBRARY_ITEMS', ({ items, libraryRoot }) => {
|
||||||
|
if (Array.isArray(items)) setLibItems(items)
|
||||||
|
if (libraryRoot) setLibRoot(libraryRoot)
|
||||||
|
})
|
||||||
|
onMessage('LIBRARY_ERROR', ({ msg }) => {
|
||||||
|
if (msg) alert(msg)
|
||||||
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Backend-File-Picker-Antwort: aktualisiert das Slot im aktuell
|
// Backend-File-Picker-Antwort: aktualisiert das Slot im aktuell
|
||||||
@@ -741,6 +752,7 @@ export default function ProjectSettingsDialog({
|
|||||||
{ key: 'materials', label: 'Materialien' },
|
{ key: 'materials', label: 'Materialien' },
|
||||||
{ key: 'linetypes', label: 'Linientypen' },
|
{ key: 'linetypes', label: 'Linientypen' },
|
||||||
{ key: 'hatches', label: 'Schraffuren' },
|
{ key: 'hatches', label: 'Schraffuren' },
|
||||||
|
{ key: 'symbols', label: 'Symbole' },
|
||||||
]} active={tab} onChange={setTab} />
|
]} active={tab} onChange={setTab} />
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
@@ -1074,6 +1086,195 @@ export default function ProjectSettingsDialog({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{tab === 'symbols' && (
|
||||||
|
<div style={{ display: 'flex', height: '100%',
|
||||||
|
margin: '-8px -14px', minHeight: 0 }}>
|
||||||
|
{/* Links: Items-Liste */}
|
||||||
|
<div style={{
|
||||||
|
width: 240, flexShrink: 0,
|
||||||
|
display: 'flex', flexDirection: 'column',
|
||||||
|
borderRight: '1px solid var(--border)',
|
||||||
|
background: 'var(--bg-dialog)',
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: 1, overflowY: 'auto', padding: '4px 0' }}>
|
||||||
|
{libItems.length === 0 && (
|
||||||
|
<div style={{ padding: 20, textAlign: 'center',
|
||||||
|
color: 'var(--text-muted)', fontSize: 10 }}>
|
||||||
|
Noch keine Symbole/Objekte.
|
||||||
|
<div style={{ marginTop: 6 }}>
|
||||||
|
Erstes Item via ⬇ unten hinzufügen.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{libItems.map((it) => {
|
||||||
|
const isSel = selLib === it.id
|
||||||
|
return (
|
||||||
|
<div key={it.id}
|
||||||
|
onClick={() => setSelLib(it.id)}
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '20px 1fr',
|
||||||
|
alignItems: 'center', gap: 6,
|
||||||
|
padding: '5px 10px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
background: isSel ? 'var(--accent-dim)' : 'transparent',
|
||||||
|
borderLeft: '2px solid ' + (isSel ? 'var(--accent)' : 'transparent'),
|
||||||
|
transition: 'background 0.12s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (!isSel) e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
if (!isSel) e.currentTarget.style.background = 'transparent'
|
||||||
|
}}>
|
||||||
|
<Icon name={it.type === 'symbol' ? 'navigation' : 'forest'}
|
||||||
|
size={13}
|
||||||
|
style={{ color: 'var(--text-muted)' }} />
|
||||||
|
<div style={{ minWidth: 0 }}>
|
||||||
|
<div style={{ fontSize: 11,
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap' }}>
|
||||||
|
{it.name || 'Unbenannt'}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 9,
|
||||||
|
color: 'var(--text-muted)' }}>
|
||||||
|
{(it.files2d || []).length > 0 ? '2D ' : ''}
|
||||||
|
{(it.files3d || []).length > 0 ? '3D' : ''}
|
||||||
|
{!(it.files2d || []).length && !(it.files3d || []).length && '— leer'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 4,
|
||||||
|
padding: '6px 8px',
|
||||||
|
borderTop: '1px solid var(--border-light)' }}>
|
||||||
|
<BarToggle icon="add" label="Neues Objekt"
|
||||||
|
onClick={() => addLibraryFile('2d', null, 'object')}
|
||||||
|
title="Datei waehlen, kopiert in Library + neues Item" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rechts: Item-Detail */}
|
||||||
|
<div style={{ flex: 1, minWidth: 0, overflow: 'hidden',
|
||||||
|
padding: '12px 14px', overflowY: 'auto' }}>
|
||||||
|
{(() => {
|
||||||
|
const it = libItems.find(x => x.id === selLib)
|
||||||
|
if (!it) return (
|
||||||
|
<div style={{ padding: 40, textAlign: 'center',
|
||||||
|
color: 'var(--text-muted)', fontSize: 11 }}>
|
||||||
|
Item links auswählen oder neues anlegen.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DetailSection title="Identität">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<Icon name={it.type === 'symbol' ? 'navigation' : 'forest'}
|
||||||
|
size={28}
|
||||||
|
style={{ color: 'var(--accent)' }} />
|
||||||
|
<input type="text" value={it.name || ''}
|
||||||
|
onChange={(ev) => {
|
||||||
|
const nm = ev.target.value
|
||||||
|
setLibItems(arr => arr.map(x =>
|
||||||
|
x.id === it.id ? { ...x, name: nm } : x))
|
||||||
|
}}
|
||||||
|
onBlur={(ev) => updateLibraryItem(it.id,
|
||||||
|
{ name: ev.target.value })}
|
||||||
|
style={{ flex: 1, height: BAR_H, padding: '0 12px',
|
||||||
|
fontSize: 12, fontWeight: 500 }} />
|
||||||
|
<BarButton icon="delete"
|
||||||
|
onClick={() => {
|
||||||
|
deleteLibraryItem(it.id)
|
||||||
|
setSelLib(null)
|
||||||
|
}}
|
||||||
|
title="Item löschen" />
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 8, marginTop: 6 }}>
|
||||||
|
<BarToggle label="Symbol (nur 2D)"
|
||||||
|
active={it.type === 'symbol'}
|
||||||
|
onClick={() => updateLibraryItem(it.id,
|
||||||
|
{ type: 'symbol' })} />
|
||||||
|
<BarToggle label="Objekt (2D + 3D)"
|
||||||
|
active={it.type === 'object'}
|
||||||
|
onClick={() => updateLibraryItem(it.id,
|
||||||
|
{ type: 'object' })} />
|
||||||
|
</div>
|
||||||
|
</DetailSection>
|
||||||
|
|
||||||
|
<DetailSection title="2D-Darstellung (Plan)">
|
||||||
|
{(it.files2d || []).length === 0 ? (
|
||||||
|
<div style={{ fontSize: 10,
|
||||||
|
color: 'var(--text-muted)',
|
||||||
|
padding: '6px 0 8px' }}>
|
||||||
|
Keine 2D-Datei zugewiesen.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ fontSize: 10,
|
||||||
|
fontFamily: 'var(--font-mono)',
|
||||||
|
color: 'var(--text-muted)',
|
||||||
|
padding: '4px 0' }}>
|
||||||
|
{(it.files2d || []).join(', ')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<BarToggle icon="upload_file" label="Datei wählen"
|
||||||
|
onClick={() => addLibraryFile('2d', it.id, it.type)} />
|
||||||
|
</DetailSection>
|
||||||
|
|
||||||
|
<DetailSection title="3D-Darstellung (Perspektive)">
|
||||||
|
{(it.files3d || []).length === 0 ? (
|
||||||
|
<div style={{ fontSize: 10,
|
||||||
|
color: 'var(--text-muted)',
|
||||||
|
padding: '6px 0 8px' }}>
|
||||||
|
Keine 3D-Datei zugewiesen.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ fontSize: 10,
|
||||||
|
fontFamily: 'var(--font-mono)',
|
||||||
|
color: 'var(--text-muted)',
|
||||||
|
padding: '4px 0' }}>
|
||||||
|
{(it.files3d || []).join(', ')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<BarToggle icon="upload_file" label="Datei wählen"
|
||||||
|
onClick={() => addLibraryFile('3d', it.id, it.type)} />
|
||||||
|
</DetailSection>
|
||||||
|
|
||||||
|
<DetailSection title="Tags">
|
||||||
|
<input type="text"
|
||||||
|
value={(it.tags || []).join(', ')}
|
||||||
|
placeholder="komma-getrennt"
|
||||||
|
onChange={(ev) => {
|
||||||
|
const tg = ev.target.value
|
||||||
|
setLibItems(arr => arr.map(x =>
|
||||||
|
x.id === it.id ? {
|
||||||
|
...x, tags: tg.split(',').map(s => s.trim()).filter(Boolean)
|
||||||
|
} : x))
|
||||||
|
}}
|
||||||
|
onBlur={(ev) => updateLibraryItem(it.id, {
|
||||||
|
tags: ev.target.value.split(',')
|
||||||
|
.map(s => s.trim()).filter(Boolean)
|
||||||
|
})}
|
||||||
|
style={{ width: '100%', height: BAR_H,
|
||||||
|
padding: '0 12px', fontSize: 11,
|
||||||
|
boxSizing: 'border-box' }} />
|
||||||
|
</DetailSection>
|
||||||
|
|
||||||
|
<div style={{ fontSize: 9, color: 'var(--text-muted)',
|
||||||
|
marginTop: 14, lineHeight: 1.5 }}>
|
||||||
|
ID: <span style={{ fontFamily: 'var(--font-mono)' }}>{it.id}</span><br />
|
||||||
|
Library: <span style={{ fontFamily: 'var(--font-mono)' }}>{libRoot}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer — Pill-Buttons */}
|
{/* Footer — Pill-Buttons */}
|
||||||
|
|||||||
@@ -204,6 +204,19 @@ export function importLinetypeFile() { send('IMPORT_LINETYPE_FILE', {})
|
|||||||
export function renameHatch(index, name) { send('RENAME_HATCH', { index, name }) }
|
export function renameHatch(index, name) { send('RENAME_HATCH', { index, name }) }
|
||||||
export function deleteHatch(index) { send('DELETE_HATCH', { index }) }
|
export function deleteHatch(index) { send('DELETE_HATCH', { index }) }
|
||||||
export function importHatchFile() { send('IMPORT_HATCH_FILE', {}) }
|
export function importHatchFile() { send('IMPORT_HATCH_FILE', {}) }
|
||||||
|
// Library-Item-CRUD (Project-Settings → Symbole-Tab)
|
||||||
|
export function listLibraryItems() { send('LIST_LIBRARY_ITEMS', {}) }
|
||||||
|
export function addLibraryFile(variant, targetId, itemType) {
|
||||||
|
send('ADD_LIBRARY_FILE', {
|
||||||
|
variant: variant || '2d',
|
||||||
|
targetId: targetId || null,
|
||||||
|
itemType: itemType || 'object',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function updateLibraryItem(id, patch) {
|
||||||
|
send('UPDATE_LIBRARY_ITEM', { id, patch })
|
||||||
|
}
|
||||||
|
export function deleteLibraryItem(id) { send('DELETE_LIBRARY_ITEM', { id }) }
|
||||||
// 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