Make conveyors/chutes/inductions/extendos/spurs white; fix PE stroke and size
- Change all conveyance SVGs from black fill to white fill with black stroke - Update programmatic rendering (curves, induction, spur) to white fill - Replace PE 3-slice rendering with programmatic canvas paths for consistent stroke width at any size - Reduce PE default size from 56x20 to 30x14 to fit around conveyor devices - Update SVG export to match new white fills Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ -58,7 +58,9 @@ export const THEME = {
|
||||
rightBoxStrokeWidth: 1.5,
|
||||
},
|
||||
induction: {
|
||||
fillColor: '#000000',
|
||||
fillColor: '#ffffff',
|
||||
strokeColor: '#000000',
|
||||
lineWidth: 0.5,
|
||||
},
|
||||
canvas: {
|
||||
maxRenderScale: 4,
|
||||
|
||||
@ -186,7 +186,6 @@ function drawEpcSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
|
||||
ctx.fillStyle = THEME.epcBody.rightBoxFill;
|
||||
ctx.strokeStyle = THEME.epcBody.rightBoxStroke;
|
||||
ctx.lineWidth = THEME.epcBody.rightBoxStrokeWidth;
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
ctx.fillRect(-rb.w, -rb.h / 2, rb.w, rb.h);
|
||||
ctx.strokeRect(-rb.w, -rb.h / 2, rb.w, rb.h);
|
||||
ctx.restore();
|
||||
@ -269,7 +268,7 @@ function traceEpcOutlinePath(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, p
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(plx, ply);
|
||||
ctx.rotate(rAngle - Math.PI / 2);
|
||||
ctx.rotate(rAngle);
|
||||
ctx.beginPath();
|
||||
ctx.rect(-rb.w - pad, -rb.h / 2 - pad, rb.w + pad * 2, rb.h + pad * 2);
|
||||
ctx.stroke();
|
||||
@ -376,26 +375,47 @@ function drawInductionSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = THEME.induction.fillColor;
|
||||
ctx.strokeStyle = THEME.induction.strokeColor;
|
||||
ctx.lineWidth = THEME.induction.lineWidth;
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
/** Draw photoeye with 3-slice: fixed left cap, stretched middle beam, fixed right cap */
|
||||
function drawPhotoeye3Slice(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, img: HTMLImageElement) {
|
||||
const { leftCap, rightCap, defaultWidth } = PHOTOEYE_CONFIG;
|
||||
const srcW = img.naturalWidth;
|
||||
const srcH = img.naturalHeight;
|
||||
const scale = srcW / defaultWidth;
|
||||
const srcLeftW = leftCap * scale;
|
||||
const srcRightW = rightCap * scale;
|
||||
const srcMiddleW = srcW - srcLeftW - srcRightW;
|
||||
const dstMiddleW = sym.w - leftCap - rightCap;
|
||||
/** Draw photoeye programmatically for consistent stroke at any size */
|
||||
function drawPhotoeyeSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
|
||||
const { leftCap, rightCap } = PHOTOEYE_CONFIG;
|
||||
const x = sym.x, y = sym.y, w = sym.w, h = sym.h;
|
||||
|
||||
// Left cap (fixed)
|
||||
ctx.drawImage(img, 0, 0, srcLeftW, srcH, sym.x, sym.y, leftCap, sym.h);
|
||||
// Middle beam (stretched)
|
||||
ctx.drawImage(img, srcLeftW, 0, srcMiddleW, srcH, sym.x + leftCap, sym.y, dstMiddleW, sym.h);
|
||||
// Right cap (fixed)
|
||||
ctx.drawImage(img, srcW - srcRightW, 0, srcRightW, srcH, sym.x + sym.w - rightCap, sym.y, rightCap, sym.h);
|
||||
// Y positions as fractions of height (derived from original SVG path)
|
||||
const beamTop = y + h * 0.42;
|
||||
const beamBottom = y + h * 0.585;
|
||||
const arrowInnerTop = y + h * 0.248;
|
||||
const arrowInnerBottom = y + h * 0.744;
|
||||
const recvTop = y + h * 0.181;
|
||||
const recvBottom = y + h * 0.826;
|
||||
const arrowTipTop = y + h * 0.05;
|
||||
const arrowTipBottom = y + h * 0.948;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + leftCap, beamTop);
|
||||
ctx.lineTo(x + leftCap, arrowInnerTop);
|
||||
ctx.lineTo(x, arrowTipTop);
|
||||
ctx.lineTo(x, arrowTipBottom);
|
||||
ctx.lineTo(x + leftCap, arrowInnerBottom);
|
||||
ctx.lineTo(x + leftCap, beamBottom);
|
||||
ctx.lineTo(x + w - rightCap, beamBottom);
|
||||
ctx.lineTo(x + w - rightCap, recvBottom);
|
||||
ctx.lineTo(x + w, recvBottom);
|
||||
ctx.lineTo(x + w, recvTop);
|
||||
ctx.lineTo(x + w - rightCap, recvTop);
|
||||
ctx.lineTo(x + w - rightCap, beamTop);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
/** Draw curved conveyor/chute programmatically with fixed band width */
|
||||
@ -409,7 +429,7 @@ function drawCurvedSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
|
||||
ctx.arc(arcCx, arcCy, innerR, -sweepRad, 0, false);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.fill();
|
||||
@ -432,20 +452,18 @@ function drawSymbolBody(ctx: CanvasRenderingContext2D, sym: PlacedSymbol): boole
|
||||
ctx.lineTo(sym.x + sym.w, sym.y + sym.h);
|
||||
ctx.lineTo(sym.x, sym.y + sym.h);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
} else if (isPhotoeyeType(sym.symbolId)) {
|
||||
drawPhotoeyeSymbol(ctx, sym);
|
||||
} else {
|
||||
const img = getSymbolImage(sym.file);
|
||||
if (!img) return false;
|
||||
if (isPhotoeyeType(sym.symbolId)) {
|
||||
drawPhotoeye3Slice(ctx, sym, img);
|
||||
} else {
|
||||
ctx.drawImage(img, sym.x, sym.y, sym.w, sym.h);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@ export async function exportSVG() {
|
||||
const stripBottomY = sym.y + sym.h * INDUCTION_CONFIG.stripBottomFrac;
|
||||
const pts = INDUCTION_CONFIG.arrowPoints.map(([xf, yf]) => [sym.x + xf * hw, sym.y + yf * sym.h] as const);
|
||||
const d = `M ${sym.x + sym.w},${stripTopY} L ${pts[0][0]},${stripTopY} ${pts.map(([px, py]) => `L ${px},${py}`).join(' ')} L ${pts[5][0]},${stripBottomY} L ${sym.x + sym.w},${stripBottomY} Z`;
|
||||
lines.push(` <path ${idAttr} d="${d}" fill="#000000"${outerTransform ? ` transform="${outerTransform}"` : ''} />`);
|
||||
lines.push(` <path ${idAttr} d="${d}" fill="#ffffff" stroke="#000000" stroke-width="0.5"${outerTransform ? ` transform="${outerTransform}"` : ''} />`);
|
||||
} else if (isCurvedType(sym.symbolId)) {
|
||||
const angle = sym.curveAngle || 90;
|
||||
const { arcCx, arcCy, outerR, innerR } = getCurveGeometry(sym.symbolId, sym.x, sym.y, sym.w, sym.h);
|
||||
@ -69,11 +69,11 @@ export async function exportSVG() {
|
||||
`A ${innerR},${innerR} 0 ${largeArc},1 ${arcCx + innerR},${arcCy}`,
|
||||
'Z',
|
||||
].join(' ');
|
||||
lines.push(` <path ${idAttr} d="${d}" fill="#000000" stroke="#000000" stroke-width="0.5"${outerTransform ? ` transform="${outerTransform}"` : ''} />`);
|
||||
lines.push(` <path ${idAttr} d="${d}" fill="#ffffff" stroke="#000000" stroke-width="0.5"${outerTransform ? ` transform="${outerTransform}"` : ''} />`);
|
||||
} else if (isSpurType(sym.symbolId)) {
|
||||
const w2 = sym.w2 ?? sym.w;
|
||||
const d = `M ${sym.x},${sym.y} L ${sym.x + w2},${sym.y} L ${sym.x + sym.w},${sym.y + sym.h} L ${sym.x},${sym.y + sym.h} Z`;
|
||||
lines.push(` <path ${idAttr} d="${d}" fill="#000000" stroke="#000000" stroke-width="0.5"${outerTransform ? ` transform="${outerTransform}"` : ''} />`);
|
||||
lines.push(` <path ${idAttr} d="${d}" fill="#ffffff" stroke="#000000" stroke-width="0.5"${outerTransform ? ` transform="${outerTransform}"` : ''} />`);
|
||||
} else {
|
||||
// Regular SVG symbol
|
||||
try {
|
||||
|
||||
@ -36,8 +36,8 @@ export const SYMBOLS: SymbolDef[] = [
|
||||
{ id: 'fio_sio_fioh_v', name: 'FIO/SIO/FIOH (V)', file: '/symbols/fio_sio_fioh.svg', w: 14, h: 20, defaultRotation: 90, group: 'I/O Modules' },
|
||||
|
||||
// --- Sensors ---
|
||||
{ id: 'photoeye', name: 'Photoeye', file: '/symbols/photoeye.svg', w: 56, h: 20, group: 'Sensors' },
|
||||
{ id: 'photoeye_v', name: 'Photoeye (V)', file: '/symbols/photoeye.svg', w: 56, h: 20, defaultRotation: 90, group: 'Sensors' },
|
||||
{ id: 'photoeye', name: 'Photoeye', file: '/symbols/photoeye.svg', w: 30, h: 14, group: 'Sensors' },
|
||||
{ id: 'photoeye_v', name: 'Photoeye (V)', file: '/symbols/photoeye.svg', w: 30, h: 14, defaultRotation: 90, group: 'Sensors' },
|
||||
{ id: 'pressure_sensor', name: 'Pressure Sensor', file: '/symbols/pressure_sensor.svg', w: 20, h: 20, group: 'Sensors' },
|
||||
{ id: 'pressure_sensor_v', name: 'Pressure Sensor (V)', file: '/symbols/pressure_sensor.svg', w: 20, h: 20, defaultRotation: 90, group: 'Sensors' },
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 367 B |
|
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 587 B |
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 178 B |
|
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 367 B |