Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions cuprum-ui/src/components/drill/DrillMapCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -301,17 +305,24 @@ 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<string>;
/** 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
* path, tool-change markers at each group's first hole, and a machine-origin
* 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<Konva.Group>(null);
const W = Math.max(widthMm, 1);
Expand Down Expand Up @@ -340,6 +351,11 @@ export function DrillMapCanvas({ widthMm, heightMm, plan, route, zones, machineW
const ord = alignmentPointOrdinals(alignmentPoints);
const m = new Map<string, string>();
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,
Expand Down Expand Up @@ -465,10 +481,11 @@ export function DrillMapCanvas({ widthMm, heightMm, plan, route, zones, machineW
{alignPointList.length > 0 && viewport.pxPerMm > 0 && (
<AlignmentPointLayer
points={alignPointList}
selectedId={null}
selectedId={activeAlignmentPointId ?? null}
pxPerMm={viewport.pxPerMm}
interactive={false}
labels={alignLabels}
dimmedIds={dimmedAlignmentIds}
/>
)}

Expand Down
7 changes: 6 additions & 1 deletion cuprum-ui/src/components/drill/DrillPlanInspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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). */
Expand Down Expand Up @@ -127,6 +130,7 @@ export function DrillPlanInspector({
run,
onStart,
onSetClass,
onWizardMapStateChange,
onSetBitOverride,
selectedHoleId,
onClearHole,
Expand Down Expand Up @@ -351,6 +355,7 @@ export function DrillPlanInspector({
/* ── POINTS WIZARD mode (method 2 · manual capture) ── */
<WorkZeroPointsWizard
points={alignPoints}
onMapStateChange={onWizardMapStateChange}
datum={datum}
panelWidthMm={panelWidthMm}
panelHeightMm={panelHeightMm}
Expand Down
23 changes: 23 additions & 0 deletions cuprum-ui/src/components/drill/WorkZeroPointsWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ export interface WorkZeroPointsWizardProps {
* G54 atomically with the fit) — this callback only records the binding
* metadata and returns to the plan. */
onApplied: (result: { rmsMm: number; angleDeg: number; workOrigin: { x: number; y: number } }) => 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<string>;
/** Id of the point currently being captured; null outside the capture step. */
currentId: string | null;
}

type WizardStep = "select" | "capture" | "result";
Expand Down Expand Up @@ -107,6 +119,7 @@ export function WorkZeroPointsWizard({
plan,
onCancel,
onApplied,
onMapStateChange,
}: WorkZeroPointsWizardProps) {
const { t } = useTranslation("drill");
const { fmtLen } = useUnitFormat();
Expand Down Expand Up @@ -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
Expand Down
36 changes: 30 additions & 6 deletions cuprum-ui/src/components/operations/DrillOperationEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down Expand Up @@ -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<WizardMapState | null>(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 (
Expand Down Expand Up @@ -603,6 +624,8 @@ export function DrillOperationEditor({ snapshot }: { snapshot: DrillSnapshot })
}
onInspectHole={setInspectedHoleId}
alignmentPoints={mapAlignmentPoints}
dimmedAlignmentIds={dimmedAlignmentIds}
activeAlignmentPointId={wizardMapState?.currentId ?? null}
/>
)}
</div>
Expand Down Expand Up @@ -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}
Expand Down
12 changes: 10 additions & 2 deletions cuprum-ui/src/components/panel/AlignmentPointLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function AlignmentPointLayer({
pxPerMm,
interactive,
labels,
dimmedIds,
onPointMouseDown,
onPointDragEnd,
}: {
Expand All @@ -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<string, string>;
/** Points rendered dimmed (same opacity as unselected holes) and without a
* label — the drill map dims points excluded from the wizard selection. */
dimmedIds?: Set<string>;
onPointMouseDown?: (id: string, e: KonvaEventObject<MouseEvent>) => void;
onPointDragEnd?: (id: string, e: KonvaEventObject<DragEvent>) => void;
}) {
Expand All @@ -39,11 +43,14 @@ export function AlignmentPointLayer({
<>
{points.map((p) => {
const isSelected = p.id === selectedId;
const isDimmed = !isSelected && (dimmedIds?.has(p.id) ?? false);
return (
<Group
key={p.id}
x={p.x_mm}
y={p.y_mm}
// Same dim level as unselected holes in the drill map (0.25).
opacity={isDimmed ? 0.25 : 1}
listening={interactive}
draggable={interactive}
onMouseDown={
Expand Down Expand Up @@ -103,8 +110,9 @@ export function AlignmentPointLayer({
</>
)}

{/* 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 && (
<Text
x={arm + 3 * k}
y={-5.5 * k}
Expand Down
Loading