mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 10:58:12 +08:00
Make the Transform node use angles in degrees instead of radians for Rotation and Skew (#3160)
* Make the Transform node use degrees not radians * Migration script * Migrate skew value input to store degrees * Add comments * Fix migrations to account for the old deprecated "Pivot" parameter * Fix tooling interactions with degrees-based transforms * Upgrade demo art --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
c51967384f
commit
332088bce1
@@ -32,10 +32,13 @@ pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64
|
||||
|
||||
/// Update the inputs of the transform node to match a new transform
|
||||
pub fn update_transform(network_interface: &mut NodeNetworkInterface, node_id: &NodeId, transform: DAffine2) {
|
||||
let (scale, angle, translation, shear) = compute_scale_angle_translation_shear(transform);
|
||||
let (scale, rotation, translation, shear) = compute_scale_angle_translation_shear(transform);
|
||||
|
||||
let rotation = rotation.to_degrees();
|
||||
let shear = DVec2::new(shear.x.atan().to_degrees(), shear.y.atan().to_degrees());
|
||||
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(translation), false), &[]);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(angle), false), &[]);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(rotation), false), &[]);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::DVec2(scale), false), &[]);
|
||||
network_interface.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::DVec2(shear), false), &[]);
|
||||
}
|
||||
@@ -76,14 +79,14 @@ pub fn get_current_transform(inputs: &[NodeInput]) -> DAffine2 {
|
||||
} else {
|
||||
DVec2::ZERO
|
||||
};
|
||||
|
||||
let angle = if let Some(&TaggedValue::F64(angle)) = inputs[2].as_value() { angle } else { 0. };
|
||||
|
||||
let rotation = if let Some(&TaggedValue::F64(rotation)) = inputs[2].as_value() { rotation } else { 0. };
|
||||
let scale = if let Some(&TaggedValue::DVec2(scale)) = inputs[3].as_value() { scale } else { DVec2::ONE };
|
||||
|
||||
let shear = if let Some(&TaggedValue::DVec2(shear)) = inputs[4].as_value() { shear } else { DVec2::ZERO };
|
||||
|
||||
DAffine2::from_scale_angle_translation(scale, angle, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.])
|
||||
let rotation = rotation.to_radians();
|
||||
let shear = DVec2::new(shear.x.to_radians().tan(), shear.y.to_radians().tan());
|
||||
|
||||
DAffine2::from_scale_angle_translation(scale, rotation, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.])
|
||||
}
|
||||
|
||||
/// Extract the current normalized pivot from the layer
|
||||
|
||||
@@ -1351,31 +1351,52 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
inputs: vec![
|
||||
// Value
|
||||
NodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),
|
||||
// Translation
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
// Rotation
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
// Scale
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
||||
// Skew
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
// Origin Offset
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
// Scale Appearance
|
||||
NodeInput::value(TaggedValue::Bool(true), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
exports: vec![
|
||||
// From the Transform node
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
],
|
||||
nodes: [
|
||||
// Monitor node
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
inputs: vec![
|
||||
// From the Value import
|
||||
NodeInput::network(generic!(T), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
call_argument: generic!(T),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
// Transform node
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
// From the Monitor node
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
// From the Translation import
|
||||
NodeInput::network(concrete!(DVec2), 1),
|
||||
// From the Rotation import
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
// From the Scale import
|
||||
NodeInput::network(concrete!(DVec2), 3),
|
||||
// From the Skew import
|
||||
NodeInput::network(concrete!(DVec2), 4),
|
||||
],
|
||||
call_argument: concrete!(Context),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1441,6 +1462,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
|
||||
InputMetadata::with_name_description_override("Origin Offset", "TODO", WidgetOverride::Custom("hidden".to_string())),
|
||||
InputMetadata::with_name_description_override("Scale Appearance", "TODO", WidgetOverride::Custom("hidden".to_string())),
|
||||
],
|
||||
output_names: vec!["Data".to_string()],
|
||||
..Default::default()
|
||||
@@ -2190,13 +2213,13 @@ fn static_input_properties() -> InputProperties {
|
||||
if let Some(&TaggedValue::F64(val)) = input.as_non_exposed_value() {
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(val.to_degrees()))
|
||||
NumberInput::new(Some(val))
|
||||
.unit("°")
|
||||
.mode(NumberInputMode::Range)
|
||||
.range_min(Some(-180.))
|
||||
.range_max(Some(180.))
|
||||
.on_update(node_properties::update_value(
|
||||
|number_input: &NumberInput| TaggedValue::F64(number_input.value.unwrap().to_radians()),
|
||||
|number_input: &NumberInput| TaggedValue::F64(number_input.value.unwrap()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
@@ -2219,29 +2242,28 @@ fn static_input_properties() -> InputProperties {
|
||||
return Err("Input not found in transform skew input override".to_string());
|
||||
};
|
||||
if let Some(&TaggedValue::DVec2(val)) = input.as_non_exposed_value() {
|
||||
let to_skew = |input: &NumberInput| input.value.unwrap().to_radians().tan();
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(val.x.atan().to_degrees()))
|
||||
NumberInput::new(Some(val.x))
|
||||
.label("X")
|
||||
.unit("°")
|
||||
.min(-89.9)
|
||||
.max(89.9)
|
||||
.on_update(node_properties::update_value(
|
||||
move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(to_skew(input), val.y)),
|
||||
move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), val.y)),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
.on_commit(node_properties::commit_value)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(val.y.atan().to_degrees()))
|
||||
NumberInput::new(Some(val.y))
|
||||
.label("Y")
|
||||
.unit("°")
|
||||
.min(-89.9)
|
||||
.max(89.9)
|
||||
.on_update(node_properties::update_value(
|
||||
move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(val.x, to_skew(input))),
|
||||
move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(val.x, input.value.unwrap())),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
|
||||
@@ -1232,6 +1232,17 @@ impl NodeNetworkInterface {
|
||||
Some(&metadata.transient_metadata)
|
||||
}
|
||||
|
||||
pub fn set_input_override(&mut self, node_id: &NodeId, index: usize, widget_override: Option<String>, network_path: &[NodeId]) {
|
||||
let Some(metadata) = self
|
||||
.node_metadata_mut(node_id, network_path)
|
||||
.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get_mut(index))
|
||||
else {
|
||||
log::error!("Could not get input metadata for {node_id} index {index} in set_input_override");
|
||||
return;
|
||||
};
|
||||
metadata.persistent_metadata.widget_override = widget_override;
|
||||
}
|
||||
|
||||
/// Returns the input name to display in the properties panel. If the name is empty then the type is used.
|
||||
pub fn displayed_input_name_and_description(&mut self, node_id: &NodeId, input_index: usize, network_path: &[NodeId]) -> (String, String) {
|
||||
let Some(input_metadata) = self.persistent_input_metadata(node_id, input_index, network_path) else {
|
||||
|
||||
Reference in New Issue
Block a user