Add SVG import, JSON export, and embed layout data in SVG export
- SVG export now embeds layout JSON as HTML comment for re-import - New loadLayoutSVG() extracts embedded data from exported SVGs - Import accepts both .json and .svg files - New exportJSON() saves layout as MCM_layout.json - JSON export button added to toolbar Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
37f3700a18
commit
1e67c3de47
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { layout } from '$lib/stores/layout.svelte.js';
|
||||
import { exportSVG, loadLayoutJSON } from '$lib/export.js';
|
||||
import { exportSVG, exportJSON, loadLayoutJSON, loadLayoutSVG } from '$lib/export.js';
|
||||
import { loadPdfFile, loadPdfFromPath, pdfZoomIn, pdfZoomOut, removePdf, toggleEditBackground, restorePdf } from '$lib/pdf.js';
|
||||
import { discoverProjects } from '$lib/projects.js';
|
||||
import { onMount } from 'svelte';
|
||||
@ -93,7 +93,11 @@
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
await loadLayoutJSON(file);
|
||||
if (file.name.endsWith('.svg')) {
|
||||
await loadLayoutSVG(file);
|
||||
} else {
|
||||
await loadLayoutJSON(file);
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Invalid layout file: ' + (err instanceof Error ? err.message : String(err)));
|
||||
}
|
||||
@ -239,9 +243,10 @@
|
||||
</div>
|
||||
<div class="setting btn-row">
|
||||
<button onclick={exportSVG}>Save SVG</button>
|
||||
<button onclick={exportJSON}>Save JSON</button>
|
||||
<button onclick={() => importFileEl.click()}>Load JSON</button>
|
||||
<button onclick={clearCanvas}>Clear</button>
|
||||
<input bind:this={importFileEl} type="file" accept=".json" style="display:none" onchange={onImportFile}>
|
||||
<input bind:this={importFileEl} type="file" accept=".json,.svg" style="display:none" onchange={onImportFile}>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { layout } from './stores/layout.svelte.js';
|
||||
import { isEpcType, isInductionType, isSpurType, isCurvedType, isRectConveyanceType, isExtendoType, isPhotoeyeType, getSymbolGroup, EPC_CONFIG, INDUCTION_CONFIG, PHOTOEYE_CONFIG, getCurveGeometry } from './symbols.js';
|
||||
import { deserializeSymbol } from './serialization.js';
|
||||
import { serializeSymbol, deserializeSymbol } from './serialization.js';
|
||||
import type { PlacedSymbol } from './types.js';
|
||||
|
||||
/** Parse conveyance label into display lines — same logic as renderer */
|
||||
@ -70,6 +70,18 @@ function downloadBlob(blob: Blob, filename: string) {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function exportJSON() {
|
||||
const data = {
|
||||
symbols: layout.symbols.map(s => serializeSymbol(s)),
|
||||
gridSize: layout.gridSize,
|
||||
minSpacing: layout.minSpacing,
|
||||
canvasW: layout.canvasW,
|
||||
canvasH: layout.canvasH,
|
||||
};
|
||||
const mcmName = layout.currentMcm || 'export';
|
||||
downloadBlob(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }), `${mcmName}_layout.json`);
|
||||
}
|
||||
|
||||
/** Serialize child elements of an SVG, stripping xmlns added by XMLSerializer */
|
||||
function serializeChildren(parent: Element): string {
|
||||
return Array.from(parent.children)
|
||||
@ -247,6 +259,16 @@ export async function exportSVG() {
|
||||
}
|
||||
}
|
||||
|
||||
// Embed layout data as metadata for re-import
|
||||
const layoutData = {
|
||||
symbols: layout.symbols.filter(s => !s.hidden && !layout.hiddenGroups.has(getSymbolGroup(s.symbolId))).map(s => serializeSymbol(s)),
|
||||
gridSize: layout.gridSize,
|
||||
minSpacing: layout.minSpacing,
|
||||
canvasW: layout.canvasW,
|
||||
canvasH: layout.canvasH,
|
||||
};
|
||||
lines.push(` <!-- LAYOUT_DATA:${JSON.stringify(layoutData)}:END_LAYOUT_DATA -->`);
|
||||
|
||||
lines.push('</svg>');
|
||||
const mcmName = layout.currentMcm || 'export';
|
||||
downloadBlob(new Blob([lines.join('\n')], { type: 'image/svg+xml' }), `${mcmName}_Detailed_View.svg`);
|
||||
@ -315,6 +337,36 @@ async function emitEpc(lines: string[], sym: PlacedSymbol, label: string, outerT
|
||||
lines.push(' </g>');
|
||||
}
|
||||
|
||||
export function loadLayoutSVG(file: File): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (ev) => {
|
||||
try {
|
||||
const svgText = ev.target!.result as string;
|
||||
const match = svgText.match(/<!-- LAYOUT_DATA:(.*?):END_LAYOUT_DATA -->/);
|
||||
if (!match) throw new Error('No layout data found in SVG. Only SVGs exported from this tool can be imported.');
|
||||
const data = JSON.parse(match[1]);
|
||||
layout.pushUndo();
|
||||
if (data.gridSize) layout.gridSize = data.gridSize;
|
||||
if (data.minSpacing) layout.minSpacing = data.minSpacing;
|
||||
if (data.canvasW) layout.canvasW = data.canvasW;
|
||||
if (data.canvasH) layout.canvasH = data.canvasH;
|
||||
layout.symbols = [];
|
||||
layout.nextId = 1;
|
||||
for (const s of data.symbols) {
|
||||
layout.symbols.push(deserializeSymbol(s, layout.nextId++));
|
||||
}
|
||||
layout.markDirty();
|
||||
layout.saveMcmState();
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
export function loadLayoutJSON(file: File): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user