Add Ignition deploy: write view.json directly to Ignition project dir
- New "Ignition" section in toolbar with Project and View name fields
- View name defaults to current MCM
- "Deploy to Ignition" button writes view.json + resource.json to:
C:/Program Files/Inductive Automation/Ignition/data/projects/{Project}/
com.inductiveautomation.perspective/views/{ViewName}/
- Vite dev server plugin handles file writing via /api/deploy-ignition
- view.json wraps ia.shapes.svg component in proper Perspective view structure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2c38950cb7
commit
a0ceb56309
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { layout } from '$lib/stores/layout.svelte.js';
|
import { layout } from '$lib/stores/layout.svelte.js';
|
||||||
import { exportSVG, exportJSON, exportIgnitionJSON, loadLayoutJSON, loadLayoutSVG } from '$lib/export.js';
|
import { exportSVG, exportJSON, exportIgnitionJSON, deployToIgnition, loadLayoutJSON, loadLayoutSVG } from '$lib/export.js';
|
||||||
import { loadPdfFile, loadPdfFromPath, pdfZoomIn, pdfZoomOut, removePdf, toggleEditBackground, restorePdf } from '$lib/pdf.js';
|
import { loadPdfFile, loadPdfFromPath, pdfZoomIn, pdfZoomOut, removePdf, toggleEditBackground, restorePdf } from '$lib/pdf.js';
|
||||||
import { discoverProjects } from '$lib/projects.js';
|
import { discoverProjects } from '$lib/projects.js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
let pdfOpen = $state(false);
|
let pdfOpen = $state(false);
|
||||||
let settingsOpen = $state(false);
|
let settingsOpen = $state(false);
|
||||||
|
let ignitionOpen = $state(false);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const projects = await discoverProjects();
|
const projects = await discoverProjects();
|
||||||
@ -197,6 +198,27 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Ignition Deploy -->
|
||||||
|
<button class="section-toggle" onclick={() => ignitionOpen = !ignitionOpen}>
|
||||||
|
<span class="chevron" class:open={ignitionOpen}></span>
|
||||||
|
Ignition
|
||||||
|
</button>
|
||||||
|
{#if ignitionOpen}
|
||||||
|
<div class="section-body">
|
||||||
|
<div class="inline-row">
|
||||||
|
<label>Project</label>
|
||||||
|
<input type="text" bind:value={layout.ignitionProject} placeholder="Testing_Project">
|
||||||
|
</div>
|
||||||
|
<div class="inline-row">
|
||||||
|
<label>View</label>
|
||||||
|
<input type="text" bind:value={layout.ignitionViewName} placeholder={layout.currentMcm || 'MCM01'}>
|
||||||
|
</div>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button onclick={deployToIgnition}>Deploy to Ignition</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<!-- Symbols -->
|
<!-- Symbols -->
|
||||||
@ -435,7 +457,8 @@
|
|||||||
min-width: 14px;
|
min-width: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-row input[type="number"] {
|
.inline-row input[type="number"],
|
||||||
|
.inline-row input[type="text"] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 3px 4px;
|
padding: 3px 4px;
|
||||||
background: #1f2937;
|
background: #1f2937;
|
||||||
|
|||||||
@ -507,22 +507,21 @@ function addFillStroke(obj: Record<string, any>, el: Element) {
|
|||||||
if (obj.stroke?.paint === 'none') obj.stroke.paint = 'transparent';
|
if (obj.stroke?.paint === 'none') obj.stroke.paint = 'transparent';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Export as Ignition SCADA JSON (ia.shapes.svg format) */
|
/** Build Ignition ia.shapes.svg component data */
|
||||||
export async function exportIgnitionJSON() {
|
async function buildIgnitionComponent(): Promise<Record<string, any>> {
|
||||||
const svgStr = await buildSvgString();
|
const svgStr = await buildSvgString();
|
||||||
const doc = new DOMParser().parseFromString(svgStr, 'image/svg+xml');
|
const doc = new DOMParser().parseFromString(svgStr, 'image/svg+xml');
|
||||||
const svgEl = doc.documentElement;
|
const svgEl = doc.documentElement;
|
||||||
|
|
||||||
const elements: Record<string, any>[] = [];
|
const elements: Record<string, any>[] = [];
|
||||||
for (const child of Array.from(svgEl.children)) {
|
for (const child of Array.from(svgEl.children)) {
|
||||||
// Skip comments
|
|
||||||
if (child.nodeType === 8) continue;
|
if (child.nodeType === 8) continue;
|
||||||
const converted = svgElementToIgnition(child);
|
const converted = svgElementToIgnition(child);
|
||||||
if (converted) elements.push(converted);
|
if (converted) elements.push(converted);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mcmName = layout.currentMcm || 'export';
|
const mcmName = layout.currentMcm || 'export';
|
||||||
const ignitionData = [{
|
return {
|
||||||
type: 'ia.shapes.svg',
|
type: 'ia.shapes.svg',
|
||||||
version: 0,
|
version: 0,
|
||||||
props: {
|
props: {
|
||||||
@ -532,14 +531,70 @@ export async function exportIgnitionJSON() {
|
|||||||
meta: { name: `${mcmName}_Detailed_View` },
|
meta: { name: `${mcmName}_Detailed_View` },
|
||||||
position: { width: 1, height: 1 },
|
position: { width: 1, height: 1 },
|
||||||
custom: {},
|
custom: {},
|
||||||
}];
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Export as Ignition SCADA JSON file (download) */
|
||||||
|
export async function exportIgnitionJSON() {
|
||||||
|
const component = await buildIgnitionComponent();
|
||||||
|
const mcmName = layout.currentMcm || 'export';
|
||||||
downloadBlob(
|
downloadBlob(
|
||||||
new Blob([JSON.stringify(ignitionData, null, 2)], { type: 'application/json' }),
|
new Blob([JSON.stringify([component], null, 2)], { type: 'application/json' }),
|
||||||
`${mcmName}_Detailed_View.json`
|
`${mcmName}_Detailed_View.json`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Deploy directly to Ignition project directory */
|
||||||
|
export async function deployToIgnition() {
|
||||||
|
const component = await buildIgnitionComponent();
|
||||||
|
const projectName = layout.ignitionProject || 'Testing_Project';
|
||||||
|
const viewName = layout.ignitionViewName || layout.currentMcm || 'MCM01';
|
||||||
|
|
||||||
|
const viewJson = JSON.stringify({
|
||||||
|
custom: {},
|
||||||
|
params: {},
|
||||||
|
props: {
|
||||||
|
defaultSize: { height: layout.canvasH, width: layout.canvasW },
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
children: [component],
|
||||||
|
meta: { name: 'root' },
|
||||||
|
props: { direction: 'column' },
|
||||||
|
type: 'ia.container.coord',
|
||||||
|
},
|
||||||
|
}, null, 2);
|
||||||
|
|
||||||
|
const resourceJson = JSON.stringify({
|
||||||
|
scope: 'G',
|
||||||
|
version: 1,
|
||||||
|
restricted: false,
|
||||||
|
overridable: true,
|
||||||
|
files: ['view.json'],
|
||||||
|
attributes: {
|
||||||
|
lastModification: {
|
||||||
|
actor: 'scada-layout-tool',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, null, 2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/deploy-ignition', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ projectName, viewName, viewJson, resourceJson }),
|
||||||
|
});
|
||||||
|
const result = await resp.json();
|
||||||
|
if (result.ok) {
|
||||||
|
alert(`Deployed to Ignition!\n${result.path}`);
|
||||||
|
} else {
|
||||||
|
alert(`Deploy failed: ${result.error}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Emit EPC symbol — polyline + icon + right box, wrapped in <g> with id/label */
|
/** Emit EPC symbol — polyline + icon + right box, wrapped in <g> with id/label */
|
||||||
async function emitEpc(lines: string[], sym: PlacedSymbol, label: string, outerTransform: string) {
|
async function emitEpc(lines: string[], sym: PlacedSymbol, label: string, outerTransform: string) {
|
||||||
const waypoints = sym.epcWaypoints || EPC_CONFIG.defaultWaypoints;
|
const waypoints = sym.epcWaypoints || EPC_CONFIG.defaultWaypoints;
|
||||||
|
|||||||
@ -25,6 +25,10 @@ class LayoutStore {
|
|||||||
currentProject = $state<string>('');
|
currentProject = $state<string>('');
|
||||||
currentMcm = $state<string>('');
|
currentMcm = $state<string>('');
|
||||||
|
|
||||||
|
// Ignition export settings
|
||||||
|
ignitionProject = $state<string>('Testing_Project');
|
||||||
|
ignitionViewName = $state<string>('');
|
||||||
|
|
||||||
// PDF state
|
// PDF state
|
||||||
pdfScale = $state(1.0);
|
pdfScale = $state(1.0);
|
||||||
pdfOffsetX = $state(0);
|
pdfOffsetX = $state(0);
|
||||||
|
|||||||
@ -254,6 +254,31 @@ export default defineConfig({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'ignition-deploy',
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use('/api/deploy-ignition', async (req, res) => {
|
||||||
|
if (req.method !== 'POST') { res.statusCode = 405; res.end('Method not allowed'); return; }
|
||||||
|
let body = '';
|
||||||
|
req.on('data', (chunk: Buffer) => { body += chunk.toString(); });
|
||||||
|
req.on('end', () => {
|
||||||
|
try {
|
||||||
|
const { projectName, viewName, viewJson, resourceJson } = JSON.parse(body);
|
||||||
|
const ignitionBase = 'C:/Program Files/Inductive Automation/Ignition/data/projects';
|
||||||
|
const viewDir = path.join(ignitionBase, projectName, 'com.inductiveautomation.perspective/views', viewName);
|
||||||
|
fs.mkdirSync(viewDir, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(viewDir, 'view.json'), viewJson);
|
||||||
|
fs.writeFileSync(path.join(viewDir, 'resource.json'), resourceJson);
|
||||||
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
res.end(JSON.stringify({ ok: true, path: viewDir }));
|
||||||
|
} catch (err: any) {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end(JSON.stringify({ error: err.message }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
sveltekit(),
|
sveltekit(),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user