Skip to content

Commit fd4e7d0

Browse files
stackdumpclaude
andcommitted
Convert metamodel to pflow visual format for editor rendering
The /api/templates endpoint returns metamodel format (states/actions/arcs) but petri-view expects pflow.xyz format (places/transitions with x,y). metamodelToPflow() converts between formats with circular auto-layout so templates render visually in the editor instead of showing an empty canvas. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a60c7d1 commit fd4e7d0

1 file changed

Lines changed: 55 additions & 1 deletion

File tree

public/bitwrap.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,60 @@ function downloadFile(filename, content) {
335335
URL.revokeObjectURL(url);
336336
}
337337

338+
// Convert metamodel format (states/actions/arcs) to pflow.xyz format (places/transitions)
339+
// with auto-generated circular layout positions.
340+
function metamodelToPflow(schema) {
341+
const places = {};
342+
const transitions = {};
343+
const states = schema.states || [];
344+
const actions = schema.actions || [];
345+
const arcs = schema.arcs || [];
346+
const total = states.length + actions.length;
347+
const radius = Math.max(120, total * 30);
348+
const cx = 300, cy = 250;
349+
350+
states.forEach((s, i) => {
351+
const angle = (2 * Math.PI * i) / total;
352+
places[s.id] = {
353+
x: Math.round(cx + radius * Math.cos(angle)),
354+
y: Math.round(cy + radius * Math.sin(angle)),
355+
initial: typeof s.initial === 'number' ? s.initial : 0,
356+
};
357+
});
358+
359+
actions.forEach((a, i) => {
360+
const angle = (2 * Math.PI * (i + states.length)) / total;
361+
transitions[a.id] = {
362+
x: Math.round(cx + radius * Math.cos(angle)),
363+
y: Math.round(cy + radius * Math.sin(angle)),
364+
};
365+
});
366+
367+
const pflowArcs = arcs.map(a => ({
368+
source: a.source,
369+
target: a.target,
370+
weight: [1],
371+
inhibitTransition: false,
372+
'@type': 'Arrow',
373+
...(a.keys && a.keys.length ? { keys: a.keys } : {}),
374+
...(a.value ? { value: a.value } : {}),
375+
}));
376+
377+
return {
378+
'@context': 'https://pflow.xyz/schema',
379+
'@type': 'PetriNet',
380+
'@version': '1.1',
381+
name: schema.name || 'Model',
382+
version: schema.version || '1.0.0',
383+
token: ['https://pflow.xyz/tokens/black'],
384+
places,
385+
transitions,
386+
arcs: pflowArcs,
387+
...(schema.events ? { events: schema.events } : {}),
388+
...(schema.constraints ? { constraints: schema.constraints } : {}),
389+
};
390+
}
391+
338392
// Load model from URL params on page load
339393
(function() {
340394
const params = new URLSearchParams(window.location.search);
@@ -369,7 +423,7 @@ function downloadFile(filename, content) {
369423
.then(r => r.ok ? r.json() : null)
370424
.then(data => {
371425
if (data && petriView && petriView.setModel) {
372-
petriView.setModel(data);
426+
petriView.setModel(metamodelToPflow(data));
373427
}
374428
})
375429
.catch(err => console.error('Failed to load template:', err));

0 commit comments

Comments
 (0)