From 51bfd11dc8ab79259a0c404c449e7470f874d2ce Mon Sep 17 00:00:00 2001 From: Ivan Chirkin Date: Sat, 18 Jul 2026 18:52:47 +0300 Subject: [PATCH] feat(drill): mirror points-wizard selection on the drill map --- .../src/components/drill/DrillMapCanvas.tsx | 27 +++++++++++--- .../components/drill/DrillPlanInspector.tsx | 7 +++- .../components/drill/WorkZeroPointsWizard.tsx | 23 ++++++++++++ .../operations/DrillOperationEditor.tsx | 36 +++++++++++++++---- .../components/panel/AlignmentPointLayer.tsx | 12 +++++-- 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/cuprum-ui/src/components/drill/DrillMapCanvas.tsx b/cuprum-ui/src/components/drill/DrillMapCanvas.tsx index 3419130a..136635be 100644 --- a/cuprum-ui/src/components/drill/DrillMapCanvas.tsx +++ b/cuprum-ui/src/components/drill/DrillMapCanvas.tsx @@ -22,7 +22,11 @@ import { enumerateHoles } from "@/lib/drillSelection"; import { useDrillHoleHover } from "@/hooks/useDrillHoleHover"; import { DrillCanvasToolPalette } from "./DrillCanvasToolPalette"; import { AlignmentPointLayer } from "@/components/panel/AlignmentPointLayer"; -import { alignmentPointOrdinals, type EffectiveAlignmentPoint } from "@/lib/alignmentPoints"; +import { + alignmentPointOrdinals, + cornerOfPoint, + type EffectiveAlignmentPoint, +} from "@/lib/alignmentPoints"; // Re-export Viewport so callers (Task 2 rulers) can import it from here directly. export type { Viewport }; @@ -301,9 +305,16 @@ export interface DrillMapCanvasProps { onInspectHole?: (id: string | null) => void; /** Stable id of the inspected hole (drives the copper selection ring). */ inspectedHoleId?: string | null; - /** Effective alignment points (auto fiducials + user points) drawn as labelled - * crosshair markers so the map matches the work-zero wizard list. */ + /** Effective alignment points (auto fiducials + user points, plus selected + * panel corners while the points wizard is open) drawn as labelled crosshair + * markers so the map matches the work-zero wizard list. */ alignmentPoints?: EffectiveAlignmentPoint[]; + /** Alignment points excluded from the wizard selection — rendered dimmed and + * unlabelled, like unselected holes. Absent = no dimming (wizard closed). */ + dimmedAlignmentIds?: Set; + /** Point currently being captured in the wizard — gets a copper highlight + * ring so the operator sees on the map which point the machine is after. */ + activeAlignmentPointId?: string | null; } /** Read-only 2D drill map canvas: panel outline, holes by tool colour, traverse @@ -311,7 +322,7 @@ export interface DrillMapCanvasProps { * indicator. Hole coordinates are panel-space mm (0,0 = top-left of blank). The * work-zero marker is placed at the chosen datum corner (default: bottom-left). * Supports pinch/scroll zoom and Space-to-pan, mirroring PanelBlankCanvas. */ -export function DrillMapCanvas({ widthMm, heightMm, plan, route, zones, machineWork, datum = "bottom-left", selectedHoleIds, drilledHoleIds, currentHoleId, showPath = true, showDiameters = false, currentHolePhase, currentBitColor, runIdle, currentPhaseLabel, onViewportChange, onToggleHole, onInspectHole, inspectedHoleId, alignmentPoints }: DrillMapCanvasProps) { +export function DrillMapCanvas({ widthMm, heightMm, plan, route, zones, machineWork, datum = "bottom-left", selectedHoleIds, drilledHoleIds, currentHoleId, showPath = true, showDiameters = false, currentHolePhase, currentBitColor, runIdle, currentPhaseLabel, onViewportChange, onToggleHole, onInspectHole, inspectedHoleId, alignmentPoints, dimmedAlignmentIds, activeAlignmentPointId }: DrillMapCanvasProps) { // Ref to the fit-group for pointer → mm coordinate conversion. const fitGroupRef = useRef(null); const W = Math.max(widthMm, 1); @@ -340,6 +351,11 @@ export function DrillMapCanvas({ widthMm, heightMm, plan, route, zones, machineW const ord = alignmentPointOrdinals(alignmentPoints); const m = new Map(); for (const p of alignmentPoints) { + const corner = cornerOfPoint(p); + if (corner) { + m.set(p.point.id, t("wizard2.pointNameCorner", { corner: t(`datum.${corner}`) })); + continue; + } const n = ord.get(p.point.id) ?? 0; m.set( p.point.id, @@ -465,10 +481,11 @@ export function DrillMapCanvas({ widthMm, heightMm, plan, route, zones, machineW {alignPointList.length > 0 && viewport.pxPerMm > 0 && ( )} diff --git a/cuprum-ui/src/components/drill/DrillPlanInspector.tsx b/cuprum-ui/src/components/drill/DrillPlanInspector.tsx index 779d2f51..47723f0a 100644 --- a/cuprum-ui/src/components/drill/DrillPlanInspector.tsx +++ b/cuprum-ui/src/components/drill/DrillPlanInspector.tsx @@ -22,7 +22,7 @@ import { DrillPreflightSummary } from "@/components/drill/DrillPreflightSummary" import { WorkZeroStatusCard } from "@/components/drill/WorkZeroStatusCard"; import { ConnBar } from "@/components/machine/ConnBar"; import { DrillZeroInspector } from "@/components/drill/DrillZeroInspector"; -import { WorkZeroPointsWizard } from "@/components/drill/WorkZeroPointsWizard"; +import { WorkZeroPointsWizard, type WizardMapState } from "@/components/drill/WorkZeroPointsWizard"; import { api } from "@/lib/api"; import { formatXYViolations } from "@/lib/xyGate"; import { formatZReasons } from "@/lib/zGate"; @@ -81,6 +81,9 @@ export interface DrillPlanInspectorProps { maxZMm: number; /** Last work-zero bind error from GRBL (null = none). Shown as a banner. */ zeroError: string | null; + /** Points-wizard state relay for the drill map (dim non-selected points, + * highlight the one being captured). See WorkZeroPointsWizard. */ + onWizardMapStateChange?: (state: WizardMapState | null) => void; /** Pre-computed XY gate result (hole bbox vs machine envelope) for the start button. */ xyGate: XYGateResult; /** Pre-computed Z gate result (depth / tool-change retract vs Z travel). */ @@ -127,6 +130,7 @@ export function DrillPlanInspector({ run, onStart, onSetClass, + onWizardMapStateChange, onSetBitOverride, selectedHoleId, onClearHole, @@ -351,6 +355,7 @@ export function DrillPlanInspector({ /* ── POINTS WIZARD mode (method 2 · manual capture) ── */ void; + /** Mirrors the wizard's point state onto the drill map: which points are in + * the selection (others are dimmed) and which one is being captured now. + * Reported null when the wizard unmounts (map returns to normal). */ + onMapStateChange?: (state: WizardMapState | null) => void; +} + +/** Wizard point state projected onto the drill map (see onMapStateChange). */ +export interface WizardMapState { + /** Ids of the points in the wizard selection (or the frozen capture session). */ + selectedIds: Set; + /** Id of the point currently being captured; null outside the capture step. */ + currentId: string | null; } type WizardStep = "select" | "capture" | "result"; @@ -107,6 +119,7 @@ export function WorkZeroPointsWizard({ plan, onCancel, onApplied, + onMapStateChange, }: WorkZeroPointsWizardProps) { const { t } = useTranslation("drill"); const { fmtLen } = useUnitFormat(); @@ -144,6 +157,16 @@ export function WorkZeroPointsWizard({ // Cancels an in-flight auto-navigate sequence (STOP / leave). const navCancelRef = useRef(0); + // Mirror the wizard's point state onto the drill map (dim non-selected points, + // highlight the one being captured); cleared when the wizard unmounts. + useEffect(() => { + onMapStateChange?.({ + selectedIds, + currentId: step === "capture" ? (sessionPoints[curIdx]?.point.id ?? null) : null, + }); + }, [onMapStateChange, selectedIds, step, sessionPoints, curIdx]); + useEffect(() => () => onMapStateChange?.(null), [onMapStateChange]); + // Display names: "Fiducial N" for registration-derived points, "Point N" for // user-placed ones, numbered independently per source (shared ordinals with // the drill-map overlay so both label the same point identically). Corner diff --git a/cuprum-ui/src/components/operations/DrillOperationEditor.tsx b/cuprum-ui/src/components/operations/DrillOperationEditor.tsx index 3a74110d..5d05b0c8 100644 --- a/cuprum-ui/src/components/operations/DrillOperationEditor.tsx +++ b/cuprum-ui/src/components/operations/DrillOperationEditor.tsx @@ -31,7 +31,8 @@ import { useWorkZeroMethod } from "@/workZeroMethodStore"; import { useMachine } from "@/machineStore"; import { api } from "@/lib/api"; import { useDrillGates } from "@/hooks/useDrillGates"; -import { effectiveAlignmentPoints } from "@/lib/alignmentPoints"; +import { effectiveAlignmentPoints, panelCornerPoints } from "@/lib/alignmentPoints"; +import type { WizardMapState } from "@/components/drill/WorkZeroPointsWizard"; /** Phases in which a run is live; a transition out of this set into done/error/idle * is the run's terminal event (used to journal the outcome). */ @@ -535,12 +536,32 @@ export function DrillOperationEditor({ snapshot }: { snapshot: DrillSnapshot }) const hasAnyHoles = !!(plan && plan.totalHoles > 0); + // Points-wizard state relayed from the inspector: while the wizard is open, + // the map dims points outside its selection and highlights the one being + // captured. Null = wizard closed, map renders all points normally. + const [wizardMapState, setWizardMapState] = useState(null); + // Effective alignment points (auto fiducials + user points) drawn on the map - // with labels matching the work-zero wizard list. - const mapAlignmentPoints = useMemo( - () => effectiveAlignmentPoints(panel?.tooling_holes ?? [], panel?.alignment_points ?? []), - [panel?.tooling_holes, panel?.alignment_points], - ); + // with labels matching the work-zero wizard list. While the wizard is open, + // panel corners it has in the selection are appended so they show up too. + const mapAlignmentPoints = useMemo(() => { + const base = effectiveAlignmentPoints(panel?.tooling_holes ?? [], panel?.alignment_points ?? []); + if (!wizardMapState || !panel) return base; + const corners = panelCornerPoints(panel.width_mm, panel.height_mm).filter((p) => + wizardMapState.selectedIds.has(p.point.id), + ); + return [...base, ...corners]; + }, [panel, wizardMapState]); + + // Ids outside the wizard selection → dimmed, unlabelled markers. + const dimmedAlignmentIds = useMemo(() => { + if (!wizardMapState) return undefined; + return new Set( + mapAlignmentPoints + .filter((p) => !wizardMapState.selectedIds.has(p.point.id)) + .map((p) => p.point.id), + ); + }, [wizardMapState, mapAlignmentPoints]); if (!hasProject || (plan !== null && !loading && !hasAnyHoles)) { return ( @@ -603,6 +624,8 @@ export function DrillOperationEditor({ snapshot }: { snapshot: DrillSnapshot }) } onInspectHole={setInspectedHoleId} alignmentPoints={mapAlignmentPoints} + dimmedAlignmentIds={dimmedAlignmentIds} + activeAlignmentPointId={wizardMapState?.currentId ?? null} /> )} @@ -644,6 +667,7 @@ export function DrillOperationEditor({ snapshot }: { snapshot: DrillSnapshot }) maxYMm={cncProfile.workEnvelopeMm.y} maxZMm={cncProfile.workEnvelopeMm.z} zeroError={zeroError} + onWizardMapStateChange={setWizardMapState} xyGate={xyGate} zGate={zGate} connected={machineConnected} diff --git a/cuprum-ui/src/components/panel/AlignmentPointLayer.tsx b/cuprum-ui/src/components/panel/AlignmentPointLayer.tsx index 3a7314f5..ed74b029 100644 --- a/cuprum-ui/src/components/panel/AlignmentPointLayer.tsx +++ b/cuprum-ui/src/components/panel/AlignmentPointLayer.tsx @@ -14,6 +14,7 @@ export function AlignmentPointLayer({ pxPerMm, interactive, labels, + dimmedIds, onPointMouseDown, onPointDragEnd, }: { @@ -24,6 +25,9 @@ export function AlignmentPointLayer({ /** Optional display label per point id, drawn beside the marker (constant * screen size). Used by the drill map so points match the wizard list. */ labels?: Map; + /** Points rendered dimmed (same opacity as unselected holes) and without a + * label — the drill map dims points excluded from the wizard selection. */ + dimmedIds?: Set; onPointMouseDown?: (id: string, e: KonvaEventObject) => void; onPointDragEnd?: (id: string, e: KonvaEventObject) => void; }) { @@ -39,11 +43,14 @@ export function AlignmentPointLayer({ <> {points.map((p) => { const isSelected = p.id === selectedId; + const isDimmed = !isSelected && (dimmedIds?.has(p.id) ?? false); return ( )} - {/* Label beside the marker (constant screen size) */} - {labels?.get(p.id) && k > 0 && ( + {/* Label beside the marker (constant screen size); dimmed points + * carry no label — they read as background, like unselected holes. */} + {!isDimmed && labels?.get(p.id) && k > 0 && (