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) <noreply@anthropic.com>
This commit is contained in:
igurielidze 2026-03-30 17:22:14 +04:00
parent 775c6e2e99
commit c01173aa7b
2 changed files with 43 additions and 1 deletions

View File

@ -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 {

View File

@ -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);