feat: persist primitive task parameters

This commit is contained in:
2026-08-02 19:33:43 -04:00
parent 589b2eb0f6
commit ca9b18ea59
3 changed files with 15 additions and 2 deletions

View File

@@ -347,7 +347,11 @@ function TaskPanel({ workbench, facade, showNotice }: { workbench: Workbench; fa
const isSketch = workbench === 'Sketcher'
const activeTask = facade.task.getActive()
const primitiveType = activeTask?.commandId === 'primitive' && typeof activeTask.draft.primitiveType === 'string' ? activeTask.draft.primitiveType : 'Box'
return <div className="task-panel"><div className="task-actions-top"><button className="button button-primary" onClick={() => { facade.task.apply(); showNotice('Task accepted') }}>OK</button><button className="button button-outline" onClick={() => { facade.task.update({ preview: true }); showNotice('Preview applied') }}>Apply</button><button className="button button-quiet" onClick={() => { facade.task.cancel(); showNotice('Task cancelled') }}>Cancel</button></div><div className="task-header"><div className="task-icon"><Pencil size={16} /></div><div><span className="eyebrow">Active command</span><h2>{isSketch ? 'Edit Sketch' : definition.taskTitle}</h2></div><Badge tone="cyan">Preview</Badge></div><div className="task-body"><div className="task-step"><span className="step-index">1</span><div><strong>{isSketch ? 'Geometry and constraints' : definition.objectType}</strong><span>{definition.taskSummary}</span></div></div>{activeTask?.commandId === 'primitive' && <label className="field-label">Primitive type<select className="field-input" value={primitiveType} onChange={(event) => facade.task.update({ primitiveType: event.target.value })}><option>Box</option><option>Cylinder</option><option>Sphere</option><option>Cone</option></select></label>}<label className="field-label">Primary value <span className="field-unit">mm</span><input className="field-input" value={isSketch ? 'Fully constrained' : '42.00'} readOnly /></label><label className="field-label">Operation<select className="field-input"><option>{workbench === 'Part Design' ? 'Dimension' : 'Contextual preview'}</option><option>Through all</option><option>Up to face</option></select></label><label className="check-row"><input type="checkbox" defaultChecked /><span>Preview result in viewport</span></label><div className="task-note"><AlertTriangle size={14} /><span>Changes remain local until the document is recomputed.</span></div></div></div>
const primitiveFields = primitiveType === 'Box' ? ['width', 'length', 'height'] : primitiveType === 'Cylinder' ? ['radius', 'height', 'angle'] : primitiveType === 'Sphere' ? ['radius'] : ['radius1', 'radius2', 'height', 'angle']
const primitiveUnits: Record<string, string> = { width: 'mm', length: 'mm', height: 'mm', radius: 'mm', radius1: 'mm', radius2: 'mm', angle: 'deg' }
const primitiveFallbacks: Record<string, number> = { width: 10, length: 10, height: 10, radius: 5, radius1: 5, radius2: 0, angle: 360 }
const primitiveLabel = (field: string) => field === 'radius1' ? 'Bottom radius' : field === 'radius2' ? 'Top radius' : field[0].toUpperCase() + field.slice(1)
return <div className="task-panel"><div className="task-actions-top"><button className="button button-primary" onClick={() => { facade.task.apply(); showNotice('Task accepted') }}>OK</button><button className="button button-outline" onClick={() => { facade.task.update({ preview: true }); showNotice('Preview applied') }}>Apply</button><button className="button button-quiet" onClick={() => { facade.task.cancel(); showNotice('Task cancelled') }}>Cancel</button></div><div className="task-header"><div className="task-icon"><Pencil size={16} /></div><div><span className="eyebrow">Active command</span><h2>{isSketch ? 'Edit Sketch' : definition.taskTitle}</h2></div><Badge tone="cyan">Preview</Badge></div><div className="task-body"><div className="task-step"><span className="step-index">1</span><div><strong>{isSketch ? 'Geometry and constraints' : definition.objectType}</strong><span>{definition.taskSummary}</span></div></div>{activeTask?.commandId === 'primitive' && <><label className="field-label">Primitive type<select className="field-input" value={primitiveType} onChange={(event) => facade.task.update({ primitiveType: event.target.value })}><option>Box</option><option>Cylinder</option><option>Sphere</option><option>Cone</option></select></label>{primitiveFields.map((field) => <label className="field-label" key={field}>{primitiveLabel(field)} <span className="field-unit">{primitiveUnits[field]}</span><input className="field-input" type="number" min={field === 'radius2' ? 0 : 0.001} step={primitiveUnits[field] === 'deg' ? 1 : 0.1} value={typeof activeTask.draft[field] === 'number' ? Number(activeTask.draft[field]) : primitiveFallbacks[field]} onChange={(event) => facade.task.update({ [field]: Number(event.target.value) })} /></label>)}</>}<label className="field-label">Primary value <span className="field-unit">mm</span><input className="field-input" value={isSketch ? 'Fully constrained' : '42.00'} readOnly /></label><label className="field-label">Operation<select className="field-input"><option>{workbench === 'Part Design' ? 'Dimension' : 'Contextual preview'}</option><option>Through all</option><option>Up to face</option></select></label><label className="check-row"><input type="checkbox" defaultChecked /><span>Preview result in viewport</span></label><div className="task-note"><AlertTriangle size={14} /><span>Changes remain local until the document is recomputed.</span></div></div></div>
}
function PropertyPanel({ facade, objectId, scope, showNotice }: { facade: BitBybitWebCadFacade; objectId: string; scope: 'data' | 'view'; showNotice: (message: string) => void }) {

View File

@@ -303,6 +303,12 @@ export function createMockFacade(): BitBybitWebCadFacade {
tree.push(item)
}
const objectSnapshot = createObjectSnapshot(item)
if (commandId === 'primitive') {
objectSnapshot.properties = objectSnapshot.properties.map((property) => {
const candidate = draft[property.name.toLowerCase()]
return typeof candidate === 'number' && Number.isFinite(candidate) ? { ...property, value: candidate } : property
})
}
if (isPartObject && commandId !== 'primitive') {
objectSnapshot.properties = objectSnapshot.properties.map((property) => property.name === 'Base' && state.selectedObjectId ? { ...property, value: state.selectedObjectId } : property)
}

View File

@@ -794,9 +794,12 @@ test('Part workbench commands create primitive and boolean document objects', ()
facade.task.apply()
assert.equal(facade.app.document.getObject('box')?.typeId, 'Part::Box')
facade.gui.command.execute({ commandId: 'primitive' })
facade.task.update({ primitiveType: 'Cylinder' })
facade.task.update({ primitiveType: 'Cylinder', radius: 3, height: 12, angle: 180 })
facade.task.apply()
assert.equal(facade.app.document.getObject('cylinder')?.typeId, 'Part::Cylinder')
assert.equal(facade.app.document.getObject('cylinder')?.properties.find((property) => property.name === 'Radius')?.value, 3)
assert.equal(facade.app.document.getObject('cylinder')?.properties.find((property) => property.name === 'Height')?.value, 12)
assert.equal(facade.app.document.getObject('cylinder')?.properties.find((property) => property.name === 'Angle')?.value, 180)
assert.equal(facade.gui.command.getState('union').status, 'enabled')
facade.gui.command.execute({ commandId: 'union' })