Library-Thumbnails: Auto-Capture + Base64-Preview in der UI
Beim Hinzufuegen oder Importieren eines Library-Items wird automatisch ein PNG-Thumbnail vom Item generiert (Top-View, 128x128) und in library/previews/<id>.png abgelegt. Frontend rendert die Previews als Base64-Data-URIs (sicher gegen WebKit-file://-Restriktionen). library.py: - _previews_dir(): legt previews/-Folder an - _preview_rel_for(asset_rel): predictable PNG-Pfad pro Item - _capture_thumbnail_of_objects(): hided andere Objekte temporaer, switcht auf Top-Parallel, ZoomBoundingBox, CaptureToBitmap → PNG, restored Viewport + Hidden-State - read_preview_data_uri(): liest PNG + encoded als data:image/png;base64 - Hook in convert_to_3dm_via_import (vor Cleanup) + save_selection_to_asset rhinopanel.py: - _enrich_library_items_with_previews(): haengt previewDataUri an jedes Item das ein preview-Feld hat - Initial-Params + _send_library + ElementeBridge._cmd_list_library liefern angereicherte Items - _add_library_file + _save_selection_as_library setzen preview-Pfad im Item wenn Thumbnail-Datei existiert Frontend: - SymbolPicker.ItemPreview: rendert <div backgroundImage> mit Base64-URI wenn vorhanden, sonst Icon-Fallback - ProjectSettingsDialog Symbole-Tab: List-Row + Detail-Identity zeigen Thumbnail (32px in Liste, 56px im Detail), Icon nur als Fallback Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1115,8 +1115,8 @@ export default function ProjectSettingsDialog({
|
||||
onClick={() => setSelLib(it.id)}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '20px 1fr',
|
||||
alignItems: 'center', gap: 6,
|
||||
gridTemplateColumns: '32px 1fr',
|
||||
alignItems: 'center', gap: 8,
|
||||
padding: '5px 10px',
|
||||
cursor: 'pointer',
|
||||
background: isSel ? 'var(--accent-dim)' : 'transparent',
|
||||
@@ -1129,9 +1129,21 @@ export default function ProjectSettingsDialog({
|
||||
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={{
|
||||
width: 32, height: 32, borderRadius: 4,
|
||||
background: it.previewDataUri
|
||||
? `url("${it.previewDataUri}") center/contain no-repeat, var(--bg-input)`
|
||||
: 'var(--bg-input)',
|
||||
border: '1px solid var(--border-light)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{!it.previewDataUri && (
|
||||
<Icon name={it.type === 'symbol' ? 'navigation' : 'forest'}
|
||||
size={14}
|
||||
style={{ color: 'var(--text-muted)' }} />
|
||||
)}
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontSize: 11,
|
||||
color: 'var(--text-primary)',
|
||||
@@ -1177,10 +1189,22 @@ export default function ProjectSettingsDialog({
|
||||
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)' }} />
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{
|
||||
width: 56, height: 56, borderRadius: 6,
|
||||
background: it.previewDataUri
|
||||
? `url("${it.previewDataUri}") center/contain no-repeat, var(--bg-input)`
|
||||
: 'var(--bg-input)',
|
||||
border: '1px solid var(--border)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
{!it.previewDataUri && (
|
||||
<Icon name={it.type === 'symbol' ? 'navigation' : 'forest'}
|
||||
size={22}
|
||||
style={{ color: 'var(--accent)' }} />
|
||||
)}
|
||||
</div>
|
||||
<input type="text" value={it.name || ''}
|
||||
onChange={(ev) => {
|
||||
const nm = ev.target.value
|
||||
|
||||
Reference in New Issue
Block a user