Geometry Nodes: add conversion nodes for Grease Pencil and Curves #124279

Merged
Jacques Lucke merged 32 commits from JacquesLucke/blender:grease-pencil-curves-conversion into main 2024-07-22 12:29:42 +02:00
6 changed files with 398 additions and 0 deletions

View File

@ -114,11 +114,13 @@ class NODE_MT_geometry_node_GEO_CURVE_OPERATIONS(Menu):
def draw(self, _context):
layout = self.layout
node_add_menu.add_node_type(layout, "GeometryNodeCurvesToGreasePencil")
node_add_menu.add_node_type(layout, "GeometryNodeCurveToMesh")
node_add_menu.add_node_type(layout, "GeometryNodeCurveToPoints")
node_add_menu.add_node_type(layout, "GeometryNodeDeformCurvesOnSurface")
node_add_menu.add_node_type(layout, "GeometryNodeFillCurve")
node_add_menu.add_node_type(layout, "GeometryNodeFilletCurve")
node_add_menu.add_node_type(layout, "GeometryNodeGreasePencilToCurves")
node_add_menu.add_node_type(layout, "GeometryNodeInterpolateCurves")
node_add_menu.add_node_type(layout, "GeometryNodeResampleCurve")
node_add_menu.add_node_type(layout, "GeometryNodeReverseCurve")

View File

@ -1371,6 +1371,8 @@ void BKE_nodetree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index
#define GEO_NODE_GIZMO_LINEAR 2141
#define GEO_NODE_GIZMO_DIAL 2142
#define GEO_NODE_GIZMO_TRANSFORM 2143
#define GEO_NODE_CURVES_TO_GREASE_PENCIL 2144
#define GEO_NODE_GREASE_PENCIL_TO_CURVES 2145
/** \} */

View File

@ -332,6 +332,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "
DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, 0, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "Generate a point cloud by sampling positions along curves")
DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT, 0, "CURVE_OF_POINT", CurveOfPoint, "Curve of Point", "Retrieve the curve a control point is part of")
DefNode(GeometryNode, GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE, 0, "POINTS_OF_CURVE", PointsOfCurve, "Points of Curve", "Retrieve a point index within a curve")
DefNode(GeometryNode, GEO_NODE_CURVES_TO_GREASE_PENCIL, 0, "CURVES_TO_GREASE_PENCIL", CurvesToGreasePencil, "Curves to Grease Pencil", "Convert the curves in each top-level instance into Grease Pencil layer")
JacquesLucke marked this conversation as resolved Outdated

a grease pencil layer -> Grease Pencil layers

A layer is created for each instance so better if it was plural. And GP is a "brand name" so it should be capitalized. At least I heard Pablo say something like that. (Same in the other description).

`a grease pencil layer` -> `Grease Pencil layers` A layer is created for each instance so better if it was plural. And GP is a "brand name" so it should be capitalized. At least I heard Pablo say something like that. (Same in the other description).

Correct. Grease Pencil always needs to be capitalized in the UI 👍

Correct. `Grease Pencil` always needs to be capitalized in the UI 👍
DefNode(GeometryNode, GEO_NODE_DEFORM_CURVES_ON_SURFACE, 0, "DEFORM_CURVES_ON_SURFACE", DeformCurvesOnSurface, "Deform Curves on Surface", "Translate and rotate curves based on changes between the object's original and evaluated surface mesh")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "Remove selected elements of a geometry")
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_IN_GRID, 0, "DISTRIBUTE_POINTS_IN_GRID", DistributePointsInGrid, "Distribute Points in Grid", "Generate points inside a volume grid")
@ -353,6 +354,7 @@ DefNode(GeometryNode, GEO_NODE_GET_NAMED_GRID, 0, "GET_NAMED_GRID", GetNamedGrid
DefNode(GeometryNode, GEO_NODE_GIZMO_LINEAR, 0, "GIZMO_LINEAR", GizmoLinear, "Linear Gizmo", "Show a linear gizmo in the viewport for a value")
DefNode(GeometryNode, GEO_NODE_GIZMO_DIAL, 0, "GIZMO_DIAL", GizmoDial, "Dial Gizmo", "Show a dial gizmo in the viewport for a value")
DefNode(GeometryNode, GEO_NODE_GIZMO_TRANSFORM, rna_def_geo_gizmo_transform, "GIZMO_TRANSFORM", GizmoTransform, "Transform Gizmo", "Show a transform gizmo in the viewport")
DefNode(GeometryNode, GEO_NODE_GREASE_PENCIL_TO_CURVES, 0, "GREASE_PENCIL_TO_CURVES", GreasePencilToCurves, "Grease Pencil to Curves", "Convert Grease Pencil layers into curve instances")
JacquesLucke marked this conversation as resolved Outdated

Here too

Here too
DefNode(GeometryNode, GEO_NODE_GRID_TO_MESH, 0, "GRID_TO_MESH", GridToMesh, "Grid to Mesh", "Generate a mesh on the \"surface\" of a volume grid")
DefNode(GeometryNode, GEO_NODE_IMAGE_INFO, 0, "IMAGE_INFO", ImageInfo, "Image Info", "Retrieve information about an image")
DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture")

View File

@ -67,6 +67,7 @@ set(SRC
nodes/node_geo_curve_topology_curve_of_point.cc
nodes/node_geo_curve_topology_points_of_curve.cc
nodes/node_geo_curve_trim.cc
nodes/node_geo_curves_to_grease_pencil.cc
nodes/node_geo_deform_curves_on_surface.cc
nodes/node_geo_delete_geometry.cc
nodes/node_geo_distribute_points_in_grid.cc
@ -87,6 +88,7 @@ set(SRC
nodes/node_geo_gizmo_dial.cc
nodes/node_geo_gizmo_linear.cc
nodes/node_geo_gizmo_transform.cc
nodes/node_geo_grease_pencil_to_curves.cc
nodes/node_geo_grid_to_mesh.cc
nodes/node_geo_image.cc
nodes/node_geo_image_info.cc

View File

@ -0,0 +1,238 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_curves_to_grease_pencil_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Curves").description("Either plain curves or curve instances");
b.add_input<decl::Bool>("Selection")
.default_value(true)
.hide_value()
.field_on_all()
.description("Either a curve or instance selection");
b.add_input<decl::Bool>("Instances as Layers")
.default_value(true)
.description("Create a separate layer for each instance");
b.add_output<decl::Geometry>("Grease Pencil").propagate_all();
}
static GreasePencil *curves_to_grease_pencil_with_one_layer(
const Curves &curves_id,
const Field<bool> &selection_field,
const StringRefNull layer_name,
const AnonymousAttributePropagationInfo &propagation_info)
{
bke::CurvesGeometry curves = curves_id.geometry.wrap();
const bke::CurvesFieldContext field_context{curves, AttrDomain::Curve};
FieldEvaluator evaluator{field_context, curves.curves_num()};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask curves_selection = evaluator.get_evaluated_selection_as_mask();
IndexMaskMemory memory;
const IndexMask curves_to_delete = curves_selection.complement(curves.curves_range(), memory);
curves.remove_curves(curves_to_delete, propagation_info);
GreasePencil *grease_pencil = BKE_grease_pencil_new_nomain();
JacquesLucke marked this conversation as resolved Outdated

I'd prefer if we didn't use grease_pencil->add_empty_drawings(instances_num); and just call
bke::greasepencil::Drawing *drawing = grease_pencil.insert_frame(layer, framenum); instead.

I'd prefer if we didn't use `grease_pencil->add_empty_drawings(instances_num);` and just call `bke::greasepencil::Drawing *drawing = grease_pencil.insert_frame(layer, framenum);` instead.

Am I missing something or does it result in quadratic run-time if I add the drawings one by one?

Am I missing something or does it result in quadratic run-time if I add the drawings one by one?

If you want to add multiple keyframes at once, then we should add a grease_pencil.insert_frames API.

Manipulating the drawings array and drawing indices on frames should ideally only happen within the GreasePencil methods.

If you want to add multiple keyframes at once, then we should add a `grease_pencil.insert_frames` API. Manipulating the drawings array and drawing indices on frames should ideally only happen within the `GreasePencil` methods.
bke::greasepencil::Layer &layer = grease_pencil->add_layer(layer_name);
bke::greasepencil::Drawing *drawing = grease_pencil->insert_frame(
layer, grease_pencil->runtime->eval_frame);
BLI_assert(drawing);
drawing->strokes_for_write() = std::move(curves);
/* Transfer materials. */
const int materials_num = curves_id.totcol;
grease_pencil->material_array_num = materials_num;
grease_pencil->material_array = MEM_cnew_array<Material *>(materials_num, __func__);
initialized_copy_n(curves_id.mat, materials_num, grease_pencil->material_array);
JacquesLucke marked this conversation as resolved Outdated

Seems like layer_i is unused.

Seems like `layer_i` is unused.
return grease_pencil;
}
static GreasePencil *curve_instances_to_grease_pencil_layers(
const bke::Instances &instances,
const Field<bool> &selection_field,
const AnonymousAttributePropagationInfo &propagation_info)
{
const Span<int> reference_handles = instances.reference_handles();
const Span<bke::InstanceReference> references = instances.references();
const Span<float4x4> transforms = instances.transforms();
const int instances_num = instances.instances_num();
if (instances_num == 0) {
return nullptr;
}
const bke::InstancesFieldContext field_context{instances};
FieldEvaluator evaluator{field_context, instances_num};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask instance_selection = evaluator.get_evaluated_selection_as_mask();
const int layer_num = instance_selection.size();
if (layer_num == 0) {
return nullptr;
}
GreasePencil *grease_pencil = BKE_grease_pencil_new_nomain();
VectorSet<Material *> all_materials;
instance_selection.foreach_index([&](const int instance_i) {
const bke::InstanceReference &reference = references[reference_handles[instance_i]];
bke::greasepencil::Layer &layer = grease_pencil->add_layer(reference.name());
grease_pencil->insert_frame(layer, grease_pencil->runtime->eval_frame);
layer.set_local_transform(transforms[instance_i]);
bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(layer);
BLI_assert(drawing);
GeometrySet instance_geometry;
reference.to_geometry_set(instance_geometry);
const Curves *instance_curves = instance_geometry.get_curves();
if (!instance_curves) {
return;
}
bke::CurvesGeometry &strokes = drawing->strokes_for_write();
strokes = instance_curves->geometry.wrap();
Vector<int> new_material_indices;
for (Material *material : Span{instance_curves->mat, instance_curves->totcol}) {
new_material_indices.append(all_materials.index_of_or_add(material));
}
/* Remap material indices. */
bke::SpanAttributeWriter<int> material_indices =
strokes.attributes_for_write().lookup_or_add_for_write_span<int>("material_index",
bke::AttrDomain::Curve);
for (int &material_index : material_indices.span) {
if (material_index >= 0 && material_index < new_material_indices.size()) {
material_index = new_material_indices[material_index];
}
}
material_indices.finish();
});
grease_pencil->material_array_num = all_materials.size();
grease_pencil->material_array = MEM_cnew_array<Material *>(all_materials.size(), __func__);
initialized_copy_n(all_materials.data(), all_materials.size(), grease_pencil->material_array);
const bke::AttributeAccessor instances_attributes = instances.attributes();
bke::MutableAttributeAccessor grease_pencil_attributes = grease_pencil->attributes_for_write();
instances_attributes.for_all([&](const AttributeIDRef &attribute_id,
const AttributeMetaData &meta_data) {
if (instances_attributes.is_builtin(attribute_id) &&
!grease_pencil_attributes.is_builtin(attribute_id))
{
JacquesLucke marked this conversation as resolved Outdated

I would probably add a comment here saying that we're now manually transfering the opacity to the layers and that this should be removed once the opacity is just a layer attribute.

I would probably add a comment here saying that we're now manually transfering the opacity to the layers and that this should be removed once the opacity is just a layer attribute.
return true;
}
if (ELEM(attribute_id, "opacity")) {
return true;
}
if (attribute_id.is_anonymous() && !propagation_info.propagate(attribute_id.anonymous_id())) {
return true;
}
const GAttributeReader src_attribute = instances_attributes.lookup(attribute_id);
if (!src_attribute) {
return true;
}
if (instance_selection.size() == instances_num && src_attribute.varray.is_span() &&
src_attribute.sharing_info)
{
/* Try reusing existing attribute array. */
grease_pencil_attributes.add(
attribute_id,
AttrDomain::Layer,
meta_data.data_type,
bke::AttributeInitShared{src_attribute.varray.get_internal_span().data(),
*src_attribute.sharing_info});
return true;
}
if (!grease_pencil_attributes.add(
attribute_id, AttrDomain::Layer, meta_data.data_type, bke::AttributeInitConstruct()))
{
return true;
}
bke::GSpanAttributeWriter dst_attribute = grease_pencil_attributes.lookup_for_write_span(
attribute_id);
array_utils::gather(src_attribute.varray, instance_selection, dst_attribute.span);
dst_attribute.finish();
return true;
});
{
/* Manually propagate "opacity" data, because it's not a layer attribute on grease pencil
* yet. */
if (const AttributeReader opacity_attribute = instances_attributes.lookup<float>("opacity")) {
JacquesLucke marked this conversation as resolved Outdated

Would probably wrap nicer without the outer braces and using AttributeReader instead of AttributeReader<float>

Would probably wrap nicer without the outer braces and using `AttributeReader` instead of `AttributeReader<float>`
instance_selection.foreach_index([&](const int instance_i, const int layer_i) {
grease_pencil->layer(layer_i)->opacity = opacity_attribute.varray[instance_i];
});
}
}
return grease_pencil;
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet curves_geometry = params.extract_input<GeometrySet>("Curves");
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
const bool instances_as_layers = params.extract_input<bool>("Instances as Layers");
const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info(
"Grease Pencil");
GreasePencil *grease_pencil = nullptr;
if (instances_as_layers) {
if (curves_geometry.has_curves()) {
params.error_message_add(NodeWarningType::Info, TIP_("Non-instance curves are ignored"));
}
const bke::Instances *instances = curves_geometry.get_instances();
if (!instances) {
params.set_default_remaining_outputs();
return;
}
grease_pencil = curve_instances_to_grease_pencil_layers(
*instances, selection_field, propagation_info);
}
else {
if (curves_geometry.has_instances()) {
params.error_message_add(NodeWarningType::Info, TIP_("Instances are ignored"));
}
const Curves *curves_id = curves_geometry.get_curves();
if (!curves_id) {
params.set_default_remaining_outputs();
return;
}
grease_pencil = curves_to_grease_pencil_with_one_layer(
*curves_id, selection_field, curves_geometry.name, propagation_info);
}
GeometrySet grease_pencil_geometry = GeometrySet::from_grease_pencil(grease_pencil);
grease_pencil_geometry.name = std::move(curves_geometry.name);
params.set_output("Grease Pencil", std::move(grease_pencil_geometry));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, GEO_NODE_CURVES_TO_GREASE_PENCIL, "Curves to Grease Pencil", NODE_CLASS_GEOMETRY);
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_type_size(&ntype, 160, 100, 320);
bke::nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_curves_to_grease_pencil_cc

View File

@ -0,0 +1,152 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "GEO_realize_instances.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_grease_pencil_to_curves_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Geometry>("Grease Pencil")
.supported_type(bke::GeometryComponent::Type::GreasePencil);
b.add_input<decl::Bool>("Selection")
.default_value(true)
.hide_value()
.field_on_all()
.description("Select the layers to convert");
b.add_input<decl::Bool>("Layers as Instances")
.default_value(true)
.description("Create a separate curve instance for every layer");
b.add_output<decl::Geometry>("Curves").propagate_all();
}
static void node_geo_exec(GeoNodeExecParams params)
{
GeometrySet grease_pencil_geometry = params.extract_input<GeometrySet>("Grease Pencil");
const GreasePencil *grease_pencil = grease_pencil_geometry.get_grease_pencil();
if (!grease_pencil) {
params.set_default_remaining_outputs();
return;
}
const Span<const bke::greasepencil::Layer *> layers = grease_pencil->layers();
const int layers_num = layers.size();
const bke::GreasePencilFieldContext field_context{*grease_pencil};
const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
FieldEvaluator evaluator{field_context, layers_num};
evaluator.set_selection(selection_field);
evaluator.evaluate();
const IndexMask layer_selection = evaluator.get_evaluated_selection_as_mask();
const int instances_num = layer_selection.size();
if (instances_num == 0) {
params.set_default_remaining_outputs();
return;
}
bke::Instances *instances = new bke::Instances();
std::optional<int> empty_geometry_handle;
layer_selection.foreach_index([&](const int layer_i) {
const bke::greasepencil::Layer &layer = *layers[layer_i];
const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(layer);
const float4x4 transform = layer.local_transform();
if (!drawing) {
if (!empty_geometry_handle.has_value()) {
empty_geometry_handle = instances->add_reference(bke::InstanceReference());
}
instances->add_instance(*empty_geometry_handle, transform);
return;
}
const bke::CurvesGeometry &layer_strokes = drawing->strokes();
Curves *curves_id = bke::curves_new_nomain(layer_strokes);
curves_id->mat = static_cast<Material **>(MEM_dupallocN(grease_pencil->material_array));
curves_id->totcol = grease_pencil->material_array_num;
GeometrySet curves_geometry = GeometrySet::from_curves(curves_id);
curves_geometry.name = layer.name();
const int handle = instances->add_reference(std::move(curves_geometry));
instances->add_instance(handle, transform);
});
const bke::AttributeAccessor grease_pencil_attributes = grease_pencil->attributes();
bke::MutableAttributeAccessor instances_attributes = instances->attributes_for_write();
grease_pencil_attributes.for_all(
[&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
if (ELEM(attribute_id, "opacity")) {
return true;
}
const GAttributeReader src_attribute = grease_pencil_attributes.lookup(attribute_id);
if (!src_attribute) {
return true;
}
if (src_attribute.varray.is_span() && src_attribute.sharing_info) {
/* Try reusing existing attribute array. */
instances_attributes.add(
attribute_id,
AttrDomain::Instance,
meta_data.data_type,
bke::AttributeInitShared{src_attribute.varray.get_internal_span().data(),
*src_attribute.sharing_info});
return true;
}
if (!instances_attributes.add(attribute_id,
AttrDomain::Instance,
meta_data.data_type,
bke::AttributeInitConstruct()))
{
return true;
JacquesLucke marked this conversation as resolved Outdated

Same as above.

Same as above.
}
bke::GSpanAttributeWriter dst_attribute = instances_attributes.lookup_for_write_span(
attribute_id);
array_utils::gather(src_attribute.varray, layer_selection, dst_attribute.span);
dst_attribute.finish();
return true;
});
{
/* Manually propagate "opacity" data, because it's not a layer attribute on grease pencil
* yet. */
SpanAttributeWriter<float> opacity_attribute =
instances_attributes.lookup_or_add_for_write_only_span<float>("opacity",
AttrDomain::Instance);
layer_selection.foreach_index([&](const int layer_i, const int instance_i) {
opacity_attribute.span[instance_i] = grease_pencil->layer(layer_i)->opacity;
});
opacity_attribute.finish();
}
GeometrySet curves_geometry = GeometrySet::from_instances(instances);
curves_geometry.name = std::move(grease_pencil_geometry.name);
const bool layers_as_instances = params.get_input<bool>("Layers as Instances");
if (!layers_as_instances) {
geometry::RealizeInstancesOptions options;
options.propagation_info = params.get_output_propagation_info("Curves");
curves_geometry = geometry::realize_instances(curves_geometry, options);
}
params.set_output("Curves", std::move(curves_geometry));
}
static void node_register()
{
static bke::bNodeType ntype;
geo_node_type_base(
&ntype, GEO_NODE_GREASE_PENCIL_TO_CURVES, "Grease Pencil to Curves", NODE_CLASS_GEOMETRY);
ntype.geometry_node_execute = node_geo_exec;
ntype.declare = node_declare;
bke::node_type_size(&ntype, 160, 100, 320);
bke::nodeRegisterType(&ntype);
}
NOD_REGISTER_NODE(node_register)
} // namespace blender::nodes::node_geo_grease_pencil_to_curves_cc