From c01173aa7b067f96f5a2f98b9c45fab1bbba108a Mon Sep 17 00:00:00 2001 From: igurielidze Date: Mon, 30 Mar 2026 17:22:14 +0400 Subject: [PATCH] Draw conveyors/chutes/tippers/extendos programmatically for consistent stroke SVG-based rendering caused non-uniform stroke scaling when symbols were stretched. Now conveyor, chute, tipper are drawn as canvas rects, and extendo is drawn as a canvas path with fixed left bracket proportions. All use consistent 0.5px stroke regardless of size. Co-Authored-By: Claude Opus 4.6 (1M context) --- svelte-app/src/lib/canvas/renderer.ts | 32 ++++++++++++++++++++++++++- svelte-app/src/lib/symbols.ts | 12 ++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/svelte-app/src/lib/canvas/renderer.ts b/svelte-app/src/lib/canvas/renderer.ts index 83c9a03..012ebc0 100644 --- a/svelte-app/src/lib/canvas/renderer.ts +++ b/svelte-app/src/lib/canvas/renderer.ts @@ -1,5 +1,5 @@ import { layout } from '../stores/layout.svelte.js'; -import { getSymbolImage, isResizable, isCurvedType, isSpurType, isEpcType, isInductionType, isPhotoeyeType, getCurveGeometry, getSymbolGroup, SPACING_EXEMPT, EPC_CONFIG, INDUCTION_CONFIG, PHOTOEYE_CONFIG } from '../symbols.js'; +import { getSymbolImage, isResizable, isCurvedType, isSpurType, isEpcType, isInductionType, isPhotoeyeType, isRectConveyanceType, isExtendoType, getCurveGeometry, getSymbolGroup, SPACING_EXEMPT, EPC_CONFIG, INDUCTION_CONFIG, PHOTOEYE_CONFIG } from '../symbols.js'; import { checkSpacingViolation } from './collision.js'; import { marqueeRect } from './interactions.js'; import { THEME } from './render-theme.js'; @@ -457,6 +457,36 @@ function drawSymbolBody(ctx: CanvasRenderingContext2D, sym: PlacedSymbol): boole ctx.lineWidth = 0.5; ctx.fill(); ctx.stroke(); + } else if (isExtendoType(sym.symbolId)) { + // Extendo: fixed left bracket + stretchy right belt + // Y fractions from original SVG path, X uses fixed left bracket width + const bracketW = 10.6 / 31.07 * 73; // ~24.9 px at default 73w — fixed portion + const x = sym.x, y = sym.y, w = sym.w, h = sym.h; + ctx.beginPath(); + ctx.moveTo(x + bracketW * 0.44, y + h * 0.085); // tab top-right + ctx.lineTo(x + bracketW, y + h * 0.085); // bracket top-right + ctx.lineTo(x + bracketW, y + h * 0.222); // step down to belt top + ctx.lineTo(x + w, y + h * 0.222); // belt top-right + ctx.lineTo(x + w, y + h * 0.780); // belt bottom-right + ctx.lineTo(x + bracketW, y + h * 0.780); // belt bottom-left + ctx.lineTo(x + bracketW, y + h * 0.917); // step down bracket bottom + ctx.lineTo(x + bracketW * 0.44, y + h * 0.916); // bracket bottom-left + ctx.lineTo(x + bracketW * 0.34, y + h * 0.985); // notch bottom + ctx.lineTo(x, y + h * 0.980); // far left bottom + ctx.lineTo(x, y + h * 0.017); // far left top + ctx.lineTo(x + bracketW * 0.34, y + h * 0.016); // tab top-left + ctx.closePath(); + ctx.fillStyle = '#ffffff'; + ctx.strokeStyle = '#000000'; + ctx.lineWidth = 0.5; + ctx.fill(); + ctx.stroke(); + } else if (isRectConveyanceType(sym.symbolId)) { + ctx.fillStyle = '#ffffff'; + ctx.strokeStyle = '#000000'; + ctx.lineWidth = 0.5; + ctx.fillRect(sym.x, sym.y, sym.w, sym.h); + ctx.strokeRect(sym.x, sym.y, sym.w, sym.h); } else if (isPhotoeyeType(sym.symbolId)) { drawPhotoeyeSymbol(ctx, sym); } else { diff --git a/svelte-app/src/lib/symbols.ts b/svelte-app/src/lib/symbols.ts index 9580494..807fa92 100644 --- a/svelte-app/src/lib/symbols.ts +++ b/svelte-app/src/lib/symbols.ts @@ -166,6 +166,18 @@ export function isPhotoeyeType(symbolId: string): boolean { return symbolId === 'photoeye' || symbolId === 'photoeye_v'; } +/** Simple rectangular conveyance types drawn programmatically for consistent stroke */ +const RECT_CONVEYANCE = new Set([ + 'conveyor', 'conveyor_v', 'chute', 'chute_v', 'tipper', 'tipper_v', +]); +export function isRectConveyanceType(symbolId: string): boolean { + return RECT_CONVEYANCE.has(symbolId); +} + +export function isExtendoType(symbolId: string): boolean { + return symbolId === 'extendo' || symbolId === 'extendo_v'; +} + export function isResizable(symbolId: string): boolean { return PRIORITY_TYPES.has(symbolId);