task(D12): block attributes - BlockAttrDef type, attrDefs on definitions, instance attrValues rendering as text primitives, properties panel attribute form
This commit is contained in:
@@ -3,6 +3,7 @@ import { polygonArea } from '../tools/modification/geometry';
|
||||
import { formatArea } from '../utils/format';
|
||||
import type { PropertiesPanelProps } from '../types/ui.types';
|
||||
import { formatInputValue, parseDistance, type UnitType } from '../utils/format';
|
||||
import { getActiveDocument } from '../kernel/document/documentService';
|
||||
|
||||
const parseNum = (s: string) => parseFloat(s.replace(/[^0-9.\-]/g, '')) || 0;
|
||||
|
||||
@@ -77,7 +78,37 @@ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
||||
<label className="prop-label">Fläche</label>
|
||||
<span className="prop-value">{formatArea(polygonArea(el.properties.points as Array<{ x: number; y: number }>), unit, scaleFactor)}</span>
|
||||
</div>
|
||||
)}` : ''}</span>
|
||||
)}
|
||||
|
||||
{/* ─── Task D12: Block-Attribute-Formular (block_instance) ─── */}
|
||||
{el.type === 'block_instance' && (() => {
|
||||
const blockId = el.properties.blockId as string | undefined;
|
||||
if (!blockId) return null;
|
||||
const block = getActiveDocument()?.getBlock(blockId);
|
||||
const attrDefs = block?.attrDefs ?? [];
|
||||
if (attrDefs.length === 0) return null;
|
||||
const attrValues = (el.properties.attrValues as Record<string, string> | undefined) ?? {};
|
||||
return (
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title">Block-Attribute</div>
|
||||
{attrDefs.map((def) => (
|
||||
<div className="prop-row" key={def.tag}>
|
||||
<label className="prop-label" title={def.prompt}>{def.tag}</label>
|
||||
<input
|
||||
className="prop-input"
|
||||
type="text"
|
||||
value={attrValues[def.tag] ?? def.defaultValue ?? ''}
|
||||
placeholder={def.prompt}
|
||||
onChange={(ev) => {
|
||||
const next = { ...attrValues, [def.tag]: ev.target.value };
|
||||
onUpdateProperty('attrValues', next);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})()}` : ''}</span>
|
||||
<div style={{ display: 'flex', gap: '4px' }}>
|
||||
{onDelete && el && (
|
||||
<button
|
||||
|
||||
@@ -42,7 +42,7 @@ function fillOrNull(raw: unknown): string | null {
|
||||
* Gibt ein leeres Array zurück für unbekannte Typen bzw. Elemente,
|
||||
* die nichts Sichtbares beitragen (z.B. leerer Text).
|
||||
*/
|
||||
export function elementToPrimitives(el: CADElement): ShapePrimitive[] {
|
||||
export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: string; prompt: string; defaultValue?: string }>): ShapePrimitive[] {
|
||||
const props = (el.properties ?? {}) as Record<string, unknown>;
|
||||
const stroke = (props.stroke as string | undefined) ?? FALLBACK_STROKE;
|
||||
const sw = (props.strokeWidth as number | undefined) ?? 1;
|
||||
@@ -296,6 +296,28 @@ export function elementToPrimitives(el: CADElement): ShapePrimitive[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
case 'block_instance': {
|
||||
// Task D12: Attr-Texte der Instanz zeichnen (tag=Wert, default-Fallback).
|
||||
const attrValues = (props.attrValues as Record<string, string> | undefined) ?? {};
|
||||
if (!attrDefs || attrDefs.length === 0) return [];
|
||||
const out: ShapePrimitive[] = [];
|
||||
const fontSize = 9;
|
||||
const lineH = 16;
|
||||
let i = 0;
|
||||
for (const def of attrDefs) {
|
||||
const value = attrValues[def.tag] ?? def.defaultValue ?? '—';
|
||||
out.push({
|
||||
kind: 'text',
|
||||
at: { x: el.x, y: el.y - 8 - i * lineH },
|
||||
text: `${def.tag}=${value}`,
|
||||
fontSize,
|
||||
color: stroke,
|
||||
});
|
||||
i++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -63,6 +63,16 @@ export interface ImageElement extends CADElement {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Block-Attribut-Definition (Task D12): Slot, den Instanzen befüllen.
|
||||
* Konvention konsistent zu F5 (ATTDEF-Parser) und F6 (DXF-Writer).
|
||||
*/
|
||||
export interface BlockAttrDef {
|
||||
tag: string;
|
||||
prompt: string;
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
export interface BlockDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -70,6 +80,8 @@ export interface BlockDefinition {
|
||||
category: string;
|
||||
elements: CADElement[];
|
||||
thumbnail?: string;
|
||||
/** Attribut-Slots dieser Blockdefinition (Task D12). */
|
||||
attrDefs?: BlockAttrDef[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user