Migrate device data source to DESC_IP_MERGED, fix PE/button bindings

- Rewrite generateDeviceManifest() to read DESC_IP sheet from
  PLC Data Generator/{PROJECT}/{MCM}_DESC_IP_MERGED.xlsx
- Rewrite generateProjectManifest() to discover projects from same files
- Key devices-manifest by {PROJECT}_{MCM} to avoid cross-project collisions
- Add fill.paint binding for standalone photoeye path elements
- Add fill.paint binding for button circle sub-elements
- Add s_str_ prefix to path/rect element names for consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ilia.gurielidze 2026-04-02 15:47:46 +04:00
parent 2b49454237
commit 981a33a382
7 changed files with 17177 additions and 23073 deletions

View File

@ -23,9 +23,10 @@
}
loadManifest();
// Devices for current MCM, filtered out already-placed ones
// Devices for current project+MCM, filtered out already-placed ones
let mcmDevices = $derived.by(() => {
const list = allDevices[layout.currentMcm] || [];
const key = `${layout.currentProject}_${layout.currentMcm}`;
const list = allDevices[key] || [];
const placedLabels = new Set(layout.symbols.map(s => s.label).filter(Boolean));
return list.filter(d => !placedLabels.has(d.id));
});
@ -53,7 +54,7 @@
});
let totalCount = $derived(mcmDevices.length);
let totalAll = $derived((allDevices[layout.currentMcm] || []).length);
let totalAll = $derived((allDevices[`${layout.currentProject}_${layout.currentMcm}`] || []).length);
function toggleGroup(group: string) {
const next = new Set(collapsedZones);

View File

@ -384,7 +384,8 @@ function svgElementToIgnition(el: Element): Record<string, any> | null {
if (el.getAttribute('data-tagpath')) obj.tagpaths = [el.getAttribute('data-tagpath')];
} else if (tag === 'rect') {
obj.type = 'rect';
obj.name = el.getAttribute('id') || 'rect';
const rectId = el.getAttribute('id') || 'rect';
obj.name = rectId !== 'rect' ? `s_str_${rectId}` : 'rect';
if (el.getAttribute('id')) obj.id = el.getAttribute('id');
for (const attr of ['x', 'y', 'width', 'height', 'rx', 'ry']) {
if (el.getAttribute(attr)) obj[attr] = el.getAttribute(attr);
@ -398,7 +399,8 @@ function svgElementToIgnition(el: Element): Record<string, any> | null {
if (el.getAttribute('data-tagpath')) obj.tagpaths = [el.getAttribute('data-tagpath')];
} else if (tag === 'path') {
obj.type = 'path';
obj.name = el.getAttribute('id') || 'path';
const pathId = el.getAttribute('id') || 'path';
obj.name = pathId !== 'path' ? `s_str_${pathId}` : 'path';
if (el.getAttribute('id')) obj.id = el.getAttribute('id');
obj.d = el.getAttribute('d') || '';
if (el.getAttribute('transform')) obj.transform = el.getAttribute('transform');

View File

@ -317,6 +317,7 @@ function generateElementBindings(elements: SvgElement[]): Record<string, any> {
const isDpm = /_DPM\d*/i.test(elName);
const isMcm = /^MCM\d*$/i.test(elName);
const isEpc = /_EPC\d*/i.test(elName);
const isPe = /_[TLJF]PE\d*$/i.test(elName) || /_BDS\d/i.test(elName) || /_TS\d/i.test(elName);
if (el.elements && el.elements.length > 0) {
if (isEpc) {
@ -335,12 +336,14 @@ function generateElementBindings(elements: SvgElement[]): Record<string, any> {
propConfig[`${prefix}.elements[1].fill.paint`] = fillPaintBinding(n);
}
} else if (isButton) {
// Buttons: NO fill binding on elements[0] (background rect keeps static color)
// Only text elements get contrast color binding
// Buttons: elements[0] is background rect (static), elements[1] is circle (color binding),
// text elements get contrast color binding
for (let m = 0; m < el.elements.length; m++) {
const sub = el.elements[m];
if (sub.type === 'text') {
propConfig[`${prefix}.elements[${m}].fill.paint`] = textFillBinding(n);
} else if (sub.type === 'circle') {
propConfig[`${prefix}.elements[${m}].fill.paint`] = fillPaintBinding(n);
}
}
} else {
@ -354,6 +357,9 @@ function generateElementBindings(elements: SvgElement[]): Record<string, any> {
}
}
}
} else if (isPe) {
// Photoeyes are standalone path elements — bind fill.paint directly
propConfig[`${prefix}.fill.paint`] = fillPaintBinding(n);
}
// Display filter

View File

@ -1,22 +1,13 @@
import type { ProjectInfo, McmInfo } from './types.js';
/**
* Scans the static/projectes/ directory structure to discover available projects and MCMs.
* Discovers available projects and MCMs from a build-time manifest.
*
* Expected structure:
* projectes/{PROJECT}/excel/{PROJECT}_SYSDL_{MCM}*.xlsx
* projectes/{PROJECT}/pdf/{PROJECT}_SYSDL_{MCM}*-SYSDL.pdf
*
* Since we're in a static SPA, we use a manifest approach:
* At build time or at runtime, we fetch a directory listing.
* For simplicity, we'll use a manifest file that can be auto-generated.
* Data source: DESC_IP_MERGED.xlsx files from PLC Data Generator/{PROJECT}/
* The Vite plugin scans these at build time and writes manifest.json.
*/
// We'll scan using a known project list fetched from a manifest
// For the static SPA, we generate a manifest at build time via a vite plugin,
// or we hardcode discovery by trying known paths.
const MCM_REGEX = /SYSDL[_ ]+(MCM\d+)/i;
const MCM_REGEX = /(MCM\d+)/i;
export async function discoverProjects(): Promise<ProjectInfo[]> {
try {

File diff suppressed because it is too large Load Diff

View File

@ -1,100 +1,145 @@
[
{
"name": "BNA8",
"mcms": [
{
"name": "MCM01",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/BNA8/BNA8_MCM01_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM02",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/BNA8/BNA8_MCM02_DESC_IP_MERGED.xlsx",
"pdfPath": null
}
]
},
{
"name": "CDW5",
"mcms": [
{
"name": "MCM01",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM01.xlsx",
"pdfPath": null
},
{
"name": "MCM02",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM02.xlsx",
"pdfPath": null
},
{
"name": "MCM03",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM03.xlsx",
"pdfPath": null
},
{
"name": "MCM04",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM04.xlsx",
"pdfPath": null
},
{
"name": "MCM05",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM05.xlsx",
"pdfPath": null
},
{
"name": "MCM06",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM06.xlsx",
"pdfPath": null
},
{
"name": "MCM07",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM07.xlsx",
"pdfPath": null
},
{
"name": "MCM08",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM08.xlsx",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CDW5/CDW5_MCM01_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM09",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM09 Non Con PH1.xlsx",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CDW5/CDW5_MCM09_DESC_IP_MERGED.xlsx",
"pdfPath": "/projectes/CDW5/pdf/CDW5_SYSDL_MCM09 Non Con PH1-SYSDL.pdf"
},
{
"name": "MCM10",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM10.xlsx",
"pdfPath": null
},
{
"name": "MCM11",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM11.xlsx",
"pdfPath": null
},
{
"name": "MCM12",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM12.xlsx",
"pdfPath": null
},
{
"name": "MCM13",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM13.xlsx",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CDW5/CDW5_MCM11_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM14",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM14.xlsx",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CDW5/CDW5_MCM14_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM15",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM15.xlsx",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CDW5/CDW5_MCM15_DESC_IP_MERGED.xlsx",
"pdfPath": null
}
]
},
{
"name": "CNO8",
"mcms": [
{
"name": "MCM01",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CNO8/CNO8_MCM01_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM16",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM16.xlsx",
"name": "MCM02",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CNO8/CNO8_MCM02_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM17",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM17.xlsx",
"name": "MCM03",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CNO8/CNO8_MCM03_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM18",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM18.xlsx",
"name": "MCM04",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CNO8/CNO8_MCM04_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM19",
"excelPath": "/projectes/CDW5/excel/CDW5_SYSDL_MCM19.xlsx",
"name": "MCM05",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/CNO8/CNO8_MCM05_DESC_IP_MERGED.xlsx",
"pdfPath": null
}
]
},
{
"name": "MTN6",
"mcms": [
{
"name": "MCM01",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/MTN6/MTN6_MCM01_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM02",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/MTN6/MTN6_MCM02_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM03",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/MTN6/MTN6_MCM03_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM04",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/MTN6/MTN6_MCM04_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM05",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/MTN6/MTN6_MCM05_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM06",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/MTN6/MTN6_MCM06_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM07",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/MTN6/MTN6_MCM07_DESC_IP_MERGED.xlsx",
"pdfPath": null
}
]
},
{
"name": "SAT9",
"mcms": [
{
"name": "MCM01",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/SAT9/SAT9_MCM01_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM02",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/SAT9/SAT9_MCM02_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM03",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/SAT9/SAT9_MCM03_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM04",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/SAT9/SAT9_MCM04_DESC_IP_MERGED.xlsx",
"pdfPath": null
},
{
"name": "MCM05",
"excelPath": "/home/iliagurielidze/projects/plc_generation/PLC Data Generator/SAT9/SAT9_MCM05_DESC_IP_MERGED.xlsx",
"pdfPath": null
}
]

View File

@ -5,49 +5,73 @@ import path from 'path';
// @ts-ignore — xlsx has no type declarations
import XLSX from 'xlsx';
/** Detect WSL environment and return the correct Ignition base path. */
function getIgnitionBasePath(): string {
const winPath = 'C:/Program Files/Inductive Automation/Ignition/data/projects';
try {
const procVersion = fs.readFileSync('/proc/version', 'utf-8').toLowerCase();
if (procVersion.includes('microsoft')) {
return '/mnt/c/Program Files/Inductive Automation/Ignition/data/projects';
}
} catch { /* not Linux / no /proc/version */ }
return winPath;
}
/** Return the PowerShell executable name (WSL needs .exe suffix). */
function getPowerShellCmd(): string {
try {
const procVersion = fs.readFileSync('/proc/version', 'utf-8').toLowerCase();
if (procVersion.includes('microsoft')) return 'powershell.exe';
} catch { /* not Linux */ }
return 'powershell';
}
/** Resolve the PLC Data Generator directory (two levels up from svelte-app/) */
function getPlcDataGenDir(): string {
return path.resolve('..', '..', 'PLC Data Generator');
}
/** Regex to extract project and MCM from DESC_IP_MERGED filenames */
const DESC_IP_REGEX = /^([A-Z0-9]+)_(MCM\d+)_DESC_IP_MERGED\.xlsx$/;
function generateProjectManifest() {
const staticDir = path.resolve('static');
const projectesDir = path.join(staticDir, 'projectes');
if (!fs.existsSync(projectesDir)) fs.mkdirSync(projectesDir, { recursive: true });
if (!fs.existsSync(projectesDir)) return;
const plcDataGenDir = getPlcDataGenDir();
if (!fs.existsSync(plcDataGenDir)) return;
const MCM_REGEX = /SYSDL[_ ]+(MCM\d+)/i;
const projects: { name: string; mcms: { name: string; excelPath: string; pdfPath: string | null }[] }[] = [];
for (const projectName of fs.readdirSync(projectesDir)) {
const projectDir = path.join(projectesDir, projectName);
for (const projectName of fs.readdirSync(plcDataGenDir)) {
const projectDir = path.join(plcDataGenDir, projectName);
if (!fs.statSync(projectDir).isDirectory()) continue;
const excelDir = path.join(projectDir, 'excel');
const pdfDir = path.join(projectDir, 'pdf');
// Find DESC_IP_MERGED files in this project directory
const mergedFiles = fs.readdirSync(projectDir).filter((f: string) => DESC_IP_REGEX.test(f));
if (mergedFiles.length === 0) continue;
const mcmMap = new Map<string, { excelPath: string; pdfPath: string | null }>();
if (fs.existsSync(excelDir)) {
for (const f of fs.readdirSync(excelDir)) {
if (!f.endsWith('.xlsx')) continue;
const match = f.match(MCM_REGEX);
if (!match) continue;
const mcm = match[1];
mcmMap.set(mcm, {
excelPath: `/projectes/${projectName}/excel/${f}`,
pdfPath: null,
});
}
for (const f of mergedFiles) {
const match = f.match(DESC_IP_REGEX);
if (!match) continue;
const mcm = match[2];
mcmMap.set(mcm, { excelPath: path.join(projectDir, f), pdfPath: null });
}
// Check for PDFs in static/projectes/{PROJECT}/pdf/ (if they exist)
const pdfDir = path.join(projectesDir, projectName, 'pdf');
if (fs.existsSync(pdfDir)) {
const PDF_MCM_REGEX = /SYSDL[_ ]+(MCM\d+)/i;
for (const f of fs.readdirSync(pdfDir)) {
if (!f.endsWith('.pdf')) continue;
const match = f.match(MCM_REGEX);
const match = f.match(PDF_MCM_REGEX);
if (!match) continue;
const mcm = match[1];
if (mcmMap.has(mcm)) {
mcmMap.get(mcm)!.pdfPath = `/projectes/${projectName}/pdf/${f}`;
} else {
mcmMap.set(mcm, {
excelPath: '',
pdfPath: `/projectes/${projectName}/pdf/${f}`,
});
}
}
}
@ -114,64 +138,74 @@ function extractZone(dev: string): string {
}
function generateDeviceManifest() {
const projectesDir = path.resolve('..', 'projectes');
const plcDataGenDir = getPlcDataGenDir();
const staticDir = path.resolve('static', 'projectes');
if (!fs.existsSync(projectesDir)) return;
if (!fs.existsSync(plcDataGenDir)) return;
if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir, { recursive: true });
const manifest: Record<string, { id: string; svg: string; zone: string }[]> = {};
for (const projectName of fs.readdirSync(projectesDir)) {
const projectDir = path.join(projectesDir, projectName);
for (const projectName of fs.readdirSync(plcDataGenDir)) {
const projectDir = path.join(plcDataGenDir, projectName);
if (!fs.statSync(projectDir).isDirectory()) continue;
const excelDir = path.join(projectDir, 'excel');
if (!fs.existsSync(excelDir)) continue;
// Find device IO file and IP addresses file
const files = fs.readdirSync(excelDir).filter((f: string) => f.endsWith('.xlsx'));
const ioFile = files.find((f: string) => /Devices?\s*IO/i.test(f));
const ipFile = files.find((f: string) => /IP\s*Address/i.test(f));
if (!ioFile) continue;
const mergedFiles = fs.readdirSync(projectDir).filter((f: string) => DESC_IP_REGEX.test(f));
const ioWb = XLSX.readFile(path.join(excelDir, ioFile));
for (const fileName of mergedFiles) {
const fileMatch = fileName.match(DESC_IP_REGEX);
if (!fileMatch) continue;
const mcm = fileMatch[2]; // e.g. MCM09
// Also load IP addresses for VFDs, FIOMs, DPMs
let ipWb: XLSX.WorkBook | null = null;
if (ipFile) {
try { ipWb = XLSX.readFile(path.join(excelDir, ipFile)); } catch {}
}
let wb: XLSX.WorkBook;
try { wb = XLSX.readFile(path.join(projectDir, fileName)); } catch { continue; }
for (const sheetName of ioWb.SheetNames) {
const mcmMatch = sheetName.match(/^(MCM\d+)$/);
if (!mcmMatch) continue;
const mcm = mcmMatch[1];
const ws = wb.Sheets['DESC_IP'];
if (!ws) continue;
const rows = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' }) as string[][];
if (rows.length < 2) continue;
// Column indices: TAGNAME=0, DESCA=2, DPM=6, DEVICE_TYPE=9
const COL_TAGNAME = 0;
const COL_DESCA = 2;
const COL_DPM = 6;
const COL_DEVICE_TYPE = 9;
const seen = new Set<string>();
const devices: { id: string; svg: string; zone: string }[] = [];
// Parse IO sheet
const ws = ioWb.Sheets[sheetName];
const rows = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' }) as string[][];
// Extract FIO/SIO controllers from column 0 (controller name)
for (let i = 1; i < rows.length; i++) {
const ctrl = String(rows[i][0] || '').trim();
if (!ctrl || seen.has(ctrl)) continue;
if (/FIO|SIO/i.test(ctrl)) {
seen.add(ctrl);
devices.push({ id: ctrl, svg: 'fio_sio_fioh', zone: extractZone(ctrl) });
const tagname = String(rows[i][COL_TAGNAME] || '').trim();
const desca = String(rows[i][COL_DESCA] || '').trim();
const dpm = String(rows[i][COL_DPM] || '').trim();
const deviceType = String(rows[i][COL_DEVICE_TYPE] || '').trim();
// Extract VFDs from TAGNAME where DEVICE_TYPE=APF
if (deviceType === 'APF' && tagname && !seen.has(tagname)) {
seen.add(tagname);
devices.push({ id: tagname, svg: 'conveyor', zone: extractZone(tagname) });
}
}
// Extract assigned devices from column 3
for (let i = 1; i < rows.length; i++) {
const dev = String(rows[i][3] || '').trim();
if (!dev || seen.has(dev)) continue;
seen.add(dev);
// Extract FIOMs from TAGNAME where DEVICE_TYPE=FIOM
if (deviceType === 'FIOM' && tagname && !seen.has(tagname)) {
seen.add(tagname);
devices.push({ id: tagname, svg: 'fio_sio_fioh', zone: extractZone(tagname) });
}
const svg = classifyDevice(dev);
// Extract DPMs from DPM column (only actual DPM names, not part numbers)
if (dpm && /DPM\d*$/i.test(dpm) && !seen.has(dpm)) {
seen.add(dpm);
devices.push({ id: dpm, svg: 'dpm', zone: extractZone(dpm) });
}
// Extract individual devices from DESCA column
if (!desca || desca === 'SPARE' || seen.has(desca)) continue;
seen.add(desca);
const svg = classifyDevice(desca);
if (!svg) continue;
const zone = extractZone(dev);
const zone = extractZone(desca);
// Consolidate PDP: only add once per PDP number
if (svg === 'pdp') {
@ -183,38 +217,13 @@ function generateDeviceManifest() {
// Consolidate beacon: BCN1_A + BCN1_H -> one BCN1 per zone
if (svg === 'beacon') {
const bcnBase = dev.replace(/_[ARHBWG]$/, '');
const bcnBase = desca.replace(/_[ARHBWG]$/, '');
if (devices.some(d => d.id === bcnBase)) continue;
devices.push({ id: bcnBase, svg, zone });
continue;
}
devices.push({ id: dev, svg, zone });
}
// Parse IP/network sheet for VFDs, FIOMs, DPMs
if (ipWb) {
const ipSheets = ipWb.SheetNames.filter((s: string) => s.toUpperCase().includes(mcm));
for (const ipSheet of ipSheets) {
const ipWs = ipWb.Sheets[ipSheet];
const ipRows = XLSX.utils.sheet_to_json(ipWs, { header: 1, defval: '' }) as string[][];
for (const row of ipRows) {
for (const cell of row) {
const val = String(cell || '').trim();
if (!val || val.startsWith('11.') || seen.has(val)) continue;
if (/^[A-Z]+\d*_\d+.*VFD$/.test(val)) {
seen.add(val);
devices.push({ id: val, svg: 'conveyor', zone: extractZone(val) });
} else if (/^[A-Z]+\d*_\d+.*FIOM\d*$/.test(val)) {
seen.add(val);
devices.push({ id: val, svg: 'fio_sio_fioh', zone: extractZone(val) });
} else if (/^[A-Z]+\d*_\d+.*DPM\d*$/.test(val) && !/_P\d+$/.test(val)) {
seen.add(val);
devices.push({ id: val, svg: 'dpm', zone: extractZone(val) });
}
}
}
}
devices.push({ id: desca, svg, zone });
}
// Always include the MCM symbol itself
@ -224,7 +233,7 @@ function generateDeviceManifest() {
// Sort by zone then id
devices.sort((a, b) => a.zone.localeCompare(b.zone) || a.id.localeCompare(b.id));
manifest[mcm] = devices;
manifest[`${projectName}_${mcm}`] = devices;
}
}
@ -264,7 +273,7 @@ export default defineConfig({
req.on('end', () => {
try {
const { projectName, viewName, viewPath, viewJson, resourceJson } = JSON.parse(body);
const ignitionBase = 'C:/Program Files/Inductive Automation/Ignition/data/projects';
const ignitionBase = getIgnitionBasePath();
const viewSubPath = viewPath ? `${viewPath}/${viewName}` : viewName;
const viewDir = path.join(ignitionBase, projectName, 'com.inductiveautomation.perspective/views', viewSubPath);
fs.mkdirSync(viewDir, { recursive: true });
@ -299,7 +308,7 @@ try {
Invoke-WebRequest -Uri 'http://localhost:8088/system/gateway-scripts' -Method POST -Body $body -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5
} catch {}
`;
try { execSync(`powershell -Command "${script.replace(/\n/g, ' ')}"`, { timeout: 10000 }); } catch {}
try { execSync(`${getPowerShellCmd()} -Command "${script.replace(/\n/g, ' ')}"`, { timeout: 10000 }); } catch {}
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ ok: true }));
} catch (err: any) {