forked from 1b-agent/opencadstudio-upstream
feat(graph): save and open node graphs as .ocg files
The graph panel gets New, Open and Save. A .ocg file is JSON holding the graph (nodes, parameters, links), the canvas (positions, open sections, pan, zoom) and each object node's entity handles, plus the fingerprint GUID of the drawing those entities live in. On open, handles are reused only in that same drawing and only while the entity is still the node's kind; otherwise evaluation creates new entities, so a graph opened in another drawing never takes over unrelated objects. A drawing without a fingerprint gets one when its graph is first saved. Pins opencadcodec d6d534f (DXF now writes $FINGERPRINTGUID and $VERSIONGUID), opencadkernel 5d9e921 and opencadgraph 5c9b004 (graph JSON round trip). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5639b29209
commit
857f2f2e58
9 files changed
+197
-16
No files matched your search
@@ -219,6 +219,83 @@ impl OpenCADStudio {
|
||||
return self.graph_set(i, port, value);
|
||||
}
|
||||
GraphMsg::Set(port, value) => return self.graph_set(i, port, value),
|
||||
GraphMsg::New => self.tabs[i].graph = Default::default(),
|
||||
GraphMsg::Save => {
|
||||
// The file names the drawing by its fingerprint so reopening it
|
||||
// elsewhere cannot take over unrelated entities; a drawing that
|
||||
// never had one gets it now and saves it with its next save.
|
||||
let tab = &mut self.tabs[i];
|
||||
let header = &mut tab.scene.document.header;
|
||||
if header.fingerprint_guid.is_empty() {
|
||||
header.fingerprint_guid =
|
||||
format!("{{{}}}", crate::app::control::new_guid_v4().to_uppercase());
|
||||
tab.dirty = true;
|
||||
}
|
||||
let bytes = tab.graph.to_file(&tab.scene.document.header.fingerprint_guid);
|
||||
return Task::perform(
|
||||
async move {
|
||||
let handle = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Save Node Graph").as_ref())
|
||||
.add_filter(crate::t!("Node Graph").as_ref(), &["ocg"])
|
||||
.set_file_name("graph.ocg")
|
||||
.save_file()
|
||||
.await?;
|
||||
let name = crate::sys::handle_path(&handle).to_string_lossy().into_owned();
|
||||
Some(handle.write(&bytes).await.map(|()| name).map_err(|e| e.to_string()))
|
||||
},
|
||||
|result| Message::Graph(GraphMsg::Saved(result)),
|
||||
);
|
||||
}
|
||||
GraphMsg::Saved(Some(Ok(name))) => {
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Node graph saved to {}", name).as_ref());
|
||||
}
|
||||
GraphMsg::Saved(Some(Err(error))) => self.command_line.push_error(&error),
|
||||
GraphMsg::Open => {
|
||||
return Task::perform(
|
||||
async {
|
||||
let handle = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Open Node Graph").as_ref())
|
||||
.add_filter(crate::t!("Node Graph").as_ref(), &["ocg"])
|
||||
.pick_file()
|
||||
.await?;
|
||||
let _ = crate::sys::handle_path(&handle);
|
||||
Some(handle.read().await)
|
||||
},
|
||||
|bytes| Message::Graph(GraphMsg::Loaded(bytes)),
|
||||
);
|
||||
}
|
||||
GraphMsg::Loaded(Some(bytes)) => {
|
||||
let (mut graph, drawing) = match crate::ui::node_graph::Graph::from_file(&bytes) {
|
||||
Ok(loaded) => loaded,
|
||||
Err(error) => {
|
||||
self.command_line.push_error(&error);
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
// Entities are reused only in the drawing the graph was saved
|
||||
// against, and only while they are still the node's kind;
|
||||
// otherwise evaluation creates fresh ones.
|
||||
let document = &self.tabs[i].scene.document;
|
||||
let same_drawing =
|
||||
!drawing.is_empty() && drawing == document.header.fingerprint_guid;
|
||||
for node in &graph.engine.nodes {
|
||||
let kind = match &node.kind {
|
||||
Kind::Object(name) => ObjectKind::from_name(name),
|
||||
Kind::Op(_) => None,
|
||||
};
|
||||
if let Some(ui) = graph.ui.get_mut(&node.id) {
|
||||
ui.handles.retain(|handle| {
|
||||
same_drawing
|
||||
&& kind.zip(document.get_entity(*handle)).is_some_and(
|
||||
|(kind, entity)| kind.matches(entity),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
self.tabs[i].graph = graph;
|
||||
return self.graph_run(i);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Task::none()
|
||||
|
||||
Reference in new issue
Block a user