feat: Hull/Fill nodes freely addable+deletable; remove Fill view tab
NodeGraph: isFixed now only applies to Source — Hull and Fill are fully deletable, supporting arbitrary multi-pass branching (multiple Hull/Fill chains from the same detection graph). addNode() handles Hull (defaultHullParams) and Fill (defaultFillParams). Toolbar gains teal '+ Hull' and purple '+ Fill' buttons alongside the existing Kernel/Combine buttons. App.jsx: Remove 'fill' from VIEW_MODES — fill output is visible in the detection graph's Fill node thumbnail and in the G-code view. Remove the dead case 'fill': branch from the viewport refresh switch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { defaultPass, defaultGcodeConfig, PAPER_SIZES } from './store.js'
|
|||||||
import * as tauri from './hooks/useTauri.js'
|
import * as tauri from './hooks/useTauri.js'
|
||||||
import { useFps } from './hooks/useFps.js'
|
import { useFps } from './hooks/useFps.js'
|
||||||
|
|
||||||
const VIEW_MODES = ['source', 'detection', 'contours', 'fill', 'gcode']
|
const VIEW_MODES = ['source', 'detection', 'contours', 'gcode']
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [image, setImage] = useState(null)
|
const [image, setImage] = useState(null)
|
||||||
@@ -101,10 +101,6 @@ export default function App() {
|
|||||||
setDisplayB64(null)
|
setDisplayB64(null)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'fill':
|
|
||||||
// Fill uses the same full-res canvas rendering as gcode (via strokes prop).
|
|
||||||
setDisplayB64(null)
|
|
||||||
break
|
|
||||||
case 'gcode':
|
case 'gcode':
|
||||||
if (passes.some(p => p.strokeCount > 0)) {
|
if (passes.some(p => p.strokeCount > 0)) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -189,9 +189,10 @@ export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB6
|
|||||||
const x = (r.width / 2 - p.x) / z - NODE_W / 2
|
const x = (r.width / 2 - p.x) / z - NODE_W / 2
|
||||||
const y = (r.height / 2 - p.y) / z - 60
|
const y = (r.height / 2 - p.y) / z - 60
|
||||||
const id = newNodeId(kind)
|
const id = newNodeId(kind)
|
||||||
const node = kind === 'Kernel'
|
const node = kind === 'Kernel' ? { id, kind, x, y, ...defaultKernelProps() }
|
||||||
? { id, kind, x, y, ...defaultKernelProps() }
|
: kind === 'Hull' ? { id, kind, x, y, ...defaultHullParams() }
|
||||||
: { id, kind, x, y, blend_mode: 'Average', inputCount: 2 }
|
: kind === 'Fill' ? { id, kind, x, y, ...defaultFillParams() }
|
||||||
|
: { id, kind, x, y, blend_mode: 'Average', inputCount: 2 }
|
||||||
const g = graphRef.current
|
const g = graphRef.current
|
||||||
onChangeRef.current({ ...g, nodes: [...g.nodes, node] })
|
onChangeRef.current({ ...g, nodes: [...g.nodes, node] })
|
||||||
}
|
}
|
||||||
@@ -222,7 +223,7 @@ export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB6
|
|||||||
|
|
||||||
// ── Node rendering ─────────────────────────────────────────────────────────
|
// ── Node rendering ─────────────────────────────────────────────────────────
|
||||||
function renderNode(node) {
|
function renderNode(node) {
|
||||||
const isFixed = node.kind === 'Source' || node.kind === 'Hull' || node.kind === 'Fill'
|
const isFixed = node.kind === 'Source'
|
||||||
const inputCnt = node.kind === 'Combine' ? (node.inputCount ?? 2)
|
const inputCnt = node.kind === 'Combine' ? (node.inputCount ?? 2)
|
||||||
: (node.kind === 'Kernel' || node.kind === 'Output' || node.kind === 'Hull' || node.kind === 'Fill') ? 1 : 0
|
: (node.kind === 'Kernel' || node.kind === 'Output' || node.kind === 'Hull' || node.kind === 'Fill') ? 1 : 0
|
||||||
const hasOut = node.kind !== 'Output' && node.kind !== 'Hull' && node.kind !== 'Fill'
|
const hasOut = node.kind !== 'Output' && node.kind !== 'Hull' && node.kind !== 'Fill'
|
||||||
@@ -440,12 +441,16 @@ export default function NodeGraph({ graph, onChange, nodePreviews, sourceImageB6
|
|||||||
>
|
>
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div style={{ position: 'absolute', top: 8, left: 8, zIndex: 30, display: 'flex', gap: 4, alignItems: 'center' }}>
|
<div style={{ position: 'absolute', top: 8, left: 8, zIndex: 30, display: 'flex', gap: 4, alignItems: 'center' }}>
|
||||||
<button onClick={() => addNode('Kernel')}
|
{[
|
||||||
style={{ padding: '3px 10px', borderRadius: 4, fontSize: 11, cursor: 'pointer', border: '1px solid #374151', background: '#1e293b', color: '#94a3b8' }}
|
['Kernel', '#374151', '#94a3b8'],
|
||||||
>+ Kernel</button>
|
['Combine', '#374151', '#94a3b8'],
|
||||||
<button onClick={() => addNode('Combine')}
|
['Hull', '#0d9488', '#5eead4'],
|
||||||
style={{ padding: '3px 10px', borderRadius: 4, fontSize: 11, cursor: 'pointer', border: '1px solid #374151', background: '#1e293b', color: '#94a3b8' }}
|
['Fill', '#7c3aed', '#c4b5fd'],
|
||||||
>+ Combine</button>
|
].map(([kind, border, color]) => (
|
||||||
|
<button key={kind} onClick={() => addNode(kind)}
|
||||||
|
style={{ padding: '3px 10px', borderRadius: 4, fontSize: 11, cursor: 'pointer', border: `1px solid ${border}`, background: '#1e293b', color }}
|
||||||
|
>+ {kind}</button>
|
||||||
|
))}
|
||||||
<span style={{ fontSize: 10, color: '#2d3748', paddingLeft: 4 }}>
|
<span style={{ fontSize: 10, color: '#2d3748', paddingLeft: 4 }}>
|
||||||
scroll=zoom · drag=pan · click wire=delete
|
scroll=zoom · drag=pan · click wire=delete
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user