mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-28 07:38:11 +08:00
Add nondestructive vector editing (#1676)
* Initial vector modify node * Initial extraction of data from monitor nodes * Migrate to point id * Start converting to modify node * Non destructive spline tool (tout le reste est cassé) * Fix unconnected modify node * Fix freehand tool * Pen tool * Migrate demo art * Select points * Fix the demo artwork * Fix the X and Y inputs for path tool * G1 continous toggle * Delete points * Fix test * Insert point * Improve robustness of handles * Fix GRS shortcuts on path * Dragging points * Fix build * Preserve opposing handle lengths * Update demo art and snapping * Fix polygon tool * Double click end anchor * Improve dragging * Fix text shifting * Select only connected verts * Colinear alt * Cleanup * Fix imports * Improve pen tool avoiding handle placement * Improve disolve * Remove pivot widget from Transform node properties * Fix demo art * Fix bugs * Re-save demo artwork * Code review * Serialize hashmap as tuple vec to enable deserialize_inputs * Fix migrate * Add document upgrade function to editor_api.rs * Finalize document upgrading * Rename to the Path node * Remove smoothing from Freehand tool * Upgrade demo artwork * Propertly disable raw-rs tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Adam <adamgerhant@gmail.com> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
co-authored by
Keavon Chambers
Adam
Dennis Kobert
parent
fd3613018a
commit
1652c713a6
@@ -636,6 +636,166 @@ impl EditorHandle {
|
||||
pub fn inject_imaginate_poll_server_status(&self) {
|
||||
self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this (probably starting late 2024)
|
||||
#[wasm_bindgen(js_name = triggerUpgradeDocumentToVectorManipulationFormat)]
|
||||
pub async fn upgrade_document_to_vector_manipulation_format(
|
||||
&self,
|
||||
document_id: u64,
|
||||
document_name: String,
|
||||
document_is_auto_saved: bool,
|
||||
document_is_saved: bool,
|
||||
document_serialized_content: String,
|
||||
) {
|
||||
use editor::messages::portfolio::document::graph_operation::transform_utils::*;
|
||||
use editor::messages::portfolio::document::graph_operation::utility_types::*;
|
||||
use editor::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||
use editor::node_graph_executor::replace_node_runtime;
|
||||
use editor::node_graph_executor::NodeRuntime;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::{value::TaggedValue, DocumentNodeImplementation};
|
||||
use graphene_core::vector::*;
|
||||
|
||||
let (_, request_receiver) = std::sync::mpsc::channel();
|
||||
let (response_sender, _) = std::sync::mpsc::channel();
|
||||
let old_runtime = replace_node_runtime(NodeRuntime::new(request_receiver, response_sender));
|
||||
|
||||
let document_serialized_content = document_serialized_content.replace("\"ManipulatorGroupIds\"", "\"PointIds\"");
|
||||
|
||||
let mut editor = Editor::new();
|
||||
let document_id = DocumentId(document_id);
|
||||
editor.handle_message(PortfolioMessage::OpenDocumentFileWithId {
|
||||
document_id,
|
||||
document_name: document_name.clone(),
|
||||
document_is_auto_saved,
|
||||
document_is_saved,
|
||||
document_serialized_content: document_serialized_content.clone(),
|
||||
});
|
||||
|
||||
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
|
||||
for node in document.network.nodes.values_mut().filter(|d| d.name == "Artboard") {
|
||||
if let Some(network) = node.implementation.get_network_mut() {
|
||||
for node in network.nodes.values_mut() {
|
||||
if node.name == "To Artboard" {
|
||||
node.implementation = DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>");
|
||||
if node.inputs.len() != 6 {
|
||||
node.inputs.insert(2, NodeInput::value(TaggedValue::IVec2(glam::IVec2::default()), false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
||||
portfolio
|
||||
.executor
|
||||
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id().unwrap()).unwrap(), glam::UVec2::ONE)
|
||||
.unwrap();
|
||||
editor::node_graph_executor::run_node_graph().await;
|
||||
|
||||
let mut messages = VecDeque::new();
|
||||
if let Err(err) = editor.poll_node_graph_evaluation(&mut messages) {
|
||||
log::warn!(
|
||||
"While attempting to upgrade the old document format, the graph evaluation failed which is necessary for the upgrade process:\n{:#?}",
|
||||
err
|
||||
);
|
||||
|
||||
replace_node_runtime(old_runtime.unwrap());
|
||||
|
||||
let document_name = document_name.clone() + "__DO_NOT_UPGRADE__";
|
||||
self.dispatch(PortfolioMessage::OpenDocumentFileWithId {
|
||||
document_id,
|
||||
document_name,
|
||||
document_is_auto_saved,
|
||||
document_is_saved,
|
||||
document_serialized_content,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let mut updated_nodes = HashSet::new();
|
||||
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
|
||||
document.metadata.load_structure(&document.network);
|
||||
for node in document.network.nodes.iter().filter(|(_, d)| d.name == "Merge").map(|(id, _)| *id).collect::<Vec<_>>() {
|
||||
let layer = LayerNodeIdentifier::new(node, &document.network);
|
||||
if document.metadata.is_folder(layer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let bounds = LayerBounds::new(&document.metadata, layer);
|
||||
|
||||
let mut responses = VecDeque::new();
|
||||
let mut shape = None;
|
||||
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), &mut document.network, &mut document.metadata, &mut document.node_graph_handler, &mut responses) {
|
||||
modify_inputs.modify_existing_inputs("Transform", |inputs, node_id, metadata| {
|
||||
if !updated_nodes.insert(node_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = get_current_transform(&inputs);
|
||||
let upstream_transform = metadata.upstream_transform(node_id);
|
||||
let pivot_transform = glam::DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(get_current_normalized_pivot(&inputs))));
|
||||
|
||||
update_transform(inputs, pivot_transform * transform * pivot_transform.inverse());
|
||||
});
|
||||
modify_inputs.modify_existing_inputs("Shape", |inputs, node_id, _metadata| {
|
||||
if !updated_nodes.insert(node_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let empty_vec = Vec::new();
|
||||
let path_data = if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Subpaths(translation),
|
||||
..
|
||||
} = &inputs[0]
|
||||
{
|
||||
translation
|
||||
} else {
|
||||
&empty_vec
|
||||
};
|
||||
|
||||
let empty_vec = Vec::new();
|
||||
let colinear_manipulators = if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::PointIds(translation),
|
||||
..
|
||||
} = &inputs[1]
|
||||
{
|
||||
translation
|
||||
} else {
|
||||
&empty_vec
|
||||
};
|
||||
|
||||
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
||||
vector_data.colinear_manipulators = colinear_manipulators
|
||||
.iter()
|
||||
.filter_map(|&point| ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||
.collect();
|
||||
|
||||
shape = Some((node_id, VectorModification::create_from_vector(&vector_data)));
|
||||
});
|
||||
}
|
||||
if let Some((id, modification)) = shape {
|
||||
let metadata = document.network.nodes.remove(&id).map(|node| node.metadata).unwrap_or_default();
|
||||
let node_type = resolve_document_node_type("Path").unwrap();
|
||||
|
||||
let document_node = node_type.to_document_node_default_inputs([None, Some(NodeInput::value(TaggedValue::VectorModification(modification), false))], metadata);
|
||||
document.network.nodes.insert(id, document_node);
|
||||
}
|
||||
}
|
||||
|
||||
let document_serialized_content = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap().serialize_document();
|
||||
|
||||
replace_node_runtime(old_runtime.unwrap());
|
||||
|
||||
self.dispatch(PortfolioMessage::OpenDocumentFileWithId {
|
||||
document_id,
|
||||
document_name,
|
||||
document_is_auto_saved,
|
||||
document_is_saved,
|
||||
document_serialized_content,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -765,7 +925,12 @@ async fn poll_node_graph_evaluation() {
|
||||
|
||||
editor_and_handle(|editor, handle| {
|
||||
let mut messages = VecDeque::new();
|
||||
editor.poll_node_graph_evaluation(&mut messages);
|
||||
if let Err(e) = editor.poll_node_graph_evaluation(&mut messages) {
|
||||
// TODO: This is a hacky way to suppress the error, but it shouldn't be generated in the first place
|
||||
if e != "No active document" {
|
||||
error!("Error evaluating node graph:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
// Send each `FrontendMessage` to the JavaScript frontend
|
||||
for response in messages.into_iter().flat_map(|message| editor.handle_message(message)) {
|
||||
|
||||
Reference in New Issue
Block a user