task(CAD-14): phase 1 - tool exclusivity, V2 dispatcher exclusive for V2 tools (was: both engines had same tool = multi elements per click), selection handles now render in pixi mode via overlay pass, E2E proof test added
This commit is contained in:
@@ -57,6 +57,12 @@ export class RenderEngine {
|
||||
private dpr = 1;
|
||||
/** CAD-13-fix: true = Legacy-Canvas zeichnet KEINE Elemente (Pixi-Overlay zeichnet sie). Grid/Hintergrund bleiben. */
|
||||
skipElementRendering = false;
|
||||
/** CAD-14: Elemente-Spiegel fuer Auswahl-Griffe im Pixi-Modus (RenderEngine zeichnet Elemente nicht, aber die Griffe). */
|
||||
private overlayElements: CADElement[] = [];
|
||||
/** CAD-14: Setzt die Elemente, deren Auswahl-Griffe im Overlay gezeichnet werden. */
|
||||
setOverlayElements(els: CADElement[]): void {
|
||||
this.overlayElements = els;
|
||||
}
|
||||
|
||||
constructor(
|
||||
canvas: HTMLCanvasElement,
|
||||
@@ -164,19 +170,19 @@ export class RenderEngine {
|
||||
if (this.options.backgroundSrc) this.drawBackground();
|
||||
this.drawRuler(w, h);
|
||||
|
||||
// CAD-13-fix: Wenn Pixi-Renderer aktiv ist (skipElementRendering=true),
|
||||
// zeichnet das Legacy-Canvas KEINE Elemente mehr (sonst doppelt) —
|
||||
// nur Grid/Hintergrund/Lineal bleiben hier.
|
||||
if (this.skipElementRendering) {
|
||||
this.ctx.restore();
|
||||
return;
|
||||
}
|
||||
// CAD-14: skipElementRendering = Pixi rendert die Elemente. Das Legacy-
|
||||
// Canvas zeichnet KEINE Elemente mehr — ABER weiterhin alle Overlays
|
||||
// (Vorschau, Auswahl-Griffe, Auswahl-Rechteck, Snap-Punkte, Ortho),
|
||||
// sonst waere Auswahl im Pixi-Modus unsichtbar.
|
||||
const skipElements = this.skipElementRendering;
|
||||
|
||||
const viewport = this.zoomPan.getViewport();
|
||||
const visibleElements = this.spatialIndex.search({
|
||||
minX: viewport.minX, minY: viewport.minY,
|
||||
maxX: viewport.maxX, maxY: viewport.maxY,
|
||||
});
|
||||
const visibleElements = skipElements
|
||||
? []
|
||||
: this.spatialIndex.search({
|
||||
minX: viewport.minX, minY: viewport.minY,
|
||||
maxX: viewport.maxX, maxY: viewport.maxY,
|
||||
});
|
||||
|
||||
const layers = this.layerManager.getLayers();
|
||||
for (const layer of layers) {
|
||||
@@ -187,6 +193,21 @@ export class RenderEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// CAD-14: Auswahl-Griffe auch fuer Pixi-gerenderte Elemente weiterhin
|
||||
// auf dem Overlay-Canvas zeigen (Elemente selbst kommen von Pixi).
|
||||
if (skipElements && this.selection.selectedIds.size > 0) {
|
||||
const hs = this.zoomPan.getScale();
|
||||
const hox = this.zoomPan.getTransform().e;
|
||||
const hoy = this.zoomPan.getTransform().f;
|
||||
const hsx = (val: number) => val * hs + hox;
|
||||
const hsy = (val: number) => val * hs + hoy;
|
||||
for (const el of this.overlayElements) {
|
||||
if (this.selection.selectedIds.has(el.id)) {
|
||||
this.drawSelectionHandles(el, hsx, hsy, hs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw preview element with dashed style
|
||||
if (this.previewElement) {
|
||||
this.ctx.save();
|
||||
|
||||
@@ -304,6 +304,7 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
|
||||
interaction.setElements(elements);
|
||||
spatialIndex.clear();
|
||||
spatialIndex.bulkInsert(elements);
|
||||
renderEngine.setOverlayElements(elements);
|
||||
renderEngine.render();
|
||||
|
||||
// CAD-13-fix: Bestands-Elemente (aus DB geladen) müssen in Pixi gespiegelt
|
||||
@@ -447,12 +448,13 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
|
||||
useEffect(() => {
|
||||
const interaction = interactionRef.current;
|
||||
if (!interaction) return;
|
||||
interaction.setTool(activeTool as ToolType);
|
||||
// Task A4.1: V2-Werkzeuge zusätzlich an den Dispatcher routen (Flag aktiv).
|
||||
// Legacy bleibt parallel aktiv für noch nicht migrierte Tools.
|
||||
dispatcherRef.current?.setActive(
|
||||
pluginRegistry.getToolV2(activeTool) ? activeTool : null,
|
||||
);
|
||||
// CAD-14: EXKLUSIVITAET — V2-Werkzeuge laufen AUSSCHLIESSLICH ueber den
|
||||
// InteractionDispatcher. Die Legacy-Engine bekommt sie nicht mehr
|
||||
// (vorher hatten BEIDE dasselbe Tool aktiv => jeder Klick erzeugte
|
||||
// mehrfach Elemente). Nur Tools ohne V2-Entsprechung laufen legacy.
|
||||
const isV2 = !!pluginRegistry.getToolV2(activeTool);
|
||||
interaction.setTool(isV2 ? null : (activeTool as ToolType));
|
||||
dispatcherRef.current?.setActive(isV2 ? activeTool : null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTool]);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { formatDistance, type UnitType } from '../utils/format';
|
||||
export type ToolPhase = 'idle' | 'drawing' | 'modifying' | 'panning' | 'selecting';
|
||||
|
||||
export interface ToolState {
|
||||
activeTool: ToolType;
|
||||
activeTool: ToolType | null;
|
||||
phase: ToolPhase;
|
||||
points: Array<{ x: number; y: number }>; // accumulated world points for current operation
|
||||
previewElement: CADElement | null; // temporary element being drawn
|
||||
@@ -138,13 +138,13 @@ export class InteractionEngine {
|
||||
this.selectedTemplate = templateName;
|
||||
}
|
||||
|
||||
setTool(tool: ToolType): void {
|
||||
setTool(tool: ToolType | null): void {
|
||||
this.state.activeTool = tool;
|
||||
this.state.phase = 'idle';
|
||||
this.state.points = [];
|
||||
this.state.previewElement = null;
|
||||
const drawingTools: ToolType[] = ['line', 'rect', 'circle', 'arc', 'polyline', 'polygon', 'revcloud', 'text', 'dimension', 'leader', 'chair', 'seating-row', 'seating-block', 'table', 'stage', 'seating-template', 'hatch'];
|
||||
if (drawingTools.includes(tool)) {
|
||||
if (tool && drawingTools.includes(tool)) {
|
||||
this.selectionEngine.clearSelection();
|
||||
this.emitSelectionChange();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user