import type { SymbolDef } from './types.js'; import { EPC_CONFIG, INDUCTION_CONFIG, CURVE_CONFIG, PHOTOEYE_CONFIG } from './symbol-config.js'; export { EPC_CONFIG, INDUCTION_CONFIG, CURVE_CONFIG, PHOTOEYE_CONFIG }; export const SYMBOLS: SymbolDef[] = [ // --- Conveyance > Conveyor --- { id: 'conveyor', name: 'Conveyor', file: '/symbols/conveyor.svg', w: 154, h: 30, group: 'Conveyance', subgroup: 'Conveyor' }, { id: 'conveyor_v', name: 'Conveyor (V)', file: '/symbols/conveyor.svg', w: 154, h: 30, defaultRotation: 90, group: 'Conveyance', subgroup: 'Conveyor' }, { id: 'curved_conv_30', name: 'Curve 30\u00B0', file: '/symbols/curved_conveyor_30.svg', w: 154, h: 154, group: 'Conveyance', subgroup: 'Conveyor', curveAngle: 30 }, { id: 'curved_conv_45', name: 'Curve 45\u00B0', file: '/symbols/curved_conveyor_45.svg', w: 154, h: 154, group: 'Conveyance', subgroup: 'Conveyor', curveAngle: 45 }, { id: 'curved_conv_60', name: 'Curve 60\u00B0', file: '/symbols/curved_conveyor_60.svg', w: 154, h: 154, group: 'Conveyance', subgroup: 'Conveyor', curveAngle: 60 }, { id: 'curved_conv_90', name: 'Curve 90\u00B0', file: '/symbols/curved_conveyor_90.svg', w: 154, h: 154, group: 'Conveyance', subgroup: 'Conveyor', curveAngle: 90 }, // --- Conveyance > Chute --- { id: 'chute', name: 'Chute', file: '/symbols/chute.svg', w: 68, h: 30, group: 'Conveyance', subgroup: 'Chute' }, { id: 'chute_v', name: 'Chute (V)', file: '/symbols/chute.svg', w: 68, h: 30, defaultRotation: 90, group: 'Conveyance', subgroup: 'Chute' }, { id: 'tipper', name: 'Tipper', file: '/symbols/tipper.svg', w: 68, h: 30, group: 'Conveyance', subgroup: 'Chute' }, { id: 'tipper_v', name: 'Tipper (V)', file: '/symbols/tipper.svg', w: 68, h: 30, defaultRotation: 90, group: 'Conveyance', subgroup: 'Chute' }, { id: 'curved_chute_30', name: 'C.Chute 30\u00B0', file: '/symbols/curved_chute_30.svg', w: 100, h: 100, group: 'Conveyance', subgroup: 'Chute', curveAngle: 30 }, { id: 'curved_chute_45', name: 'C.Chute 45\u00B0', file: '/symbols/curved_chute_45.svg', w: 100, h: 100, group: 'Conveyance', subgroup: 'Chute', curveAngle: 45 }, { id: 'curved_chute_60', name: 'C.Chute 60\u00B0', file: '/symbols/curved_chute_60.svg', w: 100, h: 100, group: 'Conveyance', subgroup: 'Chute', curveAngle: 60 }, { id: 'curved_chute_90', name: 'C.Chute 90\u00B0', file: '/symbols/curved_chute_90.svg', w: 100, h: 100, group: 'Conveyance', subgroup: 'Chute', curveAngle: 90 }, // --- Conveyance > Other --- { id: 'spur', name: 'Spur', file: '/symbols/spur.svg', w: 80, h: 30, w2: 40, group: 'Conveyance', subgroup: 'Other' }, { id: 'spur_v', name: 'Spur (V)', file: '/symbols/spur.svg', w: 80, h: 30, w2: 40, defaultRotation: 90, group: 'Conveyance', subgroup: 'Other' }, { id: 'extendo', name: 'Extendo', file: '/symbols/extendo.svg', w: 73, h: 54, group: 'Conveyance', subgroup: 'Other' }, { id: 'extendo_v', name: 'Extendo (V)', file: '/symbols/extendo.svg', w: 73, h: 54, defaultRotation: 90, group: 'Conveyance', subgroup: 'Other' }, { id: 'induction', name: 'Induction', file: '/symbols/induction.svg', w: 154, h: 75, group: 'Conveyance', subgroup: 'Other' }, { id: 'induction_v', name: 'Induction (V)', file: '/symbols/induction.svg', w: 154, h: 75, defaultRotation: 90, group: 'Conveyance', subgroup: 'Other' }, { id: 'diverter', name: 'Diverter', file: '/symbols/diverter.svg', w: 31, h: 20, group: 'Conveyance', subgroup: 'Other' }, { id: 'diverter_v', name: 'Diverter (V)', file: '/symbols/diverter.svg', w: 31, h: 20, defaultRotation: 90, group: 'Conveyance', subgroup: 'Other' }, // --- I/O Modules --- { id: 'fio_sio_fioh', name: 'FIO/SIO/FIOH', file: '/symbols/fio_sio_fioh.svg', w: 14, h: 20, group: 'I/O Modules' }, { 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: 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' }, // --- Controls --- { id: 'jam_reset', name: 'Jam Reset (JR)', file: '/symbols/jam_reset.svg', w: 20, h: 20, group: 'Controls' }, { id: 'jam_reset_v', name: 'Jam Reset (V)', file: '/symbols/jam_reset.svg', w: 20, h: 20, defaultRotation: 90, group: 'Controls' }, { id: 'start', name: 'Start (S)', file: '/symbols/start.svg', w: 20, h: 20, group: 'Controls' }, { id: 'start_v', name: 'Start (V)', file: '/symbols/start.svg', w: 20, h: 20, defaultRotation: 90, group: 'Controls' }, { id: 'start_stop', name: 'Start Stop (SS)', file: '/symbols/start_stop.svg', w: 40, h: 20, group: 'Controls' }, { id: 'start_stop_v', name: 'Start Stop (V)', file: '/symbols/start_stop.svg', w: 40, h: 20, defaultRotation: 90, group: 'Controls' }, { id: 'chute_enable', name: 'Chute Enable', file: '/symbols/chute_enable.svg', w: 20, h: 20, group: 'Controls' }, { id: 'chute_enable_v', name: 'Chute Enable (V)', file: '/symbols/chute_enable.svg', w: 20, h: 20, defaultRotation: 90, group: 'Controls' }, { id: 'package_release', name: 'Package Release', file: '/symbols/package_release.svg', w: 20, h: 20, group: 'Controls' }, { id: 'package_release_v', name: 'Package Release (V)', file: '/symbols/package_release.svg', w: 20, h: 20, defaultRotation: 90, group: 'Controls' }, { id: 'beacon', name: 'Beacon', file: '/symbols/beacon.svg', w: 20, h: 20, group: 'Controls' }, { id: 'beacon_v', name: 'Beacon (V)', file: '/symbols/beacon.svg', w: 20, h: 20, defaultRotation: 90, group: 'Controls' }, { id: 'solenoid', name: '[SOL]', file: '/symbols/solenoid.svg', w: 20, h: 20, group: 'Controls' }, { id: 'solenoid_v', name: '[SOL] (V)', file: '/symbols/solenoid.svg', w: 20, h: 20, defaultRotation: 90, group: 'Controls' }, // --- Other --- { id: 'pdp', name: 'PDP', file: '/symbols/pdp.svg', w: 20, h: 20, group: 'Other' }, { id: 'pdp_v', name: 'PDP (V)', file: '/symbols/pdp.svg', w: 20, h: 20, defaultRotation: 90, group: 'Other' }, { id: 'dpm', name: 'DPM', file: '/symbols/dpm.svg', w: 35, h: 20, group: 'Other' }, { id: 'dpm_v', name: 'DPM (V)', file: '/symbols/dpm.svg', w: 35, h: 20, defaultRotation: 90, group: 'Other' }, { id: 'mcm', name: 'MCM', file: '/symbols/mcm.svg', w: 60, h: 20, group: 'Other' }, { id: 'mcm_v', name: 'MCM (V)', file: '/symbols/mcm.svg', w: 60, h: 20, defaultRotation: 90, group: 'Other' }, { id: 'epc', name: 'EPC', file: '/symbols/epc.svg', w: 67, h: 20, group: 'Other' }, { id: 'epc_v', name: 'EPC (V)', file: '/symbols/epc.svg', w: 67, h: 20, defaultRotation: 90, group: 'Other' }, { id: 'ip_camera', name: 'IP Camera', file: '/symbols/ip_camera.svg', w: 20, h: 20, group: 'Other' }, { id: 'ip_camera_v', name: 'IP Camera (V)', file: '/symbols/ip_camera.svg', w: 20, h: 20, defaultRotation: 90, group: 'Other' }, ]; export const SYMBOL_GROUPS = [...new Set(SYMBOLS.map(s => s.group))]; const SYMBOL_GROUP_MAP = new Map(SYMBOLS.map(s => [s.id, s.group])); export function getSymbolGroup(symbolId: string): string { return SYMBOL_GROUP_MAP.get(symbolId) || ''; } export const PRIORITY_TYPES = new Set([ 'conveyor', 'conveyor_v', 'chute', 'chute_v', 'tipper', 'tipper_v', 'extendo', 'extendo_v', 'induction', 'induction_v', 'curved_conv_30', 'curved_conv_45', 'curved_conv_60', 'curved_conv_90', 'curved_chute_30', 'curved_chute_45', 'curved_chute_60', 'curved_chute_90', 'spur', 'spur_v', 'photoeye', 'photoeye_v', ]); // Overlay types: exempt from spacing — can be placed freely on top of anything export const SPACING_EXEMPT = new Set([ 'photoeye', 'photoeye_v', 'fio_sio_fioh', 'fio_sio_fioh_v', ]); /** Get the fixed band width for a curved symbol (same as straight segment height) */ export function getCurveBandWidth(symbolId: string): number { if (symbolId.startsWith('curved_chute')) return CURVE_CONFIG.chuteBandWidth; return CURVE_CONFIG.convBandWidth; } /** Compute arc center and radii in display coordinates. * Band width is fixed — only the radius changes when resizing. */ export function getCurveGeometry(symbolId: string, x: number, y: number, w: number, h: number) { const arcCx = x + CURVE_CONFIG.centerOffsetX * w; const arcCy = y + CURVE_CONFIG.centerOffsetY * h; const outerR = CURVE_CONFIG.outerRFrac * w; const bandW = getCurveBandWidth(symbolId); const innerR = Math.max(0, outerR - bandW); return { arcCx, arcCy, outerR, innerR, bandW }; } const imageCache = new Map(); const SVG_SCALE = 10; // Rasterize SVGs at 10x for crisp canvas rendering async function loadSvgImage(file: string): Promise { if (imageCache.has(file)) return; try { const resp = await fetch(file); const svgText = await resp.text(); const scaled = svgText.replace( /]*)\bwidth="([^"]*)"([^>]*)\bheight="([^"]*)"/, (_, before, w, mid, h) => `((resolve) => { img.onload = () => { URL.revokeObjectURL(url); imageCache.set(file, img); resolve(); }; img.onerror = () => { URL.revokeObjectURL(url); resolve(); }; img.src = url; }); } catch { console.warn(`Failed to load symbol image: ${file}`); } } export function preloadSymbolImages(): Promise { const uniqueFiles = [...new Set(SYMBOLS.map(s => s.file)), EPC_CONFIG.iconFile]; return Promise.all(uniqueFiles.map(loadSvgImage)); } export function getSymbolImage(file: string): HTMLImageElement | undefined { return imageCache.get(file); } export function isCurvedType(symbolId: string): boolean { return symbolId.startsWith('curved_'); } export function isSpurType(symbolId: string): boolean { return symbolId === 'spur' || symbolId === 'spur_v'; } export function isEpcType(symbolId: string): boolean { return symbolId === 'epc' || symbolId === 'epc_v'; } export function isInductionType(symbolId: string): boolean { return symbolId === 'induction' || symbolId === 'induction_v'; } 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); }