Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_blender_version.h2
-rw-r--r--source/blender/blenkernel/BKE_curve_to_mesh.hh (renamed from source/blender/render/intern/initrender.h)27
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.hh4
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h7
-rw-r--r--source/blender/blenkernel/BKE_node.h8
-rw-r--r--source/blender/blenkernel/BKE_spline.hh1
-rw-r--r--source/blender/blenkernel/CMakeLists.txt2
-rw-r--r--source/blender/blenkernel/intern/curve_eval.cc17
-rw-r--r--source/blender/blenkernel/intern/curve_to_mesh_convert.cc739
-rw-r--r--source/blender/blenkernel/intern/geometry_component_instances.cc53
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.cc258
-rw-r--r--source/blender/blenkernel/intern/layer.c1
-rw-r--r--source/blender/blenkernel/intern/lib_override.c13
-rw-r--r--source/blender/blenkernel/intern/mesh_convert.cc2
-rw-r--r--source/blender/blenkernel/intern/node.cc8
-rw-r--r--source/blender/blenkernel/intern/screen.c2
-rw-r--r--source/blender/blenkernel/intern/spline_bezier.cc5
-rw-r--r--source/blender/blenlib/BLI_array.hh15
-rw-r--r--source/blender/blenlib/BLI_uuid.h15
-rw-r--r--source/blender/blenlib/intern/uuid.cc20
-rw-r--r--source/blender/blenlib/tests/BLI_uuid_test.cc28
-rw-r--r--source/blender/blenloader/intern/versioning_270.c16
-rw-r--r--source/blender/blenloader/intern/versioning_280.c4
-rw-r--r--source/blender/blenloader/intern/versioning_290.c1
-rw-r--r--source/blender/blenloader/intern/versioning_300.c145
-rw-r--r--source/blender/blenloader/intern/versioning_cycles.c26
-rw-r--r--source/blender/blenloader/intern/versioning_defaults.c12
-rw-r--r--source/blender/compositor/nodes/COM_IDMaskNode.cc4
-rw-r--r--source/blender/compositor/nodes/COM_ZCombineNode.cc4
-rw-r--r--source/blender/draw/DRW_engine.h3
-rw-r--r--source/blender/draw/engines/eevee/eevee_cryptomatte.c10
-rw-r--r--source/blender/draw/engines/eevee/eevee_effects.c24
-rw-r--r--source/blender/draw/engines/eevee/eevee_engine.c2
-rw-r--r--source/blender/draw/engines/eevee/eevee_private.h1
-rw-r--r--source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl2
-rw-r--r--source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl13
-rw-r--r--source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl19
-rw-r--r--source/blender/draw/engines/external/external_engine.c202
-rw-r--r--source/blender/draw/engines/external/external_engine.h8
-rw-r--r--source/blender/draw/engines/select/select_engine.c2
-rw-r--r--source/blender/draw/engines/workbench/workbench_engine.c2
-rw-r--r--source/blender/draw/intern/DRW_render.h1
-rw-r--r--source/blender/draw/intern/draw_manager.c78
-rw-r--r--source/blender/draw/intern/draw_manager_exec.c5
-rw-r--r--source/blender/editors/animation/anim_deps.c2
-rw-r--r--source/blender/editors/animation/anim_ops.c5
-rw-r--r--source/blender/editors/armature/armature_intern.h1
-rw-r--r--source/blender/editors/armature/armature_ops.c1
-rw-r--r--source/blender/editors/armature/pose_slide.c102
-rw-r--r--source/blender/editors/gpencil/gpencil_interpolate.c6
-rw-r--r--source/blender/editors/interface/interface_eyedropper.c11
-rw-r--r--source/blender/editors/interface/interface_style.c7
-rw-r--r--source/blender/editors/interface/interface_template_search_menu.c40
-rw-r--r--source/blender/editors/interface/interface_templates.c5
-rw-r--r--source/blender/editors/interface/view2d_ops.c16
-rw-r--r--source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c4
-rw-r--r--source/blender/editors/object/object_bake_api.c18
-rw-r--r--source/blender/editors/render/render_preview.c9
-rw-r--r--source/blender/editors/screen/area.c12
-rw-r--r--source/blender/editors/space_file/filesel.c2
-rw-r--r--source/blender/editors/space_node/node_draw.cc4
-rw-r--r--source/blender/editors/space_node/node_intern.h1
-rw-r--r--source/blender/editors/space_node/node_ops.c1
-rw-r--r--source/blender/editors/space_node/node_view.cc90
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c5
-rw-r--r--source/blender/editors/space_outliner/outliner_tree.c13
-rw-r--r--source/blender/editors/space_sequencer/sequencer_draw.c595
-rw-r--r--source/blender/editors/space_sequencer/sequencer_edit.c6
-rw-r--r--source/blender/editors/space_sequencer/sequencer_intern.h1
-rw-r--r--source/blender/editors/space_sequencer/sequencer_select.c123
-rw-r--r--source/blender/editors/space_sequencer/space_sequencer.c88
-rw-r--r--source/blender/editors/space_view3d/view3d_gizmo_preselect.c4
-rw-r--r--source/blender/editors/space_view3d/view3d_select.c4
-rw-r--r--source/blender/editors/transform/CMakeLists.txt1
-rw-r--r--source/blender/editors/transform/transform.c12
-rw-r--r--source/blender/editors/transform/transform.h12
-rw-r--r--source/blender/editors/transform/transform_convert.c18
-rw-r--r--source/blender/editors/transform/transform_convert.h4
-rw-r--r--source/blender/editors/transform/transform_convert_sequencer_image.c195
-rw-r--r--source/blender/editors/transform/transform_draw_cursors.c2
-rw-r--r--source/blender/editors/transform/transform_generics.c7
-rw-r--r--source/blender/editors/transform/transform_gizmo_2d.c71
-rw-r--r--source/blender/editors/transform/transform_mode.c2
-rw-r--r--source/blender/editors/transform/transform_snap_sequencer.c4
-rw-r--r--source/blender/editors/uvedit/uvedit_select.c12
-rw-r--r--source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp12
-rw-r--r--source/blender/functions/FN_cpp_type.hh11
-rw-r--r--source/blender/functions/FN_cpp_type_make.hh1
-rw-r--r--source/blender/functions/intern/multi_function_procedure.cc4
-rw-r--r--source/blender/gpencil_modifiers/CMakeLists.txt3
-rw-r--r--source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h3
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c3
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencillength.c136
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilweight_angle.c260
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c (renamed from source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c)122
-rw-r--r--source/blender/gpu/GPU_material.h5
-rw-r--r--source/blender/gpu/intern/gpu_material.c114
-rw-r--r--source/blender/gpu/intern/gpu_material_library.h2
-rw-r--r--source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl4
-rw-r--r--source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl14
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_defaults.h21
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_types.h56
-rw-r--r--source/blender/makesdna/DNA_layer_types.h2
-rw-r--r--source/blender/makesdna/DNA_node_types.h17
-rw-r--r--source/blender/makesdna/DNA_scene_defaults.h2
-rw-r--r--source/blender/makesdna/DNA_scene_types.h20
-rw-r--r--source/blender/makesdna/DNA_sequence_types.h5
-rw-r--r--source/blender/makesdna/DNA_space_types.h11
-rw-r--r--source/blender/makesdna/DNA_uuid_types.h6
-rw-r--r--source/blender/makesdna/DNA_view2d_types.h2
-rw-r--r--source/blender/makesdna/DNA_workspace_types.h11
-rw-r--r--source/blender/makesdna/intern/dna_defaults.c6
-rw-r--r--source/blender/makesrna/intern/rna_gpencil_modifier.c309
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c97
-rw-r--r--source/blender/makesrna/intern/rna_render.c98
-rw-r--r--source/blender/makesrna/intern/rna_scene.c77
-rw-r--r--source/blender/makesrna/intern/rna_sequencer.c6
-rw-r--r--source/blender/makesrna/intern/rna_space.c10
-rw-r--r--source/blender/makesrna/intern/rna_workspace_api.c9
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc18
-rw-r--r--source/blender/modifiers/intern/MOD_nodes_evaluator.cc8
-rw-r--r--source/blender/nodes/CMakeLists.txt8
-rw-r--r--source/blender/nodes/NOD_function.h3
-rw-r--r--source/blender/nodes/NOD_geometry.h5
-rw-r--r--source/blender/nodes/NOD_geometry_nodes_eval_log.hh1
-rw-r--r--source/blender/nodes/NOD_static_types.h10
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_image.c45
-rw-r--r--source/blender/nodes/function/nodes/node_fn_string_length.cc49
-rw-r--r--source/blender/nodes/function/nodes/node_fn_string_substring.cc54
-rw-r--r--source/blender/nodes/function/nodes/node_fn_value_to_string.cc51
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc378
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc44
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc206
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc288
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc710
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_normal.cc96
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc174
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_string_join.cc53
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c29
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c30
-rw-r--r--source/blender/render/CMakeLists.txt1
-rw-r--r--source/blender/render/RE_engine.h46
-rw-r--r--source/blender/render/RE_pipeline.h3
-rw-r--r--source/blender/render/intern/bake.c19
-rw-r--r--source/blender/render/intern/engine.c282
-rw-r--r--source/blender/render/intern/initrender.c91
-rw-r--r--source/blender/render/intern/pipeline.c64
-rw-r--r--source/blender/render/intern/render_result.c253
-rw-r--r--source/blender/render/intern/render_result.h18
-rw-r--r--source/blender/render/intern/render_types.h30
-rw-r--r--source/blender/sequencer/SEQ_iterator.h8
-rw-r--r--source/blender/sequencer/SEQ_render.h21
-rw-r--r--source/blender/sequencer/SEQ_sequencer.h1
-rw-r--r--source/blender/sequencer/SEQ_transform.h9
-rw-r--r--source/blender/sequencer/SEQ_utils.h2
-rw-r--r--source/blender/sequencer/intern/image_cache.c81
-rw-r--r--source/blender/sequencer/intern/image_cache.h6
-rw-r--r--source/blender/sequencer/intern/iterator.c120
-rw-r--r--source/blender/sequencer/intern/render.c283
-rw-r--r--source/blender/sequencer/intern/sequencer.c11
-rw-r--r--source/blender/sequencer/intern/strip_transform.c98
-rw-r--r--source/blender/sequencer/intern/utils.c1
-rw-r--r--source/blender/windowmanager/WM_api.h31
-rw-r--r--source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c2
-rw-r--r--source/blender/windowmanager/intern/wm_dragdrop.c14
-rw-r--r--source/blender/windowmanager/intern/wm_event_system.c124
-rw-r--r--source/blender/windowmanager/intern/wm_jobs.c2
-rw-r--r--source/blender/windowmanager/intern/wm_keymap.c24
-rw-r--r--source/blender/windowmanager/intern/wm_operator_utils.c18
-rw-r--r--source/blender/windowmanager/intern/wm_toolsystem.c5
-rw-r--r--source/blender/windowmanager/intern/wm_window.c13
171 files changed, 6447 insertions, 2280 deletions
diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h
index 63d6b9121d2..1f25106404a 100644
--- a/source/blender/blenkernel/BKE_blender_version.h
+++ b/source/blender/blenkernel/BKE_blender_version.h
@@ -39,7 +39,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
-#define BLENDER_FILE_SUBVERSION 23
+#define BLENDER_FILE_SUBVERSION 25
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file
diff --git a/source/blender/render/intern/initrender.h b/source/blender/blenkernel/BKE_curve_to_mesh.hh
index f5ac352752f..cc1ef08908d 100644
--- a/source/blender/render/intern/initrender.h
+++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh
@@ -12,27 +12,20 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
- * All rights reserved.
- */
-
-/** \file
- * \ingroup render
*/
#pragma once
-#ifdef __cplusplus
-extern "C" {
-#endif
+struct Mesh;
+struct CurveEval;
+
+/** \file
+ * \ingroup geo
+ */
-/* Functions */
+namespace blender::bke {
-void RE_parts_init(Render *re);
-void RE_parts_free(Render *re);
-void RE_parts_clamp(Render *re);
+Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile);
+Mesh *curve_to_wire_mesh(const CurveEval &curve);
-#ifdef __cplusplus
-}
-#endif
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh
index 98f5de43f84..317d513dae6 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -580,6 +580,9 @@ class InstancesComponent : public GeometryComponent {
blender::Span<InstanceReference> references() const;
+ void ensure_geometry_instances();
+ GeometrySet &geometry_set_from_reference(const int reference_index);
+
blender::Span<int> instance_reference_handles() const;
blender::MutableSpan<int> instance_reference_handles();
blender::MutableSpan<blender::float4x4> instance_transforms();
@@ -588,6 +591,7 @@ class InstancesComponent : public GeometryComponent {
blender::Span<int> instance_ids() const;
int instances_amount() const;
+ int references_amount() const;
blender::Span<int> almost_unique_ids() const;
diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h
index d472fd6f02b..a9cd553a8fe 100644
--- a/source/blender/blenkernel/BKE_gpencil_geom.h
+++ b/source/blender/blenkernel/BKE_gpencil_geom.h
@@ -114,7 +114,12 @@ void BKE_gpencil_dissolve_points(struct bGPdata *gpd,
bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps,
const float dist,
const float overshoot_fac,
- const short mode);
+ const short mode,
+ const bool follow_curvature,
+ const int extra_point_count,
+ const float segment_influence,
+ const float max_angle,
+ const bool invert_curvature);
bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps,
const int index_from,
const int index_to);
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 8e82ab6d6be..42e2cda8de3 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1494,6 +1494,11 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_MATERIAL_SELECTION 1081
#define GEO_NODE_MATERIAL_ASSIGN 1082
#define GEO_NODE_REALIZE_INSTANCES 1083
+#define GEO_NODE_ATTRIBUTE_STATISTIC 1084
+#define GEO_NODE_CURVE_SAMPLE 1085
+#define GEO_NODE_INPUT_TANGENT 1086
+#define GEO_NODE_STRING_JOIN 1087
+#define GEO_NODE_CURVE_PARAMETER 1088
/** \} */
@@ -1507,6 +1512,9 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define FN_NODE_INPUT_VECTOR 1207
#define FN_NODE_INPUT_STRING 1208
#define FN_NODE_FLOAT_TO_INT 1209
+#define FN_NODE_VALUE_TO_STRING 1210
+#define FN_NODE_STRING_LENGTH 1211
+#define FN_NODE_STRING_SUBSTRING 1212
/** \} */
diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh
index 0fbf39a52fa..541ff19c1cd 100644
--- a/source/blender/blenkernel/BKE_spline.hh
+++ b/source/blender/blenkernel/BKE_spline.hh
@@ -565,6 +565,7 @@ struct CurveEval {
blender::Array<int> control_point_offsets() const;
blender::Array<int> evaluated_point_offsets() const;
+ blender::Array<float> accumulated_spline_lengths() const;
void assert_valid_point_attributes() const;
};
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 0b082bf1c5a..de7864ef36a 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -116,6 +116,7 @@ set(SRC
intern/curve_decimate.c
intern/curve_deform.c
intern/curve_eval.cc
+ intern/curve_to_mesh_convert.cc
intern/curveprofile.c
intern/customdata.c
intern/customdata_file.c
@@ -332,6 +333,7 @@ set(SRC
BKE_cryptomatte.h
BKE_cryptomatte.hh
BKE_curve.h
+ BKE_curve_to_mesh.hh
BKE_curveprofile.h
BKE_customdata.h
BKE_customdata_file.h
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index ea84766943d..8eec7f5dfab 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -143,6 +143,23 @@ blender::Array<int> CurveEval::evaluated_point_offsets() const
return offsets;
}
+/**
+ * Return the accumulated length at the start of every spline in the curve.
+ *
+ * \note The result is one longer than the spline count; the last element is the total length.
+ */
+blender::Array<float> CurveEval::accumulated_spline_lengths() const
+{
+ Array<float> spline_lengths(splines_.size() + 1);
+ float spline_length = 0.0f;
+ for (const int i : splines_.index_range()) {
+ spline_lengths[i] = spline_length;
+ spline_length += splines_[i]->length();
+ }
+ spline_lengths.last() = spline_length;
+ return spline_lengths;
+}
+
static BezierSpline::HandleType handle_type_from_dna_bezt(const eBezTriple_Handle dna_handle_type)
{
switch (dna_handle_type) {
diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
new file mode 100644
index 00000000000..5f2f945192c
--- /dev/null
+++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
@@ -0,0 +1,739 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_array.hh"
+#include "BLI_set.hh"
+#include "BLI_task.hh"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_attribute_access.hh"
+#include "BKE_attribute_math.hh"
+#include "BKE_geometry_set.hh"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_spline.hh"
+
+#include "BKE_curve_to_mesh.hh"
+
+using blender::fn::GMutableSpan;
+using blender::fn::GSpan;
+using blender::fn::GVArray_Typed;
+using blender::fn::GVArrayPtr;
+
+namespace blender::bke {
+
+/** Information about the creation of one curve spline and profile spline combination. */
+struct ResultInfo {
+ const Spline &spline;
+ const Spline &profile;
+ int vert_offset;
+ int edge_offset;
+ int loop_offset;
+ int poly_offset;
+ int spline_vert_len;
+ int spline_edge_len;
+ int profile_vert_len;
+ int profile_edge_len;
+};
+
+static void vert_extrude_to_mesh_data(const Spline &spline,
+ const float3 profile_vert,
+ MutableSpan<MVert> r_verts,
+ MutableSpan<MEdge> r_edges,
+ const int vert_offset,
+ const int edge_offset)
+{
+ Span<float3> positions = spline.evaluated_positions();
+
+ for (const int i : IndexRange(positions.size() - 1)) {
+ MEdge &edge = r_edges[edge_offset + i];
+ edge.v1 = vert_offset + i;
+ edge.v2 = vert_offset + i + 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+
+ if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) {
+ MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1];
+ edge.v1 = vert_offset;
+ edge.v2 = vert_offset + positions.size() - 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+
+ for (const int i : positions.index_range()) {
+ MVert &vert = r_verts[vert_offset + i];
+ copy_v3_v3(vert.co, positions[i] + profile_vert);
+ }
+}
+
+static void mark_edges_sharp(MutableSpan<MEdge> edges)
+{
+ for (MEdge &edge : edges) {
+ edge.flag |= ME_SHARP;
+ }
+}
+
+static void spline_extrude_to_mesh_data(const ResultInfo &info,
+ MutableSpan<MVert> r_verts,
+ MutableSpan<MEdge> r_edges,
+ MutableSpan<MLoop> r_loops,
+ MutableSpan<MPoly> r_polys)
+{
+ const Spline &spline = info.spline;
+ const Spline &profile = info.profile;
+ if (info.profile_vert_len == 1) {
+ vert_extrude_to_mesh_data(spline,
+ profile.evaluated_positions()[0],
+ r_verts,
+ r_edges,
+ info.vert_offset,
+ info.edge_offset);
+ return;
+ }
+
+ /* Add the edges running along the length of the curve, starting at each profile vertex. */
+ const int spline_edges_start = info.edge_offset;
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len;
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
+
+ const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
+ const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
+
+ MEdge &edge = r_edges[profile_edge_offset + i_ring];
+ edge.v1 = ring_vert_offset + i_profile;
+ edge.v2 = next_ring_vert_offset + i_profile;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+
+ /* Add the edges running along each profile ring. */
+ const int profile_edges_start = spline_edges_start +
+ info.profile_vert_len * info.spline_edge_len;
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
+
+ const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len;
+ for (const int i_profile : IndexRange(info.profile_edge_len)) {
+ const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
+
+ MEdge &edge = r_edges[ring_edge_offset + i_profile];
+ edge.v1 = ring_vert_offset + i_profile;
+ edge.v2 = ring_vert_offset + i_next_profile;
+ edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
+ }
+ }
+
+ /* Calculate poly and corner indices. */
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
+
+ const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
+ const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
+
+ const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring;
+ const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring;
+
+ const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len;
+ const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4;
+
+ for (const int i_profile : IndexRange(info.profile_edge_len)) {
+ const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4;
+ const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
+
+ const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile;
+ const int next_spline_edge_start = spline_edges_start +
+ info.spline_edge_len * i_next_profile;
+
+ MPoly &poly = r_polys[ring_poly_offset + i_profile];
+ poly.loopstart = ring_segment_loop_offset;
+ poly.totloop = 4;
+ poly.flag = ME_SMOOTH;
+
+ MLoop &loop_a = r_loops[ring_segment_loop_offset];
+ loop_a.v = ring_vert_offset + i_profile;
+ loop_a.e = ring_edge_start + i_profile;
+ MLoop &loop_b = r_loops[ring_segment_loop_offset + 1];
+ loop_b.v = ring_vert_offset + i_next_profile;
+ loop_b.e = next_spline_edge_start + i_ring;
+ MLoop &loop_c = r_loops[ring_segment_loop_offset + 2];
+ loop_c.v = next_ring_vert_offset + i_next_profile;
+ loop_c.e = next_ring_edge_offset + i_profile;
+ MLoop &loop_d = r_loops[ring_segment_loop_offset + 3];
+ loop_d.v = next_ring_vert_offset + i_profile;
+ loop_d.e = spline_edge_start + i_ring;
+ }
+ }
+
+ /* Calculate the positions of each profile ring profile along the spline. */
+ Span<float3> positions = spline.evaluated_positions();
+ Span<float3> tangents = spline.evaluated_tangents();
+ Span<float3> normals = spline.evaluated_normals();
+ Span<float3> profile_positions = profile.evaluated_positions();
+
+ GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii());
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ float4x4 point_matrix = float4x4::from_normalized_axis_data(
+ positions[i_ring], normals[i_ring], tangents[i_ring]);
+ point_matrix.apply_scale(radii[i_ring]);
+
+ const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ MVert &vert = r_verts[ring_vert_start + i_profile];
+ copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]);
+ }
+ }
+
+ /* Mark edge loops from sharp vector control points sharp. */
+ if (profile.type() == Spline::Type::Bezier) {
+ const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile);
+ Span<int> control_point_offsets = bezier_spline.control_point_offsets();
+ for (const int i : IndexRange(bezier_spline.size())) {
+ if (bezier_spline.point_is_sharp(i)) {
+ mark_edges_sharp(
+ r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i],
+ info.spline_edge_len));
+ }
+ }
+ }
+}
+
+static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile)
+{
+ return curve.evaluated_points_size() * profile.evaluated_points_size();
+}
+
+static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile)
+{
+ /* Add the ring edges, with one ring for every curve vertex, and the edge loops
+ * that run along the length of the curve, starting on the first profile. */
+ return curve.evaluated_points_size() * profile.evaluated_edges_size() +
+ curve.evaluated_edges_size() * profile.evaluated_points_size();
+}
+
+static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile)
+{
+ return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4;
+}
+
+static inline int spline_extrude_poly_size(const Spline &curve, const Spline &profile)
+{
+ return curve.evaluated_edges_size() * profile.evaluated_edges_size();
+}
+
+struct ResultOffsets {
+ Array<int> vert;
+ Array<int> edge;
+ Array<int> loop;
+ Array<int> poly;
+};
+static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves)
+{
+ const int total = profiles.size() * curves.size();
+ Array<int> vert(total + 1);
+ Array<int> edge(total + 1);
+ Array<int> loop(total + 1);
+ Array<int> poly(total + 1);
+
+ int mesh_index = 0;
+ int vert_offset = 0;
+ int edge_offset = 0;
+ int loop_offset = 0;
+ int poly_offset = 0;
+ for (const int i_spline : curves.index_range()) {
+ for (const int i_profile : profiles.index_range()) {
+ vert[mesh_index] = vert_offset;
+ edge[mesh_index] = edge_offset;
+ loop[mesh_index] = loop_offset;
+ poly[mesh_index] = poly_offset;
+ vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]);
+ edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]);
+ loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile]);
+ poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile]);
+ mesh_index++;
+ }
+ }
+ vert.last() = vert_offset;
+ edge.last() = edge_offset;
+ loop.last() = loop_offset;
+ poly.last() = poly_offset;
+
+ return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)};
+}
+
+static AttributeDomain get_result_attribute_domain(const MeshComponent &component,
+ const AttributeIDRef &attribute_id)
+{
+ /* Only use a different domain if it is builtin and must only exist on one domain. */
+ if (!component.attribute_is_builtin(attribute_id)) {
+ return ATTR_DOMAIN_POINT;
+ }
+
+ std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id);
+ if (!meta_data) {
+ /* This function has to return something in this case, but it shouldn't be used,
+ * so return an output that will assert later if the code attempts to handle it. */
+ return ATTR_DOMAIN_AUTO;
+ }
+
+ return meta_data->domain;
+}
+
+/**
+ * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling
+ * `as_span()` for every single profile and curve spline combination, and for readability.
+ */
+struct ResultAttributeData {
+ GMutableSpan data;
+ AttributeDomain domain;
+};
+
+static std::optional<ResultAttributeData> create_attribute_and_get_span(
+ MeshComponent &component,
+ const AttributeIDRef &attribute_id,
+ AttributeMetaData meta_data,
+ Vector<OutputAttribute> &r_attributes)
+{
+ const AttributeDomain domain = get_result_attribute_domain(component, attribute_id);
+ OutputAttribute attribute = component.attribute_try_get_for_output_only(
+ attribute_id, domain, meta_data.data_type);
+ if (!attribute) {
+ return std::nullopt;
+ }
+
+ GMutableSpan span = attribute.as_span();
+ r_attributes.append(std::move(attribute));
+ return std::make_optional<ResultAttributeData>({span, domain});
+}
+
+/**
+ * Store the references to the attribute data from the curve and profile inputs. Here we rely on
+ * the invariants of the storage of curve attributes, that the order will be consistent between
+ * splines, and all splines will have the same attributes.
+ */
+struct ResultAttributes {
+ /**
+ * Result attributes on the mesh corresponding to each attribute on the curve input, in the same
+ * order. The data is optional only in case the attribute does not exist on the mesh for some
+ * reason, like "shade_smooth" when the result has no faces.
+ */
+ Vector<std::optional<ResultAttributeData>> curve_point_attributes;
+ Vector<std::optional<ResultAttributeData>> curve_spline_attributes;
+
+ /**
+ * Result attributes corresponding the attributes on the profile input, in the same order. The
+ * attributes are optional in case the attribute names correspond to a names used by the curve
+ * input, in which case the curve input attributes take precedence.
+ */
+ Vector<std::optional<ResultAttributeData>> profile_point_attributes;
+ Vector<std::optional<ResultAttributeData>> profile_spline_attributes;
+
+ /**
+ * Because some builtin attributes are not stored contiguously, and the curve inputs might have
+ * attributes with those names, it's necessary to keep OutputAttributes around to give access to
+ * the result data in a contiguous array.
+ */
+ Vector<OutputAttribute> attributes;
+};
+static ResultAttributes create_result_attributes(const CurveEval &curve,
+ const CurveEval &profile,
+ Mesh &mesh)
+{
+ MeshComponent mesh_component;
+ mesh_component.replace(&mesh, GeometryOwnershipType::Editable);
+ Set<AttributeIDRef> curve_attributes;
+
+ /* In order to prefer attributes on the main curve input when there are name collisions, first
+ * check the attributes on the curve, then add attributes on the profile that are not also on the
+ * main curve input. */
+ ResultAttributes result;
+ curve.splines().first()->attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ curve_attributes.add_new(id);
+ result.curve_point_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ curve.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ curve_attributes.add_new(id);
+ result.curve_spline_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+ profile.splines().first()->attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ if (curve_attributes.contains(id)) {
+ result.profile_point_attributes.append({});
+ }
+ else {
+ result.profile_point_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ }
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ profile.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
+ if (curve_attributes.contains(id)) {
+ result.profile_spline_attributes.append({});
+ }
+ else {
+ result.profile_spline_attributes.append(
+ create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
+ }
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+
+ return result;
+}
+
+template<typename T>
+static void copy_curve_point_data_to_mesh_verts(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
+ dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]);
+ }
+}
+
+template<typename T>
+static void copy_curve_point_data_to_mesh_edges(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len;
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int ring_edge_start = edges_start + info.profile_edge_len * i_ring;
+ dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]);
+ }
+}
+
+template<typename T>
+static void copy_curve_point_data_to_mesh_faces(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring;
+ dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]);
+ }
+}
+
+static void copy_curve_point_attribute_to_mesh(const GSpan src,
+ const ResultInfo &info,
+ ResultAttributeData &dst)
+{
+ GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src);
+ GSpan interpolated = interpolated_gvarray->get_internal_span();
+
+ attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ switch (dst.domain) {
+ case ATTR_DOMAIN_POINT:
+ copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_EDGE:
+ copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_FACE:
+ copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_CORNER:
+ /* Unsupported for now, since there are no builtin attributes to convert into. */
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+}
+
+template<typename T>
+static void copy_profile_point_data_to_mesh_verts(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_vert_len)) {
+ const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ dst[profile_vert_start + i_profile] = src[i_profile];
+ }
+ }
+}
+
+template<typename T>
+static void copy_profile_point_data_to_mesh_edges(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len;
+ dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]);
+ }
+}
+
+template<typename T>
+static void copy_profile_point_data_to_mesh_faces(const Span<T> src,
+ const ResultInfo &info,
+ MutableSpan<T> dst)
+{
+ for (const int i_ring : IndexRange(info.spline_edge_len)) {
+ const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len;
+ for (const int i_profile : IndexRange(info.profile_edge_len)) {
+ dst[profile_face_start + i_profile] = src[i_profile];
+ }
+ }
+}
+
+static void copy_profile_point_attribute_to_mesh(const GSpan src,
+ const ResultInfo &info,
+ ResultAttributeData &dst)
+{
+ GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src);
+ GSpan interpolated = interpolated_gvarray->get_internal_span();
+
+ attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ switch (dst.domain) {
+ case ATTR_DOMAIN_POINT:
+ copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_EDGE:
+ copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_FACE:
+ copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_CORNER:
+ /* Unsupported for now, since there are no builtin attributes to convert into. */
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+}
+
+static void copy_point_domain_attributes_to_mesh(const ResultInfo &info,
+ ResultAttributes &attributes)
+{
+ if (!attributes.curve_point_attributes.is_empty()) {
+ int i = 0;
+ info.spline.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.curve_point_attributes[i]) {
+ copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id),
+ info,
+ *attributes.curve_point_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ }
+ if (!attributes.profile_point_attributes.is_empty()) {
+ int i = 0;
+ info.profile.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.profile_point_attributes[i]) {
+ copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id),
+ info,
+ *attributes.profile_point_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_POINT);
+ }
+}
+
+template<typename T>
+static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst)
+{
+ for (const int i : IndexRange(src.size())) {
+ dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]);
+ }
+}
+
+/**
+ * Since the offsets for each combination of curve and profile spline are stored for every mesh
+ * domain, and this just needs to fill the chunks corresponding to each combination, we can use
+ * the same function for all mesh domains.
+ */
+static void copy_spline_attribute_to_mesh(const GSpan src,
+ const ResultOffsets &offsets,
+ ResultAttributeData &dst_attribute)
+{
+ attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ switch (dst_attribute.domain) {
+ case ATTR_DOMAIN_POINT:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_EDGE:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_FACE:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>());
+ break;
+ case ATTR_DOMAIN_CORNER:
+ copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>());
+ break;
+ default:
+ BLI_assert_unreachable();
+ break;
+ }
+ });
+}
+
+static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve,
+ const CurveEval &profile,
+ const ResultOffsets &offsets,
+ ResultAttributes &attributes)
+{
+ if (!attributes.curve_spline_attributes.is_empty()) {
+ int i = 0;
+ curve.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.curve_spline_attributes[i]) {
+ copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id),
+ offsets,
+ *attributes.curve_spline_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+ }
+ if (!attributes.profile_spline_attributes.is_empty()) {
+ int i = 0;
+ profile.attributes.foreach_attribute(
+ [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
+ if (attributes.profile_spline_attributes[i]) {
+ copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id),
+ offsets,
+ *attributes.profile_spline_attributes[i]);
+ }
+ i++;
+ return true;
+ },
+ ATTR_DOMAIN_CURVE);
+ }
+}
+
+/**
+ * Extrude all splines in the profile curve along the path of every spline in the curve input.
+ * Transfer curve attributes to the mesh.
+ *
+ * \note Normal calculation is by far the slowest part of calculations relating to the result mesh.
+ * Although it would be a sensible decision to use the better topology information available while
+ * generating the mesh to also generate the normals, that work may wasted if the output mesh is
+ * changed anyway in a way that affects the normals. So currently this code uses the safer /
+ * simpler solution of deferring normal calculation to the rest of Blender.
+ */
+Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile)
+{
+ Span<SplinePtr> profiles = profile.splines();
+ Span<SplinePtr> curves = curve.splines();
+
+ const ResultOffsets offsets = calculate_result_offsets(profiles, curves);
+ if (offsets.vert.last() == 0) {
+ return nullptr;
+ }
+
+ Mesh *mesh = BKE_mesh_new_nomain(
+ offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last());
+ BKE_id_material_eval_ensure_default_slot(&mesh->id);
+ mesh->flag |= ME_AUTOSMOOTH;
+ mesh->smoothresh = DEG2RADF(180.0f);
+ BKE_mesh_normals_tag_dirty(mesh);
+
+ ResultAttributes attributes = create_result_attributes(curve, profile, *mesh);
+
+ threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) {
+ for (const int i_spline : curves_range) {
+ const Spline &spline = *curves[i_spline];
+ if (spline.evaluated_points_size() == 0) {
+ continue;
+ }
+ const int spline_start_index = i_spline * profiles.size();
+ threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) {
+ for (const int i_profile : profiles_range) {
+ const Spline &profile = *profiles[i_profile];
+ const int i_mesh = spline_start_index + i_profile;
+ ResultInfo info{
+ spline,
+ profile,
+ offsets.vert[i_mesh],
+ offsets.edge[i_mesh],
+ offsets.loop[i_mesh],
+ offsets.poly[i_mesh],
+ spline.evaluated_points_size(),
+ spline.evaluated_edges_size(),
+ profile.evaluated_points_size(),
+ profile.evaluated_edges_size(),
+ };
+
+ spline_extrude_to_mesh_data(info,
+ {mesh->mvert, mesh->totvert},
+ {mesh->medge, mesh->totedge},
+ {mesh->mloop, mesh->totloop},
+ {mesh->mpoly, mesh->totpoly});
+
+ copy_point_domain_attributes_to_mesh(info, attributes);
+ }
+ });
+ }
+ });
+
+ copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes);
+
+ for (OutputAttribute &output_attribute : attributes.attributes) {
+ output_attribute.save();
+ }
+
+ return mesh;
+}
+
+static CurveEval get_curve_single_vert()
+{
+ CurveEval curve;
+ std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
+ spline->add_point(float3(0), 0, 0.0f);
+ curve.add_spline(std::move(spline));
+
+ return curve;
+}
+
+/**
+ * Create a loose-edge mesh based on the evaluated path of the curve's splines.
+ * Transfer curve attributes to the mesh.
+ */
+Mesh *curve_to_wire_mesh(const CurveEval &curve)
+{
+ static const CurveEval vert_curve = get_curve_single_vert();
+ return curve_to_mesh_sweep(curve, vert_curve);
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc
index c4e1fe2f8e9..9479d012cb8 100644
--- a/source/blender/blenkernel/intern/geometry_component_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_component_instances.cc
@@ -24,6 +24,7 @@
#include "DNA_collection_types.h"
#include "BKE_geometry_set.hh"
+#include "BKE_geometry_set_instances.hh"
#include "attribute_access_intern.hh"
@@ -32,6 +33,7 @@ using blender::Map;
using blender::MutableSpan;
using blender::Set;
using blender::Span;
+using blender::VectorSet;
/* -------------------------------------------------------------------- */
/** \name Geometry Component Implementation
@@ -120,6 +122,52 @@ blender::Span<int> InstancesComponent::instance_ids() const
}
/**
+ * If references have a collection or object type, convert them into geometry instances. This
+ * will join geometry components from nested instances if necessary. After that, the geometry
+ * sets can be edited.
+ */
+void InstancesComponent::ensure_geometry_instances()
+{
+ VectorSet<InstanceReference> new_references;
+ new_references.reserve(references_.size());
+ for (const InstanceReference &reference : references_) {
+ if (reference.type() == InstanceReference::Type::Object) {
+ GeometrySet geometry_set;
+ InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>();
+ const int handle = instances.add_reference(reference.object());
+ instances.add_instance(handle, float4x4::identity());
+ new_references.add_new(geometry_set);
+ }
+ else if (reference.type() == InstanceReference::Type::Collection) {
+ GeometrySet geometry_set;
+ InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>();
+ const int handle = instances.add_reference(reference.collection());
+ instances.add_instance(handle, float4x4::identity());
+ new_references.add_new(geometry_set);
+ }
+ else {
+ new_references.add_new(reference);
+ }
+ }
+ references_ = std::move(new_references);
+}
+
+/**
+ * With write access to the instances component, the data in the instanced geometry sets can be
+ * changed. This is a function on the component rather than each reference to ensure `const`
+ * correctness for that reason.
+ */
+GeometrySet &InstancesComponent::geometry_set_from_reference(const int reference_index)
+{
+ /* If this assert fails, it means #ensure_geometry_instances must be called first. */
+ BLI_assert(references_[reference_index].type() == InstanceReference::Type::GeometrySet);
+
+ /* The const cast is okay because the instance's hash in the set
+ * is not changed by adjusting the data inside the geometry set. */
+ return const_cast<GeometrySet &>(references_[reference_index].geometry_set());
+}
+
+/**
* Returns a handle for the given reference.
* If the reference exists already, the handle of the existing reference is returned.
* Otherwise a new handle is added.
@@ -139,6 +187,11 @@ int InstancesComponent::instances_amount() const
return instance_transforms_.size();
}
+int InstancesComponent::references_amount() const
+{
+ return references_.size();
+}
+
bool InstancesComponent::is_empty() const
{
return this->instance_reference_handles_.size() == 0;
diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc
index 8ff026231f5..976b26a1f3a 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.cc
+++ b/source/blender/blenkernel/intern/gpencil_geom.cc
@@ -541,64 +541,241 @@ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist,
}
/**
+ * Give extra stroke points before and after the original tip points.
+ * \param gps: Target stroke
+ * \param count_before: how many extra points to be added before a stroke
+ * \param count_after: how many extra points to be added after a stroke
+ */
+static bool BKE_gpencil_stroke_extra_points(bGPDstroke *gps,
+ const int count_before,
+ const int count_after)
+{
+ bGPDspoint *pts = gps->points;
+
+ BLI_assert(count_before >= 0);
+ BLI_assert(count_after >= 0);
+ if (!count_before && !count_after) {
+ return false;
+ }
+
+ const int new_count = count_before + count_after + gps->totpoints;
+
+ bGPDspoint *new_pts = (bGPDspoint *)MEM_mallocN(sizeof(bGPDspoint) * new_count, __func__);
+
+ for (int i = 0; i < count_before; i++) {
+ memcpy(&new_pts[i], &pts[0], sizeof(bGPDspoint));
+ }
+ memcpy(&new_pts[count_before], pts, sizeof(bGPDspoint) * gps->totpoints);
+ for (int i = new_count - count_after; i < new_count; i++) {
+ memcpy(&new_pts[i], &pts[gps->totpoints - 1], sizeof(bGPDspoint));
+ }
+
+ if (gps->dvert) {
+ MDeformVert *new_dv = (MDeformVert *)MEM_mallocN(sizeof(MDeformVert) * new_count, __func__);
+
+ for (int i = 0; i < new_count; i++) {
+ MDeformVert *dv = &gps->dvert[CLAMPIS(i - count_before, 0, gps->totpoints - 1)];
+ int inew = i;
+ new_dv[inew].flag = dv->flag;
+ new_dv[inew].totweight = dv->totweight;
+ new_dv[inew].dw = (MDeformWeight *)MEM_mallocN(sizeof(MDeformWeight) * dv->totweight,
+ __func__);
+ memcpy(new_dv[inew].dw, dv->dw, sizeof(MDeformWeight) * dv->totweight);
+ }
+ BKE_gpencil_free_stroke_weights(gps);
+ MEM_freeN(gps->dvert);
+ gps->dvert = new_dv;
+ }
+
+ MEM_freeN(gps->points);
+ gps->points = new_pts;
+ gps->totpoints = new_count;
+
+ return true;
+}
+
+/**
* Backbone stretch similar to Freestyle.
* \param gps: Stroke to sample.
- * \param dist: Distance of one segment.
- * \param overshoot_fac: How exact is the follow curve algorithm.
+ * \param dist: Length of the added section.
+ * \param overshoot_fac: Relative length of the curve which is used to determine the extension.
* \param mode: Affect to Start, End or Both extremes (0->Both, 1->Start, 2->End)
+ * \param follow_curvature: True for approximating curvature of given overshoot.
+ * \param extra_point_count: When follow_curvature is true, use this amount of extra points
*/
bool BKE_gpencil_stroke_stretch(bGPDstroke *gps,
const float dist,
const float overshoot_fac,
- const short mode)
+ const short mode,
+ const bool follow_curvature,
+ const int extra_point_count,
+ const float segment_influence,
+ const float max_angle,
+ const bool invert_curvature)
{
#define BOTH 0
#define START 1
#define END 2
- bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt;
- int i;
- float threshold = (overshoot_fac == 0 ? 0.001f : overshoot_fac);
+ const bool do_start = ELEM(mode, BOTH, START);
+ const bool do_end = ELEM(mode, BOTH, END);
+ float used_percent_length = overshoot_fac;
+ CLAMP(used_percent_length, 1e-4f, 1.0f);
+ if (!isfinite(used_percent_length)) {
+ /* #used_percent_length must always be finite, otherwise a segfault occurs.
+ * Since this function should never segfault, set #used_percent_length to a safe fallback. */
+ /* NOTE: This fallback is used if gps->totpoints == 2, see MOD_gpencillength.c */
+ used_percent_length = 0.1f;
+ }
- if (gps->totpoints < 2 || dist < FLT_EPSILON) {
+ if (gps->totpoints <= 1 || dist < FLT_EPSILON || extra_point_count <= 0) {
return false;
}
- last_pt = &pt[gps->totpoints - 1];
- second_last = &pt[gps->totpoints - 2];
- next_pt = &pt[1];
-
- if (mode == BOTH || mode == START) {
- float len1 = 0.0f;
- i = 1;
- while (len1 < threshold && gps->totpoints > i) {
- next_pt = &pt[i];
- len1 = len_v3v3(&next_pt->x, &pt->x);
- i++;
+ /* NOTE: When it's just a straight line, we don't need to do the curvature stuff. */
+ if (!follow_curvature || gps->totpoints <= 2) {
+ /* Not following curvature, just straight line. */
+ /* NOTE: #overshoot_point_param can not be zero. */
+ float overshoot_point_param = used_percent_length * (gps->totpoints - 1);
+ float result[3];
+
+ if (do_start) {
+ int index1 = floor(overshoot_point_param);
+ int index2 = ceil(overshoot_point_param);
+ interp_v3_v3v3(result,
+ &gps->points[index1].x,
+ &gps->points[index2].x,
+ fmodf(overshoot_point_param, 1.0f));
+ sub_v3_v3(result, &gps->points[0].x);
+ if (UNLIKELY(is_zero_v3(result))) {
+ sub_v3_v3v3(result, &gps->points[1].x, &gps->points[0].x);
+ }
+ madd_v3_v3fl(&gps->points[0].x, result, -dist / len_v3(result));
+ }
+
+ if (do_end) {
+ int index1 = gps->totpoints - 1 - floor(overshoot_point_param);
+ int index2 = gps->totpoints - 1 - ceil(overshoot_point_param);
+ interp_v3_v3v3(result,
+ &gps->points[index1].x,
+ &gps->points[index2].x,
+ fmodf(overshoot_point_param, 1.0f));
+ sub_v3_v3(result, &gps->points[gps->totpoints - 1].x);
+ if (UNLIKELY(is_zero_v3(result))) {
+ sub_v3_v3v3(
+ result, &gps->points[gps->totpoints - 2].x, &gps->points[gps->totpoints - 1].x);
+ }
+ madd_v3_v3fl(&gps->points[gps->totpoints - 1].x, result, -dist / len_v3(result));
}
- float extend1 = (len1 + dist) / len1;
- float result1[3];
-
- interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1);
- copy_v3_v3(&pt->x, result1);
+ return true;
}
- if (mode == BOTH || mode == END) {
- float len2 = 0.0f;
- i = 2;
- while (len2 < threshold && gps->totpoints >= i) {
- second_last = &pt[gps->totpoints - i];
- len2 = len_v3v3(&last_pt->x, &second_last->x);
- i++;
+ /* Curvature calculation. */
+
+ /* First allocate the new stroke size. */
+ const int first_old_index = do_start ? extra_point_count : 0;
+ const int last_old_index = gps->totpoints - 1 + first_old_index;
+ const int orig_totpoints = gps->totpoints;
+ BKE_gpencil_stroke_extra_points(gps, first_old_index, do_end ? extra_point_count : 0);
+
+ /* The fractional amount of points to query when calculating the average curvature of the
+ * strokes. */
+ const float overshoot_parameter = used_percent_length * (orig_totpoints - 2);
+ int overshoot_pointcount = ceil(overshoot_parameter);
+ CLAMP(overshoot_pointcount, 1, orig_totpoints - 2);
+
+ /* Do for both sides without code duplication. */
+ float no[3], vec1[3], vec2[3], total_angle[3];
+ for (int k = 0; k < 2; k++) {
+ if ((k == 0 && !do_start) || (k == 1 && !do_end)) {
+ continue;
}
- float extend2 = (len2 + dist) / len2;
- float result2[3];
- interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2);
+ const int start_i = k == 0 ? first_old_index :
+ last_old_index; // first_old_index, last_old_index
+ const int dir_i = 1 - k * 2; // 1, -1
- copy_v3_v3(&last_pt->x, result2);
- }
+ sub_v3_v3v3(vec1, &gps->points[start_i + dir_i].x, &gps->points[start_i].x);
+ zero_v3(total_angle);
+ float segment_length = normalize_v3(vec1);
+ float overshoot_length = 0.0f;
+
+ /* Accumulate rotation angle and length. */
+ int j = 0;
+ for (int i = start_i; j < overshoot_pointcount; i += dir_i, j++) {
+ /* Don't fully add last segment to get continuity in overshoot_fac. */
+ float fac = fmin(overshoot_parameter - j, 1.0f);
+
+ /* Read segments. */
+ copy_v3_v3(vec2, vec1);
+ sub_v3_v3v3(vec1, &gps->points[i + dir_i * 2].x, &gps->points[i + dir_i].x);
+ const float len = normalize_v3(vec1);
+ float angle = angle_normalized_v3v3(vec1, vec2) * fac;
+
+ /* Add half of both adjacent legs of the current angle. */
+ const float added_len = (segment_length + len) * 0.5f * fac;
+ overshoot_length += added_len;
+ segment_length = len;
+
+ if (angle > max_angle) {
+ continue;
+ }
+ if (angle > M_PI * 0.995f) {
+ continue;
+ }
+
+ angle *= powf(added_len, segment_influence);
+
+ cross_v3_v3v3(no, vec1, vec2);
+ normalize_v3_length(no, angle);
+ add_v3_v3(total_angle, no);
+ }
+ if (UNLIKELY(overshoot_length == 0.0f)) {
+ /* Don't do a proper extension if the used points are all in the same position. */
+ continue;
+ }
+
+ sub_v3_v3v3(vec1, &gps->points[start_i].x, &gps->points[start_i + dir_i].x);
+ /* In general curvature = 1/radius. For the case without the
+ * weights introduced by #segment_influence, the calculation is
+ * curvature = delta angle/delta arclength = len_v3(total_angle) / overshoot_length */
+ float curvature = normalize_v3(total_angle) / overshoot_length;
+ /* Compensate for the weights powf(added_len, segment_influence). */
+ curvature /= powf(overshoot_length / fminf(overshoot_parameter, (float)j), segment_influence);
+ if (invert_curvature) {
+ curvature = -curvature;
+ }
+ const float angle_step = curvature * dist / extra_point_count;
+ float step_length = dist / extra_point_count;
+ if (fabsf(angle_step) > FLT_EPSILON) {
+ /* Make a direct step length from the assigned arc step length. */
+ step_length *= sin(angle_step * 0.5f) / (angle_step * 0.5f);
+ }
+ else {
+ zero_v3(total_angle);
+ }
+ const float prev_length = normalize_v3_length(vec1, step_length);
+
+ /* Build rotation matrix here to get best performance. */
+ float rot[3][3];
+ float q[4];
+ axis_angle_to_quat(q, total_angle, angle_step);
+ quat_to_mat3(rot, q);
+
+ /* Rotate the starting direction to account for change in edge lengths. */
+ axis_angle_to_quat(q,
+ total_angle,
+ fmaxf(0.0f, 1.0f - fabs(segment_influence)) *
+ (curvature * prev_length - angle_step) / 2.0f);
+ mul_qt_v3(q, vec1);
+
+ /* Now iteratively accumulate the segments with a rotating added direction. */
+ for (int i = start_i - dir_i, j = 0; j < extra_point_count; i -= dir_i, j++) {
+ mul_v3_m3v3(vec1, rot, vec1);
+ add_v3_v3v3(&gps->points[i].x, vec1, &gps->points[i + dir_i].x);
+ }
+ }
return true;
}
@@ -749,6 +926,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
second_last = &pt[gps->totpoints - 2];
+ float len;
float len1, cut_len1;
float len2, cut_len2;
len1 = len2 = cut_len1 = cut_len2 = 0.0f;
@@ -759,11 +937,13 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
i = 0;
index_end = gps->totpoints - 1;
while (len1 < dist && gps->totpoints > i + 1) {
- len1 += len_v3v3(&pt[i].x, &pt[i + 1].x);
+ len = len_v3v3(&pt[i].x, &pt[i + 1].x);
+ len1 += len;
cut_len1 = len1 - dist;
i++;
}
index_start = i - 1;
+ interp_v3_v3v3(&pt[index_start].x, &pt[index_start + 1].x, &pt[index_start].x, cut_len1 / len);
}
if (mode == END) {
@@ -771,18 +951,20 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
i = 2;
while (len2 < dist && gps->totpoints >= i) {
second_last = &pt[gps->totpoints - i];
- len2 += len_v3v3(&second_last[1].x, &second_last->x);
+ len = len_v3v3(&second_last[1].x, &second_last->x);
+ len2 += len;
cut_len2 = len2 - dist;
i++;
}
index_end = gps->totpoints - i + 2;
+ interp_v3_v3v3(&pt[index_end].x, &pt[index_end - 1].x, &pt[index_end].x, cut_len2 / len);
}
if (index_end <= index_start) {
index_start = index_end = 0; /* empty stroke */
}
- if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < dist)) {
+ if ((index_end == index_start + 1) && (cut_len1 + cut_len2 < 0)) {
index_start = index_end = 0; /* no length left to cut */
}
diff --git a/source/blender/blenkernel/intern/layer.c b/source/blender/blenkernel/intern/layer.c
index b489675cd74..434a2296d95 100644
--- a/source/blender/blenkernel/intern/layer.c
+++ b/source/blender/blenkernel/intern/layer.c
@@ -183,7 +183,6 @@ static ViewLayer *view_layer_add(const char *name)
view_layer->passflag = SCE_PASS_COMBINED;
view_layer->pass_alpha_threshold = 0.5f;
view_layer->cryptomatte_levels = 6;
- view_layer->cryptomatte_flag = VIEW_LAYER_CRYPTOMATTE_ACCURATE;
BKE_freestyle_config_init(&view_layer->freestyle_config);
return view_layer;
diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c
index 3fead8b0f39..c60a9104144 100644
--- a/source/blender/blenkernel/intern/lib_override.c
+++ b/source/blender/blenkernel/intern/lib_override.c
@@ -865,7 +865,9 @@ static void lib_override_library_create_post_process(Main *bmain,
Object *ob_ref = (Object *)id_ref;
LISTBASE_FOREACH (Collection *, collection, &bmain->collections) {
if (BKE_collection_has_object(collection, ob_ref) &&
- BKE_view_layer_has_collection(view_layer, collection) &&
+ (view_layer != NULL ?
+ BKE_view_layer_has_collection(view_layer, collection) :
+ BKE_collection_has_collection(scene->master_collection, collection)) &&
!ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) {
default_instantiating_collection = collection;
}
@@ -897,6 +899,8 @@ static void lib_override_library_create_post_process(Main *bmain,
* \note It will override all IDs tagged with \a LIB_TAG_DOIT, and it does not clear that tag at
* its beginning, so caller code can add extra data-blocks to be overridden as well.
*
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
* \param id_root: The root ID to create an override from.
* \param id_reference: Some reference ID used to do some post-processing after overrides have been
* created, may be NULL. Typically, the Empty object instantiating the linked collection we
@@ -960,6 +964,8 @@ bool BKE_lib_override_library_template_create(struct ID *id)
* \note This is a thin wrapper around \a BKE_lib_override_library_create, only extra work is to
* actually convert the proxy itself into an override first.
*
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
* \return true if override was successfully created.
*/
bool BKE_lib_override_library_proxy_convert(Main *bmain,
@@ -1002,6 +1008,8 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain,
* data, from an existing override hierarchy.
*
* \param id_root: The root liboverride ID to resync from.
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
* \return true if override was successfully resynced.
*/
bool BKE_lib_override_library_resync(Main *bmain,
@@ -1723,6 +1731,9 @@ static int lib_override_libraries_index_define(Main *bmain)
*
* Then it will handle the resync of necessary IDs (through calls to
* #BKE_lib_override_library_resync).
+ *
+ * \param view_layer: the active view layer to search instantiated collections in, can be NULL (in
+ * which case \a scene's master collection children hierarchy is used instead).
*/
void BKE_lib_override_library_main_resync(Main *bmain,
Scene *scene,
diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc
index 07dc6db05aa..467f7d4543e 100644
--- a/source/blender/blenkernel/intern/mesh_convert.cc
+++ b/source/blender/blenkernel/intern/mesh_convert.cc
@@ -1118,7 +1118,7 @@ static Mesh *mesh_new_from_mball_object(Object *object)
* balls and all evaluated child meta balls (since polygonization is only stored in the mother
* ball).
*
- * We create empty mesh so scripters don't run into None objects. */
+ * Create empty mesh so script-authors don't run into None objects. */
if (!DEG_is_evaluated_object(object) || object->runtime.curve_cache == nullptr ||
BLI_listbase_is_empty(&object->runtime.curve_cache->disp)) {
return (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)object->data)->name + 2);
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 3a76cbf6f84..2d0239740f8 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -5175,6 +5175,7 @@ static void registerGeometryNodes()
register_node_type_geo_attribute_randomize();
register_node_type_geo_attribute_remove();
register_node_type_geo_attribute_separate_xyz();
+ register_node_type_geo_attribute_statistic();
register_node_type_geo_attribute_transfer();
register_node_type_geo_attribute_vector_math();
register_node_type_geo_attribute_vector_rotate();
@@ -5182,9 +5183,11 @@ static void registerGeometryNodes()
register_node_type_geo_bounding_box();
register_node_type_geo_collection_info();
register_node_type_geo_convex_hull();
+ register_node_type_geo_curve_sample();
register_node_type_geo_curve_endpoints();
register_node_type_geo_curve_fill();
register_node_type_geo_curve_length();
+ register_node_type_geo_curve_parameter();
register_node_type_geo_curve_primitive_bezier_segment();
register_node_type_geo_curve_primitive_circle();
register_node_type_geo_curve_primitive_line();
@@ -5206,6 +5209,7 @@ static void registerGeometryNodes()
register_node_type_geo_input_material();
register_node_type_geo_input_normal();
register_node_type_geo_input_position();
+ register_node_type_geo_input_tangent();
register_node_type_geo_is_viewport();
register_node_type_geo_join_geometry();
register_node_type_geo_material_assign();
@@ -5232,6 +5236,7 @@ static void registerGeometryNodes()
register_node_type_geo_realize_instances();
register_node_type_geo_sample_texture();
register_node_type_geo_select_by_handle_type();
+ register_node_type_geo_string_join();
register_node_type_geo_material_selection();
register_node_type_geo_separate_components();
register_node_type_geo_set_position();
@@ -5251,6 +5256,9 @@ static void registerFunctionNodes()
register_node_type_fn_input_string();
register_node_type_fn_input_vector();
register_node_type_fn_random_float();
+ register_node_type_fn_string_length();
+ register_node_type_fn_string_substring();
+ register_node_type_fn_value_to_string();
}
void BKE_node_system_init(void)
diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c
index 73e25a22225..4c38536b662 100644
--- a/source/blender/blenkernel/intern/screen.c
+++ b/source/blender/blenkernel/intern/screen.c
@@ -1679,6 +1679,8 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area)
sseq->scopes.sep_waveform_ibuf = NULL;
sseq->scopes.vector_ibuf = NULL;
sseq->scopes.histogram_ibuf = NULL;
+ memset(&sseq->runtime, 0x0, sizeof(sseq->runtime));
+
}
else if (sl->spacetype == SPACE_PROPERTIES) {
SpaceProperties *sbuts = (SpaceProperties *)sl;
diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc
index 79d2137ee84..b36d7a21669 100644
--- a/source/blender/blenkernel/intern/spline_bezier.cc
+++ b/source/blender/blenkernel/intern/spline_bezier.cc
@@ -214,6 +214,11 @@ void BezierSpline::ensure_auto_handles() const
return;
}
+ if (this->size() == 1) {
+ auto_handles_dirty_ = false;
+ return;
+ }
+
for (const int i : IndexRange(this->size())) {
if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) {
const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i);
diff --git a/source/blender/blenlib/BLI_array.hh b/source/blender/blenlib/BLI_array.hh
index fc8fc615feb..352bf379d4d 100644
--- a/source/blender/blenlib/BLI_array.hh
+++ b/source/blender/blenlib/BLI_array.hh
@@ -277,6 +277,21 @@ class Array {
}
/**
+ * Return a reference to the first element in the array.
+ * This invokes undefined behavior when the array is empty.
+ */
+ const T &first() const
+ {
+ BLI_assert(size_ > 0);
+ return *data_;
+ }
+ T &first()
+ {
+ BLI_assert(size_ > 0);
+ return *data_;
+ }
+
+ /**
* Return a reference to the last element in the array.
* This invokes undefined behavior when the array is empty.
*/
diff --git a/source/blender/blenlib/BLI_uuid.h b/source/blender/blenlib/BLI_uuid.h
index 1ce294ed723..9b85f8e65bc 100644
--- a/source/blender/blenlib/BLI_uuid.h
+++ b/source/blender/blenlib/BLI_uuid.h
@@ -35,24 +35,25 @@ extern "C" {
/**
* UUID generator for random (version 4) UUIDs. See RFC4122 section 4.4.
* This function is not thread-safe. */
-UUID BLI_uuid_generate_random(void);
+bUUID BLI_uuid_generate_random(void);
/**
* Return the UUID nil value, consisting of all-zero fields.
*/
-UUID BLI_uuid_nil(void);
+bUUID BLI_uuid_nil(void);
/** Return true only if this is the nil UUID. */
-bool BLI_uuid_is_nil(UUID uuid);
+bool BLI_uuid_is_nil(bUUID uuid);
/** Compare two UUIDs, return true only if they are equal. */
-bool BLI_uuid_equal(UUID uuid1, UUID uuid2);
+bool BLI_uuid_equal(bUUID uuid1, bUUID uuid2);
/**
* Format UUID as string.
* The buffer must be at least 37 bytes (36 bytes for the UUID + terminating 0).
+ * Use `UUID_STRING_LEN` from DNA_uuid_types.h if you want to use a constant for this.
*/
-void BLI_uuid_format(char *buffer, UUID uuid) ATTR_NONNULL();
+void BLI_uuid_format(char *buffer, bUUID uuid) ATTR_NONNULL();
/**
* Parse a string as UUID.
@@ -62,7 +63,7 @@ void BLI_uuid_format(char *buffer, UUID uuid) ATTR_NONNULL();
* Return true if the string could be parsed, and false otherwise. In the latter case, the UUID may
* have been partially updated.
*/
-bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) ATTR_NONNULL();
+bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) ATTR_NONNULL();
#ifdef __cplusplus
}
@@ -70,6 +71,6 @@ bool BLI_uuid_parse_string(UUID *uuid, const char *buffer) ATTR_NONNULL();
# include <ostream>
/** Output the UUID as formatted ASCII string, see #BLI_uuid_format(). */
-std::ostream &operator<<(std::ostream &stream, UUID uuid);
+std::ostream &operator<<(std::ostream &stream, bUUID uuid);
#endif
diff --git a/source/blender/blenlib/intern/uuid.cc b/source/blender/blenlib/intern/uuid.cc
index f5edb356acc..ae34bcb3d32 100644
--- a/source/blender/blenlib/intern/uuid.cc
+++ b/source/blender/blenlib/intern/uuid.cc
@@ -27,9 +27,9 @@
#include <string>
/* Ensure the UUID struct doesn't have any padding, to be compatible with memcmp(). */
-static_assert(sizeof(UUID) == 16, "expect UUIDs to be 128 bit exactly");
+static_assert(sizeof(bUUID) == 16, "expect UUIDs to be 128 bit exactly");
-UUID BLI_uuid_generate_random()
+bUUID BLI_uuid_generate_random()
{
static std::mt19937_64 rng = []() {
std::mt19937_64 rng;
@@ -57,7 +57,7 @@ UUID BLI_uuid_generate_random()
return rng;
}();
- UUID uuid;
+ bUUID uuid;
/* RFC4122 suggests setting certain bits to a fixed value, and then randomizing the remaining
* bits. The opposite is easier to implement, though, so that's what's done here. */
@@ -78,23 +78,23 @@ UUID BLI_uuid_generate_random()
return uuid;
}
-UUID BLI_uuid_nil(void)
+bUUID BLI_uuid_nil(void)
{
- const UUID nil = {0, 0, 0, 0, 0, 0};
+ const bUUID nil = {0, 0, 0, 0, 0, 0};
return nil;
}
-bool BLI_uuid_is_nil(UUID uuid)
+bool BLI_uuid_is_nil(bUUID uuid)
{
return BLI_uuid_equal(BLI_uuid_nil(), uuid);
}
-bool BLI_uuid_equal(const UUID uuid1, const UUID uuid2)
+bool BLI_uuid_equal(const bUUID uuid1, const bUUID uuid2)
{
return std::memcmp(&uuid1, &uuid2, sizeof(uuid1)) == 0;
}
-void BLI_uuid_format(char *buffer, const UUID uuid)
+void BLI_uuid_format(char *buffer, const bUUID uuid)
{
std::sprintf(buffer,
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
@@ -111,7 +111,7 @@ void BLI_uuid_format(char *buffer, const UUID uuid)
uuid.node[5]);
}
-bool BLI_uuid_parse_string(UUID *uuid, const char *buffer)
+bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer)
{
const int num_fields_parsed = std::sscanf(
buffer,
@@ -130,7 +130,7 @@ bool BLI_uuid_parse_string(UUID *uuid, const char *buffer)
return num_fields_parsed == 11;
}
-std::ostream &operator<<(std::ostream &stream, UUID uuid)
+std::ostream &operator<<(std::ostream &stream, bUUID uuid)
{
std::string buffer(36, '\0');
BLI_uuid_format(buffer.data(), uuid);
diff --git a/source/blender/blenlib/tests/BLI_uuid_test.cc b/source/blender/blenlib/tests/BLI_uuid_test.cc
index 31c69002c1c..731489c6c9e 100644
--- a/source/blender/blenlib/tests/BLI_uuid_test.cc
+++ b/source/blender/blenlib/tests/BLI_uuid_test.cc
@@ -20,7 +20,7 @@
TEST(BLI_uuid, generate_random)
{
- const UUID uuid = BLI_uuid_generate_random();
+ const bUUID uuid = BLI_uuid_generate_random();
// The 4 MSbits represent the "version" of the UUID.
const uint16_t version = uuid.time_hi_and_version >> 12;
@@ -33,11 +33,11 @@ TEST(BLI_uuid, generate_random)
TEST(BLI_uuid, generate_many_random)
{
- const UUID first_uuid = BLI_uuid_generate_random();
+ const bUUID first_uuid = BLI_uuid_generate_random();
/* Generate lots of UUIDs to get some indication that the randomness is okay. */
for (int i = 0; i < 1000000; ++i) {
- const UUID uuid = BLI_uuid_generate_random();
+ const bUUID uuid = BLI_uuid_generate_random();
EXPECT_FALSE(BLI_uuid_equal(first_uuid, uuid));
// Check that the non-random bits are set according to RFC4122.
@@ -50,8 +50,8 @@ TEST(BLI_uuid, generate_many_random)
TEST(BLI_uuid, nil_value)
{
- const UUID nil_uuid = BLI_uuid_nil();
- const UUID zeroes_uuid = {0, 0, 0, 0, 0, 0};
+ const bUUID nil_uuid = BLI_uuid_nil();
+ const bUUID zeroes_uuid = {0, 0, 0, 0, 0, 0};
EXPECT_TRUE(BLI_uuid_equal(nil_uuid, zeroes_uuid));
EXPECT_TRUE(BLI_uuid_is_nil(nil_uuid));
@@ -63,8 +63,8 @@ TEST(BLI_uuid, nil_value)
TEST(BLI_uuid, equality)
{
- const UUID uuid1 = BLI_uuid_generate_random();
- const UUID uuid2 = BLI_uuid_generate_random();
+ const bUUID uuid1 = BLI_uuid_generate_random();
+ const bUUID uuid2 = BLI_uuid_generate_random();
EXPECT_TRUE(BLI_uuid_equal(uuid1, uuid1));
EXPECT_FALSE(BLI_uuid_equal(uuid1, uuid2));
@@ -72,7 +72,7 @@ TEST(BLI_uuid, equality)
TEST(BLI_uuid, string_formatting)
{
- UUID uuid;
+ bUUID uuid;
std::string buffer(36, '\0');
memset(&uuid, 0, sizeof(uuid));
@@ -91,12 +91,12 @@ TEST(BLI_uuid, string_formatting)
EXPECT_EQ("00000001-0002-0003-0405-060000000007", buffer);
/* Somewhat more complex bit patterns. This is a version 1 UUID generated from Python. */
- const UUID uuid1 = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}};
+ const bUUID uuid1 = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}};
BLI_uuid_format(buffer.data(), uuid1);
EXPECT_EQ("d30a0e60-14a2-11ec-8b99-f7736944db8b", buffer);
/* Namespace UUID, example listed in RFC4211. */
- const UUID namespace_dns = {
+ const bUUID namespace_dns = {
0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, {0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}};
BLI_uuid_format(buffer.data(), namespace_dns);
EXPECT_EQ("6ba7b810-9dad-11d1-80b4-00c04fd430c8", buffer);
@@ -104,7 +104,7 @@ TEST(BLI_uuid, string_formatting)
TEST(BLI_uuid, string_parsing_ok)
{
- UUID uuid;
+ bUUID uuid;
std::string buffer(36, '\0');
const bool parsed_ok = BLI_uuid_parse_string(&uuid, "d30a0e60-14a2-11ec-8b99-f7736944db8b");
@@ -115,7 +115,7 @@ TEST(BLI_uuid, string_parsing_ok)
TEST(BLI_uuid, string_parsing_capitalisation)
{
- UUID uuid;
+ bUUID uuid;
std::string buffer(36, '\0');
/* RFC4122 demands acceptance of upper-case hex digits. */
@@ -129,7 +129,7 @@ TEST(BLI_uuid, string_parsing_capitalisation)
TEST(BLI_uuid, string_parsing_fail)
{
- UUID uuid;
+ bUUID uuid;
std::string buffer(36, '\0');
const bool parsed_ok = BLI_uuid_parse_string(&uuid, "d30a0e60!14a2-11ec-8b99-f7736944db8b");
@@ -139,7 +139,7 @@ TEST(BLI_uuid, string_parsing_fail)
TEST(BLI_uuid, stream_operator)
{
std::stringstream ss;
- const UUID uuid = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}};
+ const bUUID uuid = {3540651616, 5282, 4588, 139, 153, {0xf7, 0x73, 0x69, 0x44, 0xdb, 0x8b}};
ss << uuid;
EXPECT_EQ(ss.str(), "d30a0e60-14a2-11ec-8b99-f7736944db8b");
}
diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c
index fa15e541e43..54d1efab7dd 100644
--- a/source/blender/blenloader/intern/versioning_270.c
+++ b/source/blender/blenloader/intern/versioning_270.c
@@ -651,13 +651,6 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *bmain)
mat->line_col[3] = mat->alpha;
}
}
-
- if (!DNA_struct_elem_find(fd->filesdna, "RenderData", "int", "preview_start_resolution")) {
- Scene *scene;
- for (scene = bmain->scenes.first; scene; scene = scene->id.next) {
- scene->r.preview_start_resolution = 64;
- }
- }
}
if (!MAIN_VERSION_ATLEAST(bmain, 271, 3)) {
@@ -698,15 +691,6 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
- if (!MAIN_VERSION_ATLEAST(bmain, 272, 0)) {
- if (!DNA_struct_elem_find(fd->filesdna, "RenderData", "int", "preview_start_resolution")) {
- Scene *scene;
- for (scene = bmain->scenes.first; scene; scene = scene->id.next) {
- scene->r.preview_start_resolution = 64;
- }
- }
- }
-
if (!MAIN_VERSION_ATLEAST(bmain, 272, 1)) {
Brush *br;
for (br = bmain->brushes.first; br; br = br->id.next) {
diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c
index f667361d166..69b67460a5d 100644
--- a/source/blender/blenloader/intern/versioning_280.c
+++ b/source/blender/blenloader/intern/versioning_280.c
@@ -1774,7 +1774,7 @@ static void do_versions_seq_set_cache_defaults(Editing *ed)
static bool seq_update_flags_cb(Sequence *seq, void *UNUSED(user_data))
{
- seq->flag &= ~(SEQ_FLAG_UNUSED_6 | SEQ_FLAG_UNUSED_18 | SEQ_FLAG_UNUSED_19 | SEQ_FLAG_UNUSED_21);
+ seq->flag &= ~((1 << 6) | (1 << 18) | (1 << 19) | (1 << 21));
if (seq->type == SEQ_TYPE_SPEED) {
SpeedControlVars *s = (SpeedControlVars *)seq->effectdata;
s->flags &= ~(SEQ_SPEED_UNUSED_1);
@@ -3718,7 +3718,7 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
STRNCPY(node->idname, "ShaderNodeOutputLight");
}
if (node->type == SH_NODE_BSDF_PRINCIPLED && node->custom2 == 0) {
- node->custom2 = SHD_SUBSURFACE_BURLEY;
+ node->custom2 = SHD_SUBSURFACE_DIFFUSION;
}
}
}
diff --git a/source/blender/blenloader/intern/versioning_290.c b/source/blender/blenloader/intern/versioning_290.c
index bafba486c88..be8c4b735be 100644
--- a/source/blender/blenloader/intern/versioning_290.c
+++ b/source/blender/blenloader/intern/versioning_290.c
@@ -1461,7 +1461,6 @@ void blo_do_versions_290(FileData *fd, Library *UNUSED(lib), Main *bmain)
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
view_layer->cryptomatte_levels = 6;
- view_layer->cryptomatte_flag = VIEW_LAYER_CRYPTOMATTE_ACCURATE;
}
}
}
diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c
index 30e7c9bde4c..58265bca238 100644
--- a/source/blender/blenloader/intern/versioning_300.c
+++ b/source/blender/blenloader/intern/versioning_300.c
@@ -64,6 +64,7 @@
#include "MEM_guardedalloc.h"
#include "readfile.h"
+#include "SEQ_iterator.h"
#include "SEQ_sequencer.h"
#include "RNA_access.h"
@@ -109,7 +110,8 @@ static void version_idproperty_move_data_int(IDPropertyUIDataInt *ui_data,
if (default_value != NULL) {
if (default_value->type == IDP_ARRAY) {
if (default_value->subtype == IDP_INT) {
- ui_data->default_array = MEM_dupallocN(IDP_Array(default_value));
+ ui_data->default_array = MEM_malloc_arrayN(default_value->len, sizeof(int), __func__);
+ memcpy(ui_data->default_array, IDP_Array(default_value), sizeof(int) * default_value->len);
ui_data->default_array_len = default_value->len;
}
}
@@ -151,9 +153,18 @@ static void version_idproperty_move_data_float(IDPropertyUIDataFloat *ui_data,
IDProperty *default_value = IDP_GetPropertyFromGroup(prop_ui_data, "default");
if (default_value != NULL) {
if (default_value->type == IDP_ARRAY) {
- if (ELEM(default_value->subtype, IDP_FLOAT, IDP_DOUBLE)) {
- ui_data->default_array = MEM_dupallocN(IDP_Array(default_value));
- ui_data->default_array_len = default_value->len;
+ const int size = default_value->len;
+ ui_data->default_array_len = size;
+ if (default_value->subtype == IDP_FLOAT) {
+ ui_data->default_array = MEM_malloc_arrayN(size, sizeof(double), __func__);
+ const float *old_default_array = IDP_Array(default_value);
+ for (int i = 0; i < ui_data->default_array_len; i++) {
+ ui_data->default_array[i] = (double)old_default_array[i];
+ }
+ }
+ else if (default_value->subtype == IDP_DOUBLE) {
+ ui_data->default_array = MEM_malloc_arrayN(size, sizeof(double), __func__);
+ memcpy(ui_data->default_array, IDP_Array(default_value), sizeof(double) * size);
}
}
else if (ELEM(default_value->type, IDP_DOUBLE, IDP_FLOAT)) {
@@ -774,6 +785,69 @@ static void version_geometry_nodes_change_legacy_names(bNodeTree *ntree)
}
}
}
+static bool seq_transform_origin_set(Sequence *seq, void *UNUSED(user_data))
+{
+ StripTransform *transform = seq->strip->transform;
+ if (seq->strip->transform != NULL) {
+ transform->origin[0] = transform->origin[1] = 0.5f;
+ }
+ return true;
+}
+
+static void do_version_subsurface_methods(bNode *node)
+{
+ if (node->type == SH_NODE_SUBSURFACE_SCATTERING) {
+ if (node->custom1 != SHD_SUBSURFACE_RANDOM_WALK) {
+ node->custom1 = SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS;
+ }
+ }
+ else if (node->type == SH_NODE_BSDF_PRINCIPLED) {
+ if (node->custom2 != SHD_SUBSURFACE_RANDOM_WALK) {
+ node->custom2 = SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS;
+ }
+ }
+}
+
+static void version_geometry_nodes_add_attribute_input_settings(NodesModifierData *nmd)
+{
+ /* Before versioning the properties, make sure it hasn't been done already. */
+ LISTBASE_FOREACH (const IDProperty *, property, &nmd->settings.properties->data.group) {
+ if (strstr(property->name, "_use_attribute") || strstr(property->name, "_attribute_name")) {
+ return;
+ }
+ }
+
+ LISTBASE_FOREACH_MUTABLE (IDProperty *, property, &nmd->settings.properties->data.group) {
+ if (!ELEM(property->type, IDP_FLOAT, IDP_INT, IDP_ARRAY)) {
+ continue;
+ }
+
+ if (strstr(property->name, "_use_attribute") || strstr(property->name, "_attribute_name")) {
+ continue;
+ }
+
+ char use_attribute_prop_name[MAX_IDPROP_NAME];
+ BLI_snprintf(use_attribute_prop_name,
+ sizeof(use_attribute_prop_name),
+ "%s%s",
+ property->name,
+ "_use_attribute");
+
+ IDPropertyTemplate idprop = {0};
+ IDProperty *use_attribute_prop = IDP_New(IDP_INT, &idprop, use_attribute_prop_name);
+ IDP_AddToGroup(nmd->settings.properties, use_attribute_prop);
+
+ char attribute_name_prop_name[MAX_IDPROP_NAME];
+ BLI_snprintf(attribute_name_prop_name,
+ sizeof(attribute_name_prop_name),
+ "%s%s",
+ property->name,
+ "_attribute_name");
+
+ IDProperty *attribute_prop = IDP_New(IDP_STRING, &idprop, attribute_name_prop_name);
+ IDP_AddToGroup(nmd->settings.properties, attribute_prop);
+ }
+}
/* NOLINTNEXTLINE: readability-function-size */
void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
@@ -1290,6 +1364,62 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
+ if (!MAIN_VERSION_ATLEAST(bmain, 300, 24)) {
+ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
+ SequencerToolSettings *sequencer_tool_settings = SEQ_tool_settings_ensure(scene);
+ sequencer_tool_settings->pivot_point = V3D_AROUND_CENTER_MEDIAN;
+
+ if (scene->ed != NULL) {
+ SEQ_for_each_callback(&scene->ed->seqbase, seq_transform_origin_set, NULL);
+ }
+ }
+ LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
+ if (sl->spacetype == SPACE_SEQ) {
+ SpaceSeq *sseq = (SpaceSeq *)sl;
+ sseq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED;
+ }
+ }
+ }
+ }
+
+ LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
+ if (sl->spacetype == SPACE_SEQ) {
+ ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase :
+ &sl->regionbase;
+ LISTBASE_FOREACH (ARegion *, region, regionbase) {
+ if (region->regiontype == RGN_TYPE_WINDOW) {
+ region->v2d.min[1] = 4.0f;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!MAIN_VERSION_ATLEAST(bmain, 300, 25)) {
+ FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
+ if (ntree->type == NTREE_SHADER) {
+ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
+ do_version_subsurface_methods(node);
+ }
+ }
+ }
+ FOREACH_NODETREE_END;
+
+ enum {
+ R_EXR_TILE_FILE = (1 << 10),
+ R_FULL_SAMPLE = (1 << 15),
+ };
+ LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
+ scene->r.scemode &= ~(R_EXR_TILE_FILE | R_FULL_SAMPLE);
+ }
+ }
+
/**
* Versioning code until next subversion bump goes here.
*
@@ -1301,5 +1431,12 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
*/
{
/* Keep this block, even when empty. */
+ LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
+ LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) {
+ if (md->type == eModifierType_Nodes) {
+ version_geometry_nodes_add_attribute_input_settings((NodesModifierData *)md);
+ }
+ }
+ }
}
}
diff --git a/source/blender/blenloader/intern/versioning_cycles.c b/source/blender/blenloader/intern/versioning_cycles.c
index 90e6b43f02e..da57f27af4e 100644
--- a/source/blender/blenloader/intern/versioning_cycles.c
+++ b/source/blender/blenloader/intern/versioning_cycles.c
@@ -182,8 +182,8 @@ static void displacement_principled_nodes(bNode *node)
}
}
else if (node->type == SH_NODE_BSDF_PRINCIPLED) {
- if (node->custom2 != SHD_SUBSURFACE_RANDOM_WALK) {
- node->custom2 = SHD_SUBSURFACE_BURLEY;
+ if (node->custom2 != SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS) {
+ node->custom2 = SHD_SUBSURFACE_DIFFUSION;
}
}
}
@@ -1373,6 +1373,11 @@ void blo_do_versions_cycles(FileData *UNUSED(fd), Library *UNUSED(lib), Main *bm
void do_versions_after_linking_cycles(Main *bmain)
{
+ const int DENOISER_AUTO = 0;
+ const int DENOISER_NLM = 1;
+ const int DENOISER_OPTIX = 2;
+ const int DENOISER_OPENIMAGEDENOISE = 4;
+
if (!MAIN_VERSION_ATLEAST(bmain, 280, 66)) {
/* Shader node tree changes. After lib linking so we have all the typeinfo
* pointers and updated sockets and we can use the high level node API to
@@ -1578,10 +1583,6 @@ void do_versions_after_linking_cycles(Main *bmain)
}
if (cscene) {
- const int DENOISER_AUTO = 0;
- const int DENOISER_NLM = 1;
- const int DENOISER_OPTIX = 2;
-
/* Enable denoiser if it was enabled for one view layer before. */
cycles_property_int_set(cscene, "denoiser", (use_optix) ? DENOISER_OPTIX : DENOISER_NLM);
cycles_property_boolean_set(cscene, "use_denoising", use_denoising);
@@ -1637,4 +1638,17 @@ void do_versions_after_linking_cycles(Main *bmain)
object->visibility_flag |= flag;
}
}
+
+ if (!MAIN_VERSION_ATLEAST(bmain, 300, 25)) {
+ /* Removal of NLM denoiser. */
+ for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
+ IDProperty *cscene = cycles_properties_from_ID(&scene->id);
+
+ if (cscene) {
+ if (cycles_property_int(cscene, "denoiser", DENOISER_NLM) == DENOISER_NLM) {
+ cycles_property_int_set(cscene, "denoiser", DENOISER_OPENIMAGEDENOISE);
+ }
+ }
+ }
+ }
}
diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c
index 074cae669af..152ef79a38f 100644
--- a/source/blender/blenloader/intern/versioning_defaults.c
+++ b/source/blender/blenloader/intern/versioning_defaults.c
@@ -54,6 +54,7 @@
#include "BKE_curveprofile.h"
#include "BKE_customdata.h"
#include "BKE_gpencil.h"
+#include "BKE_idprop.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
@@ -160,6 +161,7 @@ static void blo_update_defaults_screen(bScreen *screen,
seq->render_size = SEQ_RENDER_SIZE_PROXY_100;
seq->timeline_overlay.flag |= SEQ_TIMELINE_SHOW_STRIP_SOURCE | SEQ_TIMELINE_SHOW_STRIP_NAME |
SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID;
+ seq->preview_overlay.flag |= SEQ_PREVIEW_SHOW_OUTLINE_SELECTED;
}
else if (area->spacetype == SPACE_TEXT) {
/* Show syntax and line numbers in Script workspace text editor. */
@@ -355,6 +357,12 @@ static void blo_update_defaults_scene(Main *bmain, Scene *scene)
if (ts->custom_bevel_profile_preset == NULL) {
ts->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE);
}
+
+ /* Clear ID properties so Cycles gets defaults. */
+ IDProperty *idprop = IDP_GetProperties(&scene->id, false);
+ if (idprop) {
+ IDP_ClearProperty(idprop);
+ }
}
/**
@@ -581,6 +589,10 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
bNodeSocket *roughness_socket = nodeFindSocket(node, SOCK_IN, "Roughness");
bNodeSocketValueFloat *roughness_data = roughness_socket->default_value;
roughness_data->value = 0.4f;
+ node->custom2 = SHD_SUBSURFACE_RANDOM_WALK;
+ }
+ else if (node->type == SH_NODE_SUBSURFACE_SCATTERING) {
+ node->custom1 = SHD_SUBSURFACE_RANDOM_WALK;
}
}
}
diff --git a/source/blender/compositor/nodes/COM_IDMaskNode.cc b/source/blender/compositor/nodes/COM_IDMaskNode.cc
index b51e79f2dea..761cb8b98cf 100644
--- a/source/blender/compositor/nodes/COM_IDMaskNode.cc
+++ b/source/blender/compositor/nodes/COM_IDMaskNode.cc
@@ -28,7 +28,7 @@ IDMaskNode::IDMaskNode(bNode *editorNode) : Node(editorNode)
/* pass */
}
void IDMaskNode::convertToOperations(NodeConverter &converter,
- const CompositorContext &context) const
+ const CompositorContext & /*context*/) const
{
bNode *bnode = this->getbNode();
@@ -38,7 +38,7 @@ void IDMaskNode::convertToOperations(NodeConverter &converter,
converter.addOperation(operation);
converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0));
- if (bnode->custom2 == 0 || context.getRenderData()->scemode & R_FULL_SAMPLE) {
+ if (bnode->custom2 == 0) {
converter.mapOutputSocket(getOutputSocket(0), operation->getOutputSocket(0));
}
else {
diff --git a/source/blender/compositor/nodes/COM_ZCombineNode.cc b/source/blender/compositor/nodes/COM_ZCombineNode.cc
index ddf66740578..e29748dc317 100644
--- a/source/blender/compositor/nodes/COM_ZCombineNode.cc
+++ b/source/blender/compositor/nodes/COM_ZCombineNode.cc
@@ -31,9 +31,9 @@
namespace blender::compositor {
void ZCombineNode::convertToOperations(NodeConverter &converter,
- const CompositorContext &context) const
+ const CompositorContext & /*context*/) const
{
- if ((context.getRenderData()->scemode & R_FULL_SAMPLE) || this->getbNode()->custom2) {
+ if (this->getbNode()->custom2) {
ZCombineOperation *operation = nullptr;
if (this->getbNode()->custom1) {
operation = new ZCombineAlphaOperation();
diff --git a/source/blender/draw/DRW_engine.h b/source/blender/draw/DRW_engine.h
index a125a13eaf9..5e7b812c37b 100644
--- a/source/blender/draw/DRW_engine.h
+++ b/source/blender/draw/DRW_engine.h
@@ -176,6 +176,9 @@ void DRW_deferred_shader_remove(struct GPUMaterial *mat);
struct DrawDataList *DRW_drawdatalist_from_id(struct ID *id);
void DRW_drawdata_free(struct ID *id);
+bool DRW_opengl_context_release(void);
+void DRW_opengl_context_activate(bool drw_state);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/draw/engines/eevee/eevee_cryptomatte.c b/source/blender/draw/engines/eevee/eevee_cryptomatte.c
index 76a1b561972..49780abc6f4 100644
--- a/source/blender/draw/engines/eevee/eevee_cryptomatte.c
+++ b/source/blender/draw/engines/eevee/eevee_cryptomatte.c
@@ -139,8 +139,6 @@ void EEVEE_cryptomatte_renderpasses_init(EEVEE_Data *vedata)
g_data->cryptomatte_session = session;
g_data->render_passes |= EEVEE_RENDER_PASS_CRYPTOMATTE | EEVEE_RENDER_PASS_VOLUME_LIGHT;
- g_data->cryptomatte_accurate_mode = (view_layer->cryptomatte_flag &
- VIEW_LAYER_CRYPTOMATTE_ACCURATE) != 0;
}
}
@@ -405,7 +403,6 @@ void EEVEE_cryptomatte_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EE
{
EEVEE_FramebufferList *fbl = vedata->fbl;
EEVEE_StorageList *stl = vedata->stl;
- EEVEE_PrivateData *g_data = stl->g_data;
EEVEE_EffectsInfo *effects = stl->effects;
EEVEE_PassList *psl = vedata->psl;
const DRWContextState *draw_ctx = DRW_context_state_get();
@@ -413,10 +410,9 @@ void EEVEE_cryptomatte_output_accumulate(EEVEE_ViewLayerData *UNUSED(sldata), EE
const int cryptomatte_levels = view_layer->cryptomatte_levels;
const int current_sample = effects->taa_current_sample;
- /* In accurate mode all render samples are evaluated. In inaccurate mode this is limited to the
- * number of cryptomatte levels. This will reduce the overhead of downloading the GPU buffer and
- * integrating it into the accum buffer. */
- if (g_data->cryptomatte_accurate_mode || current_sample < cryptomatte_levels) {
+ /* Render samples used by cryptomatte are limited to the number of cryptomatte levels. This will
+ * reduce the overhead of downloading the GPU buffer and integrating it into the accum buffer. */
+ if (current_sample < cryptomatte_levels) {
static float clear_color[4] = {0.0};
GPU_framebuffer_bind(fbl->cryptomatte_fb);
GPU_framebuffer_clear_color(fbl->cryptomatte_fb, clear_color);
diff --git a/source/blender/draw/engines/eevee/eevee_effects.c b/source/blender/draw/engines/eevee/eevee_effects.c
index 3a38edecec6..d5960ea57d5 100644
--- a/source/blender/draw/engines/eevee/eevee_effects.c
+++ b/source/blender/draw/engines/eevee/eevee_effects.c
@@ -38,7 +38,8 @@ static struct {
struct GPUTexture *color_src;
int depth_src_layer;
- float cube_texel_size;
+ /* Size can be vec3. But we only use 2 components in the shader. */
+ float texel_size[2];
} e_data = {NULL}; /* Engine data */
#define SETUP_BUFFER(tex, fb, fb_color) \
@@ -259,6 +260,7 @@ void EEVEE_effects_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata)
DRW_PASS_CREATE(psl->color_downsample_ps, DRW_STATE_WRITE_COLOR);
grp = DRW_shgroup_create(EEVEE_shaders_effect_downsample_sh_get(), psl->color_downsample_ps);
DRW_shgroup_uniform_texture_ex(grp, "source", txl->filtered_radiance, GPU_SAMPLER_FILTER);
+ DRW_shgroup_uniform_vec2(grp, "texelSize", e_data.texel_size, 1);
DRW_shgroup_call_procedural_triangles(grp, NULL, 1);
}
@@ -267,7 +269,7 @@ void EEVEE_effects_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata)
grp = DRW_shgroup_create(EEVEE_shaders_effect_downsample_cube_sh_get(),
psl->color_downsample_cube_ps);
DRW_shgroup_uniform_texture_ref(grp, "source", &e_data.color_src);
- DRW_shgroup_uniform_float(grp, "texelSize", &e_data.cube_texel_size, 1);
+ DRW_shgroup_uniform_float(grp, "texelSize", e_data.texel_size, 1);
DRW_shgroup_uniform_int_copy(grp, "Layer", 0);
DRW_shgroup_call_instances(grp, NULL, quad, 6);
}
@@ -277,6 +279,7 @@ void EEVEE_effects_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata)
DRW_PASS_CREATE(psl->maxz_downlevel_ps, downsample_write);
grp = DRW_shgroup_create(EEVEE_shaders_effect_maxz_downlevel_sh_get(), psl->maxz_downlevel_ps);
DRW_shgroup_uniform_texture_ref_ex(grp, "depthBuffer", &txl->maxzbuffer, GPU_SAMPLER_DEFAULT);
+ DRW_shgroup_uniform_vec2(grp, "texelSize", e_data.texel_size, 1);
DRW_shgroup_call(grp, quad, NULL);
/* Copy depth buffer to top level of HiZ */
@@ -345,16 +348,22 @@ static void min_downsample_cb(void *vedata, int UNUSED(level))
}
#endif
-static void max_downsample_cb(void *vedata, int UNUSED(level))
+static void max_downsample_cb(void *vedata, int level)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
+ EEVEE_TextureList *txl = ((EEVEE_Data *)vedata)->txl;
+ int texture_size[3];
+ GPU_texture_get_mipmap_size(txl->maxzbuffer, level - 1, texture_size);
+ e_data.texel_size[0] = 1.0f / texture_size[0];
+ e_data.texel_size[1] = 1.0f / texture_size[1];
DRW_draw_pass(psl->maxz_downlevel_ps);
}
static void simple_downsample_cube_cb(void *vedata, int level)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
- e_data.cube_texel_size = (float)(1 << level) / (float)GPU_texture_width(e_data.color_src);
+ e_data.texel_size[0] = (float)(1 << level) / (float)GPU_texture_width(e_data.color_src);
+ e_data.texel_size[1] = e_data.texel_size[0];
DRW_draw_pass(psl->color_downsample_cube_ps);
}
@@ -390,9 +399,14 @@ void EEVEE_create_minmax_buffer(EEVEE_Data *vedata, GPUTexture *depth_src, int l
}
}
-static void downsample_radiance_cb(void *vedata, int UNUSED(level))
+static void downsample_radiance_cb(void *vedata, int level)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
+ EEVEE_TextureList *txl = ((EEVEE_Data *)vedata)->txl;
+ int texture_size[3];
+ GPU_texture_get_mipmap_size(txl->filtered_radiance, level - 1, texture_size);
+ e_data.texel_size[0] = 1.0f / texture_size[0];
+ e_data.texel_size[1] = 1.0f / texture_size[1];
DRW_draw_pass(psl->color_downsample_ps);
}
diff --git a/source/blender/draw/engines/eevee/eevee_engine.c b/source/blender/draw/engines/eevee/eevee_engine.c
index 6a66e8b1a58..f8e1cc9c923 100644
--- a/source/blender/draw/engines/eevee/eevee_engine.c
+++ b/source/blender/draw/engines/eevee/eevee_engine.c
@@ -648,6 +648,8 @@ RenderEngineType DRW_engine_viewport_eevee_type = {
NULL,
NULL,
NULL,
+ NULL,
+ NULL,
&EEVEE_render_update_passes,
&draw_engine_eevee_type,
{NULL, NULL, NULL},
diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h
index f51b4fa0127..eae5d161cc3 100644
--- a/source/blender/draw/engines/eevee/eevee_private.h
+++ b/source/blender/draw/engines/eevee/eevee_private.h
@@ -1042,7 +1042,6 @@ typedef struct EEVEE_PrivateData {
int aov_hash;
int num_aovs_used;
struct CryptomatteSession *cryptomatte_session;
- bool cryptomatte_accurate_mode;
EEVEE_CryptomatteSample *cryptomatte_accum_buffer;
float *cryptomatte_download_buffer;
diff --git a/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl b/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl
index d4e3b879426..93641443cac 100644
--- a/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl
+++ b/source/blender/draw/engines/eevee/shaders/ambient_occlusion_lib.glsl
@@ -379,7 +379,7 @@ float specular_occlusion(
/* Visibility to cone angle (eq. 18). */
float vis_angle = fast_acos(sqrt(1 - visibility));
/* Roughness to cone angle (eq. 26). */
- float spec_angle = max(0.001, fast_acos(cone_cosine(roughness)));
+ float spec_angle = max(0.00990998744964599609375, fast_acos(cone_cosine(roughness)));
/* Angle between cone axes. */
float cone_cone_dist = fast_acos(saturate(dot(visibility_dir, specular_dir)));
float cone_nor_dist = fast_acos(saturate(dot(N, specular_dir)));
diff --git a/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl
index d1cb25af82f..9fc258da185 100644
--- a/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl
+++ b/source/blender/draw/engines/eevee/shaders/effect_downsample_frag.glsl
@@ -9,14 +9,16 @@
uniform sampler2D source;
uniform float fireflyFactor;
+#ifndef COPY_SRC
+uniform vec2 texelSize;
+#endif
+
out vec4 FragColor;
void main()
{
- vec2 texel_size = 1.0 / vec2(textureSize(source, 0));
- vec2 uvs = gl_FragCoord.xy * texel_size;
-
#ifdef COPY_SRC
+ vec2 uvs = gl_FragCoord.xy / vec2(textureSize(source, 0));
FragColor = textureLod(source, uvs, 0.0);
FragColor = safe_color(FragColor);
@@ -25,7 +27,10 @@ void main()
FragColor *= 1.0 - max(0.0, luma - fireflyFactor) / luma;
#else
- vec4 ofs = texel_size.xyxy * vec4(0.75, 0.75, -0.75, -0.75);
+ /* NOTE(@fclem): textureSize() does not work the same on all implementations
+ * when changing the min and max texture levels. Use uniform instead (see T87801). */
+ vec2 uvs = gl_FragCoord.xy * texelSize;
+ vec4 ofs = texelSize.xyxy * vec4(0.75, 0.75, -0.75, -0.75);
uvs *= 2.0;
FragColor = textureLod(source, uvs + ofs.xy, 0.0);
diff --git a/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl b/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl
index ccb65d2e5a6..8ef39a55921 100644
--- a/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl
+++ b/source/blender/draw/engines/eevee/shaders/effect_minmaxz_frag.glsl
@@ -14,6 +14,10 @@ uniform int depthLayer;
uniform sampler2D depthBuffer;
#endif
+#ifndef COPY_DEPTH
+uniform vec2 texelSize;
+#endif
+
#ifdef LAYERED
# define sampleLowerMip(t) texture(depthBuffer, vec3(t, depthLayer)).r
# define gatherLowerMip(t) textureGather(depthBuffer, vec3(t, depthLayer))
@@ -41,23 +45,24 @@ out vec4 fragColor;
void main()
{
vec2 texel = gl_FragCoord.xy;
- vec2 texel_size = 1.0 / vec2(textureSize(depthBuffer, 0).xy);
#ifdef COPY_DEPTH
- vec2 uv = texel * texel_size;
+ vec2 uv = texel / vec2(textureSize(depthBuffer, 0).xy);
float val = sampleLowerMip(uv);
#else
- vec2 uv = texel * 2.0 * texel_size;
+ /* NOTE(@fclem): textureSize() does not work the same on all implementations
+ * when changing the min and max texture levels. Use uniform instead (see T87801). */
+ vec2 uv = texel * 2.0 * texelSize;
vec4 samp;
# ifdef GPU_ARB_texture_gather
samp = gatherLowerMip(uv);
# else
- samp.x = sampleLowerMip(uv + vec2(-0.5, -0.5) * texel_size);
- samp.y = sampleLowerMip(uv + vec2(-0.5, 0.5) * texel_size);
- samp.z = sampleLowerMip(uv + vec2(0.5, -0.5) * texel_size);
- samp.w = sampleLowerMip(uv + vec2(0.5, 0.5) * texel_size);
+ samp.x = sampleLowerMip(uv + vec2(-0.5, -0.5) * texelSize);
+ samp.y = sampleLowerMip(uv + vec2(-0.5, 0.5) * texelSize);
+ samp.z = sampleLowerMip(uv + vec2(0.5, -0.5) * texelSize);
+ samp.w = sampleLowerMip(uv + vec2(0.5, 0.5) * texelSize);
# endif
float val = minmax4(samp.x, samp.y, samp.z, samp.w);
diff --git a/source/blender/draw/engines/external/external_engine.c b/source/blender/draw/engines/external/external_engine.c
index 89ee3f1b293..cc548a53a8e 100644
--- a/source/blender/draw/engines/external/external_engine.c
+++ b/source/blender/draw/engines/external/external_engine.c
@@ -32,13 +32,19 @@
#include "BKE_object.h"
#include "BKE_particle.h"
+#include "ED_image.h"
#include "ED_screen.h"
+#include "GPU_batch.h"
+#include "GPU_debug.h"
#include "GPU_matrix.h"
#include "GPU_shader.h"
#include "GPU_state.h"
#include "GPU_viewport.h"
+#include "RE_engine.h"
+#include "RE_pipeline.h"
+
#include "external_engine.h" /* own include */
/* Shaders */
@@ -137,6 +143,22 @@ static void external_engine_init(void *vedata)
}
}
+/* Add shading group call which will take care of writing to the depth buffer, so that the
+ * alpha-under overlay will happen for the render buffer. */
+static void external_cache_image_add(DRWShadingGroup *grp)
+{
+ float obmat[4][4];
+ unit_m4(obmat);
+ scale_m4_fl(obmat, 0.5f);
+
+ /* NOTE: Use the same Z-depth value as in the regular image drawing engine. */
+ translate_m4(obmat, 1.0f, 1.0f, 0.75f);
+
+ GPUBatch *geom = DRW_cache_quad_get();
+
+ DRW_shgroup_call_obmat(grp, geom, obmat);
+}
+
static void external_cache_init(void *vedata)
{
EXTERNAL_PassList *psl = ((EXTERNAL_Data *)vedata)->psl;
@@ -162,14 +184,33 @@ static void external_cache_init(void *vedata)
stl->g_data->depth_shgrp = DRW_shgroup_create(e_data.depth_sh, psl->depth_pass);
}
- /* Do not draw depth pass when overlays are turned off. */
- stl->g_data->need_depth = (v3d->flag2 & V3D_HIDE_OVERLAYS) == 0;
+ if (v3d != NULL) {
+ /* Do not draw depth pass when overlays are turned off. */
+ stl->g_data->need_depth = (v3d->flag2 & V3D_HIDE_OVERLAYS) == 0;
+ }
+ else if (draw_ctx->space_data != NULL) {
+ const eSpace_Type space_type = draw_ctx->space_data->spacetype;
+ if (space_type == SPACE_IMAGE) {
+ external_cache_image_add(stl->g_data->depth_shgrp);
+
+ stl->g_data->need_depth = true;
+ stl->g_data->update_depth = true;
+ }
+ }
}
static void external_cache_populate(void *vedata, Object *ob)
{
+ const DRWContextState *draw_ctx = DRW_context_state_get();
EXTERNAL_StorageList *stl = ((EXTERNAL_Data *)vedata)->stl;
+ if (draw_ctx->space_data != NULL) {
+ const eSpace_Type space_type = draw_ctx->space_data->spacetype;
+ if (space_type == SPACE_IMAGE) {
+ return;
+ }
+ }
+
if (!(DRW_object_is_renderable(ob) &&
DRW_object_visibility_in_active_context(ob) & OB_VISIBLE_SELF)) {
return;
@@ -210,13 +251,11 @@ static void external_cache_finish(void *UNUSED(vedata))
{
}
-static void external_draw_scene_do(void *vedata)
+static void external_draw_scene_do_v3d(void *vedata)
{
const DRWContextState *draw_ctx = DRW_context_state_get();
- Scene *scene = draw_ctx->scene;
RegionView3D *rv3d = draw_ctx->rv3d;
ARegion *region = draw_ctx->region;
- const RenderEngineType *type;
DRW_state_reset_ex(DRW_STATE_DEFAULT & ~DRW_STATE_DEPTH_LESS_EQUAL);
@@ -229,8 +268,6 @@ static void external_draw_scene_do(void *vedata)
}
RenderEngine *engine = RE_engine_create(engine_type);
- engine->tile_x = scene->r.tilex;
- engine->tile_y = scene->r.tiley;
engine_type->view_update(engine, draw_ctx->evil_C, draw_ctx->depsgraph);
rv3d->render_engine = engine;
}
@@ -241,7 +278,7 @@ static void external_draw_scene_do(void *vedata)
ED_region_pixelspace(region);
/* Render result draw. */
- type = rv3d->render_engine->type;
+ const RenderEngineType *type = rv3d->render_engine->type;
type->view_draw(rv3d->render_engine, draw_ctx->evil_C, draw_ctx->depsgraph);
GPU_bgl_end();
@@ -259,6 +296,116 @@ static void external_draw_scene_do(void *vedata)
}
}
+/* Configure current matrix stack so that the external engine can use the same drawing code for
+ * both viewport and image editor drawing.
+ *
+ * The engine draws result in the pixel space, and is applying render offset. For image editor we
+ * need to switch from normalized space to pixel space, and "un-apply" offset. */
+static void external_image_space_matrix_set(const RenderEngine *engine)
+{
+ BLI_assert(engine != NULL);
+
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ const DRWView *view = DRW_view_get_active();
+ struct SpaceImage *space_image = (struct SpaceImage *)draw_ctx->space_data;
+
+ /* Apply current view as transformation matrix.
+ * This will configure drawing for normalized space with current zoom and pan applied. */
+
+ float view_matrix[4][4];
+ DRW_view_viewmat_get(view, view_matrix, false);
+
+ float projection_matrix[4][4];
+ DRW_view_winmat_get(view, projection_matrix, false);
+
+ GPU_matrix_projection_set(projection_matrix);
+ GPU_matrix_set(view_matrix);
+
+ /* Switch from normalized space to pixel space. */
+ {
+ int width, height;
+ ED_space_image_get_size(space_image, &width, &height);
+
+ const float width_inv = width ? 1.0f / width : 0.0f;
+ const float height_inv = height ? 1.0f / height : 0.0f;
+ GPU_matrix_scale_2f(width_inv, height_inv);
+ }
+
+ /* Un-apply render offset. */
+ {
+ Render *render = engine->re;
+ rctf view_rect;
+ rcti render_rect;
+ RE_GetViewPlane(render, &view_rect, &render_rect);
+
+ GPU_matrix_translate_2f(-render_rect.xmin, -render_rect.ymin);
+ }
+}
+
+static void external_draw_scene_do_image(void *UNUSED(vedata))
+{
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+ Render *re = RE_GetSceneRender(scene);
+ RenderEngine *engine = RE_engine_get(re);
+
+ /* Is tested before enabling the drawing engine. */
+ BLI_assert(re != NULL);
+ BLI_assert(engine != NULL);
+
+ const DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
+
+ /* Clear the depth buffer to the value used by the background overlay so that the overlay is not
+ * happening outside of the drawn image.
+ *
+ * NOTE: The external engine only draws color. The depth is taken care of using the depth pass
+ * which initialized the depth to the values expected by the background overlay. */
+ GPU_framebuffer_clear_depth(dfbl->default_fb, 1.0f);
+
+ GPU_matrix_push_projection();
+ GPU_matrix_push();
+
+ external_image_space_matrix_set(engine);
+
+ GPU_debug_group_begin("External Engine");
+
+ const RenderEngineType *engine_type = engine->type;
+ BLI_assert(engine_type != NULL);
+ BLI_assert(engine_type->draw != NULL);
+
+ engine_type->draw(engine, draw_ctx->evil_C, draw_ctx->depsgraph);
+
+ GPU_debug_group_end();
+
+ GPU_matrix_pop();
+ GPU_matrix_pop_projection();
+
+ DRW_state_reset();
+ GPU_bgl_end();
+
+ RE_engine_draw_release(re);
+}
+
+static void external_draw_scene_do(void *vedata)
+{
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+
+ if (draw_ctx->v3d != NULL) {
+ external_draw_scene_do_v3d(vedata);
+ return;
+ }
+
+ if (draw_ctx->space_data == NULL) {
+ return;
+ }
+
+ const eSpace_Type space_type = draw_ctx->space_data->spacetype;
+ if (space_type == SPACE_IMAGE) {
+ external_draw_scene_do_image(vedata);
+ return;
+ }
+}
+
static void external_draw_scene(void *vedata)
{
const DRWContextState *draw_ctx = DRW_context_state_get();
@@ -297,7 +444,7 @@ static void external_engine_free(void)
static const DrawEngineDataSize external_data_size = DRW_VIEWPORT_DATA_SIZE(EXTERNAL_Data);
-static DrawEngineType draw_engine_external_type = {
+DrawEngineType draw_engine_external_type = {
NULL,
NULL,
N_("External"),
@@ -330,8 +477,45 @@ RenderEngineType DRW_engine_viewport_external_type = {
NULL,
NULL,
NULL,
+ NULL,
+ NULL,
&draw_engine_external_type,
{NULL, NULL, NULL},
};
+bool DRW_engine_external_acquire_for_image_editor(void)
+{
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ const SpaceLink *space_data = draw_ctx->space_data;
+ Scene *scene = draw_ctx->scene;
+
+ if (space_data == NULL) {
+ return false;
+ }
+
+ const eSpace_Type space_type = draw_ctx->space_data->spacetype;
+ if (space_type != SPACE_IMAGE) {
+ return false;
+ }
+
+ struct SpaceImage *space_image = (struct SpaceImage *)space_data;
+ const Image *image = ED_space_image(space_image);
+ if (image == NULL || image->type != IMA_TYPE_R_RESULT) {
+ return false;
+ }
+
+ if (image->render_slot != image->last_render_slot) {
+ return false;
+ }
+
+ /* Render is allocated on main thread, so it is safe to access it from here. */
+ Render *re = RE_GetSceneRender(scene);
+
+ if (re == NULL) {
+ return false;
+ }
+
+ return RE_engine_draw_acquire(re);
+}
+
#undef EXTERNAL_ENGINE
diff --git a/source/blender/draw/engines/external/external_engine.h b/source/blender/draw/engines/external/external_engine.h
index c645fb99e0e..14ec4e2d3c5 100644
--- a/source/blender/draw/engines/external/external_engine.h
+++ b/source/blender/draw/engines/external/external_engine.h
@@ -22,4 +22,12 @@
#pragma once
+extern DrawEngineType draw_engine_external_type;
extern RenderEngineType DRW_engine_viewport_external_type;
+
+/* Check whether an external engine is to be used to draw content of an image editor.
+ * If the drawing is possible, the render engine is "acquired" so that it is not freed by the
+ * render engine for until drawing is finished.
+ *
+ * NOTE: Released by the draw engine when it is done drawing. */
+bool DRW_engine_external_acquire_for_image_editor(void);
diff --git a/source/blender/draw/engines/select/select_engine.c b/source/blender/draw/engines/select/select_engine.c
index 96ab8a28e09..20edd78597b 100644
--- a/source/blender/draw/engines/select/select_engine.c
+++ b/source/blender/draw/engines/select/select_engine.c
@@ -388,6 +388,8 @@ RenderEngineType DRW_engine_viewport_select_type = {
NULL,
NULL,
NULL,
+ NULL,
+ NULL,
&draw_engine_select_type,
{NULL, NULL, NULL},
};
diff --git a/source/blender/draw/engines/workbench/workbench_engine.c b/source/blender/draw/engines/workbench/workbench_engine.c
index f09c019ef8d..635aa7cef25 100644
--- a/source/blender/draw/engines/workbench/workbench_engine.c
+++ b/source/blender/draw/engines/workbench/workbench_engine.c
@@ -651,6 +651,8 @@ RenderEngineType DRW_engine_viewport_workbench_type = {
NULL,
NULL,
NULL,
+ NULL,
+ NULL,
&workbench_render_update_passes,
&draw_engine_workbench,
{NULL, NULL, NULL},
diff --git a/source/blender/draw/intern/DRW_render.h b/source/blender/draw/intern/DRW_render.h
index 660a4adaf51..fb8b8536897 100644
--- a/source/blender/draw/intern/DRW_render.h
+++ b/source/blender/draw/intern/DRW_render.h
@@ -623,6 +623,7 @@ const DRWView *DRW_view_default_get(void);
void DRW_view_default_set(DRWView *view);
void DRW_view_reset(void);
void DRW_view_set_active(DRWView *view);
+const DRWView *DRW_view_get_active(void);
void DRW_view_clip_planes_set(DRWView *view, float (*planes)[4], int plane_len);
void DRW_view_camtexco_set(DRWView *view, float texco[4]);
diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c
index 47adc0acc60..e65fdce5f2e 100644
--- a/source/blender/draw/intern/draw_manager.c
+++ b/source/blender/draw/intern/draw_manager.c
@@ -1197,6 +1197,18 @@ static void drw_engines_enable_basic(void)
use_drw_engine(&draw_engine_basic_type);
}
+static void drw_engine_enable_image_editor(void)
+{
+ if (DRW_engine_external_acquire_for_image_editor()) {
+ use_drw_engine(&draw_engine_external_type);
+ }
+ else {
+ use_drw_engine(&draw_engine_image_type);
+ }
+
+ use_drw_engine(&draw_engine_overlay_type);
+}
+
static void drw_engines_enable_editors(void)
{
SpaceLink *space_data = DST.draw_ctx.space_data;
@@ -1205,8 +1217,7 @@ static void drw_engines_enable_editors(void)
}
if (space_data->spacetype == SPACE_IMAGE) {
- use_drw_engine(&draw_engine_image_type);
- use_drw_engine(&draw_engine_overlay_type);
+ drw_engine_enable_image_editor();
}
else if (space_data->spacetype == SPACE_NODE) {
/* Only enable when drawing the space image backdrop. */
@@ -3188,3 +3199,66 @@ void DRW_draw_state_init_gtests(eGPUShaderConfig sh_cfg)
#endif
/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Draw manager context release/activation
+ *
+ * These functions are used in cases when an OpenGL context creation is needed during the draw.
+ * This happens, for example, when an external engine needs to create its own OpenGL context from
+ * the engine initialization.
+ *
+ * Example of context creation:
+ *
+ * const bool drw_state = DRW_opengl_context_release();
+ * gl_context = WM_opengl_context_create();
+ * DRW_opengl_context_activate(drw_state);
+ *
+ * Example of context destruction:
+ *
+ * const bool drw_state = DRW_opengl_context_release();
+ * WM_opengl_context_activate(gl_context);
+ * WM_opengl_context_dispose(gl_context);
+ * DRW_opengl_context_activate(drw_state);
+ *
+ *
+ * NOTE: Will only perform context modification when on main thread. This way these functions can
+ * be used in an engine without check on whether it is a draw manager which manages OpenGL context
+ * on the current thread. The downside of this is that if the engine performs OpenGL creation from
+ * a non-main thread, that thread is supposed to not have OpenGL context ever bound by Blender.
+ *
+ * \{ */
+
+bool DRW_opengl_context_release(void)
+{
+ if (!BLI_thread_is_main()) {
+ return false;
+ }
+
+ if (GPU_context_active_get() != DST.gpu_context) {
+ /* Context release is requested from the outside of the draw manager main draw loop, indicate
+ * this to the `DRW_opengl_context_activate()` so that it restores drawable of the window. */
+ return false;
+ }
+
+ GPU_context_active_set(NULL);
+ WM_opengl_context_release(DST.gl_context);
+
+ return true;
+}
+
+void DRW_opengl_context_activate(bool drw_state)
+{
+ if (!BLI_thread_is_main()) {
+ return;
+ }
+
+ if (drw_state) {
+ WM_opengl_context_activate(DST.gl_context);
+ GPU_context_active_set(DST.gpu_context);
+ }
+ else {
+ wm_window_reset_drawable();
+ }
+}
+
+/** \} */
diff --git a/source/blender/draw/intern/draw_manager_exec.c b/source/blender/draw/intern/draw_manager_exec.c
index 22356a3c57b..aa01ca7a262 100644
--- a/source/blender/draw/intern/draw_manager_exec.c
+++ b/source/blender/draw/intern/draw_manager_exec.c
@@ -367,6 +367,11 @@ void DRW_view_set_active(DRWView *view)
DST.view_active = (view) ? view : DST.view_default;
}
+const DRWView *DRW_view_get_active(void)
+{
+ return DST.view_active;
+}
+
/* Return True if the given BoundSphere intersect the current view frustum */
static bool draw_culling_sphere_test(const BoundSphere *frustum_bsphere,
const float (*frustum_planes)[4],
diff --git a/source/blender/editors/animation/anim_deps.c b/source/blender/editors/animation/anim_deps.c
index 97679723d84..088de80bb65 100644
--- a/source/blender/editors/animation/anim_deps.c
+++ b/source/blender/editors/animation/anim_deps.c
@@ -217,8 +217,6 @@ static void animchan_sync_fcurve_scene(bAnimListElem *ale)
/* Check if this strip is selected. */
Editing *ed = SEQ_editing_get(scene);
seq = SEQ_get_sequence_by_name(ed->seqbasep, seq_name, false);
- MEM_freeN(seq_name);
-
if (seq == NULL) {
return;
}
diff --git a/source/blender/editors/animation/anim_ops.c b/source/blender/editors/animation/anim_ops.c
index 450d7cd100e..b4ea33920b2 100644
--- a/source/blender/editors/animation/anim_ops.c
+++ b/source/blender/editors/animation/anim_ops.c
@@ -241,6 +241,11 @@ static bool use_sequencer_snapping(bContext *C)
/* Modal Operator init */
static int change_frame_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
+ ARegion *region = CTX_wm_region(C);
+ if (CTX_wm_space_seq(C) != NULL && region->regiontype == RGN_TYPE_PREVIEW) {
+ return OPERATOR_CANCELLED;
+ }
+
/* Change to frame that mouse is over before adding modal handler,
* as user could click on a single frame (jump to frame) as well as
* click-dragging over a range (modal scrubbing).
diff --git a/source/blender/editors/armature/armature_intern.h b/source/blender/editors/armature/armature_intern.h
index f9950d27e97..696355324e6 100644
--- a/source/blender/editors/armature/armature_intern.h
+++ b/source/blender/editors/armature/armature_intern.h
@@ -216,6 +216,7 @@ void POSE_OT_relax(struct wmOperatorType *ot);
void POSE_OT_push_rest(struct wmOperatorType *ot);
void POSE_OT_relax_rest(struct wmOperatorType *ot);
void POSE_OT_breakdown(struct wmOperatorType *ot);
+void POSE_OT_blend_to_neighbours(struct wmOperatorType *ot);
void POSE_OT_propagate(struct wmOperatorType *ot);
diff --git a/source/blender/editors/armature/armature_ops.c b/source/blender/editors/armature/armature_ops.c
index fbd89106de5..a1070a8823a 100644
--- a/source/blender/editors/armature/armature_ops.c
+++ b/source/blender/editors/armature/armature_ops.c
@@ -150,6 +150,7 @@ void ED_operatortypes_armature(void)
WM_operatortype_append(POSE_OT_push_rest);
WM_operatortype_append(POSE_OT_relax_rest);
WM_operatortype_append(POSE_OT_breakdown);
+ WM_operatortype_append(POSE_OT_blend_to_neighbours);
}
void ED_operatormacros_armature(void)
diff --git a/source/blender/editors/armature/pose_slide.c b/source/blender/editors/armature/pose_slide.c
index f23376867af..b273d3aac76 100644
--- a/source/blender/editors/armature/pose_slide.c
+++ b/source/blender/editors/armature/pose_slide.c
@@ -117,6 +117,7 @@ typedef enum ePoseSlide_Modes {
POSESLIDE_BREAKDOWN,
POSESLIDE_PUSH_REST,
POSESLIDE_RELAX_REST,
+ POSESLIDE_BLEND,
} ePoseSlide_Modes;
/** Transforms/Channels to Affect. */
@@ -423,6 +424,25 @@ static void pose_slide_apply_val(tPoseSlideOp *pso, FCurve *fcu, Object *ob, flo
(*val) = ((sVal * w2) + (eVal * w1));
break;
}
+ case POSESLIDE_BLEND: /* Blend the current pose with the previous (<50%) or next key (>50%). */
+ {
+ /* FCurve value on current frame. */
+ const float cVal = evaluate_fcurve(fcu, cframe);
+ const float factor = ED_slider_factor_get(pso->slider);
+ /* Convert factor to absolute 0-1 range. */
+ const float blend_factor = fabs((factor - 0.5f) * 2);
+
+ if (factor < 0.5) {
+ /* Blend to previous key. */
+ (*val) = (cVal * (1 - blend_factor)) + (sVal * blend_factor);
+ }
+ else {
+ /* Blend to next key. */
+ (*val) = (cVal * (1 - blend_factor)) + (eVal * blend_factor);
+ }
+
+ break;
+ }
/* Those are handled in pose_slide_rest_pose_apply. */
case POSESLIDE_PUSH_REST:
case POSESLIDE_RELAX_REST: {
@@ -614,8 +634,7 @@ static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl)
interp_qt_qtqt(quat_final, quat_prev, quat_next, ED_slider_factor_get(pso->slider));
}
- else {
- /* POSESLIDE_PUSH and POSESLIDE_RELAX. */
+ else if (pso->mode == POSESLIDE_PUSH || pso->mode == POSESLIDE_RELAX) {
float quat_breakdown[4];
float quat_curr[4];
@@ -638,6 +657,32 @@ static void pose_slide_apply_quat(tPoseSlideOp *pso, tPChanFCurveLink *pfl)
interp_qt_qtqt(quat_final, quat_curr, quat_breakdown, ED_slider_factor_get(pso->slider));
}
}
+ else if (pso->mode == POSESLIDE_BLEND) {
+ float quat_blend[4];
+ float quat_curr[4];
+
+ copy_qt_qt(quat_curr, pchan->quat);
+
+ if (ED_slider_factor_get(pso->slider) < 0.5) {
+ quat_blend[0] = evaluate_fcurve(fcu_w, prevFrameF);
+ quat_blend[1] = evaluate_fcurve(fcu_x, prevFrameF);
+ quat_blend[2] = evaluate_fcurve(fcu_y, prevFrameF);
+ quat_blend[3] = evaluate_fcurve(fcu_z, prevFrameF);
+ }
+ else {
+ quat_blend[0] = evaluate_fcurve(fcu_w, nextFrameF);
+ quat_blend[1] = evaluate_fcurve(fcu_x, nextFrameF);
+ quat_blend[2] = evaluate_fcurve(fcu_y, nextFrameF);
+ quat_blend[3] = evaluate_fcurve(fcu_z, nextFrameF);
+ }
+
+ normalize_qt(quat_blend);
+ normalize_qt(quat_curr);
+
+ const float blend_factor = fabs((ED_slider_factor_get(pso->slider) - 0.5f) * 2);
+
+ interp_qt_qtqt(quat_final, quat_curr, quat_blend, blend_factor);
+ }
/* Apply final to the pose bone, keeping compatible for similar keyframe positions. */
quat_to_compatible_quat(pchan->quat, quat_final, pchan->quat);
@@ -868,6 +913,9 @@ static void pose_slide_draw_status(bContext *C, tPoseSlideOp *pso)
case POSESLIDE_BREAKDOWN:
strcpy(mode_str, TIP_("Breakdown"));
break;
+ case POSESLIDE_BLEND:
+ strcpy(mode_str, TIP_("Blend To Neighbour"));
+ break;
default:
/* Unknown. */
@@ -1660,6 +1708,56 @@ void POSE_OT_breakdown(wmOperatorType *ot)
pose_slide_opdef_properties(ot);
}
+/* ........................ */
+static int pose_slide_blend_to_neighbours_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ /* Initialize data. */
+ if (pose_slide_init(C, op, POSESLIDE_BLEND) == 0) {
+ pose_slide_exit(C, op);
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Do common setup work. */
+ return pose_slide_invoke_common(C, op, event);
+}
+
+static int pose_slide_blend_to_neighbours_exec(bContext *C, wmOperator *op)
+{
+ tPoseSlideOp *pso;
+
+ /* Initialize data (from RNA-props). */
+ if (pose_slide_init(C, op, POSESLIDE_BLEND) == 0) {
+ pose_slide_exit(C, op);
+ return OPERATOR_CANCELLED;
+ }
+
+ pso = op->customdata;
+
+ /* Do common exec work. */
+ return pose_slide_exec_common(C, op, pso);
+}
+
+void POSE_OT_blend_to_neighbours(wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Blend To Neighbour";
+ ot->idname = "POSE_OT_blend_to_neighbour";
+ ot->description = "Blend from current position to previous or next keyframe";
+
+ /* Callbacks. */
+ ot->exec = pose_slide_blend_to_neighbours_exec;
+ ot->invoke = pose_slide_blend_to_neighbours_invoke;
+ ot->modal = pose_slide_modal;
+ ot->cancel = pose_slide_cancel;
+ ot->poll = ED_operator_posemode;
+
+ /* Flags. */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_X;
+
+ /* Properties. */
+ pose_slide_opdef_properties(ot);
+}
+
/* **************************************************** */
/* B) Pose Propagate */
diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c
index fdd9f44605e..d7cab85abad 100644
--- a/source/blender/editors/gpencil/gpencil_interpolate.c
+++ b/source/blender/editors/gpencil/gpencil_interpolate.c
@@ -316,6 +316,9 @@ static void gpencil_stroke_pair_table(bContext *C,
if (ELEM(NULL, gps_from, gps_to)) {
continue;
}
+ if ((gps_from->totpoints == 0) || (gps_to->totpoints == 0)) {
+ continue;
+ }
/* Insert the pair entry in the hash table and the list of strokes to keep order. */
BLI_addtail(&tgpil->selected_strokes, BLI_genericNodeN(gps_from));
BLI_ghash_insert(tgpil->pair_strokes, gps_from, gps_to);
@@ -1333,6 +1336,9 @@ static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op)
if (ELEM(NULL, gps_from, gps_to)) {
continue;
}
+ if ((gps_from->totpoints == 0) || (gps_to->totpoints == 0)) {
+ continue;
+ }
/* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
if (gps_from->totpoints > gps_to->totpoints) {
diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c
index 2e7b0ce532c..58a9f362488 100644
--- a/source/blender/editors/interface/interface_eyedropper.c
+++ b/source/blender/editors/interface/interface_eyedropper.c
@@ -24,6 +24,8 @@
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
+#include "BLI_math_color.h"
+
#include "BKE_context.h"
#include "BKE_screen.h"
@@ -107,8 +109,13 @@ static void eyedropper_draw_cursor_text_ex(const int x, const int y, const char
{
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
- const float col_fg[4] = {1.0f, 1.0f, 1.0f, 1.0f};
- const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f};
+ /* Use the theme settings from tooltips. */
+ const bTheme *btheme = UI_GetTheme();
+ const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip;
+
+ float col_fg[4], col_bg[4];
+ rgba_uchar_to_float(col_fg, wcol->text);
+ rgba_uchar_to_float(col_bg, wcol->inner);
UI_fontstyle_draw_simple_backdrop(fstyle, x, y + U.widget_unit, name, col_fg, col_bg);
}
diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c
index 804156ba48c..6b1ff92a855 100644
--- a/source/blender/editors/interface/interface_style.c
+++ b/source/blender/editors/interface/interface_style.c
@@ -312,11 +312,8 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs,
const float decent = BLF_descender(fs->uifont_id);
const float margin = height / 4.0f;
- /* backdrop */
- const float color[4] = {col_bg[0], col_bg[1], col_bg[2], 0.5f};
-
UI_draw_roundbox_corner_set(UI_CNR_ALL);
- UI_draw_roundbox_aa(
+ UI_draw_roundbox_4fv(
&(const rctf){
.xmin = x - margin,
.xmax = x + width + margin,
@@ -325,7 +322,7 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs,
},
true,
margin,
- color);
+ col_bg);
}
BLF_position(fs->uifont_id, x, y, 0.0f);
diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c
index 672f1b64943..3a5d65475f7 100644
--- a/source/blender/editors/interface/interface_template_search_menu.c
+++ b/source/blender/editors/interface/interface_template_search_menu.c
@@ -350,24 +350,28 @@ static void menu_types_add_from_keymap_items(bContext *C,
if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) {
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
- wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
- if (keymap && WM_keymap_poll(C, keymap)) {
- LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
- if (kmi->flag & KMI_INACTIVE) {
- continue;
- }
- if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
- char menu_idname[MAX_NAME];
- RNA_string_get(kmi->ptr, "name", menu_idname);
- MenuType *mt = WM_menutype_find(menu_idname, false);
-
- if (mt && BLI_gset_add(menu_tagged, mt)) {
- /* Unlikely, but possible this will be included twice. */
- BLI_linklist_prepend(menuid_stack_p, mt);
-
- void **kmi_p;
- if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
- *kmi_p = kmi;
+ wmEventHandler_KeymapResult km_result;
+ WM_event_get_keymaps_from_handler(wm, handler, &km_result);
+ for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
+ wmKeyMap *keymap = km_result.keymaps[km_index];
+ if (keymap && WM_keymap_poll(C, keymap)) {
+ LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
+ if (kmi->flag & KMI_INACTIVE) {
+ continue;
+ }
+ if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
+ char menu_idname[MAX_NAME];
+ RNA_string_get(kmi->ptr, "name", menu_idname);
+ MenuType *mt = WM_menutype_find(menu_idname, false);
+
+ if (mt && BLI_gset_add(menu_tagged, mt)) {
+ /* Unlikely, but possible this will be included twice. */
+ BLI_linklist_prepend(menuid_stack_p, mt);
+
+ void **kmi_p;
+ if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
+ *kmi_p = kmi;
+ }
}
}
}
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 0c9eb20af19..320371ad9ea 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -5823,6 +5823,11 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C)
icon = ICON_SEQUENCE;
break;
}
+ if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL)) {
+ handle_event = B_STOPSEQ;
+ icon = ICON_SEQUENCE;
+ break;
+ }
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_BUILD_PROXY)) {
handle_event = B_STOPCLIP;
icon = ICON_TRACKER;
diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c
index 1fd1b6c984d..4ef4c3dbc6d 100644
--- a/source/blender/editors/interface/view2d_ops.c
+++ b/source/blender/editors/interface/view2d_ops.c
@@ -147,6 +147,8 @@ static void view_pan_init(bContext *C, wmOperator *op)
const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1);
vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx;
vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy;
+
+ vpd->v2d->flag |= V2D_IS_NAVIGATING;
}
/* apply transform to view (i.e. adjust 'cur' rect) */
@@ -190,6 +192,8 @@ static void view_pan_apply(bContext *C, wmOperator *op)
/* Cleanup temp custom-data. */
static void view_pan_exit(wmOperator *op)
{
+ v2dViewPanData *vpd = op->customdata;
+ vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@@ -358,6 +362,7 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event
View2DEdgePanData *vpd = op->customdata;
if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) {
+ vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
}
@@ -371,6 +376,8 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event
static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op)
{
+ v2dViewPanData *vpd = op->customdata;
+ vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@@ -680,6 +687,8 @@ static void view_zoomdrag_init(bContext *C, wmOperator *op)
vzd->v2d = &vzd->region->v2d;
/* False by default. Interactive callbacks (ie invoke()) can set it to true. */
vzd->zoom_to_mouse_pos = false;
+
+ vzd->v2d->flag |= V2D_IS_NAVIGATING;
}
/* apply transform to view (i.e. adjust 'cur' rect) */
@@ -809,7 +818,8 @@ static void view_zoomstep_apply(bContext *C, wmOperator *op)
static void view_zoomstep_exit(wmOperator *op)
{
UI_view2d_zoom_cache_reset();
-
+ v2dViewZoomData *vzd = op->customdata;
+ vzd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@@ -1041,6 +1051,7 @@ static void view_zoomdrag_exit(bContext *C, wmOperator *op)
if (op->customdata) {
v2dViewZoomData *vzd = op->customdata;
+ vzd->v2d->flag &= ~V2D_IS_NAVIGATING;
if (vzd->timer) {
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
@@ -1911,6 +1922,8 @@ static void scroller_activate_init(bContext *C,
vsm->scrollbar_orig = ((scrollers.vert_max + scrollers.vert_min) / 2) + region->winrct.ymin;
}
+ vsm->v2d->flag |= V2D_IS_NAVIGATING;
+
ED_region_tag_redraw_no_rebuild(region);
}
@@ -1921,6 +1934,7 @@ static void scroller_activate_exit(bContext *C, wmOperator *op)
v2dScrollerMove *vsm = op->customdata;
vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE);
+ vsm->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_freeN(op->customdata);
op->customdata = NULL;
diff --git a/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c b/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c
index ae37d6c8deb..5faafa77bba 100644
--- a/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c
+++ b/source/blender/editors/mesh/editmesh_extrude_spin_gizmo.c
@@ -461,7 +461,7 @@ void MESH_GGT_spin(struct wmGizmoGroupType *gzgt)
gzgt->name = "Mesh Spin Init";
gzgt->idname = "MESH_GGT_spin";
- gzgt->flag = WM_GIZMOGROUPTYPE_3D;
+ gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
@@ -1063,7 +1063,7 @@ void MESH_GGT_spin_redo(struct wmGizmoGroupType *gzgt)
gzgt->name = "Mesh Spin Redo";
gzgt->idname = "MESH_GGT_spin_redo";
- gzgt->flag = WM_GIZMOGROUPTYPE_3D;
+ gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c
index 0a2df655395..26f5b21a311 100644
--- a/source/blender/editors/object/object_bake_api.c
+++ b/source/blender/editors/object/object_bake_api.c
@@ -412,6 +412,7 @@ static bool is_noncolor_pass(eScenePassType pass_type)
{
return ELEM(pass_type,
SCE_PASS_Z,
+ SCE_PASS_POSITION,
SCE_PASS_NORMAL,
SCE_PASS_VECTOR,
SCE_PASS_INDEXOB,
@@ -554,19 +555,10 @@ static bool bake_pass_filter_check(eScenePassType pass_type,
return true;
}
- if ((pass_filter & R_BAKE_PASS_FILTER_AO) != 0) {
- BKE_report(
- reports,
- RPT_ERROR,
- "Combined bake pass Ambient Occlusion contribution requires an enabled light pass "
- "(bake the Ambient Occlusion pass type instead)");
- }
- else {
- BKE_report(reports,
- RPT_ERROR,
- "Combined bake pass requires Emit, or a light pass with "
- "Direct or Indirect contributions enabled");
- }
+ BKE_report(reports,
+ RPT_ERROR,
+ "Combined bake pass requires Emit, or a light pass with "
+ "Direct or Indirect contributions enabled");
return false;
}
diff --git a/source/blender/editors/render/render_preview.c b/source/blender/editors/render/render_preview.c
index 95351de45f0..81aecfdf788 100644
--- a/source/blender/editors/render/render_preview.c
+++ b/source/blender/editors/render/render_preview.c
@@ -479,15 +479,6 @@ static Scene *preview_prepare_scene(
BKE_color_managed_view_settings_free(&sce->view_settings);
BKE_color_managed_view_settings_copy(&sce->view_settings, &scene->view_settings);
- /* prevent overhead for small renders and icons (32) */
- if (id && sp->sizex < 40) {
- sce->r.tilex = sce->r.tiley = 64;
- }
- else {
- sce->r.tilex = sce->r.xsch / 4;
- sce->r.tiley = sce->r.ysch / 4;
- }
-
if ((id && sp->pr_method == PR_ICON_RENDER) && id_type != ID_WO) {
sce->r.alphamode = R_ALPHAPREMUL;
}
diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c
index 9546035375c..c71e68df2fd 100644
--- a/source/blender/editors/screen/area.c
+++ b/source/blender/editors/screen/area.c
@@ -1735,10 +1735,14 @@ static void ed_default_handlers(
WM_event_add_keymap_handler(handlers, keymap);
}
if (flag & ED_KEYMAP_TOOL) {
- WM_event_add_keymap_handler_dynamic(
- &region->handlers, WM_event_get_keymap_from_toolsystem_fallback, area);
- WM_event_add_keymap_handler_dynamic(
- &region->handlers, WM_event_get_keymap_from_toolsystem, area);
+ if (flag & ED_KEYMAP_GIZMO) {
+ WM_event_add_keymap_handler_dynamic(
+ &region->handlers, WM_event_get_keymap_from_toolsystem_fallback, area);
+ }
+ else {
+ WM_event_add_keymap_handler_dynamic(
+ &region->handlers, WM_event_get_keymap_from_toolsystem, area);
+ }
}
if (flag & ED_KEYMAP_FRAMES) {
/* frame changing/jumping (for all spaces) */
diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c
index 11b06d2b414..f7bdb4326a5 100644
--- a/source/blender/editors/space_file/filesel.c
+++ b/source/blender/editors/space_file/filesel.c
@@ -1271,7 +1271,7 @@ void file_params_rename_end(wmWindowManager *wm,
/* Ensure smooth-scroll timer is active, even if not needed, because that way rename state is
* handled properly. */
file_params_invoke_rename_postscroll(wm, win, sfile);
- /* Also always activate the rename file, even if renaming was cancelled. */
+ /* Also always activate the rename file, even if renaming was canceled. */
file_params_renamefile_activate(sfile, params);
}
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index aa241071425..10a3285be8b 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -1446,6 +1446,8 @@ static int node_error_type_to_icon(const geo_log::NodeWarningType type)
return ICON_ERROR;
case geo_log::NodeWarningType::Info:
return ICON_INFO;
+ case geo_log::NodeWarningType::Legacy:
+ return ICON_ERROR;
}
BLI_assert(false);
@@ -1456,6 +1458,8 @@ static uint8_t node_error_type_priority(const geo_log::NodeWarningType type)
{
switch (type) {
case geo_log::NodeWarningType::Error:
+ return 4;
+ case geo_log::NodeWarningType::Legacy:
return 3;
case geo_log::NodeWarningType::Warning:
return 2;
diff --git a/source/blender/editors/space_node/node_intern.h b/source/blender/editors/space_node/node_intern.h
index d35fd729131..f069038cc09 100644
--- a/source/blender/editors/space_node/node_intern.h
+++ b/source/blender/editors/space_node/node_intern.h
@@ -175,6 +175,7 @@ int space_node_view_flag(struct bContext *C,
void NODE_OT_view_all(struct wmOperatorType *ot);
void NODE_OT_view_selected(struct wmOperatorType *ot);
+void NODE_OT_geometry_node_view_legacy(struct wmOperatorType *ot);
void NODE_OT_backimage_move(struct wmOperatorType *ot);
void NODE_OT_backimage_zoom(struct wmOperatorType *ot);
diff --git a/source/blender/editors/space_node/node_ops.c b/source/blender/editors/space_node/node_ops.c
index 610c2889e7a..df4f63af20b 100644
--- a/source/blender/editors/space_node/node_ops.c
+++ b/source/blender/editors/space_node/node_ops.c
@@ -51,6 +51,7 @@ void node_operatortypes(void)
WM_operatortype_append(NODE_OT_view_all);
WM_operatortype_append(NODE_OT_view_selected);
+ WM_operatortype_append(NODE_OT_geometry_node_view_legacy);
WM_operatortype_append(NODE_OT_mute_toggle);
WM_operatortype_append(NODE_OT_hide_toggle);
diff --git a/source/blender/editors/space_node/node_view.cc b/source/blender/editors/space_node/node_view.cc
index f0db0539c4f..762b4b36a39 100644
--- a/source/blender/editors/space_node/node_view.cc
+++ b/source/blender/editors/space_node/node_view.cc
@@ -23,8 +23,10 @@
#include "DNA_node_types.h"
+#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_rect.h"
+#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "BKE_context.h"
@@ -54,6 +56,8 @@
#include "node_intern.h" /* own include */
+using blender::StringRef;
+
/* -------------------------------------------------------------------- */
/** \name View All Operator
* \{ */
@@ -700,3 +704,89 @@ void NODE_OT_backimage_sample(wmOperatorType *ot)
}
/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name View Geometry Nodes Legacy Operator
+ *
+ * This operator should be removed when the 2.93 legacy nodes are removed.
+ * \{ */
+
+static int space_node_view_geometry_nodes_legacy(bContext *C, SpaceNode *snode, wmOperator *op)
+{
+ ARegion *region = CTX_wm_region(C);
+
+ /* Only use the node editor's active node tree. Otherwise this will be too complicated. */
+ bNodeTree *node_tree = snode->nodetree;
+ if (node_tree == nullptr || node_tree->type != NTREE_GEOMETRY) {
+ return OPERATOR_CANCELLED;
+ }
+
+ bool found_legacy_node = false;
+ LISTBASE_FOREACH_BACKWARD (bNode *, node, &node_tree->nodes) {
+ StringRef idname{node->idname};
+ if (idname.find("Legacy") == StringRef::not_found) {
+ node->flag &= ~NODE_SELECT;
+ }
+ else {
+ found_legacy_node = true;
+ node->flag |= NODE_SELECT;
+ }
+ }
+
+ if (!found_legacy_node) {
+ WM_report(RPT_INFO, "Legacy node not found, may be in nested node group");
+ }
+
+ const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
+ if (space_node_view_flag(C, snode, region, NODE_SELECT, smooth_viewtx)) {
+ return OPERATOR_FINISHED;
+ }
+ return OPERATOR_CANCELLED;
+}
+
+static int geometry_node_view_legacy_exec(bContext *C, wmOperator *op)
+{
+ /* Allow running this operator directly in a specific node editor. */
+ if (SpaceNode *snode = CTX_wm_space_node(C)) {
+ return space_node_view_geometry_nodes_legacy(C, snode, op);
+ }
+
+ /* Since the operator is meant to be called from a button in the modifier panel, the node tree
+ * must be found from the screen, using the largest node editor if there is more than one. */
+ if (ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_NODE, 0)) {
+ if (SpaceNode *snode = static_cast<SpaceNode *>(area->spacedata.first)) {
+ ScrArea *old_area = CTX_wm_area(C);
+ ARegion *old_region = CTX_wm_region(C);
+
+ /* Override the context since it is used by the View2D panning code. */
+ CTX_wm_area_set(C, area);
+ CTX_wm_region_set(C, static_cast<ARegion *>(area->regionbase.last));
+ const int result = space_node_view_geometry_nodes_legacy(C, snode, op);
+ CTX_wm_area_set(C, old_area);
+ CTX_wm_region_set(C, old_region);
+ return result;
+ }
+ }
+
+ return OPERATOR_CANCELLED;
+}
+
+static bool geometry_node_view_legacy_poll(bContext *C)
+{
+ /* Allow direct execution in a node editor, but also affecting any visible node editor. */
+ return ED_operator_node_active(C) || BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_NODE, 0);
+}
+
+void NODE_OT_geometry_node_view_legacy(wmOperatorType *ot)
+{
+ ot->name = "View Deprecated Geometry Nodes";
+ ot->idname = "NODE_OT_geometry_node_view_legacy";
+ ot->description = "Select and view legacy geometry nodes in the node editor";
+
+ ot->exec = geometry_node_view_legacy_exec;
+ ot->poll = geometry_node_view_legacy_poll;
+
+ ot->flag = OPTYPE_INTERNAL;
+}
+
+/** \} */
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c
index c06a1010168..7cdfb553da5 100644
--- a/source/blender/editors/space_outliner/outliner_draw.c
+++ b/source/blender/editors/space_outliner/outliner_draw.c
@@ -2358,7 +2358,10 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
case eGpencilModifierType_Texture:
data.icon = ICON_TEXTURE;
break;
- case eGpencilModifierType_Weight:
+ case eGpencilModifierType_WeightProximity:
+ data.icon = ICON_MOD_VERTEX_WEIGHT;
+ break;
+ case eGpencilModifierType_WeightAngle:
data.icon = ICON_MOD_VERTEX_WEIGHT;
break;
diff --git a/source/blender/editors/space_outliner/outliner_tree.c b/source/blender/editors/space_outliner/outliner_tree.c
index c5ec656080a..5427ae31ac3 100644
--- a/source/blender/editors/space_outliner/outliner_tree.c
+++ b/source/blender/editors/space_outliner/outliner_tree.c
@@ -1864,6 +1864,15 @@ static void outliner_filter_tree(SpaceOutliner *space_outliner, ViewLayer *view_
space_outliner, view_layer, &space_outliner->tree, search_string, exclude_filter);
}
+static void outliner_clear_newid_from_main(Main *bmain)
+{
+ ID *id_iter;
+ FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
+ id_iter->newid = NULL;
+ }
+ FOREACH_MAIN_ID_END;
+}
+
/* ======================================================= */
/* Main Tree Building API */
@@ -1926,5 +1935,7 @@ void outliner_build_tree(Main *mainvar,
outliner_filter_tree(space_outliner, view_layer);
outliner_restore_scrolling_position(space_outliner, region, &focus);
- BKE_main_id_newptr_and_tag_clear(mainvar);
+ /* `ID.newid` pointer is abused when building tree, DO NOT call #BKE_main_id_newptr_and_tag_clear
+ * as this expects valid IDs in this pointer, not random unknown data. */
+ outliner_clear_newid_from_main(mainvar);
}
diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c
index 5b39feacfe3..53f1c35776c 100644
--- a/source/blender/editors/space_sequencer/sequencer_draw.c
+++ b/source/blender/editors/space_sequencer/sequencer_draw.c
@@ -25,6 +25,7 @@
#include <string.h>
#include "BLI_blenlib.h"
+#include "BLI_ghash.h"
#include "BLI_math.h"
#include "BLI_string_utils.h"
#include "BLI_threads.h"
@@ -44,6 +45,7 @@
#include "BKE_context.h"
#include "BKE_fcurve.h"
#include "BKE_global.h"
+#include "BKE_main.h"
#include "BKE_scene.h"
#include "BKE_sound.h"
@@ -71,6 +73,7 @@
#include "BIF_glutil.h"
#include "SEQ_effects.h"
+#include "SEQ_iterator.h"
#include "SEQ_prefetch.h"
#include "SEQ_proxy.h"
#include "SEQ_relations.h"
@@ -1282,6 +1285,526 @@ static void draw_seq_fcurve_overlay(
}
}
+typedef struct ThumbnailDrawJob {
+ SeqRenderData context;
+ GHash *sequences_ghash;
+ Scene *scene;
+ rctf *view_area;
+ float pixelx;
+ float pixely;
+} ThumbnailDrawJob;
+
+typedef struct ThumbDataItem {
+ Sequence *seq_dupli;
+ Scene *scene;
+} ThumbDataItem;
+
+static void thumbnail_hash_data_free(void *val)
+{
+ ThumbDataItem *item = val;
+ SEQ_sequence_free(item->scene, item->seq_dupli, 0);
+ MEM_freeN(val);
+}
+
+static void thumbnail_freejob(void *data)
+{
+ ThumbnailDrawJob *tj = data;
+ BLI_ghash_free(tj->sequences_ghash, NULL, thumbnail_hash_data_free);
+ MEM_freeN(tj->view_area);
+ MEM_freeN(tj);
+}
+
+static void thumbnail_endjob(void *data)
+{
+ ThumbnailDrawJob *tj = data;
+ WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, tj->scene);
+}
+
+static bool check_seq_need_thumbnails(Sequence *seq, rctf *view_area)
+{
+ if (seq->type != SEQ_TYPE_MOVIE && seq->type != SEQ_TYPE_IMAGE) {
+ return false;
+ }
+ if (min_ii(seq->startdisp, seq->start) > view_area->xmax) {
+ return false;
+ }
+ if (max_ii(seq->enddisp, seq->start + seq->len) < view_area->xmin) {
+ return false;
+ }
+ if (seq->machine + 1.0f < view_area->ymin) {
+ return false;
+ }
+ if (seq->machine > view_area->ymax) {
+ return false;
+ }
+
+ return true;
+}
+
+static void seq_get_thumb_image_dimensions(Sequence *seq,
+ float pixelx,
+ float pixely,
+ float *r_thumb_width,
+ float *r_thumb_height,
+ float *r_image_width,
+ float *r_image_height)
+{
+ float image_width = seq->strip->stripdata->orig_width;
+ float image_height = seq->strip->stripdata->orig_height;
+
+ /* Fix the dimensions to be max SEQ_RENDER_THUMB_SIZE (256) for x or y. */
+ float aspect_ratio = (float)image_width / image_height;
+ if (image_width > image_height) {
+ image_width = SEQ_RENDER_THUMB_SIZE;
+ image_height = round_fl_to_int(image_width / aspect_ratio);
+ }
+ else {
+ image_height = SEQ_RENDER_THUMB_SIZE;
+ image_width = round_fl_to_int(image_height * aspect_ratio);
+ }
+
+ /* Calculate thumb dimensions. */
+ float thumb_height = (SEQ_STRIP_OFSTOP - SEQ_STRIP_OFSBOTTOM) - (20 * U.dpi_fac * pixely);
+ aspect_ratio = ((float)image_width) / image_height;
+ float thumb_h_px = thumb_height / pixely;
+ float thumb_width = aspect_ratio * thumb_h_px * pixelx;
+
+ if (r_thumb_height == NULL) {
+ *r_thumb_width = thumb_width;
+ return;
+ }
+
+ *r_thumb_height = thumb_height;
+ *r_image_width = image_width;
+ *r_image_height = image_height;
+ *r_thumb_width = thumb_width;
+}
+
+static float seq_thumbnail_get_start_frame(Sequence *seq, float frame_step, rctf *view_area)
+{
+ if (seq->start > view_area->xmin && seq->start < view_area->xmax) {
+ return seq->start;
+ }
+
+ /* Drawing and caching both check to see if strip is in view area or not before calling this
+ * function so assuming strip/part of strip in view. */
+
+ int no_invisible_thumbs = (view_area->xmin - seq->start) / frame_step;
+ return ((no_invisible_thumbs - 1) * frame_step) + seq->start;
+}
+
+static void thumbnail_start_job(void *data,
+ short *stop,
+ short *UNUSED(do_update),
+ float *UNUSED(progress))
+{
+ ThumbnailDrawJob *tj = data;
+ float start_frame, frame_step;
+
+ GHashIterator gh_iter;
+ BLI_ghashIterator_init(&gh_iter, tj->sequences_ghash);
+ while (!BLI_ghashIterator_done(&gh_iter) & !*stop) {
+ Sequence *seq_orig = BLI_ghashIterator_getKey(&gh_iter);
+ ThumbDataItem *val = BLI_ghash_lookup(tj->sequences_ghash, seq_orig);
+
+ if (check_seq_need_thumbnails(seq_orig, tj->view_area)) {
+ seq_get_thumb_image_dimensions(
+ val->seq_dupli, tj->pixelx, tj->pixely, &frame_step, NULL, NULL, NULL);
+ start_frame = seq_thumbnail_get_start_frame(seq_orig, frame_step, tj->view_area);
+ SEQ_render_thumbnails(
+ &tj->context, val->seq_dupli, seq_orig, start_frame, frame_step, tj->view_area, stop);
+ SEQ_render_thumbnails_base_set(&tj->context, val->seq_dupli, seq_orig, tj->view_area, stop);
+ }
+ BLI_ghashIterator_step(&gh_iter);
+ }
+}
+
+static SeqRenderData sequencer_thumbnail_context_init(const bContext *C)
+{
+ struct Main *bmain = CTX_data_main(C);
+ struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+ Scene *scene = CTX_data_scene(C);
+ SpaceSeq *sseq = CTX_wm_space_seq(C);
+ SeqRenderData context = {0};
+
+ /* Taking rectx and recty as 0 as dimensions not known here, and context is used to calculate
+ * hash key but not necessary as other variables of SeqRenderData are unique enough. */
+ SEQ_render_new_render_data(bmain, depsgraph, scene, 0, 0, sseq->render_size, false, &context);
+ context.view_id = BKE_scene_multiview_view_id_get(&scene->r, STEREO_LEFT_NAME);
+ context.use_proxies = false;
+
+ return context;
+}
+
+static GHash *sequencer_thumbnail_ghash_init(const bContext *C, View2D *v2d, Editing *ed)
+{
+ Scene *scene = CTX_data_scene(C);
+
+ /* Set the data for thumbnail caching job. */
+ GHash *thumb_data_hash = BLI_ghash_ptr_new("seq_duplicates_and_origs");
+
+ LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
+ ThumbDataItem *val_need_update = BLI_ghash_lookup(thumb_data_hash, seq);
+ if (val_need_update == NULL && check_seq_need_thumbnails(seq, &v2d->cur)) {
+ ThumbDataItem *val = MEM_callocN(sizeof(ThumbDataItem), "Thumbnail Hash Values");
+ val->seq_dupli = SEQ_sequence_dupli_recursive(scene, scene, NULL, seq, 0);
+ val->scene = scene;
+ BLI_ghash_insert(thumb_data_hash, seq, val);
+ }
+ else {
+ if (val_need_update != NULL) {
+ val_need_update->seq_dupli->start = seq->start;
+ val_need_update->seq_dupli->startdisp = seq->startdisp;
+ }
+ }
+ }
+
+ return thumb_data_hash;
+}
+
+static void sequencer_thumbnail_init_job(const bContext *C, View2D *v2d, Editing *ed)
+{
+ wmJob *wm_job;
+ ThumbnailDrawJob *tj = NULL;
+ ScrArea *area = CTX_wm_area(C);
+ wm_job = WM_jobs_get(CTX_wm_manager(C),
+ CTX_wm_window(C),
+ CTX_data_scene(C),
+ "Draw Thumbnails",
+ 0,
+ WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
+
+ /* Get the thumbnail job if it exists. */
+ tj = WM_jobs_customdata_get(wm_job);
+ if (!tj) {
+ tj = MEM_callocN(sizeof(ThumbnailDrawJob), "Thumbnail cache job");
+
+ /* Duplicate value of v2d->cur and v2d->tot to have module separation. */
+ rctf *view_area = MEM_callocN(sizeof(struct rctf), "viewport area");
+ view_area->xmax = v2d->cur.xmax;
+ view_area->xmin = v2d->cur.xmin;
+ view_area->ymax = v2d->cur.ymax;
+ view_area->ymin = v2d->cur.ymin;
+
+ tj->scene = CTX_data_scene(C);
+ tj->view_area = view_area;
+ tj->context = sequencer_thumbnail_context_init(C);
+ tj->sequences_ghash = sequencer_thumbnail_ghash_init(C, v2d, ed);
+ tj->pixelx = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask);
+ tj->pixely = BLI_rctf_size_y(&v2d->cur) / BLI_rcti_size_y(&v2d->mask);
+ WM_jobs_customdata_set(wm_job, tj, thumbnail_freejob);
+ WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_SEQUENCER, NC_SCENE | ND_SEQUENCER);
+ WM_jobs_callbacks(wm_job, thumbnail_start_job, NULL, NULL, thumbnail_endjob);
+ }
+
+ if (!WM_jobs_is_running(wm_job)) {
+ G.is_break = false;
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+ }
+ else {
+ WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, NULL);
+ }
+
+ ED_area_tag_redraw(area);
+}
+
+static bool sequencer_thumbnail_v2d_is_navigating(const bContext *C)
+{
+ ARegion *region = CTX_wm_region(C);
+ View2D *v2d = &region->v2d;
+ return (v2d->flag & V2D_IS_NAVIGATING) != 0;
+}
+
+static void sequencer_thumbnail_start_job_if_necessary(const bContext *C,
+ Editing *ed,
+ View2D *v2d,
+ bool thumbnail_is_missing)
+{
+ SpaceSeq *sseq = CTX_wm_space_seq(C);
+
+ if (sequencer_thumbnail_v2d_is_navigating(C)) {
+ WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, NULL);
+ return;
+ }
+
+ /* `thumbnail_is_missing` should be set to true if missing image in strip. False when normal call
+ * to all strips done. */
+ if (v2d->cur.xmax != sseq->runtime.last_thumbnail_area.xmax ||
+ v2d->cur.ymax != sseq->runtime.last_thumbnail_area.ymax || thumbnail_is_missing) {
+
+ /* Stop the job first as view has changed. Pointless to continue old job. */
+ if (v2d->cur.xmax != sseq->runtime.last_thumbnail_area.xmax ||
+ v2d->cur.ymax != sseq->runtime.last_thumbnail_area.ymax) {
+ WM_jobs_stop(CTX_wm_manager(C), NULL, thumbnail_start_job);
+ }
+
+ sequencer_thumbnail_init_job(C, v2d, ed);
+ sseq->runtime.last_thumbnail_area = v2d->cur;
+ }
+}
+
+void last_displayed_thumbnails_list_free(void *val)
+{
+ BLI_gset_free(val, NULL);
+}
+
+static GSet *last_displayed_thumbnails_list_ensure(const bContext *C, Sequence *seq)
+{
+ SpaceSeq *sseq = CTX_wm_space_seq(C);
+ if (sseq->runtime.last_displayed_thumbnails == NULL) {
+ sseq->runtime.last_displayed_thumbnails = BLI_ghash_ptr_new(__func__);
+ }
+
+ GSet *displayed_thumbnails = BLI_ghash_lookup(sseq->runtime.last_displayed_thumbnails, seq);
+ if (displayed_thumbnails == NULL) {
+ displayed_thumbnails = BLI_gset_int_new(__func__);
+ BLI_ghash_insert(sseq->runtime.last_displayed_thumbnails, seq, displayed_thumbnails);
+ }
+
+ return displayed_thumbnails;
+}
+
+static void last_displayed_thumbnails_list_cleanup(GSet *previously_displayed,
+ float range_start,
+ float range_end)
+{
+ GSetIterator gset_iter;
+ BLI_gsetIterator_init(&gset_iter, previously_displayed);
+ while (!BLI_gsetIterator_done(&gset_iter)) {
+ int frame = (float)POINTER_AS_INT(BLI_gsetIterator_getKey(&gset_iter));
+ BLI_gsetIterator_step(&gset_iter);
+
+ if (frame > range_start && frame < range_end) {
+ BLI_gset_remove(previously_displayed, POINTER_FROM_INT(frame), NULL);
+ }
+ }
+}
+
+static int sequencer_thumbnail_closest_previous_frame_get(int timeline_frame,
+ GSet *previously_displayed)
+{
+ int best_diff = INT_MAX;
+ int best_frame = timeline_frame;
+
+ /* Previously displayed thumbnails. */
+ GSetIterator gset_iter;
+ BLI_gsetIterator_init(&gset_iter, previously_displayed);
+ while (!BLI_gsetIterator_done(&gset_iter)) {
+ int frame = POINTER_AS_INT(BLI_gsetIterator_getKey(&gset_iter));
+ int diff = abs(frame - timeline_frame);
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_frame = frame;
+ }
+ BLI_gsetIterator_step(&gset_iter);
+ }
+ return best_frame;
+}
+
+static int sequencer_thumbnail_closest_guaranteed_frame_get(Sequence *seq, int timeline_frame)
+{
+ if (timeline_frame <= seq->startdisp) {
+ return seq->startdisp;
+ }
+
+ /* Set of "guaranteed" thumbnails. */
+ const int frame_index = timeline_frame - seq->startdisp;
+ const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(seq);
+ const int relative_base_frame = round_fl_to_int((frame_index / (float)frame_step)) * frame_step;
+ const int nearest_guaranted_absolute_frame = relative_base_frame + seq->startdisp;
+ return nearest_guaranted_absolute_frame;
+}
+
+static ImBuf *sequencer_thumbnail_closest_from_memory(const SeqRenderData *context,
+ Sequence *seq,
+ int timeline_frame,
+ GSet *previously_displayed,
+ rcti *crop,
+ bool clipped)
+{
+ int frame_previous = sequencer_thumbnail_closest_previous_frame_get(timeline_frame,
+ previously_displayed);
+ ImBuf *ibuf_previous = SEQ_get_thumbnail(context, seq, frame_previous, crop, clipped);
+
+ int frame_guaranteed = sequencer_thumbnail_closest_guaranteed_frame_get(seq, timeline_frame);
+ ImBuf *ibuf_guaranteed = SEQ_get_thumbnail(context, seq, frame_guaranteed, crop, clipped);
+
+ ImBuf *closest_in_memory = NULL;
+
+ if (ibuf_previous && ibuf_guaranteed) {
+ if (abs(frame_previous - timeline_frame) < abs(frame_guaranteed - timeline_frame)) {
+ IMB_freeImBuf(ibuf_guaranteed);
+ closest_in_memory = ibuf_previous;
+ }
+ else {
+ IMB_freeImBuf(ibuf_previous);
+ closest_in_memory = ibuf_guaranteed;
+ }
+ }
+
+ if (ibuf_previous == NULL) {
+ closest_in_memory = ibuf_guaranteed;
+ }
+
+ if (ibuf_guaranteed == NULL) {
+ closest_in_memory = ibuf_previous;
+ }
+
+ return closest_in_memory;
+}
+
+static void draw_seq_strip_thumbnail(View2D *v2d,
+ const bContext *C,
+ Scene *scene,
+ Sequence *seq,
+ float y1,
+ float y2,
+ float pixelx,
+ float pixely)
+{
+ bool clipped = false;
+ float image_height, image_width, thumb_width, thumb_height;
+ rcti crop;
+
+ /* If width of the strip too small ignore drawing thumbnails. */
+ if ((y2 - y1) / pixely <= 40 * U.dpi_fac) {
+ return;
+ }
+
+ SeqRenderData context = sequencer_thumbnail_context_init(C);
+
+ if ((seq->flag & SEQ_FLAG_SKIP_THUMBNAILS) != 0) {
+ return;
+ }
+
+ seq_get_thumb_image_dimensions(
+ seq, pixelx, pixely, &thumb_width, &thumb_height, &image_width, &image_height);
+
+ float thumb_y_end = y1 + thumb_height - pixely;
+
+ float cut_off = 0;
+ float upper_thumb_bound = (seq->endstill) ? (seq->start + seq->len) : seq->enddisp;
+ if (seq->type == SEQ_TYPE_IMAGE) {
+ upper_thumb_bound = seq->enddisp;
+ }
+
+ float thumb_x_start = seq_thumbnail_get_start_frame(seq, thumb_width, &v2d->cur);
+ float thumb_x_end;
+
+ while (thumb_x_start + thumb_width < v2d->cur.xmin) {
+ thumb_x_start += thumb_width;
+ }
+
+ /* Ignore thumbs to the left of strip. */
+ while (thumb_x_start + thumb_width < seq->startdisp) {
+ thumb_x_start += thumb_width;
+ }
+
+ GSet *last_displayed_thumbnails = last_displayed_thumbnails_list_ensure(C, seq);
+ /* Cleanup thumbnail list outside of rendered range, which is cleaned up one by one to prevent
+ * flickering after zooming. */
+ if (!sequencer_thumbnail_v2d_is_navigating(C)) {
+ last_displayed_thumbnails_list_cleanup(last_displayed_thumbnails, -FLT_MAX, thumb_x_start);
+ }
+
+ /* Start drawing. */
+ while (thumb_x_start < upper_thumb_bound) {
+ thumb_x_end = thumb_x_start + thumb_width;
+ clipped = false;
+
+ /* Checks to make sure that thumbs are loaded only when in view and within the confines of the
+ * strip. Some may not be required but better to have conditions for safety as x1 here is
+ * point to start caching from and not drawing. */
+ if (thumb_x_start > v2d->cur.xmax) {
+ break;
+ }
+
+ /* Set the clipping bound to show the left handle moving over thumbs and not shift thumbs. */
+ if (IN_RANGE_INCL(seq->startdisp, thumb_x_start, thumb_x_end)) {
+ cut_off = seq->startdisp - thumb_x_start;
+ clipped = true;
+ }
+
+ /* Clip if full thumbnail cannot be displayed. */
+ if (thumb_x_end > (upper_thumb_bound)) {
+ thumb_x_end = upper_thumb_bound;
+ clipped = true;
+ if (thumb_x_end - thumb_x_start < 1) {
+ break;
+ }
+ }
+
+ float zoom_x = thumb_width / image_width;
+ float zoom_y = thumb_height / image_height;
+
+ float cropx_min = (cut_off / pixelx) / (zoom_y / pixely);
+ float cropx_max = ((thumb_x_end - thumb_x_start) / pixelx) / (zoom_y / pixely);
+ if (cropx_max == (thumb_x_end - thumb_x_start)) {
+ cropx_max = cropx_max + 1;
+ }
+ BLI_rcti_init(&crop, (int)(cropx_min), (int)cropx_max, 0, (int)(image_height)-1);
+
+ int timeline_frame = round_fl_to_int(thumb_x_start);
+
+ /* Get the image. */
+ ImBuf *ibuf = SEQ_get_thumbnail(&context, seq, timeline_frame, &crop, clipped);
+
+ if (!ibuf) {
+ sequencer_thumbnail_start_job_if_necessary(C, scene->ed, v2d, true);
+
+ ibuf = sequencer_thumbnail_closest_from_memory(
+ &context, seq, timeline_frame, last_displayed_thumbnails, &crop, clipped);
+ }
+ /* Store recently rendered frames, so they can be reused when zooming. */
+ else if (!sequencer_thumbnail_v2d_is_navigating(C)) {
+ /* Clear images in frame range occupied by new thumbnail. */
+ last_displayed_thumbnails_list_cleanup(
+ last_displayed_thumbnails, thumb_x_start, thumb_x_end);
+ /* Insert new thumbnail frame to list. */
+ BLI_gset_add(last_displayed_thumbnails, POINTER_FROM_INT(timeline_frame));
+ }
+
+ /* If there is no image still, abort. */
+ if (!ibuf) {
+ break;
+ }
+
+ /* Transparency on overlap. */
+ if (seq->flag & SEQ_OVERLAP) {
+ GPU_blend(GPU_BLEND_ALPHA);
+ if (ibuf->rect) {
+ unsigned char *buf = (unsigned char *)ibuf->rect;
+ for (int pixel = ibuf->x * ibuf->y; pixel--; buf += 4) {
+ buf[3] = OVERLAP_ALPHA;
+ }
+ }
+ else if (ibuf->rect_float) {
+ float *buf = (float *)ibuf->rect_float;
+ for (int pixel = ibuf->x * ibuf->y; pixel--; buf += ibuf->channels) {
+ buf[3] = (OVERLAP_ALPHA / 255.0f);
+ }
+ }
+ }
+
+ ED_draw_imbuf_ctx_clipping(C,
+ ibuf,
+ thumb_x_start + cut_off,
+ y1,
+ true,
+ thumb_x_start + cut_off,
+ y1,
+ thumb_x_end,
+ thumb_y_end,
+ zoom_x,
+ zoom_y);
+ IMB_freeImBuf(ibuf);
+ GPU_blend(GPU_BLEND_NONE);
+ cut_off = 0;
+ thumb_x_start += thumb_width;
+ }
+ last_displayed_thumbnails_list_cleanup(last_displayed_thumbnails, thumb_x_start, FLT_MAX);
+}
+
/* Draw visible strips. Bounds check are already made. */
static void draw_seq_strip(const bContext *C,
SpaceSeq *sseq,
@@ -1356,6 +1879,12 @@ static void draw_seq_strip(const bContext *C,
}
if ((sseq->flag & SEQ_SHOW_OVERLAY) &&
+ (sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_THUMBNAILS) &&
+ (seq->type == SEQ_TYPE_MOVIE || seq->type == SEQ_TYPE_IMAGE)) {
+ draw_seq_strip_thumbnail(v2d, C, scene, seq, y1, y2, pixelx, pixely);
+ }
+
+ if ((sseq->flag & SEQ_SHOW_OVERLAY) &&
(sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_FCURVES)) {
draw_seq_fcurve_overlay(scene, v2d, seq, x1, y1, x2, y2, pixelx);
}
@@ -2056,6 +2585,64 @@ static int sequencer_draw_get_transform_preview_frame(Scene *scene)
return preview_frame;
}
+static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq)
+{
+ SpaceSeq *sseq = CTX_wm_space_seq(C);
+ if ((seq->flag & SELECT) == 0) {
+ return;
+ }
+ if (ED_screen_animation_no_scrub(CTX_wm_manager(C))) {
+ return;
+ }
+ if ((sseq->flag & SEQ_SHOW_OVERLAY) == 0 ||
+ (sseq->preview_overlay.flag & SEQ_PREVIEW_SHOW_OUTLINE_SELECTED) == 0) {
+ return;
+ }
+ if (ELEM(sseq->mainb, SEQ_DRAW_IMG_WAVEFORM, SEQ_DRAW_IMG_VECTORSCOPE, SEQ_DRAW_IMG_HISTOGRAM)) {
+ return;
+ }
+
+ float origin[2];
+ SEQ_image_transform_origin_offset_pixelspace_get(CTX_data_scene(C), seq, origin);
+
+ /* Origin. */
+ GPUVertFormat *format = immVertexFormat();
+ uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+ immBindBuiltinProgram(GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA);
+ immUniform1f("outlineWidth", 1.5f);
+ immUniformColor3f(1.0f, 1.0f, 1.0f);
+ immUniform4f("outlineColor", 0.0f, 0.0f, 0.0f, 1.0f);
+ immUniform1f("size", 15.0f * U.pixelsize);
+ immBegin(GPU_PRIM_POINTS, 1);
+ immVertex2f(pos, origin[0], origin[1]);
+ immEnd();
+ immUnbindProgram();
+
+ /* Outline. */
+ float seq_image_quad[4][2];
+ SEQ_image_transform_final_quad_get(CTX_data_scene(C), seq, seq_image_quad);
+
+ GPU_line_smooth(true);
+ GPU_blend(GPU_BLEND_ALPHA);
+ GPU_line_width(2);
+ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+
+ float col[3];
+ UI_GetThemeColor3fv(TH_SEQ_SELECTED, col);
+ immUniformColor3fv(col);
+ immUniform1f("lineWidth", U.pixelsize);
+ immBegin(GPU_PRIM_LINE_LOOP, 4);
+ immVertex2f(pos, seq_image_quad[0][0], seq_image_quad[0][1]);
+ immVertex2f(pos, seq_image_quad[1][0], seq_image_quad[1][1]);
+ immVertex2f(pos, seq_image_quad[2][0], seq_image_quad[2][1]);
+ immVertex2f(pos, seq_image_quad[3][0], seq_image_quad[3][1]);
+ immEnd();
+ immUnbindProgram();
+ GPU_line_width(1);
+ GPU_blend(GPU_BLEND_NONE);
+ GPU_line_smooth(false);
+}
+
void sequencer_draw_preview(const bContext *C,
Scene *scene,
ARegion *region,
@@ -2132,9 +2719,17 @@ void sequencer_draw_preview(const bContext *C,
sequencer_draw_borders_overlay(sseq, v2d, scene);
}
+ SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0);
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, collection) {
+ seq_draw_image_origin_and_outline(C, seq);
+ }
+ SEQ_collection_free(collection);
+
if (draw_gpencil && show_imbuf && (sseq->flag & SEQ_SHOW_OVERLAY)) {
sequencer_draw_gpencil_overlay(C);
}
+
#if 0
sequencer_draw_maskedit(C, scene, region, sseq);
#endif
diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c
index b95b7fa0620..9f21fc0676c 100644
--- a/source/blender/editors/space_sequencer/sequencer_edit.c
+++ b/source/blender/editors/space_sequencer/sequencer_edit.c
@@ -579,7 +579,6 @@ static int sequencer_slip_invoke(bContext *C, wmOperator *op, const wmEvent *eve
static bool sequencer_slip_recursively(Scene *scene, SlipData *data, int offset)
{
/* Only data types supported for now. */
- Editing *ed = SEQ_editing_get(scene);
bool changed = false;
/* Iterate in reverse so meta-strips are iterated after their children. */
@@ -633,7 +632,10 @@ static bool sequencer_slip_recursively(Scene *scene, SlipData *data, int offset)
}
}
if (changed) {
- SEQ_relations_free_imbuf(scene, &ed->seqbase, false);
+ for (int i = data->num_seq - 1; i >= 0; i--) {
+ Sequence *seq = data->seq_array[i];
+ SEQ_relations_invalidate_cache_preprocessed(scene, seq);
+ }
}
return changed;
}
diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h
index 767ac76efe6..5b5c381509f 100644
--- a/source/blender/editors/space_sequencer/sequencer_intern.h
+++ b/source/blender/editors/space_sequencer/sequencer_intern.h
@@ -67,6 +67,7 @@ struct ImBuf *sequencer_ibuf_get(struct Main *bmain,
int timeline_frame,
int frame_ofs,
const char *viewname);
+void last_displayed_thumbnails_list_free(void *val);
/* sequencer_edit.c */
struct View2D;
diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c
index 80d3e2cbdaa..aa6599a7c53 100644
--- a/source/blender/editors/space_sequencer/sequencer_select.c
+++ b/source/blender/editors/space_sequencer/sequencer_select.c
@@ -44,6 +44,7 @@
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
#include "SEQ_transform.h"
+#include "SEQ_utils.h"
/* For menu, popup, icons, etc. */
@@ -385,6 +386,20 @@ void recurs_sel_seq(Sequence *seq_meta)
}
}
+static bool seq_point_image_isect(const Scene *scene, const Sequence *seq, float point[2])
+{
+ float seq_image_quad[4][2];
+ SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad);
+ return isect_point_quad_v2(
+ point, seq_image_quad[0], seq_image_quad[1], seq_image_quad[2], seq_image_quad[3]);
+}
+
+static void sequencer_select_do_updates(bContext *C, Scene *scene)
+{
+ ED_outliner_select_sync_from_sequence_tag(C);
+ WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
@@ -523,12 +538,6 @@ static void sequencer_select_set_active(Scene *scene, Sequence *seq)
recurs_sel_seq(seq);
}
-static void sequencer_select_do_updates(bContext *C, Scene *scene)
-{
- ED_outliner_select_sync_from_sequence_tag(C);
- WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
-}
-
static void sequencer_select_side_of_frame(const bContext *C,
const View2D *v2d,
const int mval[2],
@@ -626,6 +635,45 @@ static void sequencer_select_linked_handle(const bContext *C,
}
}
+/* Check if click happened on image which belongs to strip. If multiple strips are found, loop
+ * through them in order. */
+static Sequence *seq_select_seq_from_preview(const bContext *C, const int mval[2])
+{
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene);
+ ListBase *seqbase = SEQ_active_seqbase_get(ed);
+ SpaceSeq *sseq = CTX_wm_space_seq(C);
+ View2D *v2d = UI_view2d_fromcontext(C);
+
+ float mouseco_view[2];
+ UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseco_view[0], &mouseco_view[1]);
+
+ SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, sseq->chanshown);
+ ListBase strips_ordered = {NULL};
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, strips) {
+ if (seq_point_image_isect(scene, seq, mouseco_view)) {
+ BLI_remlink(seqbase, seq);
+ BLI_addtail(&strips_ordered, seq);
+ }
+ }
+ SEQ_collection_free(strips);
+ SEQ_sort(&strips_ordered);
+
+ Sequence *seq_active = SEQ_select_active_get(scene);
+ Sequence *seq_select = strips_ordered.first;
+ LISTBASE_FOREACH (Sequence *, seq_iter, &strips_ordered) {
+ if (seq_iter == seq_active && seq_iter->next != NULL) {
+ seq_select = seq_iter->next;
+ break;
+ }
+ }
+
+ BLI_movelisttolist(seqbase, &strips_ordered);
+
+ return seq_select;
+}
+
static bool element_already_selected(const Sequence *seq, const int handle_clicked)
{
const bool handle_already_selected = ((handle_clicked == SEQ_SIDE_LEFT) &&
@@ -680,8 +728,15 @@ static int sequencer_select_exec(bContext *C, wmOperator *op)
mval[0] = RNA_int_get(op->ptr, "mouse_x");
mval[1] = RNA_int_get(op->ptr, "mouse_y");
- int handle_clicked;
- Sequence *seq = find_nearest_seq(scene, v2d, &handle_clicked, mval);
+ ARegion *region = CTX_wm_region(C);
+ int handle_clicked = SEQ_SIDE_NONE;
+ Sequence *seq = NULL;
+ if (region->regiontype == RGN_TYPE_PREVIEW) {
+ seq = seq_select_seq_from_preview(C, mval);
+ }
+ else {
+ seq = find_nearest_seq(scene, v2d, &handle_clicked, mval);
+ }
/* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap,
* therefore both properties can be true at the same time. */
@@ -1311,6 +1366,47 @@ void SEQUENCER_OT_select_side(wmOperatorType *ot)
/** \name Box Select Operator
* \{ */
+static bool seq_box_select_rect_image_isect(const Scene *scene, const Sequence *seq, rctf *rect)
+{
+ float seq_image_quad[4][2];
+ SEQ_image_transform_final_quad_get(scene, seq, seq_image_quad);
+ float rect_quad[4][2] = {{rect->xmax, rect->ymax},
+ {rect->xmax, rect->ymin},
+ {rect->xmin, rect->ymin},
+ {rect->xmin, rect->ymax}};
+
+ return seq_point_image_isect(scene, seq, rect_quad[0]) ||
+ seq_point_image_isect(scene, seq, rect_quad[1]) ||
+ seq_point_image_isect(scene, seq, rect_quad[2]) ||
+ seq_point_image_isect(scene, seq, rect_quad[3]) ||
+ isect_point_quad_v2(
+ seq_image_quad[0], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
+ isect_point_quad_v2(
+ seq_image_quad[1], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
+ isect_point_quad_v2(
+ seq_image_quad[2], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]) ||
+ isect_point_quad_v2(
+ seq_image_quad[3], rect_quad[0], rect_quad[1], rect_quad[2], rect_quad[3]);
+}
+
+static void seq_box_select_seq_from_preview(const bContext *C, rctf *rect)
+{
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene);
+ ListBase *seqbase = SEQ_active_seqbase_get(ed);
+ SpaceSeq *sseq = CTX_wm_space_seq(C);
+
+ SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, sseq->chanshown);
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, strips) {
+ if (seq_box_select_rect_image_isect(scene, seq, rect)) {
+ seq->flag |= SELECT;
+ }
+ }
+
+ SEQ_collection_free(strips);
+}
+
static int sequencer_box_select_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
@@ -1333,6 +1429,13 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op)
WM_operator_properties_border_to_rctf(op, &rectf);
UI_view2d_region_to_view_rctf(v2d, &rectf, &rectf);
+ ARegion *region = CTX_wm_region(C);
+ if (region->regiontype == RGN_TYPE_PREVIEW) {
+ seq_box_select_seq_from_preview(C, &rectf);
+ sequencer_select_do_updates(C, scene);
+ return OPERATOR_FINISHED;
+ }
+
LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) {
rctf rq;
seq_rectf(seq, &rq);
@@ -1378,9 +1481,7 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op)
}
}
- ED_outliner_select_sync_from_sequence_tag(C);
-
- WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
+ sequencer_select_do_updates(C, scene);
return OPERATOR_FINISHED;
}
diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c
index 0d09f2564e8..99b75f82922 100644
--- a/source/blender/editors/space_sequencer/space_sequencer.c
+++ b/source/blender/editors/space_sequencer/space_sequencer.c
@@ -32,6 +32,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
+#include "BLI_ghash.h"
#include "BLI_utildefines.h"
#include "BKE_context.h"
@@ -42,6 +43,7 @@
#include "ED_screen.h"
#include "ED_space_api.h"
+#include "ED_transform.h"
#include "ED_view3d.h"
#include "ED_view3d_offscreen.h" /* Only for sequencer view3d drawing callback. */
@@ -98,10 +100,14 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
sseq->chanshown = 0;
sseq->view = SEQ_VIEW_SEQUENCE;
sseq->mainb = SEQ_DRAW_IMG_IMBUF;
- sseq->flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_USE_ALPHA | SEQ_SHOW_MARKERS |
- SEQ_TIMELINE_SHOW_FCURVES | SEQ_ZOOM_TO_FIT | SEQ_SHOW_OVERLAY |
- SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE |
- SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID;
+ sseq->flag = SEQ_USE_ALPHA | SEQ_SHOW_MARKERS | SEQ_ZOOM_TO_FIT | SEQ_SHOW_OVERLAY;
+ sseq->preview_overlay.flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_PREVIEW_SHOW_OUTLINE_SELECTED;
+ sseq->timeline_overlay.flag = SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE |
+ SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID |
+ SEQ_TIMELINE_SHOW_FCURVES;
+
+ BLI_rctf_init(&sseq->runtime.last_thumbnail_area, 0.0f, 0.0f, 0.0f, 0.0f);
+ sseq->runtime.last_displayed_thumbnails = NULL;
/* Tool header. */
region = MEM_callocN(sizeof(ARegion), "tool header for sequencer");
@@ -172,7 +178,7 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
region->v2d.cur = region->v2d.tot;
region->v2d.min[0] = 10.0f;
- region->v2d.min[1] = 0.5f;
+ region->v2d.min[1] = 4.0f;
region->v2d.max[0] = MAXFRAMEF;
region->v2d.max[1] = MAXSEQ;
@@ -186,6 +192,8 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
region->v2d.keeptot = 0;
region->v2d.align = V2D_ALIGN_NO_NEG_Y;
+ sseq->runtime.last_displayed_thumbnails = NULL;
+
return (SpaceLink *)sseq;
}
@@ -216,6 +224,12 @@ static void sequencer_free(SpaceLink *sl)
if (scopes->histogram_ibuf) {
IMB_freeImBuf(scopes->histogram_ibuf);
}
+
+ if (sseq->runtime.last_displayed_thumbnails) {
+ BLI_ghash_free(
+ sseq->runtime.last_displayed_thumbnails, NULL, last_displayed_thumbnails_list_free);
+ sseq->runtime.last_displayed_thumbnails = NULL;
+ }
}
/* Spacetype init callback. */
@@ -330,6 +344,7 @@ static SpaceLink *sequencer_duplicate(SpaceLink *sl)
/* XXX sseq->gpd = gpencil_data_duplicate(sseq->gpd, false); */
memset(&sseqn->scopes, 0, sizeof(sseqn->scopes));
+ memset(&sseqn->runtime, 0, sizeof(sseqn->runtime));
return (SpaceLink *)sseqn;
}
@@ -481,11 +496,72 @@ static void SEQUENCER_GGT_navigate(wmGizmoGroupType *gzgt)
VIEW2D_GGT_navigate_impl(gzgt, "SEQUENCER_GGT_navigate");
}
+static void SEQUENCER_GGT_gizmo2d(wmGizmoGroupType *gzgt)
+{
+ gzgt->name = "Sequencer Transform Gizmo";
+ gzgt->idname = "SEQUENCER_GGT_gizmo2d";
+
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
+
+ gzgt->gzmap_params.spaceid = SPACE_SEQ;
+ gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW;
+
+ ED_widgetgroup_gizmo2d_xform_callbacks_set(gzgt);
+}
+
+static void SEQUENCER_GGT_gizmo2d_translate(wmGizmoGroupType *gzgt)
+{
+ gzgt->name = "Sequencer Translate Gizmo";
+ gzgt->idname = "SEQUENCER_GGT_gizmo2d_translate";
+
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
+
+ gzgt->gzmap_params.spaceid = SPACE_SEQ;
+ gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW;
+
+ ED_widgetgroup_gizmo2d_xform_no_cage_callbacks_set(gzgt);
+}
+
+static void SEQUENCER_GGT_gizmo2d_resize(wmGizmoGroupType *gzgt)
+{
+ gzgt->name = "Sequencer Transform Gizmo Resize";
+ gzgt->idname = "SEQUENCER_GGT_gizmo2d_resize";
+
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
+
+ gzgt->gzmap_params.spaceid = SPACE_SEQ;
+ gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW;
+
+ ED_widgetgroup_gizmo2d_resize_callbacks_set(gzgt);
+}
+
+static void SEQUENCER_GGT_gizmo2d_rotate(wmGizmoGroupType *gzgt)
+{
+ gzgt->name = "Sequencer Transform Gizmo Resize";
+ gzgt->idname = "SEQUENCER_GGT_gizmo2d_rotate";
+
+ gzgt->flag |= (WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP |
+ WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK);
+
+ gzgt->gzmap_params.spaceid = SPACE_SEQ;
+ gzgt->gzmap_params.regionid = RGN_TYPE_PREVIEW;
+
+ ED_widgetgroup_gizmo2d_rotate_callbacks_set(gzgt);
+}
+
static void sequencer_gizmos(void)
{
wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(
&(const struct wmGizmoMapType_Params){SPACE_SEQ, RGN_TYPE_PREVIEW});
+ WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d);
+ WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_translate);
+ WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_resize);
+ WM_gizmogrouptype_append(SEQUENCER_GGT_gizmo2d_rotate);
+
WM_gizmogrouptype_append_and_link(gzmap_type, SEQUENCER_GGT_navigate);
}
@@ -742,6 +818,8 @@ static void sequencer_preview_region_listener(const wmRegionListenerParams *para
ARegion *region = params->region;
wmNotifier *wmn = params->notifier;
+ WM_gizmomap_tag_refresh(region->gizmo_map);
+
/* Context changes. */
switch (wmn->category) {
case NC_GPENCIL:
diff --git a/source/blender/editors/space_view3d/view3d_gizmo_preselect.c b/source/blender/editors/space_view3d/view3d_gizmo_preselect.c
index 441182d7a5f..918ecb14752 100644
--- a/source/blender/editors/space_view3d/view3d_gizmo_preselect.c
+++ b/source/blender/editors/space_view3d/view3d_gizmo_preselect.c
@@ -58,7 +58,7 @@ void VIEW3D_GGT_mesh_preselect_elem(wmGizmoGroupType *gzgt)
gzgt->name = "Mesh Preselect Element";
gzgt->idname = "VIEW3D_GGT_mesh_preselect_elem";
- gzgt->flag = WM_GIZMOGROUPTYPE_3D;
+ gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
@@ -95,7 +95,7 @@ void VIEW3D_GGT_mesh_preselect_edgering(wmGizmoGroupType *gzgt)
gzgt->name = "Mesh Preselect Edge Ring";
gzgt->idname = "VIEW3D_GGT_mesh_preselect_edgering";
- gzgt->flag = WM_GIZMOGROUPTYPE_3D;
+ gzgt->flag = WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_3D;
gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c
index 3f572bf9d5a..39aed131ea1 100644
--- a/source/blender/editors/space_view3d/view3d_select.c
+++ b/source/blender/editors/space_view3d/view3d_select.c
@@ -2813,7 +2813,9 @@ static int view3d_select_invoke(bContext *C, wmOperator *op, const wmEvent *even
{
RNA_int_set_array(op->ptr, "location", event->mval);
- return view3d_select_exec(C, op);
+ const int retval = view3d_select_exec(C, op);
+
+ return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void VIEW3D_OT_select(wmOperatorType *ot)
diff --git a/source/blender/editors/transform/CMakeLists.txt b/source/blender/editors/transform/CMakeLists.txt
index e9efed3cd61..64a720322c1 100644
--- a/source/blender/editors/transform/CMakeLists.txt
+++ b/source/blender/editors/transform/CMakeLists.txt
@@ -60,6 +60,7 @@ set(SRC
transform_convert_particle.c
transform_convert_sculpt.c
transform_convert_sequencer.c
+ transform_convert_sequencer_image.c
transform_convert_tracking.c
transform_draw_cursors.c
transform_generics.c
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index 58491f8c2d3..e58e524e341 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -1703,11 +1703,13 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
t->draw_handle_cursor = WM_paint_cursor_activate(
SPACE_TYPE_ANY, RGN_TYPE_ANY, transform_draw_cursor_poll, transform_draw_cursor_draw, t);
}
- else if (t->spacetype == SPACE_SEQ) {
- t->draw_handle_view = ED_region_draw_cb_activate(
- t->region->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
- }
- else if (ELEM(t->spacetype, SPACE_IMAGE, SPACE_CLIP, SPACE_NODE, SPACE_GRAPH, SPACE_ACTION)) {
+ else if (ELEM(t->spacetype,
+ SPACE_IMAGE,
+ SPACE_CLIP,
+ SPACE_NODE,
+ SPACE_GRAPH,
+ SPACE_ACTION,
+ SPACE_SEQ)) {
t->draw_handle_view = ED_region_draw_cb_activate(
t->region->type, drawTransformView, t, REGION_DRAW_POST_VIEW);
t->draw_handle_cursor = WM_paint_cursor_activate(
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index d1a1937cef1..7f4e533ccd7 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -87,15 +87,16 @@ typedef enum {
CTX_PAINT_CURVE = (1 << 7),
CTX_POSE_BONE = (1 << 8),
CTX_TEXTURE_SPACE = (1 << 9),
+ CTX_SEQUENCER_IMAGE = (1 << 10),
- CTX_NO_PET = (1 << 10),
- CTX_AUTOCONFIRM = (1 << 11),
+ CTX_NO_PET = (1 << 11),
+ CTX_AUTOCONFIRM = (1 << 12),
/** When transforming object's, adjust the object data so it stays in the same place. */
- CTX_OBMODE_XFORM_OBDATA = (1 << 12),
+ CTX_OBMODE_XFORM_OBDATA = (1 << 13),
/** Transform object parents without moving their children. */
- CTX_OBMODE_XFORM_SKIP_CHILDREN = (1 << 13),
+ CTX_OBMODE_XFORM_SKIP_CHILDREN = (1 << 14),
/** Enable edge scrolling in 2D views */
- CTX_VIEW2D_EDGE_PAN = (1 << 14),
+ CTX_VIEW2D_EDGE_PAN = (1 << 15),
} eTContext;
/** #TransInfo.flag */
@@ -240,6 +241,7 @@ typedef enum {
TC_PARTICLE_VERTS,
TC_SCULPT,
TC_SEQ_DATA,
+ TC_SEQ_IMAGE_DATA,
TC_TRACKING_DATA,
} eTConvertType;
diff --git a/source/blender/editors/transform/transform_convert.c b/source/blender/editors/transform/transform_convert.c
index d756e2c90a6..557fa79e7ac 100644
--- a/source/blender/editors/transform/transform_convert.c
+++ b/source/blender/editors/transform/transform_convert.c
@@ -955,6 +955,7 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
case TC_OBJECT_TEXSPACE:
case TC_PAINT_CURVE_VERTS:
case TC_PARTICLE_VERTS:
+ case TC_SEQ_IMAGE_DATA:
case TC_NONE:
default:
break;
@@ -1042,6 +1043,7 @@ static void init_proportional_edit(TransInfo *t)
case TC_PAINT_CURVE_VERTS:
case TC_SCULPT:
case TC_SEQ_DATA:
+ case TC_SEQ_IMAGE_DATA:
case TC_TRACKING_DATA:
case TC_NONE:
default:
@@ -1120,6 +1122,7 @@ static void init_TransDataContainers(TransInfo *t,
case TC_PARTICLE_VERTS:
case TC_SCULPT:
case TC_SEQ_DATA:
+ case TC_SEQ_IMAGE_DATA:
case TC_TRACKING_DATA:
case TC_NONE:
default:
@@ -1204,6 +1207,7 @@ static eTFlag flags_from_data_type(eTConvertType data_type)
case TC_NODE_DATA:
case TC_PAINT_CURVE_VERTS:
case TC_SEQ_DATA:
+ case TC_SEQ_IMAGE_DATA:
case TC_TRACKING_DATA:
return T_POINTS | T_2D_EDIT;
case TC_ARMATURE_VERTS:
@@ -1282,7 +1286,12 @@ static eTConvertType convert_type_get(const TransInfo *t, Object **r_obj_armatur
convert_type = TC_NLA_DATA;
}
else if (t->spacetype == SPACE_SEQ) {
- convert_type = TC_SEQ_DATA;
+ if (t->options & CTX_SEQUENCER_IMAGE) {
+ convert_type = TC_SEQ_IMAGE_DATA;
+ }
+ else {
+ convert_type = TC_SEQ_DATA;
+ }
}
else if (t->spacetype == SPACE_GRAPH) {
convert_type = TC_GRAPH_EDIT_DATA;
@@ -1470,6 +1479,10 @@ void createTransData(bContext *C, TransInfo *t)
t->num.flag |= NUM_NO_FRACTION; /* sequencer has no use for floating point transform. */
createTransSeqData(t);
break;
+ case TC_SEQ_IMAGE_DATA:
+ t->obedit_type = -1;
+ createTransSeqImageData(t);
+ break;
case TC_TRACKING_DATA:
createTransTrackingData(C, t);
break;
@@ -1746,6 +1759,9 @@ void recalcData(TransInfo *t)
case TC_SEQ_DATA:
recalcData_sequencer(t);
break;
+ case TC_SEQ_IMAGE_DATA:
+ recalcData_sequencer_image(t);
+ break;
case TC_TRACKING_DATA:
recalcData_tracking(t);
break;
diff --git a/source/blender/editors/transform/transform_convert.h b/source/blender/editors/transform/transform_convert.h
index 9cb0400cad9..66d84bca2d2 100644
--- a/source/blender/editors/transform/transform_convert.h
+++ b/source/blender/editors/transform/transform_convert.h
@@ -218,6 +218,10 @@ void createTransSeqData(TransInfo *t);
void recalcData_sequencer(TransInfo *t);
void special_aftertrans_update__sequencer(bContext *C, TransInfo *t);
+/* transform_convert_sequencer_image.c */
+void createTransSeqImageData(TransInfo *t);
+void recalcData_sequencer_image(TransInfo *t);
+
/* transform_convert_tracking.c */
void createTransTrackingData(bContext *C, TransInfo *t);
void recalcData_tracking(TransInfo *t);
diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c
new file mode 100644
index 00000000000..465f8b9a694
--- /dev/null
+++ b/source/blender/editors/transform/transform_convert_sequencer_image.c
@@ -0,0 +1,195 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2021 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edtransform
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_space_types.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+
+#include "BKE_context.h"
+#include "BKE_report.h"
+
+#include "SEQ_iterator.h"
+#include "SEQ_relations.h"
+#include "SEQ_sequencer.h"
+#include "SEQ_time.h"
+#include "SEQ_transform.h"
+#include "SEQ_utils.h"
+
+#include "UI_view2d.h"
+
+#include "transform.h"
+#include "transform_convert.h"
+
+/** Used for sequencer transform. */
+typedef struct TransDataSeq {
+ struct Sequence *seq;
+ float orig_origin_position[2];
+ float orig_translation[2];
+ float orig_scale[2];
+ float orig_rotation;
+} TransDataSeq;
+
+static TransData *SeqToTransData(const Scene *scene,
+ Sequence *seq,
+ TransData *td,
+ TransData2D *td2d,
+ TransDataSeq *tdseq,
+ int vert_index)
+{
+ const StripTransform *transform = seq->strip->transform;
+ float origin[2];
+ SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, origin);
+ float vertex[2] = {origin[0], origin[1]};
+
+ /* Add control vertex, so rotation and scale can be calculated. */
+ if (vert_index == 1) {
+ vertex[0] += 1.0f;
+ }
+ else if (vert_index == 2) {
+ vertex[1] += 1.0f;
+ }
+
+ td2d->loc[0] = vertex[0];
+ td2d->loc[1] = vertex[1];
+ td2d->loc2d = NULL;
+ td->loc = td2d->loc;
+ copy_v3_v3(td->iloc, td->loc);
+
+ td->center[0] = origin[0];
+ td->center[1] = origin[1];
+
+ memset(td->axismtx, 0, sizeof(td->axismtx));
+ td->axismtx[2][2] = 1.0f;
+ unit_m3(td->mtx);
+ unit_m3(td->smtx);
+
+ tdseq->seq = seq;
+ copy_v2_v2(tdseq->orig_origin_position, origin);
+ tdseq->orig_translation[0] = transform->xofs;
+ tdseq->orig_translation[1] = transform->yofs;
+ tdseq->orig_scale[0] = transform->scale_x;
+ tdseq->orig_scale[1] = transform->scale_y;
+ tdseq->orig_rotation = transform->rotation;
+
+ td->extra = (void *)tdseq;
+ td->ext = NULL;
+ td->flag |= TD_SELECTED;
+ td->dist = 0.0;
+
+ return td;
+}
+
+static void freeSeqData(TransInfo *UNUSED(t), TransDataContainer *tc, TransCustomData *UNUSED(custom_data))
+{
+ TransData *td = (TransData *)tc->data;
+ MEM_freeN(td->extra);
+}
+
+void createTransSeqImageData(TransInfo *t)
+{
+ Editing *ed = SEQ_editing_get(t->scene);
+ ListBase *seqbase = SEQ_active_seqbase_get(ed);
+ SeqCollection *strips = SEQ_query_rendered_strips(seqbase, t->scene->r.cfra, 0);
+ SEQ_filter_selected_strips(strips);
+
+ const int count = SEQ_collection_len(strips);
+ if (ed == NULL || count == 0) {
+ SEQ_collection_free(strips);
+ return;
+ }
+
+ TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
+ tc->custom.type.free_cb = freeSeqData;
+
+ tc->data_len = count * 3; /* 3 vertices per sequence are needed. */
+ TransData *td = tc->data = MEM_callocN(tc->data_len * sizeof(TransData), "TransSeq TransData");
+ TransData2D *td2d = tc->data_2d = MEM_callocN(tc->data_len * sizeof(TransData2D),
+ "TransSeq TransData2D");
+ TransDataSeq *tdseq = MEM_callocN(tc->data_len * sizeof(TransDataSeq), "TransSeq TransDataSeq");
+
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, strips) {
+ /* One `Sequence` needs 3 `TransData` entries - center point placed in image origin, then 2
+ * points offset by 1 in X and Y direction respectively, so rotation and scale can be
+ * calculated from these points. */
+ SeqToTransData(t->scene, seq, td++, td2d++, tdseq++, 0);
+ SeqToTransData(t->scene, seq, td++, td2d++, tdseq++, 1);
+ SeqToTransData(t->scene, seq, td++, td2d++, tdseq++, 2);
+ }
+
+ SEQ_collection_free(strips);
+}
+
+void recalcData_sequencer_image(TransInfo *t)
+{
+ TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_SINGLE(t);
+ TransData *td = NULL;
+ TransData2D *td2d = NULL;
+ int i;
+
+ for (i = 0, td = tc->data, td2d = tc->data_2d; i < tc->data_len; i++, td++, td2d++) {
+ /* Origin. */
+ float loc[2];
+ copy_v2_v2(loc, td2d->loc);
+ i++, td++, td2d++;
+
+ /* X and Y control points used to read scale and rotation. */
+ float handle_x[2];
+ copy_v2_v2(handle_x, td2d->loc);
+ sub_v2_v2(handle_x, loc);
+ i++, td++, td2d++;
+ float handle_y[2];
+ copy_v2_v2(handle_y, td2d->loc);
+ sub_v2_v2(handle_y, loc);
+
+ TransDataSeq *tdseq = td->extra;
+ Sequence *seq = tdseq->seq;
+ StripTransform *transform = seq->strip->transform;
+ float mirror[2];
+ SEQ_image_transform_mirror_factor_get(seq, mirror);
+
+ /* Calculate translation. */
+ float translation[2];
+ copy_v2_v2(translation, tdseq->orig_origin_position);
+ sub_v2_v2(translation, loc);
+ mul_v2_v2(translation, mirror);
+ transform->xofs = tdseq->orig_translation[0] - translation[0];
+ transform->yofs = tdseq->orig_translation[1] - translation[1];
+
+ /* Scale. */
+ transform->scale_x = tdseq->orig_scale[0] * fabs(len_v2(handle_x));
+ transform->scale_y = tdseq->orig_scale[1] * fabs(len_v2(handle_y));
+
+ /* Rotation. Scaling can cause negative rotation. */
+ if (t->mode == TFM_ROTATION) {
+ float rotation = angle_signed_v2v2(handle_x, (float[]){1, 0}) * mirror[0] * mirror[1];
+ transform->rotation = tdseq->orig_rotation + rotation;
+ transform->rotation += DEG2RAD(360.0);
+ transform->rotation = fmod(transform->rotation, DEG2RAD(360.0));
+ }
+ SEQ_relations_invalidate_cache_preprocessed(t->scene, seq);
+ }
+}
diff --git a/source/blender/editors/transform/transform_draw_cursors.c b/source/blender/editors/transform/transform_draw_cursors.c
index ead8eae0997..af1f3cb72a4 100644
--- a/source/blender/editors/transform/transform_draw_cursors.c
+++ b/source/blender/editors/transform/transform_draw_cursors.c
@@ -95,7 +95,7 @@ static void drawArrow(const uint pos_id, const enum eArrowDirection dir)
bool transform_draw_cursor_poll(bContext *C)
{
ARegion *region = CTX_wm_region(C);
- return (region && region->regiontype == RGN_TYPE_WINDOW) ? 1 : 0;
+ return (region && ELEM(region->regiontype, RGN_TYPE_WINDOW, RGN_TYPE_PREVIEW)) ? 1 : 0;
}
/**
diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c
index c493b9bd102..fa323f0c1f7 100644
--- a/source/blender/editors/transform/transform_generics.c
+++ b/source/blender/editors/transform/transform_generics.c
@@ -59,6 +59,8 @@
#include "UI_resources.h"
#include "UI_view2d.h"
+#include "SEQ_sequencer.h"
+
#include "transform.h"
#include "transform_convert.h"
#include "transform_mode.h"
@@ -335,6 +337,11 @@ void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
t->options |= CTX_MASK;
}
}
+ else if (t->spacetype == SPACE_SEQ && region->regiontype == RGN_TYPE_PREVIEW) {
+ t->view = &region->v2d;
+ t->around = SEQ_tool_settings_pivot_point_get(t->scene);
+ t->options |= CTX_SEQUENCER_IMAGE;
+ }
else {
if (region) {
/* XXX: For now, get View2D from the active region. */
diff --git a/source/blender/editors/transform/transform_gizmo_2d.c b/source/blender/editors/transform/transform_gizmo_2d.c
index 0b677e2560b..0d66db0d7e1 100644
--- a/source/blender/editors/transform/transform_gizmo_2d.c
+++ b/source/blender/editors/transform/transform_gizmo_2d.c
@@ -49,6 +49,11 @@
#include "ED_screen.h"
#include "ED_uvedit.h"
+#include "SEQ_iterator.h"
+#include "SEQ_sequencer.h"
+#include "SEQ_time.h"
+#include "SEQ_transform.h"
+
#include "transform.h" /* own include */
/* -------------------------------------------------------------------- */
@@ -234,17 +239,66 @@ static bool gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min
return changed;
}
+static float gizmo2d_calc_rotation(const bContext *C)
+{
+ ScrArea *area = CTX_wm_area(C);
+ if (area->spacetype != SPACE_SEQ) {
+ return 0.0f;
+ }
+
+ Scene *scene = CTX_data_scene(C);
+ Editing *ed = SEQ_editing_get(scene);
+ ListBase *seqbase = SEQ_active_seqbase_get(ed);
+ SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, 0);
+ SEQ_filter_selected_strips(strips);
+
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, strips) {
+ if (seq == ed->act_seq) {
+ StripTransform *transform = seq->strip->transform;
+ float mirror[2];
+ SEQ_image_transform_mirror_factor_get(seq, mirror);
+ SEQ_collection_free(strips);
+ return transform->rotation * mirror[0] * mirror[1];
+ }
+ }
+
+ SEQ_collection_free(strips);
+ return 0.0f;
+}
+
static bool gizmo2d_calc_center(const bContext *C, float r_center[2])
{
ScrArea *area = CTX_wm_area(C);
+ Scene *scene = CTX_data_scene(C);
bool has_select = false;
zero_v2(r_center);
if (area->spacetype == SPACE_IMAGE) {
SpaceImage *sima = area->spacedata.first;
- Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ED_uvedit_center_from_pivot_ex(sima, scene, view_layer, r_center, sima->around, &has_select);
}
+ else if (area->spacetype == SPACE_SEQ) {
+ ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene));
+ SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, 0);
+ SEQ_filter_selected_strips(strips);
+
+ if (SEQ_collection_len(strips) <= 0) {
+ SEQ_collection_free(strips);
+ return false;
+ }
+
+ has_select = true;
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, strips) {
+ float origin[2];
+ SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, origin);
+ add_v2_v2(r_center, origin);
+ }
+ mul_v2_fl(r_center, 1.0f / SEQ_collection_len(strips));
+
+ SEQ_collection_free(strips);
+ }
return has_select;
}
@@ -338,7 +392,7 @@ static void gizmo2d_xform_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup
}
}
- RNA_boolean_set(ptr, "release_confirm", 1);
+ RNA_boolean_set(ptr, "release_confirm", true);
}
{
@@ -539,6 +593,7 @@ void ED_widgetgroup_gizmo2d_xform_no_cage_callbacks_set(wmGizmoGroupType *gzgt)
typedef struct GizmoGroup_Resize2D {
wmGizmo *gizmo_xy[3];
float origin[2];
+ float rotation;
} GizmoGroup_Resize2D;
static GizmoGroup_Resize2D *gizmogroup2d_resize_init(wmGizmoGroup *gzgroup)
@@ -571,6 +626,7 @@ static void gizmo2d_resize_refresh(const bContext *C, wmGizmoGroup *gzgroup)
ggd->gizmo_xy[i]->flag &= ~WM_GIZMO_HIDDEN;
}
copy_v2_v2(ggd->origin, origin);
+ ggd->rotation = gizmo2d_calc_rotation(C);
}
}
@@ -595,6 +651,13 @@ static void gizmo2d_resize_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup
for (int i = 0; i < ARRAY_SIZE(ggd->gizmo_xy); i++) {
wmGizmo *gz = ggd->gizmo_xy[i];
WM_gizmo_set_matrix_location(gz, origin);
+
+ if (i < 2) {
+ float axis[3] = {0.0f}, rotated_axis[3];
+ axis[i] = 1.0f;
+ rotate_v3_v3v3fl(rotated_axis, axis, (float[3]){0, 0, 1}, ggd->rotation);
+ WM_gizmo_set_matrix_rotation_from_z_axis(gz, rotated_axis);
+ }
}
}
@@ -617,10 +680,6 @@ static void gizmo2d_resize_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgrou
/* set up widget data */
RNA_float_set(gz->ptr, "length", 1.0f);
- float axis[3] = {0.0f};
- axis[i] = 1.0f;
- WM_gizmo_set_matrix_rotation_from_z_axis(gz, axis);
-
RNA_enum_set(gz->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_BOX);
WM_gizmo_set_line_width(gz, GIZMO_AXIS_LINE_WIDTH);
diff --git a/source/blender/editors/transform/transform_mode.c b/source/blender/editors/transform/transform_mode.c
index b9fb8a86752..b14d499cb66 100644
--- a/source/blender/editors/transform/transform_mode.c
+++ b/source/blender/editors/transform/transform_mode.c
@@ -75,7 +75,7 @@ bool transdata_check_local_center(const TransInfo *t, short around)
/* implicit: (t->flag & T_EDIT) */
(ELEM(t->obedit_type, OB_MESH, OB_CURVE, OB_MBALL, OB_ARMATURE, OB_GPENCIL)) ||
(t->spacetype == SPACE_GRAPH) ||
- (t->options & (CTX_MOVIECLIP | CTX_MASK | CTX_PAINT_CURVE))));
+ (t->options & (CTX_MOVIECLIP | CTX_MASK | CTX_PAINT_CURVE | CTX_SEQUENCER_IMAGE))));
}
/* Informs if the mode can be switched during modal. */
diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c
index e82a00bcc77..2acdf5cfd9c 100644
--- a/source/blender/editors/transform/transform_snap_sequencer.c
+++ b/source/blender/editors/transform/transform_snap_sequencer.c
@@ -254,6 +254,10 @@ static int seq_snap_threshold_get_frame_distance(const TransInfo *t)
TransSeqSnapData *transform_snap_sequencer_data_alloc(const TransInfo *t)
{
+ if (t->data_type == TC_SEQ_IMAGE_DATA) {
+ return NULL;
+ }
+
TransSeqSnapData *snap_data = MEM_callocN(sizeof(TransSeqSnapData), __func__);
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(t->scene));
diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c
index c0ccf1b7095..86390882bed 100644
--- a/source/blender/editors/uvedit/uvedit_select.c
+++ b/source/blender/editors/uvedit/uvedit_select.c
@@ -2122,7 +2122,9 @@ static int uv_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
RNA_float_set_array(op->ptr, "location", co);
- return uv_select_exec(C, op);
+ const int retval = uv_select_exec(C, op);
+
+ return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void UV_OT_select(wmOperatorType *ot)
@@ -2281,7 +2283,9 @@ static int uv_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *eve
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
RNA_float_set_array(op->ptr, "location", co);
- return uv_select_loop_exec(C, op);
+ const int retval = uv_select_loop_exec(C, op);
+
+ return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void UV_OT_select_loop(wmOperatorType *ot)
@@ -2341,7 +2345,9 @@ static int uv_select_edge_ring_invoke(bContext *C, wmOperator *op, const wmEvent
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
RNA_float_set_array(op->ptr, "location", co);
- return uv_select_edge_ring_exec(C, op);
+ const int retval = uv_select_edge_ring_exec(C, op);
+
+ return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void UV_OT_select_edge_ring(wmOperatorType *ot)
diff --git a/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp b/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp
index 937a10f26b1..0a82c237256 100644
--- a/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp
+++ b/source/blender/freestyle/intern/blender_interface/BlenderStrokeRenderer.cpp
@@ -94,17 +94,15 @@ BlenderStrokeRenderer::BlenderStrokeRenderer(Render *re, int render_count)
freestyle_scene = BKE_scene_add(freestyle_bmain, name);
freestyle_scene->r.cfra = old_scene->r.cfra;
freestyle_scene->r.mode = old_scene->r.mode & ~(R_EDGE_FRS | R_BORDER);
- freestyle_scene->r.xsch = re->rectx; // old_scene->r.xsch
- freestyle_scene->r.ysch = re->recty; // old_scene->r.ysch
- freestyle_scene->r.xasp = 1.0f; // old_scene->r.xasp;
- freestyle_scene->r.yasp = 1.0f; // old_scene->r.yasp;
- freestyle_scene->r.tilex = old_scene->r.tilex;
- freestyle_scene->r.tiley = old_scene->r.tiley;
+ freestyle_scene->r.xsch = re->rectx; // old_scene->r.xsch
+ freestyle_scene->r.ysch = re->recty; // old_scene->r.ysch
+ freestyle_scene->r.xasp = 1.0f; // old_scene->r.xasp;
+ freestyle_scene->r.yasp = 1.0f; // old_scene->r.yasp;
freestyle_scene->r.size = 100; // old_scene->r.size
freestyle_scene->r.color_mgt_flag = 0; // old_scene->r.color_mgt_flag;
freestyle_scene->r.scemode = (old_scene->r.scemode &
~(R_SINGLE_LAYER | R_NO_FRAME_UPDATE | R_MULTIVIEW)) &
- (re->r.scemode | ~R_FULL_SAMPLE);
+ (re->r.scemode);
freestyle_scene->r.flag = old_scene->r.flag;
freestyle_scene->r.threads = old_scene->r.threads;
freestyle_scene->r.border.xmin = old_scene->r.border.xmin;
diff --git a/source/blender/functions/FN_cpp_type.hh b/source/blender/functions/FN_cpp_type.hh
index 7277bf99c12..643b2fc1f28 100644
--- a/source/blender/functions/FN_cpp_type.hh
+++ b/source/blender/functions/FN_cpp_type.hh
@@ -96,6 +96,7 @@ class CPPType : NonCopyable, NonMovable {
int64_t size_ = 0;
int64_t alignment_ = 0;
uintptr_t alignment_mask_ = 0;
+ bool is_trivial_ = false;
bool is_trivially_destructible_ = false;
bool has_special_member_functions_ = false;
@@ -340,7 +341,6 @@ class CPPType : NonCopyable, NonMovable {
*/
void copy_assign(const void *src, void *dst) const
{
- BLI_assert(src != dst);
BLI_assert(this->pointer_can_point_to_instance(src));
BLI_assert(this->pointer_can_point_to_instance(dst));
@@ -371,7 +371,7 @@ class CPPType : NonCopyable, NonMovable {
*/
void copy_construct(const void *src, void *dst) const
{
- BLI_assert(src != dst);
+ BLI_assert(src != dst || is_trivial_);
BLI_assert(this->pointer_can_point_to_instance(src));
BLI_assert(this->pointer_can_point_to_instance(dst));
@@ -402,7 +402,6 @@ class CPPType : NonCopyable, NonMovable {
*/
void move_assign(void *src, void *dst) const
{
- BLI_assert(src != dst);
BLI_assert(this->pointer_can_point_to_instance(src));
BLI_assert(this->pointer_can_point_to_instance(dst));
@@ -433,7 +432,7 @@ class CPPType : NonCopyable, NonMovable {
*/
void move_construct(void *src, void *dst) const
{
- BLI_assert(src != dst);
+ BLI_assert(src != dst || is_trivial_);
BLI_assert(this->pointer_can_point_to_instance(src));
BLI_assert(this->pointer_can_point_to_instance(dst));
@@ -464,7 +463,7 @@ class CPPType : NonCopyable, NonMovable {
*/
void relocate_assign(void *src, void *dst) const
{
- BLI_assert(src != dst);
+ BLI_assert(src != dst || is_trivial_);
BLI_assert(this->pointer_can_point_to_instance(src));
BLI_assert(this->pointer_can_point_to_instance(dst));
@@ -495,7 +494,7 @@ class CPPType : NonCopyable, NonMovable {
*/
void relocate_construct(void *src, void *dst) const
{
- BLI_assert(src != dst);
+ BLI_assert(src != dst || is_trivial_);
BLI_assert(this->pointer_can_point_to_instance(src));
BLI_assert(this->pointer_can_point_to_instance(dst));
diff --git a/source/blender/functions/FN_cpp_type_make.hh b/source/blender/functions/FN_cpp_type_make.hh
index 088f6b469f4..74dbcabf81a 100644
--- a/source/blender/functions/FN_cpp_type_make.hh
+++ b/source/blender/functions/FN_cpp_type_make.hh
@@ -195,6 +195,7 @@ CPPType::CPPType(CPPTypeParam<T, Flags> /* unused */, StringRef debug_name)
debug_name_ = debug_name;
size_ = (int64_t)sizeof(T);
alignment_ = (int64_t)alignof(T);
+ is_trivial_ = std::is_trivial_v<T>;
is_trivially_destructible_ = std::is_trivially_destructible_v<T>;
if constexpr (std::is_default_constructible_v<T>) {
default_construct_ = default_construct_cb<T>;
diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc
index fa95e8de71e..986c5dff0c4 100644
--- a/source/blender/functions/intern/multi_function_procedure.cc
+++ b/source/blender/functions/intern/multi_function_procedure.cc
@@ -419,6 +419,10 @@ bool MFProcedure::validate_initialization() const
const MultiFunction &fn = *instruction->fn_;
for (const int param_index : fn.param_indices()) {
const MFParamType param_type = fn.param_type(param_index);
+ /* If the parameter was an unneeded output, it could be null. */
+ if (!instruction->params_[param_index]) {
+ continue;
+ }
const MFVariable &variable = *instruction->params_[param_index];
const InitState state = this->find_initialization_state_before_instruction(*instruction,
variable);
diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt
index adf68e534bb..eb1f61b1862 100644
--- a/source/blender/gpencil_modifiers/CMakeLists.txt
+++ b/source/blender/gpencil_modifiers/CMakeLists.txt
@@ -69,7 +69,8 @@ set(SRC
intern/MOD_gpencilthick.c
intern/MOD_gpenciltime.c
intern/MOD_gpenciltint.c
- intern/MOD_gpencilweight.c
+ intern/MOD_gpencilweight_proximity.c
+ intern/MOD_gpencilweight_angle.c
MOD_gpencil_lineart.h
MOD_gpencil_modifiertypes.h
diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
index 043186155b7..d9285f44a37 100644
--- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
+++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
@@ -44,7 +44,8 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Armature;
extern GpencilModifierTypeInfo modifierType_Gpencil_Time;
extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply;
extern GpencilModifierTypeInfo modifierType_Gpencil_Texture;
-extern GpencilModifierTypeInfo modifierType_Gpencil_Weight;
+extern GpencilModifierTypeInfo modifierType_Gpencil_WeightProximity;
+extern GpencilModifierTypeInfo modifierType_Gpencil_WeightAngle;
extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart;
extern GpencilModifierTypeInfo modifierType_Gpencil_Dash;
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
index 5eb1eeab780..df78ac8110e 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
@@ -63,7 +63,8 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[])
INIT_GP_TYPE(Time);
INIT_GP_TYPE(Multiply);
INIT_GP_TYPE(Texture);
- INIT_GP_TYPE(Weight);
+ INIT_GP_TYPE(WeightAngle);
+ INIT_GP_TYPE(WeightProximity);
INIT_GP_TYPE(Lineart);
INIT_GP_TYPE(Dash);
#undef INIT_GP_TYPE
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c
index 6aa0e6c152e..80b60547e92 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillength.c
@@ -72,9 +72,14 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
}
static bool gpencil_modify_stroke(bGPDstroke *gps,
- float length,
+ const float length,
const float overshoot_fac,
- const short len_mode)
+ const short len_mode,
+ const bool use_curvature,
+ const int extra_point_count,
+ const float segment_influence,
+ const float max_angle,
+ const bool invert_curvature)
{
bool changed = false;
if (length == 0.0f) {
@@ -82,10 +87,18 @@ static bool gpencil_modify_stroke(bGPDstroke *gps,
}
if (length > 0.0f) {
- BKE_gpencil_stroke_stretch(gps, length, overshoot_fac, len_mode);
+ changed = BKE_gpencil_stroke_stretch(gps,
+ length,
+ overshoot_fac,
+ len_mode,
+ use_curvature,
+ extra_point_count,
+ segment_influence,
+ max_angle,
+ invert_curvature);
}
else {
- changed |= BKE_gpencil_stroke_shrink(gps, fabs(length), len_mode);
+ changed = BKE_gpencil_stroke_shrink(gps, fabs(length), len_mode);
}
return changed;
@@ -96,12 +109,51 @@ static void applyLength(LengthGpencilModifierData *lmd, bGPdata *gpd, bGPDstroke
bool changed = false;
const float len = (lmd->mode == GP_LENGTH_ABSOLUTE) ? 1.0f :
BKE_gpencil_stroke_length(gps, true);
+ const int totpoints = gps->totpoints;
if (len < FLT_EPSILON) {
return;
}
- changed |= gpencil_modify_stroke(gps, len * lmd->start_fac, lmd->overshoot_fac, 1);
- changed |= gpencil_modify_stroke(gps, len * lmd->end_fac, lmd->overshoot_fac, 2);
+ /* Always do the stretching first since it might depend on points which could be deleted by the
+ * shrink. */
+ float first_fac = lmd->start_fac;
+ int first_mode = 1;
+ float second_fac = lmd->end_fac;
+ int second_mode = 2;
+ if (first_fac < 0) {
+ SWAP(float, first_fac, second_fac);
+ SWAP(int, first_mode, second_mode);
+ }
+
+ const int first_extra_point_count = ceil(first_fac * lmd->point_density);
+ const int second_extra_point_count = ceil(second_fac * lmd->point_density);
+
+ changed |= gpencil_modify_stroke(gps,
+ len * first_fac,
+ lmd->overshoot_fac,
+ first_mode,
+ lmd->flag & GP_LENGTH_USE_CURVATURE,
+ first_extra_point_count,
+ lmd->segment_influence,
+ lmd->max_angle,
+ lmd->flag & GP_LENGTH_INVERT_CURVATURE);
+ /* HACK: The second #overshoot_fac needs to be adjusted because it is not
+ * done in the same stretch call, because it can have a different length.
+ * The adjustment needs to be stable when
+ * `ceil(overshoot_fac*(gps->totpoints - 2))` is used in stretch and never
+ * produce a result higher than `totpoints - 2`. */
+ const float second_overshoot_fac = lmd->overshoot_fac * (totpoints - 2) /
+ ((float)gps->totpoints - 2) *
+ (1.0f - 0.1f / (totpoints - 1.0f));
+ changed |= gpencil_modify_stroke(gps,
+ len * second_fac,
+ second_overshoot_fac,
+ second_mode,
+ lmd->flag & GP_LENGTH_USE_CURVATURE,
+ second_extra_point_count,
+ lmd->segment_influence,
+ lmd->max_angle,
+ lmd->flag & GP_LENGTH_INVERT_CURVATURE);
if (changed) {
BKE_gpencil_stroke_geometry_update(gpd, gps);
@@ -117,20 +169,25 @@ static void deformStroke(GpencilModifierData *md,
{
bGPdata *gpd = ob->data;
LengthGpencilModifierData *lmd = (LengthGpencilModifierData *)md;
- if (is_stroke_affected_by_modifier(ob,
- lmd->layername,
- lmd->material,
- lmd->pass_index,
- lmd->layer_pass,
- 1,
- gpl,
- gps,
- lmd->flag & GP_LENGTH_INVERT_LAYER,
- lmd->flag & GP_LENGTH_INVERT_PASS,
- lmd->flag & GP_LENGTH_INVERT_LAYERPASS,
- lmd->flag & GP_LENGTH_INVERT_MATERIAL)) {
- applyLength(lmd, gpd, gps);
+ if (!is_stroke_affected_by_modifier(ob,
+ lmd->layername,
+ lmd->material,
+ lmd->pass_index,
+ lmd->layer_pass,
+ 1,
+ gpl,
+ gps,
+ lmd->flag & GP_LENGTH_INVERT_LAYER,
+ lmd->flag & GP_LENGTH_INVERT_PASS,
+ lmd->flag & GP_LENGTH_INVERT_LAYERPASS,
+ lmd->flag & GP_LENGTH_INVERT_MATERIAL)) {
+ return;
}
+ if ((gps->flag & GP_STROKE_CYCLIC) != 0) {
+ /* Don't affect cyclic strokes as they have no start/end. */
+ return;
+ }
+ applyLength(lmd, gpd, gps);
}
static void bakeModifier(Main *UNUSED(bmain),
@@ -168,10 +225,16 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel)
uiLayout *col = uiLayoutColumn(layout, true);
- uiItemR(col, ptr, "start_factor", 0, IFACE_("Start"), ICON_NONE);
- uiItemR(col, ptr, "end_factor", 0, IFACE_("End"), ICON_NONE);
+ if (RNA_enum_get(ptr, "mode") == GP_LENGTH_RELATIVE) {
+ uiItemR(col, ptr, "start_factor", 0, IFACE_("Start"), ICON_NONE);
+ uiItemR(col, ptr, "end_factor", 0, IFACE_("End"), ICON_NONE);
+ }
+ else {
+ uiItemR(col, ptr, "start_length", 0, IFACE_("Start"), ICON_NONE);
+ uiItemR(col, ptr, "end_length", 0, IFACE_("End"), ICON_NONE);
+ }
- uiItemR(layout, ptr, "overshoot_factor", UI_ITEM_R_SLIDER, IFACE_("Overshoot"), ICON_NONE);
+ uiItemR(layout, ptr, "overshoot_factor", UI_ITEM_R_SLIDER, IFACE_("Used Length"), ICON_NONE);
gpencil_modifier_panel_end(layout, ptr);
}
@@ -181,11 +244,40 @@ static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel)
gpencil_modifier_masking_panel_draw(panel, true, false);
}
+static void curvature_header_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ uiLayout *layout = panel->layout;
+
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
+
+ uiItemR(layout, ptr, "use_curvature", 0, IFACE_("Curvature"), ICON_NONE);
+}
+
+static void curvature_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ uiLayout *layout = panel->layout;
+
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
+
+ uiLayoutSetPropSep(layout, true);
+
+ uiLayout *col = uiLayoutColumn(layout, false);
+
+ uiLayoutSetActive(col, RNA_boolean_get(ptr, "use_curvature"));
+
+ uiItemR(col, ptr, "point_density", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "segment_influence", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "max_angle", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "invert_curvature", 0, IFACE_("Invert"), ICON_NONE);
+}
+
static void panelRegister(ARegionType *region_type)
{
PanelType *panel_type = gpencil_modifier_panel_register(
region_type, eGpencilModifierType_Length, panel_draw);
gpencil_modifier_subpanel_register(
+ region_type, "curvature", "", curvature_header_draw, curvature_panel_draw, panel_type);
+ gpencil_modifier_subpanel_register(
region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type);
}
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_angle.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_angle.c
new file mode 100644
index 00000000000..2c0f3d2d8ad
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_angle.c
@@ -0,0 +1,260 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2021, Blender Foundation
+ * This is a new part of Blender
+ */
+
+/** \file
+ * \ingroup modifiers
+ */
+
+#include <stdio.h>
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_defaults.h"
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_screen_types.h"
+
+#include "BKE_colortools.h"
+#include "BKE_context.h"
+#include "BKE_deform.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_modifier.h"
+#include "BKE_lib_query.h"
+#include "BKE_modifier.h"
+#include "BKE_screen.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+#include "DEG_depsgraph_query.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "RNA_access.h"
+
+#include "MOD_gpencil_modifiertypes.h"
+#include "MOD_gpencil_ui_common.h"
+#include "MOD_gpencil_util.h"
+
+static void initData(GpencilModifierData *md)
+{
+ WeightAngleGpencilModifierData *gpmd = (WeightAngleGpencilModifierData *)md;
+
+ BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier));
+
+ MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(WeightAngleGpencilModifierData), modifier);
+}
+
+static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
+{
+ BKE_gpencil_modifier_copydata_generic(md, target);
+}
+
+/* change stroke thickness */
+static void deformStroke(GpencilModifierData *md,
+ Depsgraph *UNUSED(depsgraph),
+ Object *ob,
+ bGPDlayer *gpl,
+ bGPDframe *UNUSED(gpf),
+ bGPDstroke *gps)
+{
+ WeightAngleGpencilModifierData *mmd = (WeightAngleGpencilModifierData *)md;
+ const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname);
+
+ if (!is_stroke_affected_by_modifier(ob,
+ mmd->layername,
+ mmd->material,
+ mmd->pass_index,
+ mmd->layer_pass,
+ 1,
+ gpl,
+ gps,
+ mmd->flag & GP_WEIGHT_INVERT_LAYER,
+ mmd->flag & GP_WEIGHT_INVERT_PASS,
+ mmd->flag & GP_WEIGHT_INVERT_LAYERPASS,
+ mmd->flag & GP_WEIGHT_INVERT_MATERIAL)) {
+ return;
+ }
+
+ const int target_def_nr = BKE_object_defgroup_name_index(ob, mmd->target_vgname);
+
+ if (target_def_nr == -1) {
+ return;
+ }
+
+ /* Use default Z up. */
+ float vec_axis[3] = {0.0f, 0.0f, 1.0f};
+ float axis[3] = {0.0f, 0.0f, 0.0f};
+ axis[mmd->axis] = 1.0f;
+ float vec_ref[3];
+ /* Apply modifier rotation (sub 90 degrees for Y axis due Z-Up vector). */
+ float rot_angle = mmd->angle - ((mmd->axis == 1) ? M_PI_2 : 0.0f);
+ rotate_normalized_v3_v3v3fl(vec_ref, vec_axis, axis, rot_angle);
+
+ /* Apply the rotation of the object. */
+ if (mmd->space == GP_SPACE_LOCAL) {
+ mul_mat3_m4_v3(ob->obmat, vec_ref);
+ }
+
+ /* Ensure there is a vertex group. */
+ BKE_gpencil_dvert_ensure(gps);
+
+ float weight_pt = 1.0f;
+ for (int i = 0; i < gps->totpoints; i++) {
+ MDeformVert *dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL;
+ /* Verify point is part of vertex group. */
+ float weight = get_modifier_point_weight(
+ dvert, (mmd->flag & GP_WEIGHT_INVERT_VGROUP) != 0, def_nr);
+ if (weight < 0.0f) {
+ continue;
+ }
+
+ /* Special case for single points. */
+ if (gps->totpoints == 1) {
+ weight_pt = 1.0f;
+ break;
+ }
+
+ bGPDspoint *pt1 = (i > 0) ? &gps->points[i] : &gps->points[i + 1];
+ bGPDspoint *pt2 = (i > 0) ? &gps->points[i - 1] : &gps->points[i];
+ float fpt1[3], fpt2[3];
+ mul_v3_m4v3(fpt1, ob->obmat, &pt1->x);
+ mul_v3_m4v3(fpt2, ob->obmat, &pt2->x);
+
+ float vec[3];
+ sub_v3_v3v3(vec, fpt1, fpt2);
+ float angle = angle_on_axis_v3v3_v3(vec_ref, vec, axis);
+ /* Use sin to get a value between 0 and 1. */
+ weight_pt = 1.0f - sin(angle);
+
+ /* Invert weight if required. */
+ if (mmd->flag & GP_WEIGHT_INVERT_OUTPUT) {
+ weight_pt = 1.0f - weight_pt;
+ }
+ /* Assign weight. */
+ dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL;
+ if (dvert != NULL) {
+ MDeformWeight *dw = BKE_defvert_ensure_index(dvert, target_def_nr);
+ if (dw) {
+ dw->weight = (mmd->flag & GP_WEIGHT_MULTIPLY_DATA) ? dw->weight * weight_pt : weight_pt;
+ CLAMP(dw->weight, mmd->min_weight, 1.0f);
+ }
+ }
+ }
+}
+
+static void bakeModifier(struct Main *UNUSED(bmain),
+ Depsgraph *depsgraph,
+ GpencilModifierData *md,
+ Object *ob)
+{
+ bGPdata *gpd = ob->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ deformStroke(md, depsgraph, ob, gpl, gpf, gps);
+ }
+ }
+ }
+}
+
+static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
+{
+ WeightAngleGpencilModifierData *mmd = (WeightAngleGpencilModifierData *)md;
+
+ walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER);
+}
+
+static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams))
+{
+ WeightAngleGpencilModifierData *mmd = (WeightAngleGpencilModifierData *)md;
+
+ return (mmd->target_vgname[0] == '\0');
+}
+
+static void panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ uiLayout *row, *sub;
+ uiLayout *layout = panel->layout;
+
+ PointerRNA ob_ptr;
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr);
+
+ uiLayoutSetPropSep(layout, true);
+ row = uiLayoutRow(layout, true);
+ uiItemPointerR(row, ptr, "target_vertex_group", &ob_ptr, "vertex_groups", NULL, ICON_NONE);
+ sub = uiLayoutRow(row, true);
+ bool has_output = RNA_string_length(ptr, "target_vertex_group") != 0;
+ uiLayoutSetPropDecorate(sub, false);
+ uiLayoutSetActive(sub, has_output);
+ uiItemR(sub, ptr, "use_invert_output", 0, "", ICON_ARROW_LEFTRIGHT);
+
+ uiItemR(layout, ptr, "angle", 0, NULL, ICON_NONE);
+ uiItemR(layout, ptr, "axis", 0, NULL, ICON_NONE);
+ uiItemR(layout, ptr, "space", 0, NULL, ICON_NONE);
+
+ uiItemR(layout, ptr, "minimum_weight", 0, NULL, ICON_NONE);
+ uiItemR(layout, ptr, "use_multiply", 0, NULL, ICON_NONE);
+
+
+ gpencil_modifier_panel_end(layout, ptr);
+}
+
+static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ gpencil_modifier_masking_panel_draw(panel, true, true);
+}
+
+static void panelRegister(ARegionType *region_type)
+{
+ PanelType *panel_type = gpencil_modifier_panel_register(
+ region_type, eGpencilModifierType_WeightAngle, panel_draw);
+
+ gpencil_modifier_subpanel_register(
+ region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type);
+}
+
+GpencilModifierTypeInfo modifierType_Gpencil_WeightAngle = {
+ /* name */ "Vertex Weight Angle",
+ /* structName */ "WeightAngleGpencilModifierData",
+ /* structSize */ sizeof(WeightAngleGpencilModifierData),
+ /* type */ eGpencilModifierTypeType_Gpencil,
+ /* flags */ 0,
+
+ /* copyData */ copyData,
+
+ /* deformStroke */ deformStroke,
+ /* generateStrokes */ NULL,
+ /* bakeModifier */ bakeModifier,
+ /* remapTime */ NULL,
+
+ /* initData */ initData,
+ /* freeData */ NULL,
+ /* isDisabled */ isDisabled,
+ /* updateDepsgraph */ NULL,
+ /* dependsOnTime */ NULL,
+ /* foreachIDLink */ foreachIDLink,
+ /* foreachTexLink */ NULL,
+ /* panelRegister */ panelRegister,
+};
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c
index 686023a36d4..0885828a3a0 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilweight.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilweight_proximity.c
@@ -58,11 +58,11 @@
static void initData(GpencilModifierData *md)
{
- WeightGpencilModifierData *gpmd = (WeightGpencilModifierData *)md;
+ WeightProxGpencilModifierData *gpmd = (WeightProxGpencilModifierData *)md;
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier));
- MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(WeightGpencilModifierData), modifier);
+ MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(WeightProxGpencilModifierData), modifier);
}
static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
@@ -72,7 +72,7 @@ static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
/* Calc distance between point and target object. */
static float calc_point_weight_by_distance(Object *ob,
- WeightGpencilModifierData *mmd,
+ WeightProxGpencilModifierData *mmd,
const float dist_max,
const float dist_min,
bGPDspoint *pt)
@@ -103,9 +103,8 @@ static void deformStroke(GpencilModifierData *md,
bGPDframe *UNUSED(gpf),
bGPDstroke *gps)
{
- WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md;
+ WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md;
const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname);
- const eWeightGpencilModifierMode mode = mmd->mode;
if (!is_stroke_affected_by_modifier(ob,
mmd->layername,
@@ -130,20 +129,6 @@ static void deformStroke(GpencilModifierData *md,
return;
}
- /* Use default Z up. */
- float vec_axis[3] = {0.0f, 0.0f, 1.0f};
- float axis[3] = {0.0f, 0.0f, 0.0f};
- axis[mmd->axis] = 1.0f;
- float vec_ref[3];
- /* Apply modifier rotation (sub 90 degrees for Y axis due Z-Up vector). */
- float rot_angle = mmd->angle - ((mmd->axis == 1) ? M_PI_2 : 0.0f);
- rotate_normalized_v3_v3v3fl(vec_ref, vec_axis, axis, rot_angle);
-
- /* Apply the rotation of the object. */
- if (mmd->space == GP_SPACE_LOCAL) {
- mul_mat3_m4_v3(ob->obmat, vec_ref);
- }
-
/* Ensure there is a vertex group. */
BKE_gpencil_dvert_ensure(gps);
@@ -157,36 +142,9 @@ static void deformStroke(GpencilModifierData *md,
continue;
}
- switch (mode) {
- case GP_WEIGHT_MODE_DISTANCE: {
- if (mmd->object) {
- bGPDspoint *pt = &gps->points[i];
- weight_pt = calc_point_weight_by_distance(ob, mmd, dist_max, dist_min, pt);
- }
- break;
- }
- case GP_WEIGHT_MODE_ANGLE: {
- /* Special case for single points. */
- if (gps->totpoints == 1) {
- weight_pt = 1.0f;
- break;
- }
-
- bGPDspoint *pt1 = (i > 0) ? &gps->points[i] : &gps->points[i + 1];
- bGPDspoint *pt2 = (i > 0) ? &gps->points[i - 1] : &gps->points[i];
- float fpt1[3], fpt2[3];
- mul_v3_m4v3(fpt1, ob->obmat, &pt1->x);
- mul_v3_m4v3(fpt2, ob->obmat, &pt2->x);
-
- float vec[3];
- sub_v3_v3v3(vec, fpt1, fpt2);
- float angle = angle_on_axis_v3v3_v3(vec_ref, vec, axis);
- /* Use sin to get a value between 0 and 1. */
- weight_pt = 1.0f - sin(angle);
- break;
- }
- default:
- break;
+ if (mmd->object) {
+ bGPDspoint *pt = &gps->points[i];
+ weight_pt = calc_point_weight_by_distance(ob, mmd, dist_max, dist_min, pt);
}
/* Invert weight if required. */
@@ -198,7 +156,7 @@ static void deformStroke(GpencilModifierData *md,
if (dvert != NULL) {
MDeformWeight *dw = BKE_defvert_ensure_index(dvert, target_def_nr);
if (dw) {
- dw->weight = (mmd->flag & GP_WEIGHT_BLEND_DATA) ? dw->weight * weight_pt : weight_pt;
+ dw->weight = (mmd->flag & GP_WEIGHT_MULTIPLY_DATA) ? dw->weight * weight_pt : weight_pt;
CLAMP(dw->weight, mmd->min_weight, 1.0f);
}
}
@@ -223,7 +181,7 @@ static void bakeModifier(struct Main *UNUSED(bmain),
static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
{
- WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md;
+ WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md;
walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER);
walk(userData, ob, (ID **)&mmd->object, IDWALK_CB_NOP);
@@ -233,7 +191,7 @@ static void updateDepsgraph(GpencilModifierData *md,
const ModifierUpdateDepsgraphContext *ctx,
const int UNUSED(mode))
{
- WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md;
+ WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md;
if (mmd->object != NULL) {
DEG_add_object_relation(
ctx->node, mmd->object, DEG_OB_COMP_TRANSFORM, "GPencil Weight Modifier");
@@ -244,54 +202,36 @@ static void updateDepsgraph(GpencilModifierData *md,
static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams))
{
- WeightGpencilModifierData *mmd = (WeightGpencilModifierData *)md;
-
- return (mmd->target_vgname[0] == '\0');
-}
-
-static void distance_panel_draw(const bContext *UNUSED(C), Panel *panel)
-{
- PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
-
- uiLayout *layout = panel->layout;
- uiLayoutSetPropSep(layout, true);
+ WeightProxGpencilModifierData *mmd = (WeightProxGpencilModifierData *)md;
- uiItemR(layout, ptr, "object", 0, NULL, ICON_CUBE);
- uiLayout *sub = uiLayoutColumn(layout, true);
- uiItemR(sub, ptr, "distance_start", 0, NULL, ICON_NONE);
- uiItemR(sub, ptr, "distance_end", 0, "End", ICON_NONE);
+ return ((mmd->target_vgname[0] == '\0') || (mmd->object == NULL));
}
-static void panel_draw(const bContext *C, Panel *panel)
+static void panel_draw(const bContext *UNUSED(C), Panel *panel)
{
+ uiLayout *row, *sub;
uiLayout *layout = panel->layout;
PointerRNA ob_ptr;
PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr);
uiLayoutSetPropSep(layout, true);
- uiItemR(layout, ptr, "mode", 0, NULL, ICON_NONE);
+ row = uiLayoutRow(layout, true);
+ uiItemPointerR(row, ptr, "target_vertex_group", &ob_ptr, "vertex_groups", NULL, ICON_NONE);
+ sub = uiLayoutRow(row, true);
+ bool has_output = RNA_string_length(ptr, "target_vertex_group") != 0;
+ uiLayoutSetPropDecorate(sub, false);
+ uiLayoutSetActive(sub, has_output);
+ uiItemR(sub, ptr, "use_invert_output", 0, "", ICON_ARROW_LEFTRIGHT);
- const eWeightGpencilModifierMode mode = RNA_enum_get(ptr, "mode");
+ uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE);
- uiItemPointerR(layout, ptr, "target_vertex_group", &ob_ptr, "vertex_groups", NULL, ICON_NONE);
+ sub = uiLayoutColumn(layout, true);
+ uiItemR(sub, ptr, "distance_start", 0, NULL, ICON_NONE);
+ uiItemR(sub, ptr, "distance_end", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "minimum_weight", 0, NULL, ICON_NONE);
- uiItemR(layout, ptr, "use_invert_output", 0, NULL, ICON_NONE);
- uiItemR(layout, ptr, "use_blend", 0, NULL, ICON_NONE);
-
- switch (mode) {
- case GP_WEIGHT_MODE_DISTANCE:
- distance_panel_draw(C, panel);
- break;
- case GP_WEIGHT_MODE_ANGLE:
- uiItemR(layout, ptr, "angle", 0, NULL, ICON_NONE);
- uiItemR(layout, ptr, "axis", 0, NULL, ICON_NONE);
- uiItemR(layout, ptr, "space", 0, NULL, ICON_NONE);
- break;
- default:
- break;
- }
+ uiItemR(layout, ptr, "use_multiply", 0, NULL, ICON_NONE);
gpencil_modifier_panel_end(layout, ptr);
}
@@ -304,16 +244,16 @@ static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel)
static void panelRegister(ARegionType *region_type)
{
PanelType *panel_type = gpencil_modifier_panel_register(
- region_type, eGpencilModifierType_Weight, panel_draw);
+ region_type, eGpencilModifierType_WeightProximity, panel_draw);
gpencil_modifier_subpanel_register(
region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type);
}
-GpencilModifierTypeInfo modifierType_Gpencil_Weight = {
- /* name */ "Vertex Weight",
- /* structName */ "WeightGpencilModifierData",
- /* structSize */ sizeof(WeightGpencilModifierData),
+GpencilModifierTypeInfo modifierType_Gpencil_WeightProximity = {
+ /* name */ "Vertex Weight Proximity",
+ /* structName */ "WeightProxGpencilModifierData",
+ /* structSize */ sizeof(WeightProxGpencilModifierData),
/* type */ eGpencilModifierTypeType_Gpencil,
/* flags */ 0,
diff --git a/source/blender/gpu/GPU_material.h b/source/blender/gpu/GPU_material.h
index 312da491a36..e64521768f9 100644
--- a/source/blender/gpu/GPU_material.h
+++ b/source/blender/gpu/GPU_material.h
@@ -175,10 +175,7 @@ GPUNodeLink *GPU_uniformbuf_link_out(struct GPUMaterial *mat,
void GPU_material_output_link(GPUMaterial *material, GPUNodeLink *link);
void GPU_material_add_output_link_aov(GPUMaterial *material, GPUNodeLink *link, int hash);
-void GPU_material_sss_profile_create(GPUMaterial *material,
- float radii[3],
- const short *falloff_type,
- const float *sharpness);
+void GPU_material_sss_profile_create(GPUMaterial *material, float radii[3]);
struct GPUUniformBuf *GPU_material_sss_profile_get(GPUMaterial *material,
int sample_len,
struct GPUTexture **tex_profile);
diff --git a/source/blender/gpu/intern/gpu_material.c b/source/blender/gpu/intern/gpu_material.c
index 56e72fbeca9..6872a08e854 100644
--- a/source/blender/gpu/intern/gpu_material.c
+++ b/source/blender/gpu/intern/gpu_material.c
@@ -96,8 +96,6 @@ struct GPUMaterial {
float sss_enabled;
float sss_radii[3];
int sss_samples;
- short int sss_falloff;
- float sss_sharpness;
bool sss_dirty;
GPUTexture *coba_tex; /* 1D Texture array containing all color bands. */
@@ -266,18 +264,6 @@ static void sss_calculate_offsets(GPUSssKernelData *kd, int count, float exponen
}
}
-#define GAUSS_TRUNCATE 12.46f
-static float gaussian_profile(float r, float radius)
-{
- const float v = radius * radius * (0.25f * 0.25f);
- const float Rm = sqrtf(v * GAUSS_TRUNCATE);
-
- if (r >= Rm) {
- return 0.0f;
- }
- return expf(-r * r / (2.0f * v)) / (2.0f * M_PI * v);
-}
-
#define BURLEY_TRUNCATE 16.0f
#define BURLEY_TRUNCATE_CDF 0.9963790093708328f // cdf(BURLEY_TRUNCATE)
static float burley_profile(float r, float d)
@@ -287,45 +273,15 @@ static float burley_profile(float r, float d)
return (exp_r_d + exp_r_3_d) / (4.0f * d);
}
-static float cubic_profile(float r, float radius, float sharpness)
-{
- float Rm = radius * (1.0f + sharpness);
-
- if (r >= Rm) {
- return 0.0f;
- }
- /* custom variation with extra sharpness, to match the previous code */
- const float y = 1.0f / (1.0f + sharpness);
- float Rmy, ry, ryinv;
-
- Rmy = powf(Rm, y);
- ry = powf(r, y);
- ryinv = (r > 0.0f) ? powf(r, y - 1.0f) : 0.0f;
-
- const float Rmy5 = (Rmy * Rmy) * (Rmy * Rmy) * Rmy;
- const float f = Rmy - ry;
- const float num = f * (f * f) * (y * ryinv);
-
- return (10.0f * num) / (Rmy5 * M_PI);
-}
-
-static float eval_profile(float r, short falloff_type, float sharpness, float param)
+static float eval_profile(float r, float param)
{
r = fabsf(r);
-
- if (ELEM(falloff_type, SHD_SUBSURFACE_BURLEY, SHD_SUBSURFACE_RANDOM_WALK)) {
- return burley_profile(r, param) / BURLEY_TRUNCATE_CDF;
- }
- if (falloff_type == SHD_SUBSURFACE_CUBIC) {
- return cubic_profile(r, param, sharpness);
- }
-
- return gaussian_profile(r, param);
+ return burley_profile(r, param) / BURLEY_TRUNCATE_CDF;
}
/* Resolution for each sample of the precomputed kernel profile */
#define INTEGRAL_RESOLUTION 32
-static float eval_integral(float x0, float x1, short falloff_type, float sharpness, float param)
+static float eval_integral(float x0, float x1, float param)
{
const float range = x1 - x0;
const float step = range / INTEGRAL_RESOLUTION;
@@ -333,7 +289,7 @@ static float eval_integral(float x0, float x1, short falloff_type, float sharpne
for (int i = 0; i < INTEGRAL_RESOLUTION; i++) {
float x = x0 + range * ((float)i + 0.5f) / (float)INTEGRAL_RESOLUTION;
- float y = eval_profile(x, falloff_type, sharpness, param);
+ float y = eval_profile(x, param);
integral += y * step;
}
@@ -341,8 +297,7 @@ static float eval_integral(float x0, float x1, short falloff_type, float sharpne
}
#undef INTEGRAL_RESOLUTION
-static void compute_sss_kernel(
- GPUSssKernelData *kd, const float radii[3], int sample_len, int falloff_type, float sharpness)
+static void compute_sss_kernel(GPUSssKernelData *kd, const float radii[3], int sample_len)
{
float rad[3];
/* Minimum radius */
@@ -353,27 +308,15 @@ static void compute_sss_kernel(
/* Christensen-Burley fitting */
float l[3], d[3];
- if (ELEM(falloff_type, SHD_SUBSURFACE_BURLEY, SHD_SUBSURFACE_RANDOM_WALK)) {
- mul_v3_v3fl(l, rad, 0.25f * M_1_PI);
- const float A = 1.0f;
- const float s = 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
- /* XXX 0.6f Out of nowhere to match cycles! Empirical! Can be tweak better. */
- mul_v3_v3fl(d, l, 0.6f / s);
- mul_v3_v3fl(rad, d, BURLEY_TRUNCATE);
- kd->max_radius = MAX3(rad[0], rad[1], rad[2]);
-
- copy_v3_v3(kd->param, d);
- }
- else if (falloff_type == SHD_SUBSURFACE_CUBIC) {
- copy_v3_v3(kd->param, rad);
- mul_v3_fl(rad, 1.0f + sharpness);
- kd->max_radius = MAX3(rad[0], rad[1], rad[2]);
- }
- else {
- kd->max_radius = MAX3(rad[0], rad[1], rad[2]);
+ mul_v3_v3fl(l, rad, 0.25f * M_1_PI);
+ const float A = 1.0f;
+ const float s = 1.9f - A + 3.5f * (A - 0.8f) * (A - 0.8f);
+ /* XXX 0.6f Out of nowhere to match cycles! Empirical! Can be tweak better. */
+ mul_v3_v3fl(d, l, 0.6f / s);
+ mul_v3_v3fl(rad, d, BURLEY_TRUNCATE);
+ kd->max_radius = MAX3(rad[0], rad[1], rad[2]);
- copy_v3_v3(kd->param, rad);
- }
+ copy_v3_v3(kd->param, d);
/* Compute samples locations on the 1d kernel [-1..1] */
sss_calculate_offsets(kd, sample_len, SSS_EXPONENT);
@@ -403,9 +346,9 @@ static void compute_sss_kernel(
x0 *= kd->max_radius;
x1 *= kd->max_radius;
- kd->kernel[i][0] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[0]);
- kd->kernel[i][1] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[1]);
- kd->kernel[i][2] = eval_integral(x0, x1, falloff_type, sharpness, kd->param[2]);
+ kd->kernel[i][0] = eval_integral(x0, x1, kd->param[0]);
+ kd->kernel[i][1] = eval_integral(x0, x1, kd->param[1]);
+ kd->kernel[i][2] = eval_integral(x0, x1, kd->param[2]);
sum[0] += kd->kernel[i][0];
sum[1] += kd->kernel[i][1];
@@ -439,8 +382,6 @@ static void compute_sss_kernel(
#define INTEGRAL_RESOLUTION 512
static void compute_sss_translucence_kernel(const GPUSssKernelData *kd,
int resolution,
- short falloff_type,
- float sharpness,
float **output)
{
float(*texels)[4];
@@ -463,9 +404,9 @@ static void compute_sss_translucence_kernel(const GPUSssKernelData *kd,
float dist = hypotf(r + r_step * 0.5f, d);
float profile[3];
- profile[0] = eval_profile(dist, falloff_type, sharpness, kd->param[0]);
- profile[1] = eval_profile(dist, falloff_type, sharpness, kd->param[1]);
- profile[2] = eval_profile(dist, falloff_type, sharpness, kd->param[2]);
+ profile[0] = eval_profile(dist, kd->param[0]);
+ profile[1] = eval_profile(dist, kd->param[1]);
+ profile[2] = eval_profile(dist, kd->param[2]);
/* Since the profile and configuration are radially symmetrical we
* can just evaluate it once and weight it accordingly */
@@ -499,14 +440,9 @@ static void compute_sss_translucence_kernel(const GPUSssKernelData *kd,
}
#undef INTEGRAL_RESOLUTION
-void GPU_material_sss_profile_create(GPUMaterial *material,
- float radii[3],
- const short *falloff_type,
- const float *sharpness)
+void GPU_material_sss_profile_create(GPUMaterial *material, float radii[3])
{
copy_v3_v3(material->sss_radii, radii);
- material->sss_falloff = (falloff_type) ? *falloff_type : 0.0;
- material->sss_sharpness = (sharpness) ? *sharpness : 0.0;
material->sss_dirty = true;
material->sss_enabled = true;
@@ -527,20 +463,14 @@ struct GPUUniformBuf *GPU_material_sss_profile_get(GPUMaterial *material,
if (material->sss_dirty || (material->sss_samples != sample_len)) {
GPUSssKernelData kd;
- float sharpness = material->sss_sharpness;
-
- /* XXX Black magic but it seems to fit. Maybe because we integrate -1..1 */
- sharpness *= 0.5f;
-
- compute_sss_kernel(&kd, material->sss_radii, sample_len, material->sss_falloff, sharpness);
+ compute_sss_kernel(&kd, material->sss_radii, sample_len);
/* Update / Create UBO */
GPU_uniformbuf_update(material->sss_profile, &kd);
/* Update / Create Tex */
float *translucence_profile;
- compute_sss_translucence_kernel(
- &kd, 64, material->sss_falloff, sharpness, &translucence_profile);
+ compute_sss_translucence_kernel(&kd, 64, &translucence_profile);
if (material->sss_tex_profile != NULL) {
GPU_texture_free(material->sss_tex_profile);
diff --git a/source/blender/gpu/intern/gpu_material_library.h b/source/blender/gpu/intern/gpu_material_library.h
index 782d89d6f2a..d3b12d3a2b7 100644
--- a/source/blender/gpu/intern/gpu_material_library.h
+++ b/source/blender/gpu/intern/gpu_material_library.h
@@ -27,7 +27,7 @@
#include "GPU_material.h"
#define MAX_FUNCTION_NAME 64
-#define MAX_PARAMETER 32
+#define MAX_PARAMETER 36
struct GSet;
diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl
index d77259638fd..bba84c2be52 100644
--- a/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl
+++ b/source/blender/gpu/shaders/material/gpu_shader_material_principled.glsl
@@ -19,6 +19,8 @@ void node_bsdf_principled(vec4 base_color,
float subsurface,
vec3 subsurface_radius,
vec4 subsurface_color,
+ float subsurface_ior,
+ float subsurface_anisotropy,
float metallic,
float specular,
float specular_tint,
@@ -201,6 +203,6 @@ void node_bsdf_principled(vec4 base_color,
#else
/* clang-format off */
/* Stub principled because it is not compatible with volumetrics. */
-# define node_bsdf_principled(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, cc, dd, result) (result = CLOSURE_DEFAULT)
+# define node_bsdf_principled(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb, cc, dd, ee, ff, result) (result = CLOSURE_DEFAULT)
/* clang-format on */
#endif
diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl
index 5129bf71903..d0c159cdf37 100644
--- a/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl
+++ b/source/blender/gpu/shaders/material/gpu_shader_material_subsurface_scattering.glsl
@@ -5,8 +5,8 @@ CLOSURE_EVAL_FUNCTION_DECLARE_1(node_subsurface_scattering, Diffuse)
void node_subsurface_scattering(vec4 color,
float scale,
vec3 radius,
- float sharpen,
- float texture_blur,
+ float ior,
+ float anisotropy,
vec3 N,
float sss_id,
out Closure result)
@@ -20,15 +20,7 @@ void node_subsurface_scattering(vec4 color,
result = CLOSURE_DEFAULT;
- /* Not perfect for texture_blur values between 0.0 and 1.0.
- * Interpolate between separated color and color applied on irradiance. */
- float one_minus_texture_blur = 1.0 - texture_blur;
- vec3 sss_albedo = color.rgb * one_minus_texture_blur + texture_blur;
- vec3 radiance_tint = color.rgb * texture_blur + one_minus_texture_blur;
- /* Consider output radiance as irradiance. */
- out_Diffuse_0.radiance *= radiance_tint;
-
- closure_load_sss_data(scale, out_Diffuse_0.radiance, sss_albedo, int(sss_id), result);
+ closure_load_sss_data(scale, out_Diffuse_0.radiance, color.rgb, int(sss_id), result);
/* TODO(fclem) Try to not use this. */
closure_load_ssr_data(vec3(0.0), 0.0, in_Diffuse_0.N, -1.0, result);
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
index 450527c7443..2a3c6f4e3db 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
@@ -283,7 +283,7 @@
.colorband = NULL, \
}
-#define _DNA_DEFAULT_WeightGpencilModifierData \
+#define _DNA_DEFAULT_WeightProxGpencilModifierData \
{ \
.target_vgname = "", \
.material = NULL, \
@@ -291,12 +291,23 @@
.vgname = "", \
.pass_index = 0, \
.flag = 0, \
- .axis = 1, \
.layer_pass = 0, \
.dist_start = 0.0f, \
.dist_end = 20.0f, \
}
+#define _DNA_DEFAULT_WeightAngleGpencilModifierData \
+ { \
+ .target_vgname = "", \
+ .material = NULL, \
+ .layername = "", \
+ .vgname = "", \
+ .pass_index = 0, \
+ .flag = 0, \
+ .axis = 1, \
+ .layer_pass = 0, \
+ }
+
#define _DNA_DEFAULT_LineartGpencilModifierData \
{ \
.edge_types = LRT_EDGE_FLAG_ALL_TYPE, \
@@ -314,9 +325,13 @@
{ \
.start_fac = 0.1f,\
.end_fac = 0.1f,\
- .overshoot_fac = 0.01f,\
+ .overshoot_fac = 0.1f,\
.pass_index = 0,\
.material = NULL,\
+ .flag = GP_LENGTH_USE_CURVATURE,\
+ .point_density = 30.0f,\
+ .segment_influence = 0.0f,\
+ .max_angle = DEG2RAD(170.0f),\
}
#define _DNA_DEFAULT_DashGpencilModifierData \
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h
index d3429329ef6..8d967a38808 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_types.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h
@@ -55,8 +55,9 @@ typedef enum GpencilModifierType {
eGpencilModifierType_Texture = 18,
eGpencilModifierType_Lineart = 19,
eGpencilModifierType_Length = 20,
- eGpencilModifierType_Weight = 21,
+ eGpencilModifierType_WeightProximity = 21,
eGpencilModifierType_Dash = 22,
+ eGpencilModifierType_WeightAngle = 23,
/* Keep last. */
NUM_GREASEPENCIL_MODIFIER_TYPES,
} GpencilModifierType;
@@ -493,7 +494,10 @@ typedef struct LengthGpencilModifierData {
float overshoot_fac;
/** Modifier mode. */
int mode;
- char _pad[4];
+ /* Curvature parameters. */
+ float point_density;
+ float segment_influence;
+ float max_angle;
} LengthGpencilModifierData;
typedef enum eLengthGpencil_Flag {
@@ -501,6 +505,8 @@ typedef enum eLengthGpencil_Flag {
GP_LENGTH_INVERT_PASS = (1 << 1),
GP_LENGTH_INVERT_LAYERPASS = (1 << 2),
GP_LENGTH_INVERT_MATERIAL = (1 << 3),
+ GP_LENGTH_USE_CURVATURE = (1 << 4),
+ GP_LENGTH_INVERT_CURVATURE = (1 << 5),
} eLengthGpencil_Flag;
typedef enum eLengthGpencil_Type {
@@ -891,7 +897,7 @@ typedef enum eTextureGpencil_Mode {
STROKE_AND_FILL = 2,
} eTextureGpencil_Mode;
-typedef struct WeightGpencilModifierData {
+typedef struct WeightProxGpencilModifierData {
GpencilModifierData modifier;
/** Target vertexgroup name, MAX_VGROUP_NAME. */
char target_vgname[64];
@@ -909,22 +915,39 @@ typedef struct WeightGpencilModifierData {
float min_weight;
/** Custom index for passes. */
int layer_pass;
- /** Calculation Mode. */
- short mode;
- /** Axis. */
- short axis;
- /** Angle */
- float angle;
/** Start/end distances. */
float dist_start;
float dist_end;
- /** Space (Local/World). */
- short space;
- char _pad[6];
/** Reference object */
struct Object *object;
-} WeightGpencilModifierData;
+} WeightProxGpencilModifierData;
+
+typedef struct WeightAngleGpencilModifierData {
+ GpencilModifierData modifier;
+ /** Target vertexgroup name, MAX_VGROUP_NAME. */
+ char target_vgname[64];
+ /** Material for filtering. */
+ struct Material *material;
+ /** Layer name. */
+ char layername[64];
+ /** Optional vertexgroup filter name, MAX_VGROUP_NAME. */
+ char vgname[64];
+ /** Custom index for passes. */
+ int pass_index;
+ /** Flags. */
+ int flag;
+ /** Minimum valid weight (clamp value). */
+ float min_weight;
+ /** Custom index for passes. */
+ int layer_pass;
+ /** Axis. */
+ short axis;
+ /** Space (Local/World). */
+ short space;
+ /** Angle */
+ float angle;
+} WeightAngleGpencilModifierData;
typedef enum eWeightGpencil_Flag {
GP_WEIGHT_INVERT_LAYER = (1 << 0),
@@ -932,15 +955,10 @@ typedef enum eWeightGpencil_Flag {
GP_WEIGHT_INVERT_VGROUP = (1 << 2),
GP_WEIGHT_INVERT_LAYERPASS = (1 << 3),
GP_WEIGHT_INVERT_MATERIAL = (1 << 4),
- GP_WEIGHT_BLEND_DATA = (1 << 5),
+ GP_WEIGHT_MULTIPLY_DATA = (1 << 5),
GP_WEIGHT_INVERT_OUTPUT = (1 << 6),
} eWeightGpencil_Flag;
-typedef enum eWeightGpencilModifierMode {
- GP_WEIGHT_MODE_DISTANCE = 0,
- GP_WEIGHT_MODE_ANGLE = 1,
-} eWeightGpencilModifierMode;
-
typedef enum eGpencilModifierSpace {
GP_SPACE_LOCAL = 0,
GP_SPACE_WORLD = 1,
diff --git a/source/blender/makesdna/DNA_layer_types.h b/source/blender/makesdna/DNA_layer_types.h
index 63e4597150c..520f989452c 100644
--- a/source/blender/makesdna/DNA_layer_types.h
+++ b/source/blender/makesdna/DNA_layer_types.h
@@ -68,7 +68,7 @@ typedef enum eViewLayerCryptomatteFlags {
VIEW_LAYER_CRYPTOMATTE_OBJECT = (1 << 0),
VIEW_LAYER_CRYPTOMATTE_MATERIAL = (1 << 1),
VIEW_LAYER_CRYPTOMATTE_ASSET = (1 << 2),
- VIEW_LAYER_CRYPTOMATTE_ACCURATE = (1 << 3),
+ /* VIEW_LAYER_CRYPTOMATTE_ACCURATE = (1 << 3), */ /* DEPRECATED */
} eViewLayerCryptomatteFlags;
#define VIEW_LAYER_CRYPTOMATTE_ALL \
(VIEW_LAYER_CRYPTOMATTE_OBJECT | VIEW_LAYER_CRYPTOMATTE_MATERIAL | VIEW_LAYER_CRYPTOMATTE_ASSET)
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index f4c88333528..cf159a1e28d 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1032,6 +1032,11 @@ typedef struct NodeShaderTexPointDensity {
char _pad2[4];
} NodeShaderTexPointDensity;
+typedef struct NodeShaderPrincipled {
+ char use_subsurface_auto_radius;
+ char _pad[3];
+} NodeShaderPrincipled;
+
/* TEX_output */
typedef struct TexNodeOutput {
char name[64];
@@ -1452,6 +1457,11 @@ typedef struct NodeGeometryCurveToPoints {
uint8_t mode;
} NodeGeometryCurveToPoints;
+typedef struct NodeGeometryCurveSample {
+ /* GeometryNodeCurveSampleMode. */
+ uint8_t mode;
+} NodeGeometryCurveSample;
+
typedef struct NodeGeometryAttributeTransfer {
/* AttributeDomain. */
int8_t domain;
@@ -1798,11 +1808,12 @@ enum {
enum {
#ifdef DNA_DEPRECATED_ALLOW
SHD_SUBSURFACE_COMPATIBLE = 0, /* Deprecated */
-#endif
SHD_SUBSURFACE_CUBIC = 1,
SHD_SUBSURFACE_GAUSSIAN = 2,
- SHD_SUBSURFACE_BURLEY = 3,
- SHD_SUBSURFACE_RANDOM_WALK = 4,
+#endif
+ SHD_SUBSURFACE_DIFFUSION = 3,
+ SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS = 4,
+ SHD_SUBSURFACE_RANDOM_WALK = 5,
};
/* blur node */
diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h
index 61707964191..9ecf94ebd6e 100644
--- a/source/blender/makesdna/DNA_scene_defaults.h
+++ b/source/blender/makesdna/DNA_scene_defaults.h
@@ -135,8 +135,6 @@
.border.xmax = 1.0f, \
.border.ymax = 1.0f, \
\
- .preview_start_resolution = 64, \
- \
.line_thickness_mode = R_LINE_THICKNESS_ABSOLUTE, \
.unit_line_thickness = 1.0f, \
\
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index 7800e7f9efe..b28c3ac2b85 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -261,7 +261,7 @@ typedef enum eScenePassType {
SCE_PASS_UNUSED_3 = (1 << 4), /* SPEC */
SCE_PASS_SHADOW = (1 << 5),
SCE_PASS_AO = (1 << 6),
- SCE_PASS_UNUSED_4 = (1 << 7), /* REFLECT */
+ SCE_PASS_POSITION = (1 << 7),
SCE_PASS_NORMAL = (1 << 8),
SCE_PASS_VECTOR = (1 << 9),
SCE_PASS_UNUSED_5 = (1 << 10), /* REFRACT */
@@ -293,6 +293,7 @@ typedef enum eScenePassType {
#define RE_PASSNAME_COMBINED "Combined"
#define RE_PASSNAME_Z "Depth"
#define RE_PASSNAME_VECTOR "Vector"
+#define RE_PASSNAME_POSITION "Position"
#define RE_PASSNAME_NORMAL "Normal"
#define RE_PASSNAME_UV "UV"
#define RE_PASSNAME_EMIT "Emit"
@@ -592,7 +593,7 @@ typedef enum eBakeSaveMode {
/** #BakeData.pass_filter */
typedef enum eBakePassFilter {
R_BAKE_PASS_FILTER_NONE = 0,
- R_BAKE_PASS_FILTER_AO = (1 << 0),
+ R_BAKE_PASS_FILTER_UNUSED = (1 << 0),
R_BAKE_PASS_FILTER_EMIT = (1 << 1),
R_BAKE_PASS_FILTER_DIFFUSE = (1 << 2),
R_BAKE_PASS_FILTER_GLOSSY = (1 << 3),
@@ -653,7 +654,8 @@ typedef struct RenderData {
/**
* render tile dimensions
*/
- int tilex, tiley;
+ int tilex DNA_DEPRECATED;
+ int tiley DNA_DEPRECATED;
short planes DNA_DEPRECATED;
short imtype DNA_DEPRECATED;
@@ -764,13 +766,10 @@ typedef struct RenderData {
/* Cycles baking */
struct BakeData bake;
- int preview_start_resolution;
+ int _pad8;
short preview_pixel_size;
- /* Type of the debug pass to use.
- * Only used when built with debug passes support.
- */
- short debug_pass_type;
+ short _pad4;
/* MultiView */
/** SceneRenderView. */
@@ -1344,6 +1343,7 @@ typedef struct SequencerToolSettings {
/** When there are many snap points, 0-1 range corresponds to resolution from boundbox to all
* possible snap points. */
int snap_distance;
+ int pivot_point;
} SequencerToolSettings;
typedef enum eSeqOverlapMode {
@@ -1886,12 +1886,12 @@ enum {
#define R_COMP_CROP (1 << 7)
#define R_SCEMODE_UNUSED_8 (1 << 8) /* cleared */
#define R_SINGLE_LAYER (1 << 9)
-#define R_EXR_TILE_FILE (1 << 10)
+#define R_SCEMODE_UNUSED_10 (1 << 10) /* cleared */
#define R_SCEMODE_UNUSED_11 (1 << 11) /* cleared */
#define R_NO_IMAGE_LOAD (1 << 12)
#define R_SCEMODE_UNUSED_13 (1 << 13) /* cleared */
#define R_NO_FRAME_UPDATE (1 << 14)
-#define R_FULL_SAMPLE (1 << 15)
+#define R_SCEMODE_UNUSED_15 (1 << 15) /* cleared */
#define R_SCEMODE_UNUSED_16 (1 << 16) /* cleared */
#define R_SCEMODE_UNUSED_17 (1 << 17) /* cleared */
#define R_TEXNODE_PREVIEW (1 << 18)
diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h
index 03c38eb71a0..25330acd486 100644
--- a/source/blender/makesdna/DNA_sequence_types.h
+++ b/source/blender/makesdna/DNA_sequence_types.h
@@ -74,6 +74,8 @@ typedef struct StripTransform {
float scale_x;
float scale_y;
float rotation;
+ /** 0-1 range, use SEQ_image_transform_origin_offset_pixelspace_get to convert to pixel space. */
+ float origin[2];
} StripTransform;
typedef struct StripColorBalance {
@@ -516,7 +518,7 @@ enum {
SEQ_OVERLAP = (1 << 3),
SEQ_FILTERY = (1 << 4),
SEQ_MUTE = (1 << 5),
- SEQ_FLAG_UNUSED_6 = (1 << 6), /* cleared */
+ SEQ_FLAG_SKIP_THUMBNAILS = (1 << 6),
SEQ_REVERSE_FRAMES = (1 << 7),
SEQ_IPO_FRAME_LOCKED = (1 << 8),
SEQ_EFFECT_NOT_LOADED = (1 << 9),
@@ -722,6 +724,7 @@ enum {
SEQ_CACHE_PREFETCH_ENABLE = (1 << 10),
SEQ_CACHE_DISK_CACHE_ENABLE = (1 << 11),
+ SEQ_CACHE_STORE_THUMBNAIL = (1 << 12),
};
#ifdef __cplusplus
diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h
index 6505816256c..e849039fa93 100644
--- a/source/blender/makesdna/DNA_space_types.h
+++ b/source/blender/makesdna/DNA_space_types.h
@@ -583,6 +583,7 @@ typedef struct SequencerPreviewOverlay {
/* SequencerPreviewOverlay.flag */
typedef enum eSpaceSeq_SequencerPreviewOverlay_Flag {
+ SEQ_PREVIEW_SHOW_OUTLINE_SELECTED = (1 << 2),
SEQ_PREVIEW_SHOW_SAFE_MARGINS = (1 << 3),
SEQ_PREVIEW_SHOW_GPENCIL = (1 << 4),
SEQ_PREVIEW_SHOW_SAFE_CENTER = (1 << 9),
@@ -597,6 +598,7 @@ typedef struct SequencerTimelineOverlay {
/* SequencerTimelineOverlay.flag */
typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag {
SEQ_TIMELINE_SHOW_STRIP_OFFSETS = (1 << 1),
+ SEQ_TIMELINE_SHOW_THUMBNAILS = (1 << 2),
SEQ_TIMELINE_SHOW_FCURVES = (1 << 5),
SEQ_TIMELINE_ALL_WAVEFORMS = (1 << 7), /* draw all waveforms */
SEQ_TIMELINE_NO_WAVEFORMS = (1 << 8), /* draw no waveforms */
@@ -606,6 +608,13 @@ typedef enum eSpaceSeq_SequencerTimelineOverlay_Flag {
SEQ_TIMELINE_SHOW_GRID = (1 << 18),
} eSpaceSeq_SequencerTimelineOverlay_Flag;
+typedef struct SpaceSeqRuntime {
+ /** Required for Thumbnail job start condition. */
+ struct rctf last_thumbnail_area;
+ /** Stores lists of most recently displayed thumbnails. */
+ struct GHash *last_displayed_thumbnails;
+} SpaceSeqRuntime;
+
/* Sequencer */
typedef struct SpaceSeq {
SpaceLink *next, *prev;
@@ -649,6 +658,7 @@ typedef struct SpaceSeq {
char multiview_eye;
char _pad2[7];
+ SpaceSeqRuntime runtime;
} SpaceSeq;
/* SpaceSeq.mainb */
@@ -685,6 +695,7 @@ typedef enum eSpaceSeq_Flag {
SPACE_SEQ_FLAG_UNUSED_15 = (1 << 15),
SPACE_SEQ_FLAG_UNUSED_16 = (1 << 16),
SEQ_USE_PROXIES = (1 << 17),
+ SEQ_SHOW_GRID = (1 << 18),
} eSpaceSeq_Flag;
/* SpaceSeq.view */
diff --git a/source/blender/makesdna/DNA_uuid_types.h b/source/blender/makesdna/DNA_uuid_types.h
index 30c8beaa628..fa0a78f074b 100644
--- a/source/blender/makesdna/DNA_uuid_types.h
+++ b/source/blender/makesdna/DNA_uuid_types.h
@@ -28,15 +28,17 @@ extern "C" {
/**
* \brief Universally Unique Identifier according to RFC4122.
+ *
+ * Cannot be named simply `UUID`, because Windows already defines that type.
*/
-typedef struct UUID {
+typedef struct bUUID {
uint32_t time_low;
uint16_t time_mid;
uint16_t time_hi_and_version;
uint8_t clock_seq_hi_and_reserved;
uint8_t clock_seq_low;
uint8_t node[6];
-} UUID;
+} bUUID;
#ifdef __cplusplus
}
diff --git a/source/blender/makesdna/DNA_view2d_types.h b/source/blender/makesdna/DNA_view2d_types.h
index c385ac04bd3..f8166305fd9 100644
--- a/source/blender/makesdna/DNA_view2d_types.h
+++ b/source/blender/makesdna/DNA_view2d_types.h
@@ -132,6 +132,8 @@ enum {
V2D_PIXELOFS_X = (1 << 2),
/* apply pixel offsets on y-axis when setting view matrices */
V2D_PIXELOFS_Y = (1 << 3),
+ /* zoom, pan or similar action is in progress */
+ V2D_IS_NAVIGATING = (1 << 9),
/* view settings need to be set still... */
V2D_IS_INIT = (1 << 10),
};
diff --git a/source/blender/makesdna/DNA_workspace_types.h b/source/blender/makesdna/DNA_workspace_types.h
index e0294d3534c..a0856588a58 100644
--- a/source/blender/makesdna/DNA_workspace_types.h
+++ b/source/blender/makesdna/DNA_workspace_types.h
@@ -29,6 +29,15 @@
extern "C" {
#endif
+/** #bToolRef_Runtime.flag */
+enum {
+ /**
+ * This tool should use the fallback key-map.
+ * Typically gizmos handle this but some tools (such as the knife tool) don't use a gizmo.
+ */
+ TOOLREF_FLAG_FALLBACK_KEYMAP = (1 << 0),
+};
+
#
#
typedef struct bToolRef_Runtime {
@@ -47,6 +56,8 @@ typedef struct bToolRef_Runtime {
/** Index when a tool is a member of a group. */
int index;
+ /** Options: `TOOLREF_FLAG_*`. */
+ int flag;
} bToolRef_Runtime;
/* Stored per mode. */
diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c
index 4cb8610f6ac..2dbbb35c3ca 100644
--- a/source/blender/makesdna/intern/dna_defaults.c
+++ b/source/blender/makesdna/intern/dna_defaults.c
@@ -318,7 +318,8 @@ SDNA_DEFAULT_DECL_STRUCT(TextureGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(ThickGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(TimeGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData);
-SDNA_DEFAULT_DECL_STRUCT(WeightGpencilModifierData);
+SDNA_DEFAULT_DECL_STRUCT(WeightProxGpencilModifierData);
+SDNA_DEFAULT_DECL_STRUCT(WeightAngleGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData);
@@ -548,7 +549,8 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(ThickGpencilModifierData),
SDNA_DEFAULT_DECL(TimeGpencilModifierData),
SDNA_DEFAULT_DECL(TintGpencilModifierData),
- SDNA_DEFAULT_DECL(WeightGpencilModifierData),
+ SDNA_DEFAULT_DECL(WeightAngleGpencilModifierData),
+ SDNA_DEFAULT_DECL(WeightProxGpencilModifierData),
SDNA_DEFAULT_DECL(LineartGpencilModifierData),
SDNA_DEFAULT_DECL(LengthGpencilModifierData),
SDNA_DEFAULT_DECL(DashGpencilModifierData),
diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c
index 4fa33424994..a2d5b134056 100644
--- a/source/blender/makesrna/intern/rna_gpencil_modifier.c
+++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c
@@ -58,6 +58,17 @@
#include "WM_types.h"
const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
+ {0, "", 0, N_("Modify"), ""},
+ {eGpencilModifierType_WeightAngle,
+ "GP_WEIGHT_ANGLE",
+ ICON_MOD_VERTEX_WEIGHT,
+ "Vertex Weight Angle",
+ "Generate Vertex Weights base on stroke angle"},
+ {eGpencilModifierType_WeightProximity,
+ "GP_WEIGHT_PROXIMITY",
+ ICON_MOD_VERTEX_WEIGHT,
+ "Vertex Weight Proximity",
+ "Generate Vertex Weights base on distance to object"},
{0, "", 0, N_("Generate"), ""},
{eGpencilModifierType_Array,
"GP_ARRAY",
@@ -74,6 +85,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_DASH,
"Dot Dash",
"Generate dot-dash styled strokes"},
+ {eGpencilModifierType_Length,
+ "GP_LENGTH",
+ ICON_MOD_LENGTH,
+ "Length",
+ "Extend or shrink strokes"},
{eGpencilModifierType_Lineart,
"GP_LINEART",
ICON_MOD_LINEART,
@@ -99,11 +115,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_SUBSURF,
"Subdivide",
"Subdivide stroke adding more control points"},
- {eGpencilModifierType_Weight,
- "GP_WEIGHT",
- ICON_MOD_VERTEX_WEIGHT,
- "Vertex Weight",
- "Generate Vertex Weights"},
{0, "", 0, N_("Deform"), ""},
{eGpencilModifierType_Armature,
"GP_ARMATURE",
@@ -120,11 +131,6 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_LATTICE,
"Lattice",
"Deform strokes using lattice"},
- {eGpencilModifierType_Length,
- "GP_LENGTH",
- ICON_MOD_LENGTH,
- "Length",
- "Extend or shrink strokes"},
{eGpencilModifierType_Noise, "GP_NOISE", ICON_MOD_NOISE, "Noise", "Add noise to strokes"},
{eGpencilModifierType_Offset,
"GP_OFFSET",
@@ -244,8 +250,10 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr)
return &RNA_TintGpencilModifier;
case eGpencilModifierType_Time:
return &RNA_TimeGpencilModifier;
- case eGpencilModifierType_Weight:
- return &RNA_WeightGpencilModifier;
+ case eGpencilModifierType_WeightProximity:
+ return &RNA_WeightProxGpencilModifier;
+ case eGpencilModifierType_WeightAngle:
+ return &RNA_WeightAngleGpencilModifier;
case eGpencilModifierType_Color:
return &RNA_ColorGpencilModifier;
case eGpencilModifierType_Array:
@@ -346,8 +354,10 @@ RNA_GP_MOD_VGROUP_NAME_SET(Offset, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Armature, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Texture, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Tint, vgname);
-RNA_GP_MOD_VGROUP_NAME_SET(Weight, target_vgname);
-RNA_GP_MOD_VGROUP_NAME_SET(Weight, vgname);
+RNA_GP_MOD_VGROUP_NAME_SET(WeightProx, target_vgname);
+RNA_GP_MOD_VGROUP_NAME_SET(WeightProx, vgname);
+RNA_GP_MOD_VGROUP_NAME_SET(WeightAngle, target_vgname);
+RNA_GP_MOD_VGROUP_NAME_SET(WeightAngle, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Lineart, vgname);
# undef RNA_GP_MOD_VGROUP_NAME_SET
@@ -380,7 +390,7 @@ static void greasepencil_modifier_object_set(Object *self,
RNA_GP_MOD_OBJECT_SET(Armature, object, OB_ARMATURE);
RNA_GP_MOD_OBJECT_SET(Lattice, object, OB_LATTICE);
RNA_GP_MOD_OBJECT_SET(Mirror, object, OB_EMPTY);
-RNA_GP_MOD_OBJECT_SET(Weight, object, OB_EMPTY);
+RNA_GP_MOD_OBJECT_SET(WeightProx, object, OB_EMPTY);
# undef RNA_GP_MOD_OBJECT_SET
@@ -554,11 +564,21 @@ static void rna_ThickGpencilModifier_material_set(PointerRNA *ptr,
rna_GpencilModifier_material_set(ptr, value, ma_target, reports);
}
-static void rna_WeightGpencilModifier_material_set(PointerRNA *ptr,
- PointerRNA value,
- struct ReportList *reports)
+static void rna_WeightProxGpencilModifier_material_set(PointerRNA *ptr,
+ PointerRNA value,
+ struct ReportList *reports)
{
- WeightGpencilModifierData *tmd = (WeightGpencilModifierData *)ptr->data;
+ WeightProxGpencilModifierData *tmd = (WeightProxGpencilModifierData *)ptr->data;
+ Material **ma_target = &tmd->material;
+
+ rna_GpencilModifier_material_set(ptr, value, ma_target, reports);
+}
+
+static void rna_WeightAngleGpencilModifier_material_set(PointerRNA *ptr,
+ PointerRNA value,
+ struct ReportList *reports)
+{
+ WeightAngleGpencilModifierData *tmd = (WeightAngleGpencilModifierData *)ptr->data;
Material **ma_target = &tmd->material;
rna_GpencilModifier_material_set(ptr, value, ma_target, reports);
@@ -2783,24 +2803,129 @@ static void rna_def_modifier_gpenciltexture(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
-static void rna_def_modifier_gpencilweight(BlenderRNA *brna)
+static void rna_def_modifier_gpencilweight_proximity(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "WeightProxGpencilModifier", "GpencilModifier");
+ RNA_def_struct_ui_text(srna, "Weight Modifier Proximity", "Calculate Vertex Weight dynamically");
+ RNA_def_struct_sdna(srna, "WeightProxGpencilModifierData");
+ RNA_def_struct_ui_icon(srna, ICON_MOD_VERTEX_WEIGHT);
+
+ RNA_define_lib_overridable(true);
+
+ prop = RNA_def_property(srna, "target_vertex_group", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "target_vgname");
+ RNA_def_property_ui_text(prop, "Vertex Group", "Output Vertex group");
+ RNA_def_property_string_funcs(
+ prop, NULL, NULL, "rna_WeightProxGpencilModifier_target_vgname_set");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_multiply", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_MULTIPLY_DATA);
+ RNA_def_property_ui_text(
+ prop,
+ "Multiply Weights",
+ "Multiply the calculated weights with the existing values in the vertex group");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_invert_output", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_OUTPUT);
+ RNA_def_property_ui_text(prop, "Invert", "Invert output weight values");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "layername");
+ RNA_def_property_ui_text(prop, "Layer", "Layer name");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_EDITABLE);
+ RNA_def_property_pointer_funcs(prop,
+ NULL,
+ "rna_WeightProxGpencilModifier_material_set",
+ NULL,
+ "rna_GpencilModifier_material_poll");
+ RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "vgname");
+ RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modulating the deform");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightProxGpencilModifier_vgname_set");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ /* Distance reference object */
+ prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Target Object", "Object used as distance reference");
+ RNA_def_property_pointer_funcs(
+ prop, NULL, "rna_WeightProxGpencilModifier_object_set", NULL, NULL);
+ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
+
+ prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_sdna(prop, NULL, "dist_start");
+ RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2);
+ RNA_def_property_ui_text(prop, "Lowest", "Start value for distance calculation");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "minimum_weight", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "min_weight");
+ RNA_def_property_ui_text(prop, "Minimum", "Minimum value for vertex weight");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_sdna(prop, NULL, "dist_end");
+ RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2);
+ RNA_def_property_ui_text(prop, "Highest", "Max value for distance calculation");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "pass_index");
+ RNA_def_property_range(prop, 0, 100);
+ RNA_def_property_ui_text(prop, "Pass", "Pass index");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_LAYER);
+ RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_MATERIAL);
+ RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_PASS);
+ RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_vertex", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_VGROUP);
+ RNA_def_property_ui_text(prop, "Inverse VertexGroup", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "layer_pass");
+ RNA_def_property_range(prop, 0, 100);
+ RNA_def_property_ui_text(prop, "Pass", "Layer pass index");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_LAYERPASS);
+ RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ RNA_define_lib_overridable(false);
+}
+
+static void rna_def_modifier_gpencilweight_angle(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
- static const EnumPropertyItem mode_items[] = {
- {GP_WEIGHT_MODE_DISTANCE,
- "DISTANCE",
- 0,
- "Distance",
- "Calculate weights depending on the distance to the target object"},
- {GP_WEIGHT_MODE_ANGLE,
- "ANGLE",
- 0,
- "Angle",
- "Calculate weights depending on the stroke orientation"},
- {0, NULL, 0, NULL, NULL},
- };
static const EnumPropertyItem axis_items[] = {
{0, "X", 0, "X", ""},
{1, "Y", 0, "Y", ""},
@@ -2814,34 +2939,31 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
- srna = RNA_def_struct(brna, "WeightGpencilModifier", "GpencilModifier");
- RNA_def_struct_ui_text(srna, "Weight Modifier", "Calculate Vertex Weight dynamically");
- RNA_def_struct_sdna(srna, "WeightGpencilModifierData");
+ srna = RNA_def_struct(brna, "WeightAngleGpencilModifier", "GpencilModifier");
+ RNA_def_struct_ui_text(srna, "Weight Modifier Amgle", "Calculate Vertex Weight dynamically");
+ RNA_def_struct_sdna(srna, "WeightAngleGpencilModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_VERTEX_WEIGHT);
RNA_define_lib_overridable(true);
- prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
- RNA_def_property_enum_sdna(prop, NULL, "mode");
- RNA_def_property_enum_items(prop, mode_items);
- RNA_def_property_ui_text(prop, "Mode", "");
- RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
-
prop = RNA_def_property(srna, "target_vertex_group", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "target_vgname");
- RNA_def_property_ui_text(prop, "Output", "Output Vertex group");
- RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightGpencilModifier_target_vgname_set");
+ RNA_def_property_ui_text(prop, "Vertex Group", "Output Vertex group");
+ RNA_def_property_string_funcs(
+ prop, NULL, NULL, "rna_WeightProxGpencilModifier_target_vgname_set");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
- prop = RNA_def_property(srna, "use_blend", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_BLEND_DATA);
+ prop = RNA_def_property(srna, "use_multiply", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_MULTIPLY_DATA);
RNA_def_property_ui_text(
- prop, "Blend", "Blend results with existing weights in output weight group");
+ prop,
+ "Multiply Weights",
+ "Multiply the calculated weights with the existing values in the vertex group");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "use_invert_output", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_WEIGHT_INVERT_OUTPUT);
- RNA_def_property_ui_text(prop, "Invert", "Invert weight values");
+ RNA_def_property_ui_text(prop, "Invert", "Invert output weight values");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "angle", PROP_FLOAT, PROP_ANGLE);
@@ -2871,7 +2993,7 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(prop,
NULL,
- "rna_WeightGpencilModifier_material_set",
+ "rna_WeightAngleGpencilModifier_material_set",
NULL,
"rna_GpencilModifier_material_poll");
RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect");
@@ -2880,20 +3002,7 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna)
prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "vgname");
RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modulating the deform");
- RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightGpencilModifier_vgname_set");
- RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
-
- /* Distance reference object */
- prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE);
- RNA_def_property_ui_text(prop, "Object", "Object used as distance reference");
- RNA_def_property_pointer_funcs(prop, NULL, "rna_WeightGpencilModifier_object_set", NULL, NULL);
- RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
- RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
-
- prop = RNA_def_property(srna, "distance_start", PROP_FLOAT, PROP_NONE);
- RNA_def_property_float_sdna(prop, NULL, "dist_start");
- RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2);
- RNA_def_property_ui_text(prop, "Distance Start", "Start value for distance calculation");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightAngleGpencilModifier_vgname_set");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "minimum_weight", PROP_FLOAT, PROP_FACTOR);
@@ -2901,12 +3010,6 @@ static void rna_def_modifier_gpencilweight(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Minimum", "Minimum value for vertex weight");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
- prop = RNA_def_property(srna, "distance_end", PROP_FLOAT, PROP_NONE);
- RNA_def_property_float_sdna(prop, NULL, "dist_end");
- RNA_def_property_ui_range(prop, 0, 1000.0, 1.0, 2);
- RNA_def_property_ui_text(prop, "Distance End", "End value for distance calculation");
- RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
-
prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "pass_index");
RNA_def_property_range(prop, 0, 100);
@@ -3278,14 +3381,29 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna)
prop = RNA_def_property(srna, "start_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "start_fac");
- RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 1);
- RNA_def_property_ui_text(prop, "Start Factor", "Length difference for each segment");
+ RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 2);
+ RNA_def_property_ui_text(
+ prop, "Start Factor", "Added length to the start of each stroke relative to its length");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "end_factor", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "end_fac");
- RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 1);
- RNA_def_property_ui_text(prop, "End Factor", "Length difference for each segment");
+ RNA_def_property_ui_range(prop, -10.0f, 10.0f, 0.1, 2);
+ RNA_def_property_ui_text(
+ prop, "End Factor", "Added length to the end of each stroke relative to its length");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "start_length", PROP_FLOAT, PROP_DISTANCE);
+ RNA_def_property_float_sdna(prop, NULL, "start_fac");
+ RNA_def_property_ui_range(prop, -100.0f, 100.0f, 0.1f, 3);
+ RNA_def_property_ui_text(
+ prop, "Start Factor", "Absolute added length to the start of each stroke");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "end_length", PROP_FLOAT, PROP_DISTANCE);
+ RNA_def_property_float_sdna(prop, NULL, "end_fac");
+ RNA_def_property_ui_range(prop, -100.0f, 100.0f, 0.1f, 3);
+ RNA_def_property_ui_text(prop, "End Factor", "Absolute added length to the end of each stroke");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "overshoot_factor", PROP_FLOAT, PROP_FACTOR);
@@ -3293,8 +3411,8 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna)
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_text(
prop,
- "Overshoot Factor",
- "Defines how precise must follow the stroke trajectory for the overshoot extremes");
+ "Used Length",
+ "Defines what portion of the stroke is used for the calculation of the extension");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
@@ -3303,6 +3421,44 @@ static void rna_def_modifier_gpencillength(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Mode", "Mode to define length");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+ prop = RNA_def_property(srna, "use_curvature", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_USE_CURVATURE);
+ RNA_def_property_ui_text(prop, "Use Curvature", "Follow the curvature of the stroke");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_curvature", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_LENGTH_INVERT_CURVATURE);
+ RNA_def_property_ui_text(
+ prop, "Invert Curvature", "Invert the curvature of the stroke's extension");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "point_density", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_range(prop, 0.1f, 1000.0f);
+ RNA_def_property_ui_range(prop, 0.1f, 1000.0f, 1.0f, 1);
+ RNA_def_property_ui_scale_type(prop, PROP_SCALE_CUBIC);
+ RNA_def_property_ui_text(
+ prop, "Point Density", "Multiplied by Start/End for the total added point count");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "segment_influence", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_range(prop, -2.0f, 3.0f);
+ RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1f, 2);
+ RNA_def_property_ui_text(prop,
+ "Segment Influence",
+ "Factor to determine how much the length of the individual segments "
+ "should influence the final computed curvature. Higher factors makes "
+ "small segments influence the overall curvature less");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "max_angle", PROP_FLOAT, PROP_ANGLE);
+ RNA_def_property_ui_text(prop,
+ "Filter Angle",
+ "Ignore points on the stroke that deviate from their neighbors by more "
+ "than this angle when determining the extrapolation shape");
+ RNA_def_property_range(prop, 0.0f, DEG2RAD(180.0f));
+ RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(179.5f), 10.0f, 1);
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "layername");
RNA_def_property_ui_text(prop, "Layer", "Layer name");
@@ -3552,7 +3708,8 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna)
rna_def_modifier_gpencilarmature(brna);
rna_def_modifier_gpencilmultiply(brna);
rna_def_modifier_gpenciltexture(brna);
- rna_def_modifier_gpencilweight(brna);
+ rna_def_modifier_gpencilweight_angle(brna);
+ rna_def_modifier_gpencilweight_proximity(brna);
rna_def_modifier_gpencillineart(brna);
rna_def_modifier_gpencillength(brna);
rna_def_modifier_gpencildash(brna);
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index 76e37dbcdbc..ec53f35df4c 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -2168,6 +2168,17 @@ static const EnumPropertyItem *rna_GeometryNodeAttributeFill_type_itemf(bContext
return itemf_function_check(rna_enum_attribute_type_items, attribute_fill_type_supported);
}
+static bool attribute_statistic_type_supported(const EnumPropertyItem *item)
+{
+ return ELEM(item->value, CD_PROP_FLOAT, CD_PROP_FLOAT3);
+}
+static const EnumPropertyItem *rna_GeometryNodeAttributeStatistic_type_itemf(
+ bContext *UNUSED(C), PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *r_free)
+{
+ *r_free = true;
+ return itemf_function_check(rna_enum_attribute_type_items, attribute_statistic_type_supported);
+}
+
/**
* This bit of ugly code makes sure the float / attribute option shows up instead of
* vector / attribute if the node uses an operation that uses a float for input B or C.
@@ -4654,16 +4665,18 @@ static const EnumPropertyItem node_principled_distribution_items[] = {
};
static const EnumPropertyItem node_subsurface_method_items[] = {
- {SHD_SUBSURFACE_BURLEY,
- "BURLEY",
+ {SHD_SUBSURFACE_RANDOM_WALK_FIXED_RADIUS,
+ "RANDOM_WALK_FIXED_RADIUS",
0,
- "Christensen-Burley",
- "Approximation to physically based volume scattering"},
+ "Random Walk (Fixed Radius)",
+ "Volumetric approximation to physically based volume scattering, using the scattering radius "
+ "as specified"},
{SHD_SUBSURFACE_RANDOM_WALK,
"RANDOM_WALK",
0,
"Random Walk",
- "Volumetric approximation to physically based volume scattering"},
+ "Volumetric approximation to physically based volume scattering, with scattering radius "
+ "automatically adjusted to match color textures"},
{0, NULL, 0, NULL, NULL}};
/* -- Common nodes ---------------------------------------------------------- */
@@ -6133,35 +6146,12 @@ static void def_sh_ambient_occlusion(StructRNA *srna)
static void def_sh_subsurface(StructRNA *srna)
{
- static const EnumPropertyItem prop_subsurface_falloff_items[] = {
- {SHD_SUBSURFACE_CUBIC, "CUBIC", 0, "Cubic", "Simple cubic falloff function"},
- {SHD_SUBSURFACE_GAUSSIAN,
- "GAUSSIAN",
- 0,
- "Gaussian",
- "Normal distribution, multiple can be combined to fit more complex profiles"},
- {SHD_SUBSURFACE_BURLEY,
- "BURLEY",
- 0,
- "Christensen-Burley",
- "Approximation to physically based volume scattering"},
- {SHD_SUBSURFACE_RANDOM_WALK,
- "RANDOM_WALK",
- 0,
- "Random Walk",
- "Volumetric approximation to physically based volume scattering"},
- {0, NULL, 0, NULL, NULL},
- };
-
PropertyRNA *prop;
prop = RNA_def_property(srna, "falloff", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "custom1");
- RNA_def_property_enum_items(prop, prop_subsurface_falloff_items);
- RNA_def_property_ui_text(prop,
- "Falloff",
- "Function to determine how much light nearby points contribute based "
- "on their distance to the shading point");
+ RNA_def_property_enum_items(prop, node_subsurface_method_items);
+ RNA_def_property_ui_text(prop, "Method", "Method for rendering subsurface scattering");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update");
}
@@ -9077,6 +9067,30 @@ static void def_geo_curve_primitive_bezier_segment(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
+static void def_geo_curve_sample(StructRNA *srna)
+{
+ static EnumPropertyItem mode_items[] = {
+ {GEO_NODE_CURVE_SAMPLE_FACTOR,
+ "FACTOR",
+ 0,
+ "Factor",
+ "Find sample positions on the curve using a factor of its total length"},
+ {GEO_NODE_CURVE_SAMPLE_LENGTH,
+ "LENGTH",
+ 0,
+ "Length",
+ "Find sample positions on the curve using a distance from its beginning"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryCurveSample", "storage");
+
+ PropertyRNA *prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mode_items);
+ RNA_def_property_ui_text(prop, "Mode", "Method for sampling input");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
+}
+
static void def_geo_triangulate(StructRNA *srna)
{
PropertyRNA *prop;
@@ -9219,6 +9233,29 @@ static void def_geo_attribute_convert(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
+static void def_geo_attribute_statistic(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "custom1");
+ RNA_def_property_enum_items(prop, rna_enum_attribute_type_items);
+ RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeStatistic_type_itemf");
+ RNA_def_property_enum_default(prop, CD_PROP_FLOAT);
+ RNA_def_property_ui_text(
+ prop,
+ "Data Type",
+ "The data type the attribute is converted to before calculating the results");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
+
+ prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "custom2");
+ RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items);
+ RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT);
+ RNA_def_property_ui_text(prop, "Domain", "Which domain to read the data from");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
static void def_geo_attribute_math(StructRNA *srna)
{
PropertyRNA *prop;
diff --git a/source/blender/makesrna/intern/rna_render.c b/source/blender/makesrna/intern/rna_render.c
index 4400d198b4a..fcb46904e8d 100644
--- a/source/blender/makesrna/intern/rna_render.c
+++ b/source/blender/makesrna/intern/rna_render.c
@@ -52,6 +52,7 @@ const EnumPropertyItem rna_enum_render_pass_type_items[] = {
{SCE_PASS_Z, "Z", 0, "Z", ""},
{SCE_PASS_SHADOW, "SHADOW", 0, "Shadow", ""},
{SCE_PASS_AO, "AO", 0, "Ambient Occlusion", ""},
+ {SCE_PASS_POSITION, "POSITION", 0, "Position", ""},
{SCE_PASS_NORMAL, "NORMAL", 0, "Normal", ""},
{SCE_PASS_VECTOR, "VECTOR", 0, "Vector", ""},
{SCE_PASS_INDEXOB, "OBJECT_INDEX", 0, "Object Index", ""},
@@ -79,6 +80,7 @@ const EnumPropertyItem rna_enum_bake_pass_type_items[] = {
{SCE_PASS_COMBINED, "COMBINED", 0, "Combined", ""},
{SCE_PASS_AO, "AO", 0, "Ambient Occlusion", ""},
{SCE_PASS_SHADOW, "SHADOW", 0, "Shadow", ""},
+ {SCE_PASS_POSITION, "POSITION", 0, "Position", ""},
{SCE_PASS_NORMAL, "NORMAL", 0, "Normal", ""},
{SCE_PASS_UV, "UV", 0, "UV", ""},
{SCE_PASS_ROUGHNESS, "ROUGHNESS", 0, "ROUGHNESS", ""},
@@ -177,6 +179,40 @@ static void engine_render(RenderEngine *engine, Depsgraph *depsgraph)
RNA_parameter_list_free(&list);
}
+static void engine_render_frame_finish(RenderEngine *engine)
+{
+ extern FunctionRNA rna_RenderEngine_render_frame_finish_func;
+ PointerRNA ptr;
+ ParameterList list;
+ FunctionRNA *func;
+
+ RNA_pointer_create(NULL, engine->type->rna_ext.srna, engine, &ptr);
+ func = &rna_RenderEngine_render_frame_finish_func;
+
+ RNA_parameter_list_create(&list, &ptr, func);
+ engine->type->rna_ext.call(NULL, &ptr, func, &list);
+
+ RNA_parameter_list_free(&list);
+}
+
+static void engine_draw(RenderEngine *engine, const struct bContext *context, Depsgraph *depsgraph)
+{
+ extern FunctionRNA rna_RenderEngine_draw_func;
+ PointerRNA ptr;
+ ParameterList list;
+ FunctionRNA *func;
+
+ RNA_pointer_create(NULL, engine->type->rna_ext.srna, engine, &ptr);
+ func = &rna_RenderEngine_draw_func;
+
+ RNA_parameter_list_create(&list, &ptr, func);
+ RNA_parameter_set_lookup(&list, "context", &context);
+ RNA_parameter_set_lookup(&list, "depsgraph", &depsgraph);
+ engine->type->rna_ext.call(NULL, &ptr, func, &list);
+
+ RNA_parameter_list_free(&list);
+}
+
static void engine_bake(RenderEngine *engine,
struct Depsgraph *depsgraph,
struct Object *object,
@@ -315,7 +351,7 @@ static StructRNA *rna_RenderEngine_register(Main *bmain,
RenderEngineType *et, dummyet = {NULL};
RenderEngine dummyengine = {NULL};
PointerRNA dummyptr;
- int have_function[8];
+ int have_function[9];
/* setup dummy engine & engine type to store static properties in */
dummyengine.type = &dummyet;
@@ -358,11 +394,13 @@ static StructRNA *rna_RenderEngine_register(Main *bmain,
et->update = (have_function[0]) ? engine_update : NULL;
et->render = (have_function[1]) ? engine_render : NULL;
- et->bake = (have_function[2]) ? engine_bake : NULL;
- et->view_update = (have_function[3]) ? engine_view_update : NULL;
- et->view_draw = (have_function[4]) ? engine_view_draw : NULL;
- et->update_script_node = (have_function[5]) ? engine_update_script_node : NULL;
- et->update_render_passes = (have_function[6]) ? engine_update_render_passes : NULL;
+ et->render_frame_finish = (have_function[2]) ? engine_render_frame_finish : NULL;
+ et->draw = (have_function[3]) ? engine_draw : NULL;
+ et->bake = (have_function[4]) ? engine_bake : NULL;
+ et->view_update = (have_function[5]) ? engine_view_update : NULL;
+ et->view_draw = (have_function[6]) ? engine_view_draw : NULL;
+ et->update_script_node = (have_function[7]) ? engine_update_script_node : NULL;
+ et->update_render_passes = (have_function[8]) ? engine_update_render_passes : NULL;
RE_engines_register(et);
@@ -519,6 +557,19 @@ static void rna_def_render_engine(BlenderRNA *brna)
parm = RNA_def_pointer(func, "depsgraph", "Depsgraph", "", "");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ func = RNA_def_function(srna, "render_frame_finish", NULL);
+ RNA_def_function_ui_description(
+ func, "Perform finishing operations after all view layers in a frame were rendered");
+ RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL | FUNC_ALLOW_WRITE);
+
+ func = RNA_def_function(srna, "draw", NULL);
+ RNA_def_function_ui_description(func, "Draw render image");
+ RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL);
+ parm = RNA_def_pointer(func, "context", "Context", "", "");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_pointer(func, "depsgraph", "Depsgraph", "", "");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+
func = RNA_def_function(srna, "bake", NULL);
RNA_def_function_ui_description(func, "Bake passes");
RNA_def_function_flag(func, FUNC_REGISTER_OPTIONAL | FUNC_ALLOW_WRITE);
@@ -641,6 +692,14 @@ static void rna_def_render_engine(BlenderRNA *brna)
parm = RNA_def_boolean(func, "do_break", 0, "Break", "");
RNA_def_function_return(func, parm);
+ func = RNA_def_function(srna, "pass_by_index_get", "RE_engine_pass_by_index_get");
+ parm = RNA_def_string(func, "layer", NULL, 0, "Layer", "Name of render layer to get pass for");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_int(func, "index", 0, 0, INT_MAX, "Index", "Index of pass to get", 0, INT_MAX);
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_pointer(func, "render_pass", "RenderPass", "Index", "Index of pass to get");
+ RNA_def_function_return(func, parm);
+
func = RNA_def_function(srna, "active_view_get", "RE_engine_active_view_get");
parm = RNA_def_string(func, "view", NULL, 0, "View", "Single view active");
RNA_def_function_return(func, parm);
@@ -761,6 +820,22 @@ static void rna_def_render_engine(BlenderRNA *brna)
func = RNA_def_function(srna, "free_blender_memory", "RE_engine_free_blender_memory");
RNA_def_function_ui_description(func, "Free Blender side memory of render engine");
+ func = RNA_def_function(srna, "tile_highlight_set", "RE_engine_tile_highlight_set");
+ RNA_def_function_ui_description(func, "Set highlighted state of the given tile");
+ parm = RNA_def_int(func, "x", 0, 0, INT_MAX, "X", "", 0, INT_MAX);
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_int(func, "y", 0, 0, INT_MAX, "Y", "", 0, INT_MAX);
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_int(func, "width", 0, 0, INT_MAX, "Width", "", 0, INT_MAX);
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_int(func, "height", 0, 0, INT_MAX, "Height", "", 0, INT_MAX);
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_boolean(func, "highlight", 0, "Highlight", "");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+
+ func = RNA_def_function(srna, "tile_highlight_clear_all", "RE_engine_tile_highlight_clear_all");
+ RNA_def_function_ui_description(func, "Clear highlight from all tiles");
+
RNA_define_verify_sdna(0);
prop = RNA_def_property(srna, "is_animation", PROP_BOOLEAN, PROP_NONE);
@@ -777,11 +852,6 @@ static void rna_def_render_engine(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "layer_override", 1);
RNA_def_property_array(prop, 20);
- prop = RNA_def_property(srna, "tile_x", PROP_INT, PROP_UNSIGNED);
- RNA_def_property_int_sdna(prop, NULL, "tile_x");
- prop = RNA_def_property(srna, "tile_y", PROP_INT, PROP_UNSIGNED);
- RNA_def_property_int_sdna(prop, NULL, "tile_y");
-
prop = RNA_def_property(srna, "resolution_x", PROP_INT, PROP_PIXEL);
RNA_def_property_int_sdna(prop, NULL, "resolution_x");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
@@ -880,12 +950,6 @@ static void rna_def_render_engine(BlenderRNA *brna)
"Don't expose Cycles and Eevee shading nodes in the node editor user "
"interface, so own nodes can be used instead");
- prop = RNA_def_property(srna, "bl_use_save_buffers", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "type->flag", RE_USE_SAVE_BUFFERS);
- RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
- RNA_def_property_ui_text(
- prop, "Use Save Buffers", "Support render to an on disk buffer during rendering");
-
prop = RNA_def_property(srna, "bl_use_spherical_stereo", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "type->flag", RE_USE_SPHERICAL_STEREO);
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index badaaa14aa4..e45d39a1ddc 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -532,7 +532,6 @@ const EnumPropertyItem rna_enum_stereo3d_interlace_type_items[] = {
const EnumPropertyItem rna_enum_bake_pass_filter_type_items[] = {
{R_BAKE_PASS_FILTER_NONE, "NONE", 0, "None", ""},
- {R_BAKE_PASS_FILTER_AO, "AO", 0, "Ambient Occlusion", ""},
{R_BAKE_PASS_FILTER_EMIT, "EMIT", 0, "Emit", ""},
{R_BAKE_PASS_FILTER_DIRECT, "DIRECT", 0, "Direct", ""},
{R_BAKE_PASS_FILTER_INDIRECT, "INDIRECT", 0, "Indirect", ""},
@@ -3525,6 +3524,16 @@ static void rna_def_sequencer_tool_settings(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
+ static const EnumPropertyItem pivot_points[] = {
+ {V3D_AROUND_CENTER_MEDIAN, "MEDIAN", ICON_PIVOT_MEDIAN, "Median Point", ""},
+ {V3D_AROUND_LOCAL_ORIGINS,
+ "INDIVIDUAL_ORIGINS",
+ ICON_PIVOT_INDIVIDUAL,
+ "Individual Origins",
+ "Pivot around each selected island's own median point"},
+ {0, NULL, 0, NULL, NULL},
+
+ };
srna = RNA_def_struct(brna, "SequencerToolSettings", NULL);
RNA_def_struct_path_func(srna, "rna_SequencerToolSettings_path");
RNA_def_struct_ui_text(srna, "Sequencer Tool Settings", "");
@@ -3568,6 +3577,10 @@ static void rna_def_sequencer_tool_settings(BlenderRNA *brna)
prop = RNA_def_property(srna, "overlap_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, scale_overlap_modes);
RNA_def_property_ui_text(prop, "Overlap Mode", "How to resolve overlap after transformation");
+
+ prop = RNA_def_property(srna, "pivot_point", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, pivot_points);
+ RNA_def_property_ui_text(prop, "Pivot Point", "Rotation or scaling pivot point");
}
static void rna_def_unified_paint_settings(BlenderRNA *brna)
@@ -4137,13 +4150,6 @@ void rna_def_view_layer_common(BlenderRNA *brna, StructRNA *srna, const bool sce
prop, "Cryptomatte Levels", "Sets how many unique objects can be distinguished per pixel");
RNA_def_property_ui_range(prop, 2.0, 16.0, 2.0, 0.0);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_ViewLayer_pass_update");
-
- prop = RNA_def_property(srna, "use_pass_cryptomatte_accurate", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "cryptomatte_flag", VIEW_LAYER_CRYPTOMATTE_ACCURATE);
- RNA_def_property_boolean_default(prop, true);
- RNA_def_property_ui_text(
- prop, "Cryptomatte Accurate", "Generate a more accurate cryptomatte pass");
- RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_ViewLayer_pass_update");
}
prop = RNA_def_property(srna, "use_solid", PROP_BOOLEAN, PROP_NONE);
@@ -4237,6 +4243,16 @@ void rna_def_view_layer_common(BlenderRNA *brna, StructRNA *srna, const bool sce
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
}
+ prop = RNA_def_property(srna, "use_pass_position", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "passflag", SCE_PASS_POSITION);
+ RNA_def_property_ui_text(prop, "Position", "Deliver position pass");
+ if (scene) {
+ RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_ViewLayer_pass_update");
+ }
+ else {
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ }
+
prop = RNA_def_property(srna, "use_pass_normal", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "passflag", SCE_PASS_NORMAL);
RNA_def_property_ui_text(prop, "Normal", "Deliver normal pass");
@@ -5108,10 +5124,6 @@ static void rna_def_bake_data(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
/* custom passes flags */
- prop = RNA_def_property(srna, "use_pass_ambient_occlusion", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "pass_filter", R_BAKE_PASS_FILTER_AO);
- RNA_def_property_ui_text(prop, "Ambient Occlusion", "Add ambient occlusion contribution");
-
prop = RNA_def_property(srna, "use_pass_emit", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "pass_filter", R_BAKE_PASS_FILTER_EMIT);
RNA_def_property_ui_text(prop, "Emit", "Add emission contribution");
@@ -5920,29 +5932,6 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Resolution %", "Percentage scale for render resolution");
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_SceneSequencer_update");
- prop = RNA_def_property(srna, "tile_x", PROP_INT, PROP_PIXEL);
- RNA_def_property_int_sdna(prop, NULL, "tilex");
- RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
- RNA_def_property_range(prop, 8, 65536);
- RNA_def_property_ui_text(prop, "Tile X", "Horizontal tile size to use while rendering");
- RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
-
- prop = RNA_def_property(srna, "tile_y", PROP_INT, PROP_PIXEL);
- RNA_def_property_int_sdna(prop, NULL, "tiley");
- RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
- RNA_def_property_range(prop, 8, 65536);
- RNA_def_property_ui_text(prop, "Tile Y", "Vertical tile size to use while rendering");
- RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
-
- prop = RNA_def_property(srna, "preview_start_resolution", PROP_INT, PROP_NONE);
- RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
- RNA_def_property_range(prop, 8, 16384);
- RNA_def_property_ui_text(prop,
- "Start Resolution",
- "Resolution to start rendering preview at, "
- "progressively increasing it to the full viewport size");
- RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
-
prop = RNA_def_property(srna, "preview_pixel_size", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "preview_pixel_size");
RNA_def_property_enum_items(prop, pixel_size_items);
@@ -6199,24 +6188,6 @@ static void rna_def_scene_render_data(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Movie Format", "When true the format is a movie");
- prop = RNA_def_property(srna, "use_save_buffers", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "scemode", R_EXR_TILE_FILE);
- RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
- RNA_def_property_ui_text(
- prop,
- "Save Buffers",
- "Save tiles for all RenderLayers and SceneNodes to files in the temp directory "
- "(saves memory, required for Full Sample)");
- RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
-
- prop = RNA_def_property(srna, "use_full_sample", PROP_BOOLEAN, PROP_NONE);
- RNA_def_property_boolean_sdna(prop, NULL, "scemode", R_FULL_SAMPLE);
- RNA_def_property_ui_text(prop,
- "Full Sample",
- "Save for every anti-aliasing sample the entire RenderLayer results "
- "(this solves anti-aliasing issues with compositing)");
- RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL);
-
prop = RNA_def_property(srna, "use_lock_interface", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "use_lock_interface", 1);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
diff --git a/source/blender/makesrna/intern/rna_sequencer.c b/source/blender/makesrna/intern/rna_sequencer.c
index cd87e4d10c1..b713ffb68b4 100644
--- a/source/blender/makesrna/intern/rna_sequencer.c
+++ b/source/blender/makesrna/intern/rna_sequencer.c
@@ -1442,6 +1442,12 @@ static void rna_def_strip_transform(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Rotation", "Rotate around image center");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update");
+ prop = RNA_def_property(srna, "origin", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_sdna(prop, NULL, "origin");
+ RNA_def_property_ui_text(prop, "Origin", "Origin of image for transformation");
+ RNA_def_property_ui_range(prop, 0, 1, 1, 3);
+ RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_SequenceTransform_update");
+
RNA_def_struct_path_func(srna, "rna_SequenceTransform_path");
}
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 8c331bd1911..a05cef7a1cd 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -5371,6 +5371,11 @@ static void rna_def_space_sequencer_preview_overlay(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_GPENCIL);
RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
+
+ prop = RNA_def_property(srna, "show_image_outline", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_PREVIEW_SHOW_OUTLINE_SELECTED);
+ RNA_def_property_ui_text(prop, "Image Outline", "");
+ RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
}
static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna)
@@ -5439,6 +5444,11 @@ static void rna_def_space_sequencer_timeline_overlay(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_STRIP_OFFSETS);
RNA_def_property_ui_text(prop, "Show Offsets", "Display strip in/out offsets");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
+
+ prop = RNA_def_property(srna, "show_thumbnails", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", SEQ_TIMELINE_SHOW_THUMBNAILS);
+ RNA_def_property_ui_text(prop, "Show Thumbnails", "Show strip thumbnails");
+ RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SEQUENCER, NULL);
}
static void rna_def_space_sequencer(BlenderRNA *brna)
diff --git a/source/blender/makesrna/intern/rna_workspace_api.c b/source/blender/makesrna/intern/rna_workspace_api.c
index a2bb89dd5ee..15230f1198b 100644
--- a/source/blender/makesrna/intern/rna_workspace_api.c
+++ b/source/blender/makesrna/intern/rna_workspace_api.c
@@ -29,6 +29,7 @@
#include "DNA_object_types.h"
#include "DNA_windowmanager_types.h"
+#include "DNA_workspace_types.h"
#include "RNA_enum_types.h" /* own include */
@@ -51,6 +52,7 @@ static void rna_WorkSpaceTool_setup(ID *id,
const char *data_block,
const char *op_idname,
int index,
+ int options,
const char *idname_fallback,
const char *keymap_fallback)
{
@@ -62,6 +64,7 @@ static void rna_WorkSpaceTool_setup(ID *id,
STRNCPY(tref_rt.data_block, data_block);
STRNCPY(tref_rt.op, op_idname);
tref_rt.index = index;
+ tref_rt.flag = options;
/* While it's logical to assign both these values from setup,
* it's useful to stored this in DNA for re-use, exceptional case: write to the 'tref'. */
@@ -131,6 +134,11 @@ void RNA_api_workspace_tool(StructRNA *srna)
PropertyRNA *parm;
FunctionRNA *func;
+ static EnumPropertyItem options_items[] = {
+ {TOOLREF_FLAG_FALLBACK_KEYMAP, "KEYMAP_FALLBACK", 0, "Fallback", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
func = RNA_def_function(srna, "setup", "rna_WorkSpaceTool_setup");
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Set the tool settings");
@@ -146,6 +154,7 @@ void RNA_api_workspace_tool(StructRNA *srna)
RNA_def_string(func, "data_block", NULL, MAX_NAME, "Data Block", "");
RNA_def_string(func, "operator", NULL, MAX_NAME, "Operator", "");
RNA_def_int(func, "index", 0, INT_MIN, INT_MAX, "Index", "", INT_MIN, INT_MAX);
+ RNA_def_enum_flag(func, "options", options_items, 0, "Tool Options", "");
RNA_def_string(func, "idname_fallback", NULL, MAX_NAME, "Fallback Identifier", "");
RNA_def_string(func, "keymap_fallback", NULL, KMAP_MAX_NAME, "Fallback Key Map", "");
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index 6b976b016e1..8c02c83d479 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -68,6 +68,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
+#include "BLT_translation.h"
+
#include "WM_types.h"
#include "RNA_access.h"
@@ -1090,17 +1092,29 @@ static void panel_draw(const bContext *C, Panel *panel)
}
/* Draw node warnings. */
+ bool has_legacy_node = false;
if (nmd->runtime_eval_log != nullptr) {
const geo_log::ModifierLog &log = *static_cast<geo_log::ModifierLog *>(nmd->runtime_eval_log);
- log.foreach_node_log([layout](const geo_log::NodeLog &node_log) {
+ log.foreach_node_log([&](const geo_log::NodeLog &node_log) {
for (const geo_log::NodeWarning &warning : node_log.warnings()) {
- if (warning.type != geo_log::NodeWarningType::Info) {
+ if (warning.type == geo_log::NodeWarningType::Legacy) {
+ has_legacy_node = true;
+ }
+ else if (warning.type != geo_log::NodeWarningType::Info) {
uiItemL(layout, warning.message.c_str(), ICON_ERROR);
}
}
});
}
+ if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields) && has_legacy_node) {
+ uiLayout *row = uiLayoutRow(layout, false);
+ uiItemL(row, IFACE_("Node tree has legacy node"), ICON_ERROR);
+ uiLayout *sub = uiLayoutRow(row, false);
+ uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT);
+ uiItemO(sub, "", ICON_VIEWZOOM, "NODE_OT_geometry_node_view_legacy");
+ }
+
modifier_panel_end(layout, ptr);
}
diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
index 56de0f87ed8..e50c07ce6f2 100644
--- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
+++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
@@ -26,6 +26,8 @@
#include "FN_generic_value_map.hh"
#include "FN_multi_function.hh"
+#include "BLT_translation.h"
+
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_stack.hh"
#include "BLI_task.h"
@@ -868,6 +870,12 @@ class GeometryNodesEvaluator {
NodeParamsProvider params_provider{*this, node, node_state};
GeoNodeExecParams params{params_provider};
+ if (USER_EXPERIMENTAL_TEST(&U, use_geometry_nodes_fields)) {
+ if (node->idname().find("Legacy") != StringRef::not_found) {
+ params.error_message_add(geo_log::NodeWarningType::Legacy,
+ TIP_("Legacy node will be removed before Blender 4.0"));
+ }
+ }
bnode.typeinfo->geometry_node_execute(params);
}
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index b0fc55fab0c..a8795649ede 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -139,6 +139,9 @@ set(SRC
function/nodes/node_fn_input_string.cc
function/nodes/node_fn_input_vector.cc
function/nodes/node_fn_random_float.cc
+ function/nodes/node_fn_string_length.cc
+ function/nodes/node_fn_string_substring.cc
+ function/nodes/node_fn_value_to_string.cc
function/node_function_util.cc
geometry/nodes/legacy/node_geo_material_assign.cc
@@ -161,6 +164,7 @@ set(SRC
geometry/nodes/node_geo_attribute_remove.cc
geometry/nodes/node_geo_attribute_sample_texture.cc
geometry/nodes/node_geo_attribute_separate_xyz.cc
+ geometry/nodes/node_geo_attribute_statistic.cc
geometry/nodes/node_geo_attribute_transfer.cc
geometry/nodes/node_geo_attribute_vector_math.cc
geometry/nodes/node_geo_attribute_vector_rotate.cc
@@ -169,9 +173,11 @@ set(SRC
geometry/nodes/node_geo_collection_info.cc
geometry/nodes/node_geo_common.cc
geometry/nodes/node_geo_convex_hull.cc
+ geometry/nodes/node_geo_curve_sample.cc
geometry/nodes/node_geo_curve_endpoints.cc
geometry/nodes/node_geo_curve_fill.cc
geometry/nodes/node_geo_curve_length.cc
+ geometry/nodes/node_geo_curve_parameter.cc
geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
geometry/nodes/node_geo_curve_primitive_circle.cc
geometry/nodes/node_geo_curve_primitive_line.cc
@@ -193,6 +199,7 @@ set(SRC
geometry/nodes/node_geo_input_material.cc
geometry/nodes/node_geo_input_normal.cc
geometry/nodes/node_geo_input_position.cc
+ geometry/nodes/node_geo_input_tangent.cc
geometry/nodes/node_geo_input_index.cc
geometry/nodes/node_geo_is_viewport.cc
geometry/nodes/node_geo_join_geometry.cc
@@ -221,6 +228,7 @@ set(SRC
geometry/nodes/node_geo_realize_instances.cc
geometry/nodes/node_geo_separate_components.cc
geometry/nodes/node_geo_set_position.cc
+ geometry/nodes/node_geo_string_join.cc
geometry/nodes/node_geo_subdivision_surface.cc
geometry/nodes/node_geo_switch.cc
geometry/nodes/node_geo_transform.cc
diff --git a/source/blender/nodes/NOD_function.h b/source/blender/nodes/NOD_function.h
index 29f1a465491..a67458418f2 100644
--- a/source/blender/nodes/NOD_function.h
+++ b/source/blender/nodes/NOD_function.h
@@ -26,6 +26,9 @@ void register_node_type_fn_float_to_int(void);
void register_node_type_fn_input_string(void);
void register_node_type_fn_input_vector(void);
void register_node_type_fn_random_float(void);
+void register_node_type_fn_string_length(void);
+void register_node_type_fn_string_substring(void);
+void register_node_type_fn_value_to_string(void);
#ifdef __cplusplus
}
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 0d31ae2143a..24f60263d8a 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -48,6 +48,7 @@ void register_node_type_geo_attribute_proximity(void);
void register_node_type_geo_attribute_randomize(void);
void register_node_type_geo_attribute_remove(void);
void register_node_type_geo_attribute_separate_xyz(void);
+void register_node_type_geo_attribute_statistic(void);
void register_node_type_geo_attribute_transfer(void);
void register_node_type_geo_attribute_vector_math(void);
void register_node_type_geo_attribute_vector_rotate(void);
@@ -58,6 +59,8 @@ void register_node_type_geo_convex_hull(void);
void register_node_type_geo_curve_endpoints(void);
void register_node_type_geo_curve_fill(void);
void register_node_type_geo_curve_length(void);
+void register_node_type_geo_curve_parameter(void);
+void register_node_type_geo_curve_sample(void);
void register_node_type_geo_curve_primitive_bezier_segment(void);
void register_node_type_geo_curve_primitive_circle(void);
void register_node_type_geo_curve_primitive_line(void);
@@ -79,6 +82,7 @@ void register_node_type_geo_input_index(void);
void register_node_type_geo_input_material(void);
void register_node_type_geo_input_normal(void);
void register_node_type_geo_input_position(void);
+void register_node_type_geo_input_tangent(void);
void register_node_type_geo_is_viewport(void);
void register_node_type_geo_join_geometry(void);
void register_node_type_geo_material_assign(void);
@@ -108,6 +112,7 @@ void register_node_type_geo_sample_texture(void);
void register_node_type_geo_select_by_handle_type(void);
void register_node_type_geo_separate_components(void);
void register_node_type_geo_set_position(void);
+void register_node_type_geo_string_join(void);
void register_node_type_geo_subdivision_surface(void);
void register_node_type_geo_switch(void);
void register_node_type_geo_transform(void);
diff --git a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh
index 00d97b24646..ff8e137e341 100644
--- a/source/blender/nodes/NOD_geometry_nodes_eval_log.hh
+++ b/source/blender/nodes/NOD_geometry_nodes_eval_log.hh
@@ -131,6 +131,7 @@ enum class NodeWarningType {
Error,
Warning,
Info,
+ Legacy,
};
struct NodeWarning {
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index b2f1fa5e83a..8fb18e839a7 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -267,7 +267,10 @@ DefNode(FunctionNode, FN_NODE_FLOAT_COMPARE, def_float_compare, "FLOAT_COMPARE",
DefNode(FunctionNode, FN_NODE_FLOAT_TO_INT, def_float_to_int, "FLOAT_TO_INT", FloatToInt, "Float to Integer", "")
DefNode(FunctionNode, FN_NODE_INPUT_STRING, def_fn_input_string, "INPUT_STRING", InputString, "String", "")
DefNode(FunctionNode, FN_NODE_INPUT_VECTOR, def_fn_input_vector, "INPUT_VECTOR", InputVector, "Vector", "")
-DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "")
+DefNode(FunctionNode, FN_NODE_RANDOM_FLOAT, 0, "RANDOM_FLOAT", RandomFloat, "Random Float", "")
+DefNode(FunctionNode, FN_NODE_VALUE_TO_STRING, 0, "VALUE_TO_STRING", ValueToString, "Value to String", "")
+DefNode(FunctionNode, FN_NODE_STRING_LENGTH, 0, "STRING_LENGTH", StringLength, "String Length", "")
+DefNode(FunctionNode, FN_NODE_STRING_SUBSTRING, 0, "STRING_SUBSTRING", StringSubstring, "String Substring", "")
DefNode(GeometryNode, GEO_NODE_LECAGY_ATTRIBUTE_CLAMP, def_geo_attribute_clamp, "LEGACY_ATTRIBUTE_CLAMP", LegacyAttributeClamp, "Attribute Clamp", "")
DefNode(GeometryNode, GEO_NODE_LEGACY_ALIGN_ROTATION_TO_VECTOR, def_geo_align_rotation_to_vector, "LEGACY_ALIGN_ROTATION_TO_VECTOR", LegacyAlignRotationToVector, "Align Rotation to Vector", "")
@@ -307,13 +310,16 @@ DefNode(GeometryNode, GEO_NODE_LEGACY_SELECT_BY_MATERIAL, 0, "LEGACY_SELECT_BY_M
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_REMOVE, 0, "ATTRIBUTE_REMOVE", AttributeRemove, "Attribute Remove", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_VECTOR_ROTATE, def_geo_attribute_vector_rotate, "LEGACY_ATTRIBUTE_VECTOR_ROTATE", LegacyAttributeVectorRotate, "Attribute Vector Rotate", "")
+DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_STATISTIC, def_geo_attribute_statistic, "ATTRIBUTE_STATISTIC", AttributeStatistic, "Attribute Statistic", "")
DefNode(GeometryNode, GEO_NODE_BOOLEAN, def_geo_boolean, "BOOLEAN", Boolean, "Boolean", "")
DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bounding Box", "")
DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "")
DefNode(GeometryNode, GEO_NODE_CONVEX_HULL, 0, "CONVEX_HULL", ConvexHull, "Convex Hull", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_SAMPLE, def_geo_curve_sample, "CURVE_SAMPLE", CurveSample, "Curve Sample", "")
DefNode(GeometryNode, GEO_NODE_CURVE_ENDPOINTS, 0, "CURVE_ENDPOINTS", CurveEndpoints, "Curve Endpoints", "")
DefNode(GeometryNode, GEO_NODE_CURVE_FILL, def_geo_curve_fill, "CURVE_FILL", CurveFill, "Curve Fill", "")
DefNode(GeometryNode, GEO_NODE_CURVE_LENGTH, 0, "CURVE_LENGTH", CurveLength, "Curve Length", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_PARAMETER, 0, "CURVE_PARAMETER", CurveParameter, "Curve Parameter", "")
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT, def_geo_curve_primitive_bezier_segment, "CURVE_PRIMITIVE_BEZIER_SEGMENT", CurvePrimitiveBezierSegment, "Bezier Segment", "")
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_CIRCLE, def_geo_curve_primitive_circle, "CURVE_PRIMITIVE_CIRCLE", CurvePrimitiveCircle, "Curve Circle", "")
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_LINE, def_geo_curve_primitive_line, "CURVE_PRIMITIVE_LINE", CurvePrimitiveLine, "Curve Line", "")
@@ -330,6 +336,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "")
DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_TANGENT, 0, "INPUT_TANGENT", InputTangent, "Curve Tangent", "")
DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "")
DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "")
DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "")
@@ -348,6 +355,7 @@ DefNode(GeometryNode, GEO_NODE_OBJECT_INFO, def_geo_object_info, "OBJECT_INFO",
DefNode(GeometryNode, GEO_NODE_REALIZE_INSTANCES, 0, "REALIZE_INSTANCES", RealizeInstances, "Realize Instances", "")
DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "")
DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "")
+DefNode(GeometryNode, GEO_NODE_STRING_JOIN, 0, "STRING_JOIN", StringJoin, "String Join", "")
DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "")
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "")
diff --git a/source/blender/nodes/composite/nodes/node_composite_image.c b/source/blender/nodes/composite/nodes/node_composite_image.c
index 243300b0a44..a56dfea9dbf 100644
--- a/source/blender/nodes/composite/nodes/node_composite_image.c
+++ b/source/blender/nodes/composite/nodes/node_composite_image.c
@@ -45,7 +45,7 @@ static bNodeSocketTemplate cmp_node_rlayers_out[] = {
{SOCK_VECTOR, N_(RE_PASSNAME_NORMAL), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{SOCK_VECTOR, N_(RE_PASSNAME_UV), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{SOCK_VECTOR, N_(RE_PASSNAME_VECTOR), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
- {SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
+ {SOCK_VECTOR, N_(RE_PASSNAME_POSITION), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{SOCK_RGBA, N_(RE_PASSNAME_DEPRECATED), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{SOCK_RGBA, N_(RE_PASSNAME_SHADOW), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
@@ -72,7 +72,7 @@ static bNodeSocketTemplate cmp_node_rlayers_out[] = {
{SOCK_RGBA, N_(RE_PASSNAME_SUBSURFACE_COLOR), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{-1, ""},
};
-#define MAX_LEGACY_SOCKET_INDEX 30
+#define NUM_LEGACY_SOCKETS (ARRAY_SIZE(cmp_node_rlayers_out) - 1)
static void cmp_node_image_add_pass_output(bNodeTree *ntree,
bNode *node,
@@ -382,7 +382,7 @@ static void cmp_node_image_verify_outputs(bNodeTree *ntree, bNode *node, bool rl
break;
}
}
- if (!link && (!rlayer || sock_index > MAX_LEGACY_SOCKET_INDEX)) {
+ if (!link && (!rlayer || sock_index >= NUM_LEGACY_SOCKETS)) {
MEM_freeN(sock->storage);
nodeRemoveSocket(ntree, node, sock);
}
@@ -468,43 +468,12 @@ void node_cmp_rlayers_outputs(bNodeTree *ntree, bNode *node)
const char *node_cmp_rlayers_sock_to_pass(int sock_index)
{
- const char *sock_to_passname[] = {
- RE_PASSNAME_COMBINED,
- RE_PASSNAME_COMBINED,
- RE_PASSNAME_Z,
- RE_PASSNAME_NORMAL,
- RE_PASSNAME_UV,
- RE_PASSNAME_VECTOR,
- RE_PASSNAME_DEPRECATED,
- RE_PASSNAME_DEPRECATED,
- RE_PASSNAME_DEPRECATED,
- RE_PASSNAME_SHADOW,
- RE_PASSNAME_AO,
- RE_PASSNAME_DEPRECATED,
- RE_PASSNAME_DEPRECATED,
- RE_PASSNAME_DEPRECATED,
- RE_PASSNAME_INDEXOB,
- RE_PASSNAME_INDEXMA,
- RE_PASSNAME_MIST,
- RE_PASSNAME_EMIT,
- RE_PASSNAME_ENVIRONMENT,
- RE_PASSNAME_DIFFUSE_DIRECT,
- RE_PASSNAME_DIFFUSE_INDIRECT,
- RE_PASSNAME_DIFFUSE_COLOR,
- RE_PASSNAME_GLOSSY_DIRECT,
- RE_PASSNAME_GLOSSY_INDIRECT,
- RE_PASSNAME_GLOSSY_COLOR,
- RE_PASSNAME_TRANSM_DIRECT,
- RE_PASSNAME_TRANSM_INDIRECT,
- RE_PASSNAME_TRANSM_COLOR,
- RE_PASSNAME_SUBSURFACE_DIRECT,
- RE_PASSNAME_SUBSURFACE_INDIRECT,
- RE_PASSNAME_SUBSURFACE_COLOR,
- };
- if (sock_index > MAX_LEGACY_SOCKET_INDEX) {
+ if (sock_index >= NUM_LEGACY_SOCKETS) {
return NULL;
}
- return sock_to_passname[sock_index];
+ const char *name = cmp_node_rlayers_out[sock_index].name;
+ /* Exception for alpha, which is derived from Combined. */
+ return (STREQ(name, "Alpha")) ? RE_PASSNAME_COMBINED : name;
}
static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr)
diff --git a/source/blender/nodes/function/nodes/node_fn_string_length.cc b/source/blender/nodes/function/nodes/node_fn_string_length.cc
new file mode 100644
index 00000000000..a0f85dfd2bf
--- /dev/null
+++ b/source/blender/nodes/function/nodes/node_fn_string_length.cc
@@ -0,0 +1,49 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_string_utf8.h"
+
+#include <iomanip>
+
+#include "node_function_util.hh"
+
+namespace blender::nodes {
+
+static void fn_node_string_length_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::String>("String");
+ b.add_output<decl::Int>("Length");
+};
+
+} // namespace blender::nodes
+
+static void fn_node_string_length_build_multi_function(
+ blender::nodes::NodeMultiFunctionBuilder &builder)
+{
+ static blender::fn::CustomMF_SI_SO<std::string, int> str_len_fn{
+ "String Length", [](const std::string &a) { return BLI_strlen_utf8(a.c_str()); }};
+ builder.set_matching_fn(&str_len_fn);
+}
+
+void register_node_type_fn_string_length()
+{
+ static bNodeType ntype;
+
+ fn_node_type_base(&ntype, FN_NODE_STRING_LENGTH, "String Length", NODE_CLASS_CONVERTER, 0);
+ ntype.declare = blender::nodes::fn_node_string_length_declare;
+ ntype.build_multi_function = fn_node_string_length_build_multi_function;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/function/nodes/node_fn_string_substring.cc b/source/blender/nodes/function/nodes/node_fn_string_substring.cc
new file mode 100644
index 00000000000..55a01093ae9
--- /dev/null
+++ b/source/blender/nodes/function/nodes/node_fn_string_substring.cc
@@ -0,0 +1,54 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_string_utf8.h"
+
+#include "node_function_util.hh"
+
+namespace blender::nodes {
+
+static void fn_node_string_substring_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::String>("String");
+ b.add_input<decl::Int>("Position");
+ b.add_input<decl::Int>("Length").min(0);
+ b.add_output<decl::String>("String");
+};
+
+} // namespace blender::nodes
+
+static void fn_node_string_substring_build_multi_function(
+ blender::nodes::NodeMultiFunctionBuilder &builder)
+{
+ static blender::fn::CustomMF_SI_SI_SI_SO<std::string, int, int, std::string> substring_fn{
+ "Substring", [](const std::string &str, int a, int b) {
+ const int len = BLI_strlen_utf8(str.c_str());
+ const int start = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a, 0, len));
+ const int end = BLI_str_utf8_offset_from_index(str.c_str(), std::clamp(a + b, 0, len));
+ return str.substr(start, std::max<int>(end - start, 0));
+ }};
+ builder.set_matching_fn(&substring_fn);
+}
+
+void register_node_type_fn_string_substring()
+{
+ static bNodeType ntype;
+
+ fn_node_type_base(&ntype, FN_NODE_STRING_SUBSTRING, "String Substring", NODE_CLASS_CONVERTER, 0);
+ ntype.declare = blender::nodes::fn_node_string_substring_declare;
+ ntype.build_multi_function = fn_node_string_substring_build_multi_function;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/function/nodes/node_fn_value_to_string.cc b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc
new file mode 100644
index 00000000000..c1e6373cb6d
--- /dev/null
+++ b/source/blender/nodes/function/nodes/node_fn_value_to_string.cc
@@ -0,0 +1,51 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_function_util.hh"
+#include <iomanip>
+
+namespace blender::nodes {
+
+static void fn_node_value_to_string_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Float>("Value");
+ b.add_input<decl::Int>("Decimals").min(0);
+ b.add_output<decl::String>("String");
+};
+
+} // namespace blender::nodes
+
+static void fn_node_value_to_string_build_multi_function(
+ blender::nodes::NodeMultiFunctionBuilder &builder)
+{
+ static blender::fn::CustomMF_SI_SI_SO<float, int, std::string> to_str_fn{
+ "Value To String", [](float a, int b) {
+ std::stringstream stream;
+ stream << std::fixed << std::setprecision(std::max(0, b)) << a;
+ return stream.str();
+ }};
+ builder.set_matching_fn(&to_str_fn);
+}
+
+void register_node_type_fn_value_to_string()
+{
+ static bNodeType ntype;
+
+ fn_node_type_base(&ntype, FN_NODE_VALUE_TO_STRING, "Value to String", NODE_CLASS_CONVERTER, 0);
+ ntype.declare = blender::nodes::fn_node_value_to_string_declare;
+ ntype.build_multi_function = fn_node_value_to_string_build_multi_function;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc
new file mode 100644
index 00000000000..5001034518c
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_statistic.cc
@@ -0,0 +1,378 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <algorithm>
+#include <numeric>
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "BLI_math_base_safe.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_attribute_statistic_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::Float>("Attribute").hide_value();
+ b.add_input<decl::Vector>("Attribute", "Attribute_001").hide_value();
+
+ b.add_output<decl::Float>("Mean");
+ b.add_output<decl::Float>("Median");
+ b.add_output<decl::Float>("Sum");
+ b.add_output<decl::Float>("Min");
+ b.add_output<decl::Float>("Max");
+ b.add_output<decl::Float>("Range");
+ b.add_output<decl::Float>("Standard Deviation");
+ b.add_output<decl::Float>("Variance");
+
+ b.add_output<decl::Vector>("Mean", "Mean_001");
+ b.add_output<decl::Vector>("Median", "Median_001");
+ b.add_output<decl::Vector>("Sum", "Sum_001");
+ b.add_output<decl::Vector>("Min", "Min_001");
+ b.add_output<decl::Vector>("Max", "Max_001");
+ b.add_output<decl::Vector>("Range", "Range_001");
+ b.add_output<decl::Vector>("Standard Deviation", "Standard Deviation_001");
+ b.add_output<decl::Vector>("Variance", "Variance_001");
+}
+
+static void geo_node_attribute_statistic_layout(uiLayout *layout,
+ bContext *UNUSED(C),
+ PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE);
+ uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
+}
+
+static void geo_node_attribute_statistic_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ node->custom1 = CD_PROP_FLOAT;
+ node->custom2 = ATTR_DOMAIN_POINT;
+}
+
+static void geo_node_attribute_statistic_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ bNodeSocket *socket_geo = (bNodeSocket *)node->inputs.first;
+ bNodeSocket *socket_float_attr = socket_geo->next;
+ bNodeSocket *socket_float3_attr = socket_float_attr->next;
+
+ bNodeSocket *socket_float_mean = (bNodeSocket *)node->outputs.first;
+ bNodeSocket *socket_float_median = socket_float_mean->next;
+ bNodeSocket *socket_float_sum = socket_float_median->next;
+ bNodeSocket *socket_float_min = socket_float_sum->next;
+ bNodeSocket *socket_float_max = socket_float_min->next;
+ bNodeSocket *socket_float_range = socket_float_max->next;
+ bNodeSocket *socket_float_std = socket_float_range->next;
+ bNodeSocket *socket_float_variance = socket_float_std->next;
+
+ bNodeSocket *socket_vector_mean = socket_float_variance->next;
+ bNodeSocket *socket_vector_median = socket_vector_mean->next;
+ bNodeSocket *socket_vector_sum = socket_vector_median->next;
+ bNodeSocket *socket_vector_min = socket_vector_sum->next;
+ bNodeSocket *socket_vector_max = socket_vector_min->next;
+ bNodeSocket *socket_vector_range = socket_vector_max->next;
+ bNodeSocket *socket_vector_std = socket_vector_range->next;
+ bNodeSocket *socket_vector_variance = socket_vector_std->next;
+
+ const CustomDataType data_type = static_cast<CustomDataType>(node->custom1);
+
+ nodeSetSocketAvailability(socket_float_attr, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_mean, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_median, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_sum, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_min, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_max, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_range, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_std, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_float_variance, data_type == CD_PROP_FLOAT);
+
+ nodeSetSocketAvailability(socket_float3_attr, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_mean, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_median, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_sum, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_min, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_max, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_range, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_std, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_vector_variance, data_type == CD_PROP_FLOAT3);
+}
+
+template<typename T> static T compute_sum(const Span<T> data)
+{
+ return std::accumulate(data.begin(), data.end(), T());
+}
+
+static float compute_variance(const Span<float> data, const float mean)
+{
+ if (data.size() <= 1) {
+ return 0.0f;
+ }
+
+ float sum_of_squared_differences = std::accumulate(
+ data.begin(), data.end(), 0.0f, [mean](float accumulator, float value) {
+ float difference = mean - value;
+ return accumulator + difference * difference;
+ });
+
+ return sum_of_squared_differences / (data.size() - 1);
+}
+
+static float median_of_sorted_span(const Span<float> data)
+{
+ if (data.is_empty()) {
+ return 0.0f;
+ }
+
+ const float median = data[data.size() / 2];
+
+ /* For spans of even length, the median is the average of the middle two elements. */
+ if (data.size() % 2 == 0) {
+ return (median + data[data.size() / 2 - 1]) * 0.5f;
+ }
+ return median;
+}
+static void set_empty(CustomDataType data_type, GeoNodeExecParams &params)
+{
+ if (data_type == CD_PROP_FLOAT) {
+ params.set_output("Mean", 0.0f);
+ params.set_output("Median", 0.0f);
+ params.set_output("Sum", 0.0f);
+ params.set_output("Min", 0.0f);
+ params.set_output("Max", 0.0f);
+ params.set_output("Range", 0.0f);
+ params.set_output("Standard Deviation", 0.0f);
+ params.set_output("Variance", 0.0f);
+ }
+ else if (data_type == CD_PROP_FLOAT3) {
+ params.set_output("Mean_001", float3{0.0f, 0.0f, 0.0f});
+ params.set_output("Median_001", float3{0.0f, 0.0f, 0.0f});
+ params.set_output("Sum_001", float3{0.0f, 0.0f, 0.0f});
+ params.set_output("Min_001", float3{0.0f, 0.0f, 0.0f});
+ params.set_output("Max_001", float3{0.0f, 0.0f, 0.0f});
+ params.set_output("Range_001", float3{0.0f, 0.0f, 0.0f});
+ params.set_output("Standard Deviation_001", float3{0.0f, 0.0f, 0.0f});
+ params.set_output("Variance_001", float3{0.0f, 0.0f, 0.0f});
+ }
+}
+
+static void geo_node_attribute_statistic_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.get_input<GeometrySet>("Geometry");
+
+ const bNode &node = params.node();
+ const CustomDataType data_type = static_cast<CustomDataType>(node.custom1);
+ const AttributeDomain domain = static_cast<AttributeDomain>(node.custom2);
+
+ int64_t total_size = 0;
+ Vector<const GeometryComponent *> components = geometry_set.get_components_for_read();
+
+ for (const GeometryComponent *component : components) {
+ if (component->attribute_domain_supported(domain)) {
+ total_size += component->attribute_domain_size(domain);
+ }
+ }
+ if (total_size == 0) {
+ set_empty(data_type, params);
+ return;
+ }
+
+ switch (data_type) {
+ case CD_PROP_FLOAT: {
+ const Field<float> input_field = params.get_input<Field<float>>("Attribute");
+ Array<float> data = Array<float>(total_size);
+ int offset = 0;
+ for (const GeometryComponent *component : components) {
+ if (component->attribute_domain_supported(domain)) {
+ GeometryComponentFieldContext field_context{*component, domain};
+ const int domain_size = component->attribute_domain_size(domain);
+ fn::FieldEvaluator data_evaluator{field_context, domain_size};
+ MutableSpan<float> component_result = data.as_mutable_span().slice(offset, domain_size);
+ data_evaluator.add_with_destination(input_field, component_result);
+ data_evaluator.evaluate();
+ offset += domain_size;
+ }
+ }
+
+ float mean = 0.0f;
+ float median = 0.0f;
+ float sum = 0.0f;
+ float min = 0.0f;
+ float max = 0.0f;
+ float range = 0.0f;
+ float standard_deviation = 0.0f;
+ float variance = 0.0f;
+ const bool sort_required = params.output_is_required("Min") ||
+ params.output_is_required("Max") ||
+ params.output_is_required("Range") ||
+ params.output_is_required("Median");
+ const bool sum_required = params.output_is_required("Sum") ||
+ params.output_is_required("Mean");
+ const bool variance_required = params.output_is_required("Standard Deviation") ||
+ params.output_is_required("Variance");
+
+ if (total_size != 0) {
+ if (sort_required) {
+ std::sort(data.begin(), data.end());
+ median = median_of_sorted_span(data);
+
+ min = data.first();
+ max = data.last();
+ range = max - min;
+ }
+ if (sum_required || variance_required) {
+ sum = compute_sum<float>(data);
+ mean = sum / total_size;
+
+ if (variance_required) {
+ variance = compute_variance(data, mean);
+ standard_deviation = std::sqrt(variance);
+ }
+ }
+ }
+
+ if (sum_required) {
+ params.set_output("Sum", sum);
+ params.set_output("Mean", mean);
+ }
+ if (sort_required) {
+ params.set_output("Min", min);
+ params.set_output("Max", max);
+ params.set_output("Range", range);
+ params.set_output("Median", median);
+ }
+ if (variance_required) {
+ params.set_output("Standard Deviation", standard_deviation);
+ params.set_output("Variance", variance);
+ }
+ break;
+ }
+ case CD_PROP_FLOAT3: {
+ const Field<float3> input_field = params.get_input<Field<float3>>("Attribute_001");
+
+ Array<float3> data = Array<float3>(total_size);
+ int offset = 0;
+ for (const GeometryComponent *component : components) {
+ if (component->attribute_domain_supported(domain)) {
+ GeometryComponentFieldContext field_context{*component, domain};
+ const int domain_size = component->attribute_domain_size(domain);
+ fn::FieldEvaluator data_evaluator{field_context, domain_size};
+ MutableSpan<float3> component_result = data.as_mutable_span().slice(offset, domain_size);
+ data_evaluator.add_with_destination(input_field, component_result);
+ data_evaluator.evaluate();
+ offset += domain_size;
+ }
+ }
+
+ float3 median{0};
+ float3 min{0};
+ float3 max{0};
+ float3 range{0};
+ float3 sum{0};
+ float3 mean{0};
+ float3 variance{0};
+ float3 standard_deviation{0};
+ const bool sort_required = params.output_is_required("Min_001") ||
+ params.output_is_required("Max_001") ||
+ params.output_is_required("Range_001") ||
+ params.output_is_required("Median_001");
+ const bool sum_required = params.output_is_required("Sum_001") ||
+ params.output_is_required("Mean_001");
+ const bool variance_required = params.output_is_required("Standard Deviation_001") ||
+ params.output_is_required("Variance_001");
+
+ Array<float> data_x;
+ Array<float> data_y;
+ Array<float> data_z;
+ if (sort_required || variance_required) {
+ data_x.reinitialize(total_size);
+ data_y.reinitialize(total_size);
+ data_z.reinitialize(total_size);
+ for (const int i : data.index_range()) {
+ data_x[i] = data[i].x;
+ data_y[i] = data[i].y;
+ data_z[i] = data[i].z;
+ }
+ }
+
+ if (total_size != 0) {
+ if (sort_required) {
+ std::sort(data_x.begin(), data_x.end());
+ std::sort(data_y.begin(), data_y.end());
+ std::sort(data_z.begin(), data_z.end());
+
+ const float x_median = median_of_sorted_span(data_x);
+ const float y_median = median_of_sorted_span(data_y);
+ const float z_median = median_of_sorted_span(data_z);
+ median = float3(x_median, y_median, z_median);
+
+ min = float3(data_x.first(), data_y.first(), data_z.first());
+ max = float3(data_x.last(), data_y.last(), data_z.last());
+ range = max - min;
+ }
+ if (sum_required || variance_required) {
+ sum = compute_sum(data.as_span());
+ mean = sum / total_size;
+
+ if (variance_required) {
+ const float x_variance = compute_variance(data_x, mean.x);
+ const float y_variance = compute_variance(data_y, mean.y);
+ const float z_variance = compute_variance(data_z, mean.z);
+ variance = float3(x_variance, y_variance, z_variance);
+ standard_deviation = float3(
+ std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z));
+ }
+ }
+ }
+
+ if (sum_required) {
+ params.set_output("Sum_001", sum);
+ params.set_output("Mean_001", mean);
+ }
+ if (sort_required) {
+ params.set_output("Min_001", min);
+ params.set_output("Max_001", max);
+ params.set_output("Range_001", range);
+ params.set_output("Median_001", median);
+ }
+ if (variance_required) {
+ params.set_output("Standard Deviation_001", standard_deviation);
+ params.set_output("Variance_001", variance);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_attribute_statistic()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_ATTRIBUTE_STATISTIC, "Attribute Statistic", NODE_CLASS_ATTRIBUTE, 0);
+
+ ntype.declare = blender::nodes::geo_node_attribute_statistic_declare;
+ node_type_init(&ntype, blender::nodes::geo_node_attribute_statistic_init);
+ node_type_update(&ntype, blender::nodes::geo_node_attribute_statistic_update);
+ ntype.geometry_node_execute = blender::nodes::geo_node_attribute_statistic_exec;
+ ntype.draw_buttons = blender::nodes::geo_node_attribute_statistic_layout;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
index d8f40b0a0df..8de2975f9b0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fill.cc
@@ -124,37 +124,55 @@ static Mesh *cdt_to_mesh(const blender::meshintersect::CDT_result<double> &resul
return mesh;
}
-static Mesh *curve_fill_calculate(GeoNodeExecParams &params, const CurveComponent &component)
+static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCurveFillMode mode)
{
- const CurveEval &curve = *component.get_for_read();
- if (curve.splines().size() == 0) {
- return nullptr;
+ if (!geometry_set.has_curve()) {
+ return;
}
- const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage;
- const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode;
+ const CurveEval &curve = *geometry_set.get_curve_for_read();
+ if (curve.splines().is_empty()) {
+ geometry_set.replace_curve(nullptr);
+ return;
+ }
const CDT_output_type output_type = (mode == GEO_NODE_CURVE_FILL_MODE_NGONS) ?
CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES :
CDT_INSIDE_WITH_HOLES;
const blender::meshintersect::CDT_result<double> results = do_cdt(curve, output_type);
- return cdt_to_mesh(results);
+ Mesh *mesh = cdt_to_mesh(results);
+
+ geometry_set.replace_mesh(mesh);
+ geometry_set.replace_curve(nullptr);
}
static void geo_node_curve_fill_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
- geometry_set = bke::geometry_set_realize_instances(geometry_set);
- if (!geometry_set.has_curve()) {
- params.set_output("Mesh", GeometrySet());
+ const NodeGeometryCurveFill &storage = *(const NodeGeometryCurveFill *)params.node().storage;
+ const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode;
+
+ if (geometry_set.has_instances()) {
+ InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>();
+ instances.ensure_geometry_instances();
+
+ threading::parallel_for(IndexRange(instances.references_amount()), 16, [&](IndexRange range) {
+ for (int i : range) {
+ GeometrySet &geometry_set = instances.geometry_set_from_reference(i);
+ geometry_set = bke::geometry_set_realize_instances(geometry_set);
+ curve_fill_calculate(geometry_set, mode);
+ }
+ });
+
+ params.set_output("Mesh", std::move(geometry_set));
return;
}
- Mesh *mesh = curve_fill_calculate(params,
- *geometry_set.get_component_for_read<CurveComponent>());
- params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
+ curve_fill_calculate(geometry_set, mode);
+
+ params.set_output("Mesh", std::move(geometry_set));
}
} // namespace blender::nodes
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc
new file mode 100644
index 00000000000..2cde198e679
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_parameter.cc
@@ -0,0 +1,206 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_task.hh"
+
+#include "BKE_spline.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_curve_parameter_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Float>("Factor");
+}
+
+/**
+ * A basic interpolation from the point domain to the spline domain would be useless, since the
+ * average parameter for each spline would just be 0.5, or close to it. Instead, the parameter for
+ * each spline is the portion of the total length at the start of the spline.
+ */
+static Array<float> curve_parameter_spline_domain(const CurveEval &curve, const IndexMask mask)
+{
+ Span<SplinePtr> splines = curve.splines();
+ float length = 0.0f;
+ Array<float> parameters(splines.size());
+ for (const int i : splines.index_range()) {
+ parameters[i] = length;
+ length += splines[i]->length();
+ }
+ const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length;
+ mask.foreach_index([&](const int64_t i) { parameters[i] *= total_length_inverse; });
+
+ return parameters;
+}
+
+/**
+ * The parameter at each control point is the factor at the corresponding evaluated point.
+ */
+static void calculate_bezier_parameters(const BezierSpline &spline, MutableSpan<float> parameters)
+{
+ Span<int> offsets = spline.control_point_offsets();
+ Span<float> lengths = spline.evaluated_lengths();
+ const float total_length = spline.length();
+ const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length;
+
+ for (const int i : IndexRange(1, spline.size() - 1)) {
+ parameters[i] = lengths[offsets[i] - 1] * total_length_inverse;
+ }
+}
+
+/**
+ * The parameter for poly splines is simply the evaluated lengths divided by the total length.
+ */
+static void calculate_poly_parameters(const PolySpline &spline, MutableSpan<float> parameters)
+{
+ Span<float> lengths = spline.evaluated_lengths();
+ const float total_length = spline.length();
+ const float total_length_inverse = total_length == 0.0f ? 0.0f : 1.0f / total_length;
+
+ for (const int i : IndexRange(1, spline.size() - 1)) {
+ parameters[i] = lengths[i - 1] * total_length_inverse;
+ }
+}
+
+/**
+ * Since NURBS control points do not necessarily coincide with the evaluated curve's path, and
+ * each control point doesn't correspond well to a specific evaluated point, the parameter at
+ * each point is not well defined. So instead, treat the control points as if they were a poly
+ * spline.
+ */
+static void calculate_nurbs_parameters(const NURBSpline &spline, MutableSpan<float> parameters)
+{
+ Span<float3> positions = spline.positions();
+ Array<float> control_point_lengths(spline.size());
+
+ float length = 0.0f;
+ for (const int i : IndexRange(positions.size() - 1)) {
+ parameters[i] = length;
+ length += float3::distance(positions[i], positions[i + 1]);
+ }
+
+ const float total_length_inverse = length == 0.0f ? 0.0f : 1.0f / length;
+ for (float &parameter : parameters) {
+ parameter *= total_length_inverse;
+ }
+}
+
+static Array<float> curve_parameter_point_domain(const CurveEval &curve)
+{
+ Span<SplinePtr> splines = curve.splines();
+ Array<int> offsets = curve.control_point_offsets();
+ const int total_size = offsets.last();
+ Array<float> parameters(total_size);
+
+ threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
+ for (const int i : range) {
+ const Spline &spline = *splines[i];
+ MutableSpan spline_factors{parameters.as_mutable_span().slice(offsets[i], spline.size())};
+ spline_factors.first() = 0.0f;
+ switch (splines[i]->type()) {
+ case Spline::Type::Bezier: {
+ calculate_bezier_parameters(static_cast<const BezierSpline &>(spline), spline_factors);
+ break;
+ }
+ case Spline::Type::Poly: {
+ calculate_poly_parameters(static_cast<const PolySpline &>(spline), spline_factors);
+ break;
+ }
+ case Spline::Type::NURBS: {
+ calculate_nurbs_parameters(static_cast<const NURBSpline &>(spline), spline_factors);
+ break;
+ }
+ }
+ }
+ });
+ return parameters;
+}
+
+static const GVArray *construct_curve_parameter_gvarray(const CurveEval &curve,
+ const IndexMask mask,
+ const AttributeDomain domain,
+ ResourceScope &scope)
+{
+ if (domain == ATTR_DOMAIN_POINT) {
+ Array<float> parameters = curve_parameter_point_domain(curve);
+ return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters));
+ }
+
+ if (domain == ATTR_DOMAIN_CURVE) {
+ Array<float> parameters = curve_parameter_spline_domain(curve, mask);
+ return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float>>>(std::move(parameters));
+ }
+
+ return nullptr;
+}
+
+class CurveParameterFieldInput final : public fn::FieldInput {
+ public:
+ CurveParameterFieldInput() : fn::FieldInput(CPPType::get<float>(), "Curve Parameter")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+
+ if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
+ const CurveComponent &curve_component = static_cast<const CurveComponent &>(component);
+ const CurveEval *curve = curve_component.get_for_read();
+ if (curve) {
+ return construct_curve_parameter_gvarray(*curve, mask, domain, scope);
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ uint64_t hash() const override
+ {
+ /* Some random constant hash. */
+ return 29837456298;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const override
+ {
+ return dynamic_cast<const CurveParameterFieldInput *>(&other) != nullptr;
+ }
+};
+
+static void geo_node_curve_parameter_exec(GeoNodeExecParams params)
+{
+ Field<float> parameter_field{std::make_shared<CurveParameterFieldInput>()};
+ params.set_output("Factor", std::move(parameter_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_curve_parameter()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_CURVE_PARAMETER, "Curve Parameter", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_curve_parameter_exec;
+ ntype.declare = blender::nodes::geo_node_curve_parameter_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
new file mode 100644
index 00000000000..ac0cd510ffa
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_sample.cc
@@ -0,0 +1,288 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_task.hh"
+
+#include "BKE_spline.hh"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_curve_sample_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Curve");
+ b.add_input<decl::Float>("Factor").min(0.0f).max(1.0f).subtype(PROP_FACTOR);
+ b.add_input<decl::Float>("Length").min(0.0f).subtype(PROP_DISTANCE);
+
+ b.add_output<decl::Vector>("Position");
+ b.add_output<decl::Vector>("Tangent");
+ b.add_output<decl::Vector>("Normal");
+}
+
+static void geo_node_curve_sample_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "mode", UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
+}
+
+static void geo_node_curve_sample_type_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryCurveSample *data = (NodeGeometryCurveSample *)MEM_callocN(
+ sizeof(NodeGeometryCurveSample), __func__);
+ data->mode = GEO_NODE_CURVE_SAMPLE_LENGTH;
+ node->storage = data;
+}
+
+static void geo_node_curve_sample_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)node->storage;
+ const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode;
+
+ bNodeSocket *factor = ((bNodeSocket *)node->inputs.first)->next;
+ bNodeSocket *length = factor->next;
+
+ nodeSetSocketAvailability(factor, mode == GEO_NODE_CURVE_SAMPLE_FACTOR);
+ nodeSetSocketAvailability(length, mode == GEO_NODE_CURVE_SAMPLE_LENGTH);
+}
+
+template<typename T> static T sample_with_lookup(const Spline::LookupResult lookup, Span<T> data)
+{
+ return attribute_math::mix2(
+ lookup.factor, data[lookup.evaluated_index], data[lookup.next_evaluated_index]);
+}
+
+class SampleCurveFunction : public fn::MultiFunction {
+ private:
+ /**
+ * The function holds a geometry set instead of a curve or a curve component in order to
+ * maintain a reference to the geometry while the field tree is being built, so that the
+ * curve is not freed before the function can execute.
+ */
+ GeometrySet geometry_set_;
+ /**
+ * To support factor inputs, the node adds another field operation before this one to multiply by
+ * the curve's total length. Since that must calculate the spline lengths anyway, store them to
+ * reuse the calculation.
+ */
+ Array<float> spline_lengths_;
+ /** The last member of #spline_lengths_, extracted for convenience. */
+ const float total_length_;
+
+ public:
+ SampleCurveFunction(GeometrySet geometry_set, Array<float> spline_lengths)
+ : geometry_set_(std::move(geometry_set)),
+ spline_lengths_(std::move(spline_lengths)),
+ total_length_(spline_lengths_.last())
+ {
+ static fn::MFSignature signature = create_signature();
+ this->set_signature(&signature);
+ }
+
+ static fn::MFSignature create_signature()
+ {
+ blender::fn::MFSignatureBuilder signature{"Curve Sample"};
+ signature.single_input<float>("Length");
+ signature.single_output<float3>("Position");
+ signature.single_output<float3>("Tangent");
+ signature.single_output<float3>("Normal");
+ return signature.build();
+ }
+
+ void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
+ {
+ MutableSpan<float3> sampled_positions = params.uninitialized_single_output_if_required<float3>(
+ 1, "Position");
+ MutableSpan<float3> sampled_tangents = params.uninitialized_single_output_if_required<float3>(
+ 2, "Tangent");
+ MutableSpan<float3> sampled_normals = params.uninitialized_single_output_if_required<float3>(
+ 3, "Normal");
+
+ auto return_default = [&]() {
+ if (!sampled_positions.is_empty()) {
+ sampled_positions.fill_indices(mask, {0, 0, 0});
+ }
+ if (!sampled_tangents.is_empty()) {
+ sampled_tangents.fill_indices(mask, {0, 0, 0});
+ }
+ if (!sampled_normals.is_empty()) {
+ sampled_normals.fill_indices(mask, {0, 0, 0});
+ }
+ };
+
+ if (!geometry_set_.has_curve()) {
+ return return_default();
+ }
+
+ const CurveComponent *curve_component = geometry_set_.get_component_for_read<CurveComponent>();
+ const CurveEval *curve = curve_component->get_for_read();
+ Span<SplinePtr> splines = curve->splines();
+ if (splines.is_empty()) {
+ return return_default();
+ }
+
+ const VArray<float> &lengths_varray = params.readonly_single_input<float>(0, "Length");
+ const VArray_Span lengths{lengths_varray};
+#ifdef DEBUG
+ for (const float length : lengths) {
+ /* Lengths must be in range of the curve's total length. This is ensured in
+ * #get_length_input_field by adding another multi-function before this one
+ * to clamp the lengths. */
+ BLI_assert(length >= 0.0f && length <= total_length_);
+ }
+#endif
+
+ Array<int> spline_indices(mask.min_array_size());
+ for (const int i : mask) {
+ const float *offset = std::lower_bound(
+ spline_lengths_.begin(), spline_lengths_.end(), lengths[i]);
+ const int index = offset - spline_lengths_.data() - 1;
+ spline_indices[i] = std::max(index, 0);
+ }
+
+ /* Storing lookups in an array is unnecessary but will simplify custom attribute transfer. */
+ Array<Spline::LookupResult> lookups(mask.min_array_size());
+ for (const int i : mask) {
+ const float length_in_spline = lengths[i] - spline_lengths_[spline_indices[i]];
+ lookups[i] = splines[spline_indices[i]]->lookup_evaluated_length(length_in_spline);
+ }
+
+ if (!sampled_positions.is_empty()) {
+ for (const int i : mask) {
+ const Spline::LookupResult &lookup = lookups[i];
+ const Span<float3> evaluated_positions = splines[spline_indices[i]]->evaluated_positions();
+ sampled_positions[i] = sample_with_lookup(lookup, evaluated_positions);
+ }
+ }
+
+ if (!sampled_tangents.is_empty()) {
+ for (const int i : mask) {
+ const Spline::LookupResult &lookup = lookups[i];
+ const Span<float3> evaluated_tangents = splines[spline_indices[i]]->evaluated_tangents();
+ sampled_tangents[i] = sample_with_lookup(lookup, evaluated_tangents).normalized();
+ }
+ }
+
+ if (!sampled_normals.is_empty()) {
+ for (const int i : mask) {
+ const Spline::LookupResult &lookup = lookups[i];
+ const Span<float3> evaluated_normals = splines[spline_indices[i]]->evaluated_normals();
+ sampled_normals[i] = sample_with_lookup(lookup, evaluated_normals).normalized();
+ }
+ }
+ }
+};
+
+/**
+ * Pre-process the lengths or factors used for the sampling, turning factors into lengths, and
+ * clamping between zero and the total length of the curve. Do this as a separate operation in the
+ * field tree to make the sampling simpler, and to let the evaluator optimize better.
+ *
+ * \todo Use a mutable single input instead when they are supported.
+ */
+static Field<float> get_length_input_field(const GeoNodeExecParams &params,
+ const float curve_total_length)
+{
+ const NodeGeometryCurveSample &node_storage = *(NodeGeometryCurveSample *)params.node().storage;
+ const GeometryNodeCurveSampleMode mode = (GeometryNodeCurveSampleMode)node_storage.mode;
+
+ if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) {
+ /* Just make sure the length is in bounds of the curve. */
+ Field<float> length_field = params.get_input<Field<float>>("Length");
+ auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>(
+ __func__, [curve_total_length](float length) {
+ return std::clamp(length, 0.0f, curve_total_length);
+ });
+ auto clamp_op = std::make_shared<FieldOperation>(
+ FieldOperation(std::move(clamp_fn), {std::move(length_field)}));
+
+ return Field<float>(std::move(clamp_op), 0);
+ }
+
+ /* Convert the factor to a length and clamp it to the bounds of the curve. */
+ Field<float> factor_field = params.get_input<Field<float>>("Factor");
+ auto clamp_fn = std::make_unique<fn::CustomMF_SI_SO<float, float>>(
+ __func__, [curve_total_length](float factor) {
+ const float length = factor * curve_total_length;
+ return std::clamp(length, 0.0f, curve_total_length);
+ });
+ auto process_op = std::make_shared<FieldOperation>(
+ FieldOperation(std::move(clamp_fn), {std::move(factor_field)}));
+
+ return Field<float>(std::move(process_op), 0);
+}
+
+static void geo_node_curve_sample_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
+
+ auto return_default = [&]() {
+ params.set_output("Position", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f}));
+ params.set_output("Tangent", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f}));
+ params.set_output("Normal", fn::make_constant_field<float3>({0.0f, 0.0f, 0.0f}));
+ };
+
+ const CurveComponent *component = geometry_set.get_component_for_read<CurveComponent>();
+ if (component == nullptr) {
+ return return_default();
+ }
+
+ const CurveEval *curve = component->get_for_read();
+ if (curve == nullptr) {
+ return return_default();
+ }
+
+ if (curve->splines().is_empty()) {
+ return return_default();
+ }
+
+ Array<float> spline_lengths = curve->accumulated_spline_lengths();
+ const float total_length = spline_lengths.last();
+ if (total_length == 0.0f) {
+ return return_default();
+ }
+
+ Field<float> length_field = get_length_input_field(params, total_length);
+
+ auto sample_fn = std::make_unique<SampleCurveFunction>(std::move(geometry_set),
+ std::move(spline_lengths));
+ auto sample_op = std::make_shared<FieldOperation>(
+ FieldOperation(std::move(sample_fn), {length_field}));
+
+ params.set_output("Position", Field<float3>(sample_op, 0));
+ params.set_output("Tangent", Field<float3>(sample_op, 1));
+ params.set_output("Normal", Field<float3>(sample_op, 2));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_curve_sample()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_CURVE_SAMPLE, "Curve Sample", NODE_CLASS_GEOMETRY, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_curve_sample_exec;
+ ntype.declare = blender::nodes::geo_node_curve_sample_declare;
+ node_type_init(&ntype, blender::nodes::geo_node_curve_sample_type_init);
+ node_type_update(&ntype, blender::nodes::geo_node_curve_sample_update);
+ node_type_storage(
+ &ntype, "NodeGeometryCurveSample", node_free_standard_storage, node_copy_standard_storage);
+ ntype.draw_buttons = blender::nodes::geo_node_curve_sample_layout;
+
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
index b8bdb3d71d6..89ba635ff4b 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
@@ -14,17 +14,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "BLI_array.hh"
-#include "BLI_float4x4.hh"
-#include "BLI_task.hh"
-
-#include "DNA_mesh_types.h"
-#include "DNA_meshdata_types.h"
-
-#include "BKE_material.h"
-#include "BKE_mesh.h"
#include "BKE_spline.hh"
+#include "BKE_curve_to_mesh.hh"
+
#include "UI_interface.h"
#include "UI_resources.h"
@@ -39,692 +32,6 @@ static void geo_node_curve_to_mesh_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Geometry>("Mesh");
}
-/** Information about the creation of one curve spline and profile spline combination. */
-struct ResultInfo {
- const Spline &spline;
- const Spline &profile;
- int vert_offset;
- int edge_offset;
- int loop_offset;
- int poly_offset;
- int spline_vert_len;
- int spline_edge_len;
- int profile_vert_len;
- int profile_edge_len;
-};
-
-static void vert_extrude_to_mesh_data(const Spline &spline,
- const float3 profile_vert,
- MutableSpan<MVert> r_verts,
- MutableSpan<MEdge> r_edges,
- const int vert_offset,
- const int edge_offset)
-{
- Span<float3> positions = spline.evaluated_positions();
-
- for (const int i : IndexRange(positions.size() - 1)) {
- MEdge &edge = r_edges[edge_offset + i];
- edge.v1 = vert_offset + i;
- edge.v2 = vert_offset + i + 1;
- edge.flag = ME_LOOSEEDGE;
- }
-
- if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) {
- MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1];
- edge.v1 = vert_offset;
- edge.v2 = vert_offset + positions.size() - 1;
- edge.flag = ME_LOOSEEDGE;
- }
-
- for (const int i : positions.index_range()) {
- MVert &vert = r_verts[vert_offset + i];
- copy_v3_v3(vert.co, positions[i] + profile_vert);
- }
-}
-
-static void mark_edges_sharp(MutableSpan<MEdge> edges)
-{
- for (MEdge &edge : edges) {
- edge.flag |= ME_SHARP;
- }
-}
-
-static void spline_extrude_to_mesh_data(const ResultInfo &info,
- MutableSpan<MVert> r_verts,
- MutableSpan<MEdge> r_edges,
- MutableSpan<MLoop> r_loops,
- MutableSpan<MPoly> r_polys)
-{
- const Spline &spline = info.spline;
- const Spline &profile = info.profile;
- if (info.profile_vert_len == 1) {
- vert_extrude_to_mesh_data(spline,
- profile.evaluated_positions()[0],
- r_verts,
- r_edges,
- info.vert_offset,
- info.edge_offset);
- return;
- }
-
- /* Add the edges running along the length of the curve, starting at each profile vertex. */
- const int spline_edges_start = info.edge_offset;
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
- const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len;
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
-
- const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
- const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
-
- MEdge &edge = r_edges[profile_edge_offset + i_ring];
- edge.v1 = ring_vert_offset + i_profile;
- edge.v2 = next_ring_vert_offset + i_profile;
- edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
- }
- }
-
- /* Add the edges running along each profile ring. */
- const int profile_edges_start = spline_edges_start +
- info.profile_vert_len * info.spline_edge_len;
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
-
- const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len;
- for (const int i_profile : IndexRange(info.profile_edge_len)) {
- const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
-
- MEdge &edge = r_edges[ring_edge_offset + i_profile];
- edge.v1 = ring_vert_offset + i_profile;
- edge.v2 = ring_vert_offset + i_next_profile;
- edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
- }
- }
-
- /* Calculate poly and corner indices. */
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
-
- const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
- const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
-
- const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring;
- const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring;
-
- const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len;
- const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4;
-
- for (const int i_profile : IndexRange(info.profile_edge_len)) {
- const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4;
- const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
-
- const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile;
- const int next_spline_edge_start = spline_edges_start +
- info.spline_edge_len * i_next_profile;
-
- MPoly &poly = r_polys[ring_poly_offset + i_profile];
- poly.loopstart = ring_segment_loop_offset;
- poly.totloop = 4;
- poly.flag = ME_SMOOTH;
-
- MLoop &loop_a = r_loops[ring_segment_loop_offset];
- loop_a.v = ring_vert_offset + i_profile;
- loop_a.e = ring_edge_start + i_profile;
- MLoop &loop_b = r_loops[ring_segment_loop_offset + 1];
- loop_b.v = ring_vert_offset + i_next_profile;
- loop_b.e = next_spline_edge_start + i_ring;
- MLoop &loop_c = r_loops[ring_segment_loop_offset + 2];
- loop_c.v = next_ring_vert_offset + i_next_profile;
- loop_c.e = next_ring_edge_offset + i_profile;
- MLoop &loop_d = r_loops[ring_segment_loop_offset + 3];
- loop_d.v = next_ring_vert_offset + i_profile;
- loop_d.e = spline_edge_start + i_ring;
- }
- }
-
- /* Calculate the positions of each profile ring profile along the spline. */
- Span<float3> positions = spline.evaluated_positions();
- Span<float3> tangents = spline.evaluated_tangents();
- Span<float3> normals = spline.evaluated_normals();
- Span<float3> profile_positions = profile.evaluated_positions();
-
- GVArray_Typed<float> radii = spline.interpolate_to_evaluated(spline.radii());
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- float4x4 point_matrix = float4x4::from_normalized_axis_data(
- positions[i_ring], normals[i_ring], tangents[i_ring]);
- point_matrix.apply_scale(radii[i_ring]);
-
- const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
- MVert &vert = r_verts[ring_vert_start + i_profile];
- copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]);
- }
- }
-
- /* Mark edge loops from sharp vector control points sharp. */
- if (profile.type() == Spline::Type::Bezier) {
- const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile);
- Span<int> control_point_offsets = bezier_spline.control_point_offsets();
- for (const int i : IndexRange(bezier_spline.size())) {
- if (bezier_spline.point_is_sharp(i)) {
- mark_edges_sharp(
- r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i],
- info.spline_edge_len));
- }
- }
- }
-}
-
-static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile)
-{
- return curve.evaluated_points_size() * profile.evaluated_points_size();
-}
-
-static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile)
-{
- /* Add the ring edges, with one ring for every curve vertex, and the edge loops
- * that run along the length of the curve, starting on the first profile. */
- return curve.evaluated_points_size() * profile.evaluated_edges_size() +
- curve.evaluated_edges_size() * profile.evaluated_points_size();
-}
-
-static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile)
-{
- return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4;
-}
-
-static inline int spline_extrude_poly_size(const Spline &curve, const Spline &profile)
-{
- return curve.evaluated_edges_size() * profile.evaluated_edges_size();
-}
-
-struct ResultOffsets {
- Array<int> vert;
- Array<int> edge;
- Array<int> loop;
- Array<int> poly;
-};
-static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves)
-{
- const int total = profiles.size() * curves.size();
- Array<int> vert(total + 1);
- Array<int> edge(total + 1);
- Array<int> loop(total + 1);
- Array<int> poly(total + 1);
-
- int mesh_index = 0;
- int vert_offset = 0;
- int edge_offset = 0;
- int loop_offset = 0;
- int poly_offset = 0;
- for (const int i_spline : curves.index_range()) {
- for (const int i_profile : profiles.index_range()) {
- vert[mesh_index] = vert_offset;
- edge[mesh_index] = edge_offset;
- loop[mesh_index] = loop_offset;
- poly[mesh_index] = poly_offset;
- vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]);
- edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]);
- loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile]);
- poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile]);
- mesh_index++;
- }
- }
- vert.last() = vert_offset;
- edge.last() = edge_offset;
- loop.last() = loop_offset;
- poly.last() = poly_offset;
-
- return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)};
-}
-
-static AttributeDomain get_result_attribute_domain(const MeshComponent &component,
- const AttributeIDRef &attribute_id)
-{
- /* Only use a different domain if it is builtin and must only exist on one domain. */
- if (!component.attribute_is_builtin(attribute_id)) {
- return ATTR_DOMAIN_POINT;
- }
-
- std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id);
- if (!meta_data) {
- /* This function has to return something in this case, but it shouldn't be used,
- * so return an output that will assert later if the code attempts to handle it. */
- return ATTR_DOMAIN_AUTO;
- }
-
- return meta_data->domain;
-}
-
-/**
- * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling
- * `as_span()` for every single profile and curve spline combination, and for readability.
- */
-struct ResultAttributeData {
- GMutableSpan data;
- AttributeDomain domain;
-};
-
-static std::optional<ResultAttributeData> create_attribute_and_get_span(
- MeshComponent &component,
- const AttributeIDRef &attribute_id,
- AttributeMetaData meta_data,
- Vector<OutputAttribute> &r_attributes)
-{
- const AttributeDomain domain = get_result_attribute_domain(component, attribute_id);
- OutputAttribute attribute = component.attribute_try_get_for_output_only(
- attribute_id, domain, meta_data.data_type);
- if (!attribute) {
- return std::nullopt;
- }
-
- GMutableSpan span = attribute.as_span();
- r_attributes.append(std::move(attribute));
- return std::make_optional<ResultAttributeData>({span, domain});
-}
-
-/**
- * Store the references to the attribute data from the curve and profile inputs. Here we rely on
- * the invariants of the storage of curve attributes, that the order will be consistent between
- * splines, and all splines will have the same attributes.
- */
-struct ResultAttributes {
- /**
- * Result attributes on the mesh corresponding to each attribute on the curve input, in the same
- * order. The data is optional only in case the attribute does not exist on the mesh for some
- * reason, like "shade_smooth" when the result has no faces.
- */
- Vector<std::optional<ResultAttributeData>> curve_point_attributes;
- Vector<std::optional<ResultAttributeData>> curve_spline_attributes;
-
- /**
- * Result attributes corresponding the attributes on the profile input, in the same order. The
- * attributes are optional in case the attribute names correspond to a names used by the curve
- * input, in which case the curve input attributes take precedence.
- */
- Vector<std::optional<ResultAttributeData>> profile_point_attributes;
- Vector<std::optional<ResultAttributeData>> profile_spline_attributes;
-
- /**
- * Because some builtin attributes are not stored contiguously, and the curve inputs might have
- * attributes with those names, it's necessary to keep OutputAttributes around to give access to
- * the result data in a contiguous array.
- */
- Vector<OutputAttribute> attributes;
-};
-static ResultAttributes create_result_attributes(const CurveEval &curve,
- const CurveEval &profile,
- Mesh &mesh)
-{
- MeshComponent mesh_component;
- mesh_component.replace(&mesh, GeometryOwnershipType::Editable);
- Set<AttributeIDRef> curve_attributes;
-
- /* In order to prefer attributes on the main curve input when there are name collisions, first
- * check the attributes on the curve, then add attributes on the profile that are not also on the
- * main curve input. */
- ResultAttributes result;
- curve.splines().first()->attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- curve_attributes.add_new(id);
- result.curve_point_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- return true;
- },
- ATTR_DOMAIN_POINT);
- curve.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- curve_attributes.add_new(id);
- result.curve_spline_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- return true;
- },
- ATTR_DOMAIN_CURVE);
- profile.splines().first()->attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- if (curve_attributes.contains(id)) {
- result.profile_point_attributes.append({});
- }
- else {
- result.profile_point_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- }
- return true;
- },
- ATTR_DOMAIN_POINT);
- profile.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- if (curve_attributes.contains(id)) {
- result.profile_spline_attributes.append({});
- }
- else {
- result.profile_spline_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- }
- return true;
- },
- ATTR_DOMAIN_CURVE);
-
- return result;
-}
-
-template<typename T>
-static void copy_curve_point_data_to_mesh_verts(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
-{
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
- dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]);
- }
-}
-
-template<typename T>
-static void copy_curve_point_data_to_mesh_edges(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
-{
- const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len;
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int ring_edge_start = edges_start + info.profile_edge_len * i_ring;
- dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]);
- }
-}
-
-template<typename T>
-static void copy_curve_point_data_to_mesh_faces(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
-{
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring;
- dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]);
- }
-}
-
-static void copy_curve_point_attribute_to_mesh(const GSpan src,
- const ResultInfo &info,
- ResultAttributeData &dst)
-{
- GVArrayPtr interpolated_gvarray = info.spline.interpolate_to_evaluated(src);
- GSpan interpolated = interpolated_gvarray->get_internal_span();
-
- attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
- using T = decltype(dummy);
- switch (dst.domain) {
- case ATTR_DOMAIN_POINT:
- copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
- break;
- case ATTR_DOMAIN_EDGE:
- copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
- break;
- case ATTR_DOMAIN_FACE:
- copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
- break;
- case ATTR_DOMAIN_CORNER:
- /* Unsupported for now, since there are no builtin attributes to convert into. */
- break;
- default:
- BLI_assert_unreachable();
- break;
- }
- });
-}
-
-template<typename T>
-static void copy_profile_point_data_to_mesh_verts(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
-{
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
- dst[profile_vert_start + i_profile] = src[i_profile];
- }
- }
-}
-
-template<typename T>
-static void copy_profile_point_data_to_mesh_edges(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
-{
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
- const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len;
- dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]);
- }
-}
-
-template<typename T>
-static void copy_profile_point_data_to_mesh_faces(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
-{
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len;
- for (const int i_profile : IndexRange(info.profile_edge_len)) {
- dst[profile_face_start + i_profile] = src[i_profile];
- }
- }
-}
-
-static void copy_profile_point_attribute_to_mesh(const GSpan src,
- const ResultInfo &info,
- ResultAttributeData &dst)
-{
- GVArrayPtr interpolated_gvarray = info.profile.interpolate_to_evaluated(src);
- GSpan interpolated = interpolated_gvarray->get_internal_span();
-
- attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
- using T = decltype(dummy);
- switch (dst.domain) {
- case ATTR_DOMAIN_POINT:
- copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
- break;
- case ATTR_DOMAIN_EDGE:
- copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
- break;
- case ATTR_DOMAIN_FACE:
- copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
- break;
- case ATTR_DOMAIN_CORNER:
- /* Unsupported for now, since there are no builtin attributes to convert into. */
- break;
- default:
- BLI_assert_unreachable();
- break;
- }
- });
-}
-
-static void copy_point_domain_attributes_to_mesh(const ResultInfo &info,
- ResultAttributes &attributes)
-{
- if (!attributes.curve_point_attributes.is_empty()) {
- int i = 0;
- info.spline.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.curve_point_attributes[i]) {
- copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id),
- info,
- *attributes.curve_point_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_POINT);
- }
- if (!attributes.profile_point_attributes.is_empty()) {
- int i = 0;
- info.profile.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.profile_point_attributes[i]) {
- copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id),
- info,
- *attributes.profile_point_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_POINT);
- }
-}
-
-template<typename T>
-static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst)
-{
- for (const int i : IndexRange(src.size())) {
- dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]);
- }
-}
-
-/**
- * Since the offsets for each combination of curve and profile spline are stored for every mesh
- * domain, and this just needs to fill the chunks corresponding to each combination, we can use
- * the same function for all mesh domains.
- */
-static void copy_spline_attribute_to_mesh(const GSpan src,
- const ResultOffsets &offsets,
- ResultAttributeData &dst_attribute)
-{
- attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
- using T = decltype(dummy);
- switch (dst_attribute.domain) {
- case ATTR_DOMAIN_POINT:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>());
- break;
- case ATTR_DOMAIN_EDGE:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>());
- break;
- case ATTR_DOMAIN_FACE:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>());
- break;
- case ATTR_DOMAIN_CORNER:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>());
- break;
- default:
- BLI_assert_unreachable();
- break;
- }
- });
-}
-
-static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve,
- const CurveEval &profile,
- const ResultOffsets &offsets,
- ResultAttributes &attributes)
-{
- if (!attributes.curve_spline_attributes.is_empty()) {
- int i = 0;
- curve.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.curve_spline_attributes[i]) {
- copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id),
- offsets,
- *attributes.curve_spline_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_CURVE);
- }
- if (!attributes.profile_spline_attributes.is_empty()) {
- int i = 0;
- profile.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.profile_spline_attributes[i]) {
- copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id),
- offsets,
- *attributes.profile_spline_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_CURVE);
- }
-}
-
-/**
- * \note Normal calculation is by far the slowest part of calculations relating to the result mesh.
- * Although it would be a sensible decision to use the better topology information available while
- * generating the mesh to also generate the normals, that work may wasted if the output mesh is
- * changed anyway in a way that affects the normals. So currently this code uses the safer /
- * simpler solution of deferring normal calculation to the rest of Blender.
- */
-static Mesh *curve_to_mesh_calculate(const CurveEval &curve, const CurveEval &profile)
-{
- Span<SplinePtr> profiles = profile.splines();
- Span<SplinePtr> curves = curve.splines();
-
- const ResultOffsets offsets = calculate_result_offsets(profiles, curves);
- if (offsets.vert.last() == 0) {
- return nullptr;
- }
-
- Mesh *mesh = BKE_mesh_new_nomain(
- offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last());
- BKE_id_material_eval_ensure_default_slot(&mesh->id);
- mesh->flag |= ME_AUTOSMOOTH;
- mesh->smoothresh = DEG2RADF(180.0f);
- BKE_mesh_normals_tag_dirty(mesh);
-
- ResultAttributes attributes = create_result_attributes(curve, profile, *mesh);
-
- threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) {
- for (const int i_spline : curves_range) {
- const Spline &spline = *curves[i_spline];
- if (spline.evaluated_points_size() == 0) {
- continue;
- }
- const int spline_start_index = i_spline * profiles.size();
- threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) {
- for (const int i_profile : profiles_range) {
- const Spline &profile = *profiles[i_profile];
- const int i_mesh = spline_start_index + i_profile;
- ResultInfo info{
- spline,
- profile,
- offsets.vert[i_mesh],
- offsets.edge[i_mesh],
- offsets.loop[i_mesh],
- offsets.poly[i_mesh],
- spline.evaluated_points_size(),
- spline.evaluated_edges_size(),
- profile.evaluated_points_size(),
- profile.evaluated_edges_size(),
- };
-
- spline_extrude_to_mesh_data(info,
- {mesh->mvert, mesh->totvert},
- {mesh->medge, mesh->totedge},
- {mesh->mloop, mesh->totloop},
- {mesh->mpoly, mesh->totpoly});
-
- copy_point_domain_attributes_to_mesh(info, attributes);
- }
- });
- }
- });
-
- copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes);
-
- for (OutputAttribute &output_attribute : attributes.attributes) {
- output_attribute.save();
- }
-
- return mesh;
-}
-
-static CurveEval get_curve_single_vert()
-{
- CurveEval curve;
- std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
- spline->add_point(float3(0), 0, 0.0f);
- curve.add_spline(std::move(spline));
-
- return curve;
-}
-
static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params)
{
GeometrySet curve_set = params.extract_input<GeometrySet>("Curve");
@@ -750,11 +57,14 @@ static void geo_node_curve_to_mesh_exec(GeoNodeExecParams params)
const CurveEval *profile_curve = profile_set.get_curve_for_read();
- static const CurveEval vert_curve = get_curve_single_vert();
-
- Mesh *mesh = curve_to_mesh_calculate(*curve_set.get_curve_for_read(),
- (profile_curve == nullptr) ? vert_curve : *profile_curve);
- params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
+ if (profile_curve == nullptr) {
+ Mesh *mesh = bke::curve_to_wire_mesh(*curve_set.get_curve_for_read());
+ params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
+ }
+ else {
+ Mesh *mesh = bke::curve_to_mesh_sweep(*curve_set.get_curve_for_read(), *profile_curve);
+ params.set_output("Mesh", GeometrySet::create_with_mesh(mesh));
+ }
}
} // namespace blender::nodes
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
index 07818f2a3ad..f92086acdf0 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
@@ -14,10 +14,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include "BLI_task.hh"
+
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_mesh.h"
+#include "BKE_spline.hh"
#include "node_geometry_util.hh"
@@ -147,6 +150,95 @@ static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_c
}
}
+static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals)
+{
+ Span<int> offsets = spline.control_point_offsets();
+ Span<float3> evaluated_normals = spline.evaluated_normals();
+ for (const int i : IndexRange(spline.size())) {
+ normals[i] = evaluated_normals[offsets[i]];
+ }
+}
+
+static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals)
+{
+ normals.copy_from(spline.evaluated_normals());
+}
+
+/**
+ * Because NURBS control points are not necessarily on the path, the normal at the control points
+ * is not well defined, so create a temporary poly spline to find the normals. This requires extra
+ * copying currently, but may be more efficient in the future if attributes have some form of CoW.
+ */
+static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals)
+{
+ PolySpline poly_spline;
+ poly_spline.resize(spline.size());
+ poly_spline.positions().copy_from(spline.positions());
+ normals.copy_from(poly_spline.evaluated_normals());
+}
+
+static Array<float3> curve_normal_point_domain(const CurveEval &curve)
+{
+ Span<SplinePtr> splines = curve.splines();
+ Array<int> offsets = curve.control_point_offsets();
+ const int total_size = offsets.last();
+ Array<float3> normals(total_size);
+
+ threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
+ for (const int i : range) {
+ const Spline &spline = *splines[i];
+ MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())};
+ switch (splines[i]->type()) {
+ case Spline::Type::Bezier:
+ calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals);
+ break;
+ case Spline::Type::Poly:
+ calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals);
+ break;
+ case Spline::Type::NURBS:
+ calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals);
+ break;
+ }
+ }
+ });
+ return normals;
+}
+
+static const GVArray *construct_curve_normal_gvarray(const CurveComponent &component,
+ const AttributeDomain domain,
+ ResourceScope &scope)
+{
+ const CurveEval *curve = component.get_for_read();
+ if (curve == nullptr) {
+ return nullptr;
+ }
+
+ if (domain == ATTR_DOMAIN_POINT) {
+ const Span<SplinePtr> splines = curve->splines();
+
+ /* Use a reference to evaluated normals if possible to avoid an allocation and a copy.
+ * This is only possible when there is only one poly spline. */
+ if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) {
+ const PolySpline &spline = static_cast<PolySpline &>(*splines.first());
+ return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_normals());
+ }
+
+ Array<float3> normals = curve_normal_point_domain(*curve);
+ return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals));
+ }
+
+ if (domain == ATTR_DOMAIN_CURVE) {
+ Array<float3> point_normals = curve_normal_point_domain(*curve);
+ GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(
+ std::move(point_normals));
+ GVArrayPtr spline_normals = component.attribute_try_adapt_domain(
+ std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+ return scope.add_value(std::move(spline_normals)).get();
+ }
+
+ return nullptr;
+}
+
class NormalFieldInput final : public fn::FieldInput {
public:
NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal")
@@ -173,8 +265,8 @@ class NormalFieldInput final : public fn::FieldInput {
return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope);
}
if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
- /* TODO: Add curve normals support. */
- return nullptr;
+ const CurveComponent &curve_component = static_cast<const CurveComponent &>(component);
+ return construct_curve_normal_gvarray(curve_component, domain, scope);
}
}
return nullptr;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
new file mode 100644
index 00000000000..68788709f1e
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_tangent.cc
@@ -0,0 +1,174 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_task.hh"
+
+#include "BKE_spline.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_input_tangent_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Vector>("Tangent");
+}
+
+static void calculate_bezier_tangents(const BezierSpline &spline, MutableSpan<float3> tangents)
+{
+ Span<int> offsets = spline.control_point_offsets();
+ Span<float3> evaluated_tangents = spline.evaluated_tangents();
+ for (const int i : IndexRange(spline.size())) {
+ tangents[i] = evaluated_tangents[offsets[i]];
+ }
+}
+
+static void calculate_poly_tangents(const PolySpline &spline, MutableSpan<float3> tangents)
+{
+ tangents.copy_from(spline.evaluated_tangents());
+}
+
+/**
+ * Because NURBS control points are not necessarily on the path, the tangent at the control points
+ * is not well defined, so create a temporary poly spline to find the tangents. This requires extra
+ * copying currently, but may be more efficient in the future if attributes have some form of CoW.
+ */
+static void calculate_nurbs_tangents(const NURBSpline &spline, MutableSpan<float3> tangents)
+{
+ PolySpline poly_spline;
+ poly_spline.resize(spline.size());
+ poly_spline.positions().copy_from(spline.positions());
+ tangents.copy_from(poly_spline.evaluated_tangents());
+}
+
+static Array<float3> curve_tangent_point_domain(const CurveEval &curve)
+{
+ Span<SplinePtr> splines = curve.splines();
+ Array<int> offsets = curve.control_point_offsets();
+ const int total_size = offsets.last();
+ Array<float3> tangents(total_size);
+
+ threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
+ for (const int i : range) {
+ const Spline &spline = *splines[i];
+ MutableSpan spline_tangents{tangents.as_mutable_span().slice(offsets[i], spline.size())};
+ switch (splines[i]->type()) {
+ case Spline::Type::Bezier: {
+ calculate_bezier_tangents(static_cast<const BezierSpline &>(spline), spline_tangents);
+ break;
+ }
+ case Spline::Type::Poly: {
+ calculate_poly_tangents(static_cast<const PolySpline &>(spline), spline_tangents);
+ break;
+ }
+ case Spline::Type::NURBS: {
+ calculate_nurbs_tangents(static_cast<const NURBSpline &>(spline), spline_tangents);
+ break;
+ }
+ }
+ }
+ });
+ return tangents;
+}
+
+static const GVArray *construct_curve_tangent_gvarray(const CurveComponent &component,
+ const AttributeDomain domain,
+ ResourceScope &scope)
+{
+ const CurveEval *curve = component.get_for_read();
+ if (curve == nullptr) {
+ return nullptr;
+ }
+
+ if (domain == ATTR_DOMAIN_POINT) {
+ const Span<SplinePtr> splines = curve->splines();
+
+ /* Use a reference to evaluated tangents if possible to avoid an allocation and a copy.
+ * This is only possible when there is only one poly spline. */
+ if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) {
+ const PolySpline &spline = static_cast<PolySpline &>(*splines.first());
+ return &scope.construct<fn::GVArray_For_Span<float3>>(spline.evaluated_tangents());
+ }
+
+ Array<float3> tangents = curve_tangent_point_domain(*curve);
+ return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(tangents));
+ }
+
+ if (domain == ATTR_DOMAIN_CURVE) {
+ Array<float3> point_tangents = curve_tangent_point_domain(*curve);
+ GVArrayPtr gvarray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(
+ std::move(point_tangents));
+ GVArrayPtr spline_tangents = component.attribute_try_adapt_domain(
+ std::move(gvarray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE);
+ return scope.add_value(std::move(spline_tangents)).get();
+ }
+
+ return nullptr;
+}
+
+class TangentFieldInput final : public fn::FieldInput {
+ public:
+ TangentFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Tangent")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask UNUSED(mask),
+ ResourceScope &scope) const final
+ {
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+
+ if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
+ const CurveComponent &curve_component = static_cast<const CurveComponent &>(component);
+ return construct_curve_tangent_gvarray(curve_component, domain, scope);
+ }
+ }
+ return nullptr;
+ }
+
+ uint64_t hash() const override
+ {
+ /* Some random constant hash. */
+ return 91827364589;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const override
+ {
+ return dynamic_cast<const TangentFieldInput *>(&other) != nullptr;
+ }
+};
+
+static void geo_node_input_tangent_exec(GeoNodeExecParams params)
+{
+ Field<float3> tangent_field{std::make_shared<TangentFieldInput>()};
+ params.set_output("Tangent", std::move(tangent_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_input_tangent()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_INPUT_TANGENT, "Curve Tangent", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_input_tangent_exec;
+ ntype.declare = blender::nodes::geo_node_input_tangent_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_join.cc b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc
new file mode 100644
index 00000000000..1e4a4d1f68b
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_string_join.cc
@@ -0,0 +1,53 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_string_join_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::String>("Delimiter");
+ b.add_input<decl::String>("Strings").multi_input().hide_value();
+ b.add_output<decl::String>("String");
+};
+
+static void geo_node_string_join_exec(GeoNodeExecParams params)
+{
+ Vector<std::string> strings = params.extract_multi_input<std::string>("Strings");
+ const std::string delim = params.extract_input<std::string>("Delimiter");
+
+ std::string output;
+ for (const int i : strings.index_range()) {
+ output += strings[i];
+ if (i < (strings.size() - 1)) {
+ output += delim;
+ }
+ }
+ params.set_output("String", std::move(output));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_string_join()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_STRING_JOIN, "String Join", NODE_CLASS_CONVERTER, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_string_join_exec;
+ ntype.declare = blender::nodes::geo_node_string_join_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c
index f601f3e9fd0..06f4d1f1b79 100644
--- a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c
+++ b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.c
@@ -35,6 +35,8 @@ static bNodeSocketTemplate sh_node_bsdf_principled_in[] = {
PROP_NONE,
SOCK_COMPACT},
{SOCK_RGBA, N_("Subsurface Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f},
+ {SOCK_FLOAT, N_("Subsurface IOR"), 1.4f, 0.0f, 0.0f, 0.0f, 1.01f, 3.8f, PROP_FACTOR},
+ {SOCK_FLOAT, N_("Subsurface Anisotropy"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
{SOCK_FLOAT, N_("Metallic"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
{SOCK_FLOAT, N_("Specular"), 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
{SOCK_FLOAT, N_("Specular Tint"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
@@ -74,7 +76,7 @@ static bNodeSocketTemplate sh_node_bsdf_principled_out[] = {
static void node_shader_init_principled(bNodeTree *UNUSED(ntree), bNode *node)
{
node->custom1 = SHD_GLOSSY_GGX;
- node->custom2 = SHD_SUBSURFACE_BURLEY;
+ node->custom2 = SHD_SUBSURFACE_RANDOM_WALK;
}
#define socket_not_zero(sock) (in[sock].link || (clamp_f(in[sock].vec[0], 0.0f, 1.0f) > 1e-5f))
@@ -90,41 +92,40 @@ static int node_shader_gpu_bsdf_principled(GPUMaterial *mat,
GPUNodeLink *sss_scale;
/* Normals */
- if (!in[20].link) {
- GPU_link(mat, "world_normals_get", &in[20].link);
+ if (!in[22].link) {
+ GPU_link(mat, "world_normals_get", &in[22].link);
}
/* Clearcoat Normals */
- if (!in[21].link) {
- GPU_link(mat, "world_normals_get", &in[21].link);
+ if (!in[23].link) {
+ GPU_link(mat, "world_normals_get", &in[23].link);
}
#if 0 /* Not used at the moment. */
/* Tangents */
- if (!in[22].link) {
+ if (!in[24].link) {
GPUNodeLink *orco = GPU_attribute(CD_ORCO, "");
- GPU_link(mat, "tangent_orco_z", orco, &in[22].link);
+ GPU_link(mat, "tangent_orco_z", orco, &in[24].link);
GPU_link(mat,
"node_tangent",
GPU_builtin(GPU_WORLD_NORMAL),
- in[22].link,
+ in[24].link,
GPU_builtin(GPU_OBJECT_MATRIX),
- &in[22].link);
+ &in[24].link);
}
#endif
- bool use_diffuse = socket_not_one(4) && socket_not_one(15);
+ bool use_diffuse = socket_not_one(6) && socket_not_one(17);
bool use_subsurf = socket_not_zero(1) && use_diffuse && node->sss_id > 0;
- bool use_refract = socket_not_one(4) && socket_not_zero(15);
- bool use_clear = socket_not_zero(12);
+ bool use_refract = socket_not_one(6) && socket_not_zero(17);
+ bool use_clear = socket_not_zero(14);
/* SSS Profile */
if (use_subsurf) {
- static short profile = SHD_SUBSURFACE_BURLEY;
bNodeSocket *socket = BLI_findlink(&node->original->inputs, 2);
bNodeSocketValueRGBA *socket_data = socket->default_value;
/* For some reason it seems that the socket value is in ARGB format. */
- GPU_material_sss_profile_create(mat, &socket_data->value[1], &profile, NULL);
+ GPU_material_sss_profile_create(mat, &socket_data->value[1]);
}
if (in[2].link) {
diff --git a/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c b/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c
index 4b91bcbd11c..e917858e0f2 100644
--- a/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c
+++ b/source/blender/nodes/shader/nodes/node_shader_subsurface_scattering.c
@@ -25,8 +25,8 @@ static bNodeSocketTemplate sh_node_subsurface_scattering_in[] = {
{SOCK_RGBA, N_("Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f},
{SOCK_FLOAT, N_("Scale"), 1.0, 0.0f, 0.0f, 0.0f, 0.0f, 1000.0f},
{SOCK_VECTOR, N_("Radius"), 1.0f, 0.2f, 0.1f, 0.0f, 0.0f, 100.0f, PROP_NONE, SOCK_COMPACT},
- {SOCK_FLOAT, N_("Sharpness"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
- {SOCK_FLOAT, N_("Texture Blur"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
+ {SOCK_FLOAT, N_("IOR"), 1.4f, 0.0f, 0.0f, 0.0f, 1.01f, 3.8f, PROP_FACTOR},
+ {SOCK_FLOAT, N_("Anisotropy"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_FACTOR},
{SOCK_VECTOR, N_("Normal"), 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE},
{-1, ""},
};
@@ -38,7 +38,8 @@ static bNodeSocketTemplate sh_node_subsurface_scattering_out[] = {
static void node_shader_init_subsurface_scattering(bNodeTree *UNUSED(ntree), bNode *node)
{
- node->custom1 = SHD_SUBSURFACE_BURLEY;
+ node->custom1 = SHD_SUBSURFACE_RANDOM_WALK;
+ node->custom2 = true;
}
static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat,
@@ -54,11 +55,8 @@ static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat,
if (node->sss_id > 0) {
bNodeSocket *socket = BLI_findlink(&node->original->inputs, 2);
bNodeSocketValueRGBA *socket_data = socket->default_value;
- bNodeSocket *socket_sharp = BLI_findlink(&node->original->inputs, 3);
- bNodeSocketValueFloat *socket_data_sharp = socket_sharp->default_value;
/* For some reason it seems that the socket value is in ARGB format. */
- GPU_material_sss_profile_create(
- mat, &socket_data->value[1], &node->original->custom1, &socket_data_sharp->value);
+ GPU_material_sss_profile_create(mat, &socket_data->value[1]);
/* sss_id is 0 only the node is not connected to any output.
* In this case flagging the material would trigger a bug (see T68736). */
@@ -69,23 +67,6 @@ static int node_shader_gpu_subsurface_scattering(GPUMaterial *mat,
mat, node, "node_subsurface_scattering", in, out, GPU_constant(&node->sss_id));
}
-static void node_shader_update_subsurface_scattering(bNodeTree *UNUSED(ntree), bNode *node)
-{
- bNodeSocket *sock;
- int falloff = node->custom1;
-
- for (sock = node->inputs.first; sock; sock = sock->next) {
- if (STREQ(sock->name, "Sharpness")) {
- if (falloff == SHD_SUBSURFACE_CUBIC) {
- sock->flag &= ~SOCK_UNAVAIL;
- }
- else {
- sock->flag |= SOCK_UNAVAIL;
- }
- }
- }
-}
-
/* node type definition */
void register_node_type_sh_subsurface_scattering(void)
{
@@ -99,7 +80,6 @@ void register_node_type_sh_subsurface_scattering(void)
node_type_init(&ntype, node_shader_init_subsurface_scattering);
node_type_storage(&ntype, "", NULL, NULL);
node_type_gpu(&ntype, node_shader_gpu_subsurface_scattering);
- node_type_update(&ntype, node_shader_update_subsurface_scattering);
nodeRegisterType(&ntype);
}
diff --git a/source/blender/render/CMakeLists.txt b/source/blender/render/CMakeLists.txt
index 0046474d064..494415a4077 100644
--- a/source/blender/render/CMakeLists.txt
+++ b/source/blender/render/CMakeLists.txt
@@ -59,7 +59,6 @@ set(SRC
RE_pipeline.h
RE_texture.h
- intern/initrender.h
intern/pipeline.h
intern/render_result.h
intern/render_types.h
diff --git a/source/blender/render/RE_engine.h b/source/blender/render/RE_engine.h
index dfc0d5d0e9f..2a3a5964262 100644
--- a/source/blender/render/RE_engine.h
+++ b/source/blender/render/RE_engine.h
@@ -40,6 +40,7 @@ struct RenderData;
struct RenderEngine;
struct RenderEngineType;
struct RenderLayer;
+struct RenderPass;
struct RenderResult;
struct ReportList;
struct Scene;
@@ -59,7 +60,7 @@ extern "C" {
#define RE_USE_PREVIEW 4
#define RE_USE_POSTPROCESS 8
#define RE_USE_EEVEE_VIEWPORT 16
-#define RE_USE_SAVE_BUFFERS 32
+/* #define RE_USE_SAVE_BUFFERS_DEPRECATED 32 */
#define RE_USE_SHADING_NODES_CUSTOM 64
#define RE_USE_SPHERICAL_STEREO 128
#define RE_USE_STEREO_VIEWPORT 256
@@ -75,6 +76,7 @@ extern "C" {
#define RE_ENGINE_DO_UPDATE 8
#define RE_ENGINE_RENDERING 16
#define RE_ENGINE_HIGHLIGHT_TILES 32
+#define RE_ENGINE_CAN_DRAW 64
extern ListBase R_engines;
@@ -87,7 +89,20 @@ typedef struct RenderEngineType {
int flag;
void (*update)(struct RenderEngine *engine, struct Main *bmain, struct Depsgraph *depsgraph);
+
void (*render)(struct RenderEngine *engine, struct Depsgraph *depsgraph);
+
+ /* Offline rendering is finished - no more view layers will be rendered.
+ *
+ * All the pending data is to be communicated from the engine back to Blender. In a possibly
+ * most memory-efficient manner (engine might free its database before making Blender to allocate
+ * full-frame render result). */
+ void (*render_frame_finish)(struct RenderEngine *engine);
+
+ void (*draw)(struct RenderEngine *engine,
+ const struct bContext *context,
+ struct Depsgraph *depsgraph);
+
void (*bake)(struct RenderEngine *engine,
struct Depsgraph *depsgraph,
struct Object *object,
@@ -132,9 +147,6 @@ typedef struct RenderEngine {
struct Object *camera_override;
unsigned int layer_override;
- int tile_x;
- int tile_y;
-
struct Render *re;
ListBase fullresult;
char text[512]; /* IMA_MAX_RENDER_TEXT */
@@ -189,6 +201,10 @@ void RE_engine_end_result(RenderEngine *engine,
bool merge_results);
struct RenderResult *RE_engine_get_result(struct RenderEngine *engine);
+struct RenderPass *RE_engine_pass_by_index_get(struct RenderEngine *engine,
+ const char *layer_name,
+ int index);
+
const char *RE_engine_active_view_get(RenderEngine *engine);
void RE_engine_active_view_set(RenderEngine *engine, const char *viewname);
float RE_engine_get_camera_shift_x(RenderEngine *engine,
@@ -228,6 +244,24 @@ void RE_engine_register_pass(struct RenderEngine *engine,
bool RE_engine_use_persistent_data(struct RenderEngine *engine);
+struct RenderEngine *RE_engine_get(const struct Render *re);
+
+/* Acquire render engine for drawing via its `draw()` callback.
+ *
+ * If drawing is not possible false is returned. If drawing is possible then the engine is
+ * "acquired" so that it can not be freed by the render pipeline.
+ *
+ * Drawing is possible if the engine has the `draw()` callback and it is in its `render()`
+ * callback. */
+bool RE_engine_draw_acquire(struct Render *re);
+void RE_engine_draw_release(struct Render *re);
+
+/* NOTE: Only used for Cycles's BLenderGPUDisplay integration with the draw manager. A subject
+ * for re-consideration. Do not use this functionality. */
+bool RE_engine_has_render_context(struct RenderEngine *engine);
+void RE_engine_render_context_enable(struct RenderEngine *engine);
+void RE_engine_render_context_disable(struct RenderEngine *engine);
+
/* Engine Types */
void RE_engines_init(void);
@@ -252,6 +286,10 @@ void RE_bake_engine_set_engine_parameters(struct Render *re,
void RE_engine_free_blender_memory(struct RenderEngine *engine);
+void RE_engine_tile_highlight_set(
+ struct RenderEngine *engine, int x, int y, int width, int height, bool highlight);
+void RE_engine_tile_highlight_clear_all(struct RenderEngine *engine);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/render/RE_pipeline.h b/source/blender/render/RE_pipeline.h
index cd839385bfb..3237772dd80 100644
--- a/source/blender/render/RE_pipeline.h
+++ b/source/blender/render/RE_pipeline.h
@@ -141,9 +141,6 @@ typedef struct RenderResult {
volatile rcti renrect;
volatile RenderLayer *renlay;
- /* optional saved endresult on disk */
- int do_exr_tile;
-
/* for render results in Image, verify validity for sequences */
int framenr;
diff --git a/source/blender/render/intern/bake.c b/source/blender/render/intern/bake.c
index 76839651b5d..0f893ce8cd5 100644
--- a/source/blender/render/intern/bake.c
+++ b/source/blender/render/intern/bake.c
@@ -774,18 +774,6 @@ void RE_bake_pixels_populate(Mesh *me,
/* ******************** NORMALS ************************ */
-/**
- * convert a normalized normal to the -1.0 1.0 range
- * the input is expected to be POS_X, POS_Y, POS_Z
- */
-static void normal_uncompress(float out[3], const float in[3])
-{
- int i;
- for (i = 0; i < 3; i++) {
- out[i] = 2.0f * in[i] - 1.0f;
- }
-}
-
static void normal_compress(float out[3],
const float in[3],
const eBakeNormalSwizzle normal_swizzle[3])
@@ -934,7 +922,7 @@ void RE_bake_normal_world_to_tangent(const BakePixel pixel_array[],
copy_v3_v3(tsm[2], normal);
/* texture values */
- normal_uncompress(nor, &result[offset]);
+ copy_v3_v3(nor, &result[offset]);
/* converts from world space to local space */
mul_transposed_mat3_m4_v3(mat, nor);
@@ -976,7 +964,7 @@ void RE_bake_normal_world_to_object(const BakePixel pixel_array[],
}
offset = i * depth;
- normal_uncompress(nor, &result[offset]);
+ copy_v3_v3(nor, &result[offset]);
/* rotates only without translation */
mul_mat3_m4_v3(iobmat, nor);
@@ -1004,7 +992,7 @@ void RE_bake_normal_world_to_world(const BakePixel pixel_array[],
}
offset = i * depth;
- normal_uncompress(nor, &result[offset]);
+ copy_v3_v3(nor, &result[offset]);
/* save back the values */
normal_compress(&result[offset], nor, normal_swizzle);
@@ -1053,6 +1041,7 @@ int RE_pass_depth(const eScenePassType pass_type)
}
case SCE_PASS_COMBINED:
case SCE_PASS_SHADOW:
+ case SCE_PASS_POSITION:
case SCE_PASS_NORMAL:
case SCE_PASS_VECTOR:
case SCE_PASS_INDEXOB: /* XXX double check */
diff --git a/source/blender/render/intern/engine.c b/source/blender/render/intern/engine.c
index 5728b784714..389b821ca35 100644
--- a/source/blender/render/intern/engine.c
+++ b/source/blender/render/intern/engine.c
@@ -62,7 +62,6 @@
#include "DRW_engine.h"
-#include "initrender.h"
#include "pipeline.h"
#include "render_result.h"
#include "render_types.h"
@@ -283,14 +282,6 @@ static void render_result_to_bake(RenderEngine *engine, RenderResult *rr)
/* Render Results */
-static RenderPart *get_part_from_result(Render *re, RenderResult *result)
-{
- rcti key = result->tilerect;
- BLI_rcti_translate(&key, re->disprect.xmin, re->disprect.ymin);
-
- return BLI_ghash_lookup(re->parts, &key);
-}
-
static HighlightedTile highlighted_tile_from_result_get(Render *re, RenderResult *result)
{
HighlightedTile tile;
@@ -300,6 +291,37 @@ static HighlightedTile highlighted_tile_from_result_get(Render *re, RenderResult
return tile;
}
+static void engine_tile_highlight_set(RenderEngine *engine,
+ const HighlightedTile *tile,
+ bool highlight)
+{
+ if ((engine->flag & RE_ENGINE_HIGHLIGHT_TILES) == 0) {
+ return;
+ }
+
+ Render *re = engine->re;
+
+ BLI_mutex_lock(&re->highlighted_tiles_mutex);
+
+ if (re->highlighted_tiles == NULL) {
+ re->highlighted_tiles = BLI_gset_new(
+ BLI_ghashutil_inthash_v4_p, BLI_ghashutil_inthash_v4_cmp, "highlighted tiles");
+ }
+
+ if (highlight) {
+ HighlightedTile **tile_in_set;
+ if (!BLI_gset_ensure_p_ex(re->highlighted_tiles, tile, (void ***)&tile_in_set)) {
+ *tile_in_set = MEM_mallocN(sizeof(HighlightedTile), __func__);
+ **tile_in_set = *tile;
+ }
+ }
+ else {
+ BLI_gset_remove(re->highlighted_tiles, tile, MEM_freeN);
+ }
+
+ BLI_mutex_unlock(&re->highlighted_tiles_mutex);
+}
+
RenderResult *RE_engine_begin_result(
RenderEngine *engine, int x, int y, int w, int h, const char *layername, const char *viewname)
{
@@ -332,7 +354,7 @@ RenderResult *RE_engine_begin_result(
disprect.ymin = y;
disprect.ymax = y + h;
- result = render_result_new(re, &disprect, RR_USE_MEM, layername, viewname);
+ result = render_result_new(re, &disprect, layername, viewname);
/* TODO: make this thread safe. */
@@ -341,25 +363,12 @@ RenderResult *RE_engine_begin_result(
render_result_clone_passes(re, result, viewname);
render_result_passes_allocated_ensure(result);
- RenderPart *pa;
-
- /* Copy EXR tile settings, so pipeline knows whether this is a result
- * for Save Buffers enabled rendering.
- */
- result->do_exr_tile = re->result->do_exr_tile;
-
BLI_addtail(&engine->fullresult, result);
result->tilerect.xmin += re->disprect.xmin;
result->tilerect.xmax += re->disprect.xmin;
result->tilerect.ymin += re->disprect.ymin;
result->tilerect.ymax += re->disprect.ymin;
-
- pa = get_part_from_result(re, result);
-
- if (pa) {
- pa->status = PART_STATUS_IN_PROGRESS;
- }
}
return result;
@@ -426,53 +435,14 @@ void RE_engine_end_result(
re_ensure_passes_allocated_thread_safe(re);
- /* merge. on break, don't merge in result for preview renders, looks nicer */
- if (!highlight) {
- /* for exr tile render, detect tiles that are done */
- RenderPart *pa = get_part_from_result(re, result);
-
- if (pa) {
- pa->status = (!cancel && merge_results) ? PART_STATUS_MERGED : PART_STATUS_RENDERED;
- }
- else if (re->result->do_exr_tile) {
- /* If written result does not match any tile and we are using save
- * buffers, we are going to get OpenEXR save errors. */
- fprintf(stderr, "RenderEngine.end_result: dimensions do not match any OpenEXR tile.\n");
- }
- }
-
if (re->engine && (re->engine->flag & RE_ENGINE_HIGHLIGHT_TILES)) {
- BLI_mutex_lock(&re->highlighted_tiles_mutex);
-
- if (re->highlighted_tiles == NULL) {
- re->highlighted_tiles = BLI_gset_new(
- BLI_ghashutil_inthash_v4_p, BLI_ghashutil_inthash_v4_cmp, "highlighted tiles");
- }
+ const HighlightedTile tile = highlighted_tile_from_result_get(re, result);
- HighlightedTile tile = highlighted_tile_from_result_get(re, result);
- if (highlight) {
- void **tile_in_set;
- if (!BLI_gset_ensure_p_ex(re->highlighted_tiles, &tile, &tile_in_set)) {
- *tile_in_set = MEM_mallocN(sizeof(HighlightedTile), __func__);
- memcpy(*tile_in_set, &tile, sizeof(tile));
- }
- BLI_gset_add(re->highlighted_tiles, &tile);
- }
- else {
- BLI_gset_remove(re->highlighted_tiles, &tile, MEM_freeN);
- }
-
- BLI_mutex_unlock(&re->highlighted_tiles_mutex);
+ engine_tile_highlight_set(engine, &tile, highlight);
}
if (!cancel || merge_results) {
- if (re->result->do_exr_tile) {
- if (!cancel && merge_results) {
- render_result_exr_file_merge(re->result, result, re->viewname);
- render_result_merge(re->result, result);
- }
- }
- else if (!(re->test_break(re->tbh) && (re->r.scemode & R_BUTS_PREVIEW))) {
+ if (!(re->test_break(re->tbh) && (re->r.scemode & R_BUTS_PREVIEW))) {
render_result_merge(re->result, result);
}
@@ -582,6 +552,27 @@ void RE_engine_set_error_message(RenderEngine *engine, const char *msg)
}
}
+RenderPass *RE_engine_pass_by_index_get(RenderEngine *engine, const char *layer_name, int index)
+{
+ Render *re = engine->re;
+ if (re == NULL) {
+ return NULL;
+ }
+
+ RenderPass *pass = NULL;
+
+ RenderResult *rr = RE_AcquireResultRead(re);
+ if (rr != NULL) {
+ const RenderLayer *layer = RE_GetRenderLayer(rr, layer_name);
+ if (layer != NULL) {
+ pass = BLI_findlink(&layer->passes, index);
+ }
+ }
+ RE_ReleaseResult(re);
+
+ return pass;
+}
+
const char *RE_engine_active_view_get(RenderEngine *engine)
{
Render *re = engine->re;
@@ -837,12 +828,6 @@ bool RE_bake_engine(Render *re,
engine->resolution_x = re->winx;
engine->resolution_y = re->winy;
- BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE);
- RE_parts_init(re);
- engine->tile_x = re->r.tilex;
- engine->tile_y = re->r.tiley;
- BLI_rw_mutex_unlock(&re->partsmutex);
-
if (type->bake) {
engine->depsgraph = depsgraph;
@@ -870,21 +855,13 @@ bool RE_bake_engine(Render *re,
engine->depsgraph = NULL;
}
- engine->tile_x = 0;
- engine->tile_y = 0;
engine->flag &= ~RE_ENGINE_RENDERING;
- /* Free depsgraph outside of parts mutex lock, since this locks OpenGL context
- * while the UI drawing might also lock the OpenGL context and parts mutex. */
engine_depsgraph_free(engine);
- BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE);
RE_engine_free(engine);
re->engine = NULL;
- RE_parts_free(re);
- BLI_rw_mutex_unlock(&re->partsmutex);
-
if (BKE_reports_contain(re->reports, RPT_ERROR)) {
G.is_break = true;
}
@@ -928,15 +905,23 @@ static void engine_render_view_layer(Render *re,
DRW_render_context_enable(engine->re);
}
+ BLI_mutex_lock(&engine->re->engine_draw_mutex);
+ re->engine->flag |= RE_ENGINE_CAN_DRAW;
+ BLI_mutex_unlock(&engine->re->engine_draw_mutex);
+
engine->type->render(engine, engine->depsgraph);
+ BLI_mutex_lock(&engine->re->engine_draw_mutex);
+ re->engine->flag &= ~RE_ENGINE_CAN_DRAW;
+ BLI_mutex_unlock(&engine->re->engine_draw_mutex);
+
if (use_gpu_context) {
DRW_render_context_disable(engine->re);
}
}
/* Optionally composite grease pencil over render result. */
- if (engine->has_grease_pencil && use_grease_pencil && !re->result->do_exr_tile) {
+ if (engine->has_grease_pencil && use_grease_pencil) {
/* NOTE: External engine might have been requested to free its
* dependency graph, which is only allowed if there is no grease
* pencil (pipeline is taking care of that). */
@@ -981,16 +966,11 @@ bool RE_engine_render(Render *re, bool do_all)
/* create render result */
BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
if (re->result == NULL || !(re->r.scemode & R_BUTS_PREVIEW)) {
- int savebuffers = RR_USE_MEM;
-
if (re->result) {
render_result_free(re->result);
}
- if ((type->flag & RE_USE_SAVE_BUFFERS) && (re->r.scemode & R_EXR_TILE_FILE)) {
- savebuffers = RR_USE_EXR;
- }
- re->result = render_result_new(re, &re->disprect, savebuffers, RR_ALL_LAYERS, RR_ALL_VIEWS);
+ re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS);
}
BLI_rw_mutex_unlock(&re->resultmutex);
@@ -1035,32 +1015,15 @@ bool RE_engine_render(Render *re, bool do_all)
engine->resolution_x = re->winx;
engine->resolution_y = re->winy;
- BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE);
- RE_parts_init(re);
- engine->tile_x = re->partx;
- engine->tile_y = re->party;
- BLI_rw_mutex_unlock(&re->partsmutex);
-
- if (re->result->do_exr_tile) {
- render_result_exr_file_begin(re, engine);
- }
-
/* Clear UI drawing locks. */
if (re->draw_lock) {
re->draw_lock(re->dlh, false);
}
- /* Render view layers. */
- bool delay_grease_pencil = false;
-
if (type->render) {
FOREACH_VIEW_LAYER_TO_RENDER_BEGIN (re, view_layer_iter) {
engine_render_view_layer(re, engine, view_layer_iter, true, true);
- /* With save buffers there is no render buffer in memory for compositing, delay
- * grease pencil in that case. */
- delay_grease_pencil = engine->has_grease_pencil && re->result->do_exr_tile;
-
if (RE_engine_test_break(engine)) {
break;
}
@@ -1068,42 +1031,18 @@ bool RE_engine_render(Render *re, bool do_all)
FOREACH_VIEW_LAYER_TO_RENDER_END;
}
+ if (type->render_frame_finish) {
+ type->render_frame_finish(engine);
+ }
+
/* Clear tile data */
- engine->tile_x = 0;
- engine->tile_y = 0;
engine->flag &= ~RE_ENGINE_RENDERING;
render_result_free_list(&engine->fullresult, engine->fullresult.first);
- BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE);
-
- /* For save buffers, read back from disk. */
- if (re->result->do_exr_tile) {
- render_result_exr_file_end(re, engine);
- }
-
- /* Perform delayed grease pencil rendering. */
- if (delay_grease_pencil) {
- BLI_rw_mutex_unlock(&re->partsmutex);
-
- FOREACH_VIEW_LAYER_TO_RENDER_BEGIN (re, view_layer_iter) {
- engine_render_view_layer(re, engine, view_layer_iter, false, true);
- if (RE_engine_test_break(engine)) {
- break;
- }
- }
- FOREACH_VIEW_LAYER_TO_RENDER_END;
-
- BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE);
- }
-
/* re->engine becomes zero if user changed active render engine during render */
if (!engine_keep_depsgraph(engine) || !re->engine) {
- /* Free depsgraph outside of parts mutex lock, since this locks OpenGL context
- * while the UI drawing might also lock the OpenGL context and parts mutex. */
- BLI_rw_mutex_unlock(&re->partsmutex);
engine_depsgraph_free(engine);
- BLI_rw_mutex_lock(&re->partsmutex, THREAD_LOCK_WRITE);
RE_engine_free(engine);
re->engine = NULL;
@@ -1115,9 +1054,6 @@ bool RE_engine_render(Render *re, bool do_all)
BLI_rw_mutex_unlock(&re->resultmutex);
}
- RE_parts_free(re);
- BLI_rw_mutex_unlock(&re->partsmutex);
-
if (BKE_reports_contain(re->reports, RPT_ERROR)) {
G.is_break = true;
}
@@ -1179,3 +1115,81 @@ void RE_engine_free_blender_memory(RenderEngine *engine)
}
engine_depsgraph_free(engine);
}
+
+struct RenderEngine *RE_engine_get(const Render *re)
+{
+ return re->engine;
+}
+
+bool RE_engine_draw_acquire(Render *re)
+{
+ BLI_mutex_lock(&re->engine_draw_mutex);
+
+ RenderEngine *engine = re->engine;
+
+ if (engine == NULL || engine->type->draw == NULL || (engine->flag & RE_ENGINE_CAN_DRAW) == 0) {
+ BLI_mutex_unlock(&re->engine_draw_mutex);
+ return false;
+ }
+
+ return true;
+}
+
+void RE_engine_draw_release(Render *re)
+{
+ BLI_mutex_unlock(&re->engine_draw_mutex);
+}
+
+void RE_engine_tile_highlight_set(
+ RenderEngine *engine, int x, int y, int width, int height, bool highlight)
+{
+ HighlightedTile tile;
+ BLI_rcti_init(&tile.rect, x, x + width, y, y + height);
+
+ engine_tile_highlight_set(engine, &tile, highlight);
+}
+
+void RE_engine_tile_highlight_clear_all(RenderEngine *engine)
+{
+ if ((engine->flag & RE_ENGINE_HIGHLIGHT_TILES) == 0) {
+ return;
+ }
+
+ Render *re = engine->re;
+
+ BLI_mutex_lock(&re->highlighted_tiles_mutex);
+
+ if (re->highlighted_tiles != NULL) {
+ BLI_gset_clear(re->highlighted_tiles, MEM_freeN);
+ }
+
+ BLI_mutex_unlock(&re->highlighted_tiles_mutex);
+}
+
+/* -------------------------------------------------------------------- */
+/** \name OpenGL context manipulation.
+ *
+ * NOTE: Only used for Cycles's BLenderGPUDisplay integration with the draw manager. A subject
+ * for re-consideration. Do not use this functionality.
+ * \{ */
+
+bool RE_engine_has_render_context(RenderEngine *engine)
+{
+ if (engine->re == NULL) {
+ return false;
+ }
+
+ return RE_gl_context_get(engine->re) != NULL;
+}
+
+void RE_engine_render_context_enable(RenderEngine *engine)
+{
+ DRW_render_context_enable(engine->re);
+}
+
+void RE_engine_render_context_disable(RenderEngine *engine)
+{
+ DRW_render_context_disable(engine->re);
+}
+
+/** \} */
diff --git a/source/blender/render/intern/initrender.c b/source/blender/render/intern/initrender.c
index 3148625c866..2370d8e893b 100644
--- a/source/blender/render/intern/initrender.c
+++ b/source/blender/render/intern/initrender.c
@@ -43,9 +43,6 @@
#include "pipeline.h"
#include "render_types.h"
-/* Own includes */
-#include "initrender.h"
-
/* ****************** MASKS and LUTS **************** */
static float filt_quadratic(float x)
@@ -244,91 +241,3 @@ void RE_GetViewPlane(Render *re, rctf *r_viewplane, rcti *r_disprect)
BLI_rcti_init(r_disprect, 0, 0, 0, 0);
}
}
-
-/* ~~~~~~~~~~~~~~~~ part (tile) calculus ~~~~~~~~~~~~~~~~~~~~~~ */
-
-void RE_parts_free(Render *re)
-{
- if (re->parts) {
- BLI_ghash_free(re->parts, NULL, MEM_freeN);
- re->parts = NULL;
- }
-}
-
-void RE_parts_clamp(Render *re)
-{
- /* part size */
- re->partx = max_ii(1, min_ii(re->r.tilex, re->rectx));
- re->party = max_ii(1, min_ii(re->r.tiley, re->recty));
-}
-
-void RE_parts_init(Render *re)
-{
- int nr, xd, yd, partx, party, xparts, yparts;
- int xminb, xmaxb, yminb, ymaxb;
-
- RE_parts_free(re);
-
- re->parts = BLI_ghash_new(
- BLI_ghashutil_inthash_v4_p, BLI_ghashutil_inthash_v4_cmp, "render parts");
-
- /* Just for readable code. */
- xminb = re->disprect.xmin;
- yminb = re->disprect.ymin;
- xmaxb = re->disprect.xmax;
- ymaxb = re->disprect.ymax;
-
- RE_parts_clamp(re);
-
- partx = re->partx;
- party = re->party;
- /* part count */
- xparts = (re->rectx + partx - 1) / partx;
- yparts = (re->recty + party - 1) / party;
-
- for (nr = 0; nr < xparts * yparts; nr++) {
- rcti disprect;
- int rectx, recty;
-
- xd = (nr % xparts);
- yd = (nr - xd) / xparts;
-
- disprect.xmin = xminb + xd * partx;
- disprect.ymin = yminb + yd * party;
-
- /* ensure we cover the entire picture, so last parts go to end */
- if (xd < xparts - 1) {
- disprect.xmax = disprect.xmin + partx;
- if (disprect.xmax > xmaxb) {
- disprect.xmax = xmaxb;
- }
- }
- else {
- disprect.xmax = xmaxb;
- }
-
- if (yd < yparts - 1) {
- disprect.ymax = disprect.ymin + party;
- if (disprect.ymax > ymaxb) {
- disprect.ymax = ymaxb;
- }
- }
- else {
- disprect.ymax = ymaxb;
- }
-
- rectx = BLI_rcti_size_x(&disprect);
- recty = BLI_rcti_size_y(&disprect);
-
- /* so, now can we add this part? */
- if (rectx > 0 && recty > 0) {
- RenderPart *pa = MEM_callocN(sizeof(RenderPart), "new part");
-
- pa->disprect = disprect;
- pa->rectx = rectx;
- pa->recty = recty;
-
- BLI_ghash_insert(re->parts, &pa->disprect, pa);
- }
- }
-}
diff --git a/source/blender/render/intern/pipeline.c b/source/blender/render/intern/pipeline.c
index 5418f4035b1..72ff920561d 100644
--- a/source/blender/render/intern/pipeline.c
+++ b/source/blender/render/intern/pipeline.c
@@ -102,7 +102,6 @@
#include "DEG_depsgraph.h"
/* internal */
-#include "initrender.h"
#include "pipeline.h"
#include "render_result.h"
#include "render_types.h"
@@ -568,7 +567,7 @@ Render *RE_NewRender(const char *name)
BLI_addtail(&RenderGlobal.renderlist, re);
BLI_strncpy(re->name, name, RE_MAXNAME);
BLI_rw_mutex_init(&re->resultmutex);
- BLI_rw_mutex_init(&re->partsmutex);
+ BLI_mutex_init(&re->engine_draw_mutex);
BLI_mutex_init(&re->highlighted_tiles_mutex);
}
@@ -633,7 +632,7 @@ void RE_FreeRender(Render *re)
}
BLI_rw_mutex_end(&re->resultmutex);
- BLI_rw_mutex_end(&re->partsmutex);
+ BLI_mutex_end(&re->engine_draw_mutex);
BLI_mutex_end(&re->highlighted_tiles_mutex);
BLI_freelistN(&re->view_layers);
@@ -722,26 +721,6 @@ void RE_FreePersistentData(const Scene *scene)
/* ********* initialize state ******** */
-/* clear full sample and tile flags if needed */
-static int check_mode_full_sample(RenderData *rd)
-{
- int scemode = rd->scemode;
-
- /* not supported by any current renderer */
- scemode &= ~R_FULL_SAMPLE;
-
-#ifdef WITH_OPENEXR
- if (scemode & R_FULL_SAMPLE) {
- scemode |= R_EXR_TILE_FILE; /* enable automatic */
- }
-#else
- /* can't do this without openexr support */
- scemode &= ~(R_EXR_TILE_FILE | R_FULL_SAMPLE);
-#endif
-
- return scemode;
-}
-
static void re_init_resolution(Render *re, Render *source, int winx, int winy, rcti *disprect)
{
re->winx = winx;
@@ -839,8 +818,6 @@ void RE_InitState(Render *re,
return;
}
- re->r.scemode = check_mode_full_sample(&re->r);
-
if (single_layer) {
int index = BLI_findindex(render_layers, single_layer);
if (index != -1) {
@@ -890,9 +867,6 @@ void RE_InitState(Render *re,
render_result_view_new(re->result, "");
}
- /* ensure renderdatabase can use part settings correct */
- RE_parts_clamp(re);
-
BLI_rw_mutex_unlock(&re->resultmutex);
RE_init_threadcount(re);
@@ -1040,7 +1014,7 @@ static void render_result_uncrop(Render *re)
/* weak is: it chances disprect from border */
render_result_disprect_to_full_resolution(re);
- rres = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS);
+ rres = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS);
render_result_passes_allocated_ensure(rres);
rres->stamp_data = BKE_stamp_data_copy(re->result->stamp_data);
@@ -1227,7 +1201,7 @@ static void do_render_compositor(Render *re)
if ((re->r.mode & R_CROP) == 0) {
render_result_disprect_to_full_resolution(re);
}
- re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS);
+ re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS);
BLI_rw_mutex_unlock(&re->resultmutex);
@@ -1647,7 +1621,7 @@ bool RE_is_rendering_allowed(Scene *scene,
Object *camera_override,
ReportList *reports)
{
- int scemode = check_mode_full_sample(&scene->r);
+ const int scemode = scene->r.scemode;
if (scene->r.mode & R_BORDER) {
if (scene->r.border.xmax <= scene->r.border.xmin ||
@@ -1657,17 +1631,6 @@ bool RE_is_rendering_allowed(Scene *scene,
}
}
- if (scemode & (R_EXR_TILE_FILE | R_FULL_SAMPLE)) {
- char str[FILE_MAX];
-
- render_result_exr_file_path(scene, "", 0, str);
-
- if (!BLI_file_is_writable(str)) {
- BKE_report(reports, RPT_ERROR, "Cannot save render buffers, check the temp default path");
- return 0;
- }
- }
-
if (RE_seq_render_active(scene, &scene->r)) {
/* Sequencer */
if (scene->r.mode & R_BORDER) {
@@ -1686,13 +1649,6 @@ bool RE_is_rendering_allowed(Scene *scene,
BKE_report(reports, RPT_ERROR, "No render output node in scene");
return 0;
}
-
- if (scemode & R_FULL_SAMPLE) {
- if (compositor_needs_render(scene, 0) == 0) {
- BKE_report(reports, RPT_ERROR, "Full sample AA not supported without 3D rendering");
- return 0;
- }
- }
}
else {
/* Regular Render */
@@ -1710,14 +1666,6 @@ bool RE_is_rendering_allowed(Scene *scene,
return 1;
}
-static void validate_render_settings(Render *re)
-{
- if (RE_engine_is_external(re)) {
- /* not supported yet */
- re->r.scemode &= ~R_FULL_SAMPLE;
- }
-}
-
static void update_physics_cache(Render *re,
Scene *scene,
ViewLayer *view_layer,
@@ -1820,8 +1768,6 @@ static int render_init_from_main(Render *re,
/* initstate makes new result, have to send changed tags around */
ntreeCompositTagRender(re->scene);
- validate_render_settings(re);
-
re->display_init(re->dih, re->result);
re->display_clear(re->dch, re->result);
diff --git a/source/blender/render/intern/render_result.c b/source/blender/render/intern/render_result.c
index 6cb6aabe885..c308147fc5b 100644
--- a/source/blender/render/intern/render_result.c
+++ b/source/blender/render/intern/render_result.c
@@ -260,8 +260,10 @@ RenderPass *render_layer_add_pass(RenderResult *rr,
/* will read info from Render *re to define layers */
/* called in threads */
/* re->winx,winy is coordinate space of entire image, partrct the part within */
-RenderResult *render_result_new(
- Render *re, rcti *partrct, int savebuffers, const char *layername, const char *viewname)
+RenderResult *render_result_new(Render *re,
+ rcti *partrct,
+ const char *layername,
+ const char *viewname)
{
RenderResult *rr;
RenderLayer *rl;
@@ -287,10 +289,6 @@ RenderResult *render_result_new(
rr->tilerect.ymin = partrct->ymin - re->disprect.ymin;
rr->tilerect.ymax = partrct->ymax - re->disprect.ymin;
- if (savebuffers) {
- rr->do_exr_tile = true;
- }
-
rr->passes_allocated = false;
render_result_views_new(rr, &re->r);
@@ -314,10 +312,6 @@ RenderResult *render_result_new(
rl->rectx = rectx;
rl->recty = recty;
- if (rr->do_exr_tile) {
- rl->exrhandle = IMB_exr_get_handle();
- }
-
for (rv = rr->views.first; rv; rv = rv->next) {
const char *view = rv->name;
@@ -327,10 +321,6 @@ RenderResult *render_result_new(
}
}
- if (rr->do_exr_tile) {
- IMB_exr_add_view(rl->exrhandle, view);
- }
-
#define RENDER_LAYER_ADD_PASS_SAFE(rr, rl, channels, name, viewname, chan_id) \
do { \
if (render_layer_add_pass(rr, rl, channels, name, viewname, chan_id) == NULL) { \
@@ -351,6 +341,9 @@ RenderResult *render_result_new(
if (view_layer->passflag & SCE_PASS_NORMAL) {
RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_NORMAL, view, "XYZ");
}
+ if (view_layer->passflag & SCE_PASS_POSITION) {
+ RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_POSITION, view, "XYZ");
+ }
if (view_layer->passflag & SCE_PASS_UV) {
RENDER_LAYER_ADD_PASS_SAFE(rr, rl, 3, RE_PASSNAME_UV, view, "UVA");
}
@@ -424,11 +417,6 @@ RenderResult *render_result_new(
rl->rectx = rectx;
rl->recty = recty;
- /* duplicate code... */
- if (rr->do_exr_tile) {
- rl->exrhandle = IMB_exr_get_handle();
- }
-
for (rv = rr->views.first; rv; rv = rv->next) {
const char *view = rv->name;
@@ -438,10 +426,6 @@ RenderResult *render_result_new(
}
}
- if (rr->do_exr_tile) {
- IMB_exr_add_view(rl->exrhandle, view);
- }
-
/* a renderlayer should always have a Combined pass */
render_layer_add_pass(rr, rl, 4, RE_PASSNAME_COMBINED, view, "RGBA");
}
@@ -1089,227 +1073,6 @@ void render_result_single_layer_end(Render *re)
re->pushedresult = NULL;
}
-/************************* EXR Tile File Rendering ***************************/
-
-static void save_render_result_tile(RenderResult *rr, RenderResult *rrpart, const char *viewname)
-{
- RenderLayer *rlp, *rl;
- RenderPass *rpassp;
- int partx, party;
-
- BLI_thread_lock(LOCK_IMAGE);
-
- for (rlp = rrpart->layers.first; rlp; rlp = rlp->next) {
- rl = RE_GetRenderLayer(rr, rlp->name);
-
- /* should never happen but prevents crash if it does */
- BLI_assert(rl);
- if (UNLIKELY(rl == NULL)) {
- continue;
- }
-
- /* passes are allocated in sync */
- for (rpassp = rlp->passes.first; rpassp; rpassp = rpassp->next) {
- const int xstride = rpassp->channels;
- int a;
- char fullname[EXR_PASS_MAXNAME];
-
- for (a = 0; a < xstride; a++) {
- set_pass_full_name(fullname, rpassp->name, a, viewname, rpassp->chan_id);
-
- IMB_exr_set_channel(rl->exrhandle,
- rlp->name,
- fullname,
- xstride,
- xstride * rrpart->rectx,
- rpassp->rect + a);
- }
- }
- }
-
- party = rrpart->tilerect.ymin;
- partx = rrpart->tilerect.xmin;
-
- for (rlp = rrpart->layers.first; rlp; rlp = rlp->next) {
- rl = RE_GetRenderLayer(rr, rlp->name);
-
- /* should never happen but prevents crash if it does */
- BLI_assert(rl);
- if (UNLIKELY(rl == NULL)) {
- continue;
- }
-
- IMB_exrtile_write_channels(rl->exrhandle, partx, party, 0, viewname, false);
- }
-
- BLI_thread_unlock(LOCK_IMAGE);
-}
-
-void render_result_save_empty_result_tiles(Render *re)
-{
- RenderResult *rr;
- RenderLayer *rl;
-
- for (rr = re->result; rr; rr = rr->next) {
- for (rl = rr->layers.first; rl; rl = rl->next) {
- GHashIterator pa_iter;
- GHASH_ITER (pa_iter, re->parts) {
- RenderPart *pa = BLI_ghashIterator_getValue(&pa_iter);
- if (pa->status != PART_STATUS_MERGED) {
- int party = pa->disprect.ymin - re->disprect.ymin;
- int partx = pa->disprect.xmin - re->disprect.xmin;
- IMB_exrtile_write_channels(rl->exrhandle, partx, party, 0, re->viewname, true);
- }
- }
- }
- }
-}
-
-/* Compute list of passes needed by render engine. */
-static void templates_register_pass_cb(void *userdata,
- Scene *UNUSED(scene),
- ViewLayer *UNUSED(view_layer),
- const char *name,
- int channels,
- const char *chan_id,
- eNodeSocketDatatype UNUSED(type))
-{
- ListBase *templates = userdata;
- RenderPass *pass = MEM_callocN(sizeof(RenderPass), "RenderPassTemplate");
-
- pass->channels = channels;
- BLI_strncpy(pass->name, name, sizeof(pass->name));
- BLI_strncpy(pass->chan_id, chan_id, sizeof(pass->chan_id));
-
- BLI_addtail(templates, pass);
-}
-
-static void render_result_get_pass_templates(RenderEngine *engine,
- Render *re,
- RenderLayer *rl,
- ListBase *templates)
-{
- BLI_listbase_clear(templates);
-
- if (engine && engine->type->update_render_passes) {
- ViewLayer *view_layer = BLI_findstring(&re->view_layers, rl->name, offsetof(ViewLayer, name));
- if (view_layer) {
- RE_engine_update_render_passes(
- engine, re->scene, view_layer, templates_register_pass_cb, templates);
- }
- }
-}
-
-/* begin write of exr tile file */
-void render_result_exr_file_begin(Render *re, RenderEngine *engine)
-{
- char str[FILE_MAX];
-
- for (RenderResult *rr = re->result; rr; rr = rr->next) {
- LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) {
- /* Get passes needed by engine. Normally we would wait for the
- * engine to create them, but for EXR file we need to know in
- * advance. */
- ListBase templates;
- render_result_get_pass_templates(engine, re, rl, &templates);
-
- /* Create render passes requested by engine. Only this part is
- * mutex locked to avoid deadlock with Python GIL. */
- BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
- LISTBASE_FOREACH (RenderPass *, pass, &templates) {
- RE_create_render_pass(
- re->result, pass->name, pass->channels, pass->chan_id, rl->name, NULL);
- }
- BLI_rw_mutex_unlock(&re->resultmutex);
-
- BLI_freelistN(&templates);
-
- /* Open EXR file for writing. */
- render_result_exr_file_path(re->scene, rl->name, rr->sample_nr, str);
- printf("write exr tmp file, %dx%d, %s\n", rr->rectx, rr->recty, str);
- IMB_exrtile_begin_write(rl->exrhandle, str, 0, rr->rectx, rr->recty, re->partx, re->party);
- }
- }
-}
-
-/* end write of exr tile file, read back first sample */
-void render_result_exr_file_end(Render *re, RenderEngine *engine)
-{
- /* Preserve stamp data. */
- struct StampData *stamp_data = re->result->stamp_data;
- re->result->stamp_data = NULL;
-
- /* Close EXR files. */
- for (RenderResult *rr = re->result; rr; rr = rr->next) {
- LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) {
- IMB_exr_close(rl->exrhandle);
- rl->exrhandle = NULL;
- }
-
- rr->do_exr_tile = false;
- }
-
- /* Create new render result in memory instead of on disk. */
- BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
- render_result_free_list(&re->fullresult, re->result);
- re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS);
- re->result->stamp_data = stamp_data;
- render_result_passes_allocated_ensure(re->result);
- BLI_rw_mutex_unlock(&re->resultmutex);
-
- LISTBASE_FOREACH (RenderLayer *, rl, &re->result->layers) {
- /* Get passes needed by engine. */
- ListBase templates;
- render_result_get_pass_templates(engine, re, rl, &templates);
-
- /* Create render passes requested by engine. Only this part is
- * mutex locked to avoid deadlock with Python GIL. */
- BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE);
- LISTBASE_FOREACH (RenderPass *, pass, &templates) {
- RE_create_render_pass(re->result, pass->name, pass->channels, pass->chan_id, rl->name, NULL);
- }
-
- BLI_freelistN(&templates);
-
- /* Render passes contents from file. */
- char str[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100] = "";
- render_result_exr_file_path(re->scene, rl->name, 0, str);
- printf("read exr tmp file: %s\n", str);
-
- if (!render_result_exr_file_read_path(re->result, rl, str)) {
- printf("cannot read: %s\n", str);
- }
- BLI_rw_mutex_unlock(&re->resultmutex);
- }
-}
-
-/* save part into exr file */
-void render_result_exr_file_merge(RenderResult *rr, RenderResult *rrpart, const char *viewname)
-{
- for (; rr && rrpart; rr = rr->next, rrpart = rrpart->next) {
- save_render_result_tile(rr, rrpart, viewname);
- }
-}
-
-/* path to temporary exr file */
-void render_result_exr_file_path(Scene *scene, const char *layname, int sample, char *filepath)
-{
- char name[FILE_MAXFILE + MAX_ID_NAME + MAX_ID_NAME + 100];
- const char *fi = BLI_path_basename(BKE_main_blendfile_path_from_global());
-
- if (sample == 0) {
- BLI_snprintf(name, sizeof(name), "%s_%s_%s.exr", fi, scene->id.name + 2, layname);
- }
- else {
- BLI_snprintf(name, sizeof(name), "%s_%s_%s%d.exr", fi, scene->id.name + 2, layname, sample);
- }
-
- /* Make name safe for paths, see T43275. */
- BLI_filename_make_safe(name);
-
- BLI_join_dirfile(filepath, FILE_MAX, BKE_tempdir_session(), name);
-}
-
/* called for reading temp files, and for external engines */
int render_result_exr_file_read_path(RenderResult *rr,
RenderLayer *rl_single,
@@ -1416,7 +1179,7 @@ bool render_result_exr_file_cache_read(Render *re)
char *root = U.render_cachedir;
RE_FreeRenderResult(re->result);
- re->result = render_result_new(re, &re->disprect, RR_USE_MEM, RR_ALL_LAYERS, RR_ALL_VIEWS);
+ re->result = render_result_new(re, &re->disprect, RR_ALL_LAYERS, RR_ALL_VIEWS);
/* First try cache. */
render_result_exr_file_cache_path(re->scene, root, str);
diff --git a/source/blender/render/intern/render_result.h b/source/blender/render/intern/render_result.h
index 1fc64a4ea97..4145bb3b8ab 100644
--- a/source/blender/render/intern/render_result.h
+++ b/source/blender/render/intern/render_result.h
@@ -25,9 +25,6 @@
#define PASS_VECTOR_MAX 10000.0f
-#define RR_USE_MEM 0
-#define RR_USE_EXR 1
-
#define RR_ALL_LAYERS NULL
#define RR_ALL_VIEWS NULL
@@ -51,7 +48,6 @@ extern "C" {
struct RenderResult *render_result_new(struct Render *re,
struct rcti *partrct,
- int savebuffers,
const char *layername,
const char *viewname);
@@ -81,12 +77,6 @@ void render_result_free_list(struct ListBase *lb, struct RenderResult *rr);
void render_result_single_layer_begin(struct Render *re);
void render_result_single_layer_end(struct Render *re);
-/* EXR Tile File Render */
-
-void render_result_save_empty_result_tiles(struct Render *re);
-void render_result_exr_file_begin(struct Render *re, struct RenderEngine *engine);
-void render_result_exr_file_end(struct Render *re, struct RenderEngine *engine);
-
/* render pass wrapper for gpencil */
struct RenderPass *render_layer_add_pass(struct RenderResult *rr,
struct RenderLayer *rl,
@@ -95,14 +85,6 @@ struct RenderPass *render_layer_add_pass(struct RenderResult *rr,
const char *viewname,
const char *chan_id);
-void render_result_exr_file_merge(struct RenderResult *rr,
- struct RenderResult *rrpart,
- const char *viewname);
-
-void render_result_exr_file_path(struct Scene *scene,
- const char *layname,
- int sample,
- char *filepath);
int render_result_exr_file_read_path(struct RenderResult *rr,
struct RenderLayer *rl_single,
const char *filepath);
diff --git a/source/blender/render/intern/render_types.h b/source/blender/render/intern/render_types.h
index d2d2b499495..ca4f72350e1 100644
--- a/source/blender/render/intern/render_types.h
+++ b/source/blender/render/intern/render_types.h
@@ -47,30 +47,10 @@ struct ReportList;
extern "C" {
#endif
-/* this is handed over to threaded hiding/passes/shading engine */
-typedef struct RenderPart {
- struct RenderPart *next, *prev;
-
- RenderResult *result; /* result of part rendering */
- ListBase fullresult; /* optional full sample buffers */
-
- rcti disprect; /* part coordinates within total picture */
- int rectx, recty; /* the size */
- int nr; /* nr is partnr */
- short status;
-} RenderPart;
-
typedef struct HighlightedTile {
rcti rect;
} HighlightedTile;
-enum {
- /* PART_STATUS_NONE = 0, */ /* UNUSED */
- PART_STATUS_IN_PROGRESS = 1,
- PART_STATUS_RENDERED = 2,
- PART_STATUS_MERGED = 3,
-};
-
/* controls state of render, everything that's read-only during render stage */
struct Render {
struct Render *next, *prev;
@@ -91,6 +71,9 @@ struct Render {
* to not conflict with writes, so no lock used for that */
ThreadRWMutex resultmutex;
+ /* Guard for drawing render result using engine's `draw()` callback. */
+ ThreadMutex engine_draw_mutex;
+
/** Window size, display rect, viewplane.
* \note Buffer width and height with percentage applied
* without border & crop. convert to long before multiplying together to avoid overflow. */
@@ -101,10 +84,6 @@ struct Render {
/* final picture width and height (within disprect) */
int rectx, recty;
- /* real maximum size of parts after correction for minimum
- * partx*xparts can be larger than rectx, in that case last part is smaller */
- int partx, party;
-
/* Camera transform, only used by Freestyle. */
float winmat[4][4];
@@ -120,9 +99,6 @@ struct Render {
int active_view_layer;
struct Object *camera_override;
- ThreadRWMutex partsmutex;
- struct GHash *parts;
-
ThreadMutex highlighted_tiles_mutex;
struct GSet *highlighted_tiles;
diff --git a/source/blender/sequencer/SEQ_iterator.h b/source/blender/sequencer/SEQ_iterator.h
index 4f7d603fd6a..d2a47a13db3 100644
--- a/source/blender/sequencer/SEQ_iterator.h
+++ b/source/blender/sequencer/SEQ_iterator.h
@@ -94,11 +94,15 @@ SeqCollection *SEQ_query_by_reference(struct Sequence *seq_reference,
SeqCollection *collection));
SeqCollection *SEQ_query_selected_strips(struct ListBase *seqbase);
SeqCollection *SEQ_query_unselected_strips(struct ListBase *seqbase);
-SeqCollection *SEQ_query_all_strips(struct ListBase *seqbase);
-SeqCollection *SEQ_query_all_strips_recursive(struct ListBase *seqbase);
+SeqCollection *SEQ_query_all_strips(ListBase *seqbase);
+SeqCollection *SEQ_query_all_strips_recursive(ListBase *seqbase);
+SeqCollection *SEQ_query_rendered_strips(ListBase *seqbase,
+ const int timeline_frame,
+ const int displayed_channel);
void SEQ_query_strip_effect_chain(struct Sequence *seq_reference,
struct ListBase *seqbase,
SeqCollection *collection);
+void SEQ_filter_selected_strips(SeqCollection *collection);
#ifdef __cplusplus
}
diff --git a/source/blender/sequencer/SEQ_render.h b/source/blender/sequencer/SEQ_render.h
index c138daf1318..e99dc6d344f 100644
--- a/source/blender/sequencer/SEQ_render.h
+++ b/source/blender/sequencer/SEQ_render.h
@@ -27,6 +27,8 @@
extern "C" {
#endif
+#define SEQ_RENDER_THUMB_SIZE 256
+
struct ListBase;
struct Main;
struct Scene;
@@ -67,6 +69,25 @@ struct ImBuf *SEQ_render_give_ibuf(const SeqRenderData *context,
struct ImBuf *SEQ_render_give_ibuf_direct(const SeqRenderData *context,
float timeline_frame,
struct Sequence *seq);
+void SEQ_render_thumbnails(const struct SeqRenderData *context,
+ struct Sequence *seq,
+ struct Sequence *seq_orig,
+ float start_frame,
+ float frame_step,
+ rctf *view_area,
+ const short *stop);
+struct ImBuf *SEQ_get_thumbnail(const struct SeqRenderData *context,
+ struct Sequence *seq,
+ float timeline_frame,
+ rcti *crop,
+ bool clipped);
+int SEQ_render_thumbnails_guaranteed_set_frame_step_get(const struct Sequence *seq);
+void SEQ_render_thumbnails_base_set(const struct SeqRenderData *context,
+ struct Sequence *seq,
+ struct Sequence *seq_orig,
+ rctf *view_area,
+ const short *stop);
+
void SEQ_render_init_colorspace(struct Sequence *seq);
void SEQ_render_new_render_data(struct Main *bmain,
struct Depsgraph *depsgraph,
diff --git a/source/blender/sequencer/SEQ_sequencer.h b/source/blender/sequencer/SEQ_sequencer.h
index d7800d208a4..7e733817630 100644
--- a/source/blender/sequencer/SEQ_sequencer.h
+++ b/source/blender/sequencer/SEQ_sequencer.h
@@ -64,6 +64,7 @@ short SEQ_tool_settings_snap_flag_get(struct Scene *scene);
short SEQ_tool_settings_snap_mode_get(struct Scene *scene);
int SEQ_tool_settings_snap_distance_get(struct Scene *scene);
eSeqOverlapMode SEQ_tool_settings_overlap_mode_get(struct Scene *scene);
+int SEQ_tool_settings_pivot_point_get(struct Scene *scene);
struct SequencerToolSettings *SEQ_tool_settings_copy(struct SequencerToolSettings *tool_settings);
struct Editing *SEQ_editing_get(const struct Scene *scene);
struct Editing *SEQ_editing_ensure(struct Scene *scene);
diff --git a/source/blender/sequencer/SEQ_transform.h b/source/blender/sequencer/SEQ_transform.h
index 1977835f627..328efb9424a 100644
--- a/source/blender/sequencer/SEQ_transform.h
+++ b/source/blender/sequencer/SEQ_transform.h
@@ -61,6 +61,15 @@ void SEQ_transform_offset_after_frame(struct Scene *scene,
const int delta,
const int timeline_frame);
+/* Image transformation. */
+void SEQ_image_transform_mirror_factor_get(const struct Sequence *seq, float r_mirror[2]);
+void SEQ_image_transform_origin_offset_pixelspace_get(const struct Scene *scene,
+ const struct Sequence *seq,
+ float r_origin[2]);
+void SEQ_image_transform_final_quad_get(const struct Scene *scene,
+ const struct Sequence *seq,
+ float r_quad[4][2]);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/sequencer/SEQ_utils.h b/source/blender/sequencer/SEQ_utils.h
index 09de7bc67e9..d30a1b2d7ae 100644
--- a/source/blender/sequencer/SEQ_utils.h
+++ b/source/blender/sequencer/SEQ_utils.h
@@ -34,6 +34,7 @@ struct Mask;
struct Scene;
struct Sequence;
struct StripElem;
+struct SeqRenderData;
void SEQ_sort(struct ListBase *seqbase);
void SEQ_sequence_base_unique_name_recursive(struct Scene *scene,
@@ -57,7 +58,6 @@ void SEQ_set_scale_to_fit(const struct Sequence *seq,
const int preview_height,
const eSeqImageFitMethod fit_method);
void SEQ_ensure_unique_name(struct Sequence *seq, struct Scene *scene);
-
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/sequencer/intern/image_cache.c b/source/blender/sequencer/intern/image_cache.c
index 86bd840ce31..86c198075e9 100644
--- a/source/blender/sequencer/intern/image_cache.c
+++ b/source/blender/sequencer/intern/image_cache.c
@@ -104,6 +104,7 @@
#define DCACHE_IMAGES_PER_FILE 100
#define DCACHE_CURRENT_VERSION 2
#define COLORSPACE_NAME_MAX 64 /* XXX: defined in imb intern */
+#define THUMB_CACHE_LIMIT 5000
typedef struct DiskCacheHeaderEntry {
unsigned char encoding;
@@ -148,6 +149,7 @@ typedef struct SeqCache {
struct BLI_mempool *items_pool;
struct SeqCacheKey *last_key;
SeqDiskCache *disk_cache;
+ int thumbnail_count;
} SeqCache;
typedef struct SeqCacheItem {
@@ -776,7 +778,7 @@ static float seq_cache_timeline_frame_to_frame_index(Sequence *seq, float timeli
/* With raw images, map timeline_frame to strip input media frame range. This means that static
* images or extended frame range of movies will only generate one cache entry. No special
* treatment in converting frame index to timeline_frame is needed. */
- if (type == SEQ_CACHE_STORE_RAW) {
+ if (type == SEQ_CACHE_STORE_RAW || type == SEQ_CACHE_STORE_THUMBNAIL) {
return seq_give_frame_index(seq, timeline_frame);
}
@@ -875,7 +877,7 @@ static void seq_cache_put_ex(Scene *scene, SeqCacheKey *key, ImBuf *ibuf)
if (BLI_ghash_reinsert(cache->hash, key, item, seq_cache_keyfree, seq_cache_valfree)) {
IMB_refImBuf(ibuf);
- if (!key->is_temp_cache) {
+ if (!key->is_temp_cache || key->type != SEQ_CACHE_STORE_THUMBNAIL) {
cache->last_key = key;
}
}
@@ -1161,6 +1163,7 @@ static void seq_cache_create(Main *bmain, Scene *scene)
cache->hash = BLI_ghash_new(seq_cache_hashhash, seq_cache_hashcmp, "SeqCache hash");
cache->last_key = NULL;
cache->bmain = bmain;
+ cache->thumbnail_count = 0;
BLI_mutex_init(&cache->iterator_mutex);
scene->ed->cache = cache;
@@ -1217,7 +1220,7 @@ void seq_cache_free_temp_cache(Scene *scene, short id, int timeline_frame)
SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter);
BLI_ghashIterator_step(&gh_iter);
- if (key->is_temp_cache && key->task_id == id) {
+ if (key->is_temp_cache && key->task_id == id && key->type != SEQ_CACHE_STORE_THUMBNAIL) {
/* Use frame_index here to avoid freeing raw images if they are used for multiple frames. */
float frame_index = seq_cache_timeline_frame_to_frame_index(
key->seq, timeline_frame, key->type);
@@ -1278,6 +1281,7 @@ void SEQ_cache_cleanup(Scene *scene)
BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree);
}
cache->last_key = NULL;
+ cache->thumbnail_count = 0;
seq_cache_unlock(scene);
}
@@ -1345,6 +1349,46 @@ void seq_cache_cleanup_sequence(Scene *scene,
seq_cache_unlock(scene);
}
+void seq_cache_thumbnail_cleanup(Scene *scene, rctf *view_area_safe)
+{
+ /* Add offsets to the left and right end to keep some frames in cache. */
+ view_area_safe->xmax += 200;
+ view_area_safe->xmin -= 200;
+ view_area_safe->ymin -= 1;
+ view_area_safe->ymax += 1;
+
+ SeqCache *cache = seq_cache_get_from_scene(scene);
+ if (!cache) {
+ return;
+ }
+
+ GHashIterator gh_iter;
+ BLI_ghashIterator_init(&gh_iter, cache->hash);
+ while (!BLI_ghashIterator_done(&gh_iter)) {
+ SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter);
+ BLI_ghashIterator_step(&gh_iter);
+
+ const int frame_index = key->timeline_frame - key->seq->startdisp;
+ const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(key->seq);
+ const int relative_base_frame = round_fl_to_int((frame_index / (float)frame_step)) *
+ frame_step;
+ const int nearest_guaranted_absolute_frame = relative_base_frame + key->seq->startdisp;
+
+ if (nearest_guaranted_absolute_frame == key->timeline_frame) {
+ continue;
+ }
+
+ if ((key->type & SEQ_CACHE_STORE_THUMBNAIL) &&
+ (key->timeline_frame > view_area_safe->xmax ||
+ key->timeline_frame < view_area_safe->xmin || key->seq->machine > view_area_safe->ymax ||
+ key->seq->machine < view_area_safe->ymin)) {
+ BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree);
+ cache->thumbnail_count--;
+ }
+ }
+ cache->last_key = NULL;
+}
+
struct ImBuf *seq_cache_get(const SeqRenderData *context,
Sequence *seq,
float timeline_frame,
@@ -1436,6 +1480,37 @@ bool seq_cache_put_if_possible(
return false;
}
+void seq_cache_thumbnail_put(
+ const SeqRenderData *context, Sequence *seq, float timeline_frame, ImBuf *i, rctf *view_area)
+{
+ Scene *scene = context->scene;
+
+ if (!scene->ed->cache) {
+ seq_cache_create(context->bmain, scene);
+ }
+
+ seq_cache_lock(scene);
+ SeqCache *cache = seq_cache_get_from_scene(scene);
+ SeqCacheKey *key = seq_cache_allocate_key(
+ cache, context, seq, timeline_frame, SEQ_CACHE_STORE_THUMBNAIL);
+
+ /* Prevent reinserting, it breaks cache key linking. */
+ if (BLI_ghash_haskey(cache->hash, key)) {
+ seq_cache_unlock(scene);
+ return;
+ }
+
+ /* Limit cache to THUMB_CACHE_LIMIT (5000) images stored. */
+ if (cache->thumbnail_count >= THUMB_CACHE_LIMIT) {
+ rctf view_area_safe = *view_area;
+ seq_cache_thumbnail_cleanup(scene, &view_area_safe);
+ }
+
+ seq_cache_put_ex(scene, key, i);
+ cache->thumbnail_count++;
+ seq_cache_unlock(scene);
+}
+
void seq_cache_put(
const SeqRenderData *context, Sequence *seq, float timeline_frame, int type, ImBuf *i)
{
diff --git a/source/blender/sequencer/intern/image_cache.h b/source/blender/sequencer/intern/image_cache.h
index 63c559caee9..60031311985 100644
--- a/source/blender/sequencer/intern/image_cache.h
+++ b/source/blender/sequencer/intern/image_cache.h
@@ -46,6 +46,11 @@ void seq_cache_put(const struct SeqRenderData *context,
float timeline_frame,
int type,
struct ImBuf *i);
+void seq_cache_thumbnail_put(const struct SeqRenderData *context,
+ struct Sequence *seq,
+ float timeline_frame,
+ struct ImBuf *i,
+ rctf *view_area);
bool seq_cache_put_if_possible(const struct SeqRenderData *context,
struct Sequence *seq,
float timeline_frame,
@@ -60,6 +65,7 @@ void seq_cache_cleanup_sequence(struct Scene *scene,
struct Sequence *seq_changed,
int invalidate_types,
bool force_seq_changed_range);
+void seq_cache_thumbnail_cleanup(Scene *scene, rctf *view_area);
bool seq_cache_is_full(void);
#ifdef __cplusplus
diff --git a/source/blender/sequencer/intern/iterator.c b/source/blender/sequencer/intern/iterator.c
index 58f68205f51..2429405350b 100644
--- a/source/blender/sequencer/intern/iterator.c
+++ b/source/blender/sequencer/intern/iterator.c
@@ -37,6 +37,8 @@
#include "BKE_scene.h"
#include "SEQ_iterator.h"
+#include "SEQ_time.h"
+#include "render.h"
/* -------------------------------------------------------------------- */
/** \Iterator API
@@ -340,6 +342,114 @@ SeqCollection *SEQ_query_selected_strips(ListBase *seqbase)
return collection;
}
+static SeqCollection *query_strips_at_frame(ListBase *seqbase, const int timeline_frame)
+{
+ SeqCollection *collection = SEQ_collection_create(__func__);
+
+ LISTBASE_FOREACH (Sequence *, seq, seqbase) {
+ if (SEQ_time_strip_intersects_frame(seq, timeline_frame)) {
+ SEQ_collection_append_strip(seq, collection);
+ }
+ }
+ return collection;
+}
+
+static void collection_filter_channel_up_to_incl(SeqCollection *collection, const int channel)
+{
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, collection) {
+ if (seq->machine <= channel) {
+ continue;
+ }
+ SEQ_collection_remove_strip(seq, collection);
+ }
+}
+
+static bool seq_is_effect_of(const Sequence *seq_effect, const Sequence *possibly_input)
+{
+ if (seq_effect->seq1 == possibly_input || seq_effect->seq2 == possibly_input ||
+ seq_effect->seq3 == possibly_input) {
+ return true;
+ }
+ return false;
+}
+
+/* Check if seq must be rendered. This depends on whole stack in some cases, not only seq itself.
+ * Order of applying these conditions is important. */
+static bool must_render_strip(const Sequence *seq, SeqCollection *strips_at_timeline_frame)
+{
+ bool seq_have_effect_in_stack = false;
+ Sequence *seq_iter;
+ SEQ_ITERATOR_FOREACH (seq_iter, strips_at_timeline_frame) {
+ /* Strips is below another strip with replace blending are not rendered. */
+ if (seq_iter->blend_mode == SEQ_BLEND_REPLACE && seq->machine < seq_iter->machine) {
+ return false;
+ }
+
+ if ((seq_iter->type & SEQ_TYPE_EFFECT) != 0 && seq_is_effect_of(seq_iter, seq)) {
+ /* Strips in same channel or higher than its effect are rendered. */
+ if (seq->machine >= seq_iter->machine) {
+ return true;
+ }
+ /* Mark that this strip has effect in stack, that is above the strip. */
+ seq_have_effect_in_stack = true;
+ }
+ }
+
+ /* All effects are rendered (with respect to conditions above). */
+ if ((seq->type & SEQ_TYPE_EFFECT) != 0) {
+ return true;
+ }
+
+ /* If strip has effects in stack, and all effects are above this strip, it is not rendered. */
+ if (seq_have_effect_in_stack) {
+ return false;
+ }
+
+ return true;
+}
+
+/* Remove strips we don't want to render from collection. */
+static void collection_filter_rendered_strips(SeqCollection *collection)
+{
+ Sequence *seq;
+
+ /* Remove sound strips and muted strips from collection, because these are not rendered.
+ * Function #must_render_strip() don't have to check for these strips anymore. */
+ SEQ_ITERATOR_FOREACH (seq, collection) {
+ if (seq->type == SEQ_TYPE_SOUND_RAM || (seq->flag & SEQ_MUTE) != 0) {
+ SEQ_collection_remove_strip(seq, collection);
+ }
+ }
+
+ SEQ_ITERATOR_FOREACH (seq, collection) {
+ if (must_render_strip(seq, collection)) {
+ continue;
+ }
+ SEQ_collection_remove_strip(seq, collection);
+ }
+}
+
+/**
+ * Query strips that are rendered at \a timeline_frame when \a displayed channel is viewed
+ *
+ * \param seqbase: ListBase in which strips are queried
+ * \param timeline_frame: viewed frame
+ * \param displayed_channel: viewed channel. when set to 0, no channel filter is applied
+ * \return strip collection
+ */
+SeqCollection *SEQ_query_rendered_strips(ListBase *seqbase,
+ const int timeline_frame,
+ const int displayed_channel)
+{
+ SeqCollection *collection = query_strips_at_frame(seqbase, timeline_frame);
+ if (displayed_channel != 0) {
+ collection_filter_channel_up_to_incl(collection, displayed_channel);
+ }
+ collection_filter_rendered_strips(collection);
+ return collection;
+}
+
/**
* Query all unselected strips in seqbase.
*
@@ -396,3 +506,13 @@ void SEQ_query_strip_effect_chain(Sequence *seq_reference,
}
}
}
+
+void SEQ_filter_selected_strips(SeqCollection *collection)
+{
+ Sequence *seq;
+ SEQ_ITERATOR_FOREACH (seq, collection) {
+ if ((seq->flag & SELECT) == 0) {
+ SEQ_collection_remove_strip(seq, collection);
+ }
+ }
+}
diff --git a/source/blender/sequencer/intern/render.c b/source/blender/sequencer/intern/render.c
index 6c4502a3608..2578a6d4223 100644
--- a/source/blender/sequencer/intern/render.c
+++ b/source/blender/sequencer/intern/render.c
@@ -72,6 +72,7 @@
#include "SEQ_render.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
+#include "SEQ_transform.h"
#include "SEQ_utils.h"
#include "effects.h"
@@ -262,94 +263,6 @@ StripElem *SEQ_render_give_stripelem(Sequence *seq, int timeline_frame)
return se;
}
-static bool seq_is_effect_of(const Sequence *seq_effect, const Sequence *possibly_input)
-{
- if (seq_effect->seq1 == possibly_input || seq_effect->seq2 == possibly_input ||
- seq_effect->seq3 == possibly_input) {
- return true;
- }
- return false;
-}
-
-/* Check if seq must be rendered. This depends on whole stack in some cases, not only seq itself.
- * Order of applying these conditions is important. */
-static bool must_render_strip(const Sequence *seq, SeqCollection *strips_at_timeline_frame)
-{
- bool seq_have_effect_in_stack = false;
- Sequence *seq_iter;
- SEQ_ITERATOR_FOREACH (seq_iter, strips_at_timeline_frame) {
- /* Strips is below another strip with replace blending are not rendered. */
- if (seq_iter->blend_mode == SEQ_BLEND_REPLACE && seq->machine < seq_iter->machine) {
- return false;
- }
-
- if ((seq_iter->type & SEQ_TYPE_EFFECT) != 0 && seq_is_effect_of(seq_iter, seq)) {
- /* Strips in same channel or higher than its effect are rendered. */
- if (seq->machine >= seq_iter->machine) {
- return true;
- }
- /* Mark that this strip has effect in stack, that is above the strip. */
- seq_have_effect_in_stack = true;
- }
- }
-
- /* All effects are rendered (with respect to conditions above). */
- if ((seq->type & SEQ_TYPE_EFFECT) != 0) {
- return true;
- }
-
- /* If strip has effects in stack, and all effects are above this strip, it is not rendered. */
- if (seq_have_effect_in_stack) {
- return false;
- }
-
- return true;
-}
-
-static SeqCollection *query_strips_at_frame(ListBase *seqbase, const int timeline_frame)
-{
- SeqCollection *collection = SEQ_collection_create(__func__);
-
- LISTBASE_FOREACH (Sequence *, seq, seqbase) {
- if (SEQ_time_strip_intersects_frame(seq, timeline_frame)) {
- SEQ_collection_append_strip(seq, collection);
- }
- }
- return collection;
-}
-
-static void collection_filter_channel_up_to_incl(SeqCollection *collection, const int channel)
-{
- Sequence *seq;
- SEQ_ITERATOR_FOREACH (seq, collection) {
- if (seq->machine <= channel) {
- continue;
- }
- SEQ_collection_remove_strip(seq, collection);
- }
-}
-
-/* Remove strips we don't want to render from collection. */
-static void collection_filter_rendered_strips(SeqCollection *collection)
-{
- Sequence *seq;
-
- /* Remove sound strips and muted strips from collection, because these are not rendered.
- * Function #must_render_strip() don't have to check for these strips anymore. */
- SEQ_ITERATOR_FOREACH (seq, collection) {
- if (seq->type == SEQ_TYPE_SOUND_RAM || (seq->flag & SEQ_MUTE) != 0) {
- SEQ_collection_remove_strip(seq, collection);
- }
- }
-
- SEQ_ITERATOR_FOREACH (seq, collection) {
- if (must_render_strip(seq, collection)) {
- continue;
- }
- SEQ_collection_remove_strip(seq, collection);
- }
-}
-
static int seq_channel_cmp_fn(const void *a, const void *b)
{
return (*(Sequence **)a)->machine - (*(Sequence **)b)->machine;
@@ -360,13 +273,7 @@ int seq_get_shown_sequences(ListBase *seqbase,
const int chanshown,
Sequence **r_seq_arr)
{
- SeqCollection *collection = query_strips_at_frame(seqbase, timeline_frame);
-
- if (chanshown != 0) {
- collection_filter_channel_up_to_incl(collection, chanshown);
- }
- collection_filter_rendered_strips(collection);
-
+ SeqCollection *collection = SEQ_query_rendered_strips(seqbase, timeline_frame, chanshown);
const int strip_count = BLI_gset_len(collection->set);
if (strip_count > MAXSEQ) {
@@ -504,7 +411,7 @@ static void sequencer_image_crop_transform_matrix(const Sequence *seq,
const float image_center_offs_y = (out->y - in->y) / 2;
const float translate_x = transform->xofs * preview_scale_factor + image_center_offs_x;
const float translate_y = transform->yofs * preview_scale_factor + image_center_offs_y;
- const float pivot[2] = {in->x / 2, in->y / 2};
+ const float pivot[2] = {in->x * transform->origin[0], in->y * transform->origin[1]};
loc_rot_size_to_mat3(r_transform_matrix,
(const float[]){translate_x, translate_y},
transform->rotation,
@@ -527,6 +434,31 @@ static void sequencer_image_crop_init(const Sequence *seq,
BLI_rctf_init(r_crop, left, in->x - right, bottom, in->y - top);
}
+static void sequencer_thumbnail_transform(ImBuf *in, ImBuf *out)
+{
+ float image_scale_factor = (float)out->x / in->x;
+ float transform_matrix[3][3];
+
+ /* Set to keep same loc,scale,rot but change scale to thumb size limit. */
+ const float scale_x = 1 * image_scale_factor;
+ const float scale_y = 1 * image_scale_factor;
+ const float image_center_offs_x = (out->x - in->x) / 2;
+ const float image_center_offs_y = (out->y - in->y) / 2;
+ const float pivot[2] = {in->x / 2, in->y / 2};
+ loc_rot_size_to_mat3(transform_matrix,
+ (const float[]){image_center_offs_x, image_center_offs_y},
+ 0,
+ (const float[]){scale_x, scale_y});
+ transform_pivot_set_m3(transform_matrix, pivot);
+ invert_m3(transform_matrix);
+
+ /* No crop. */
+ rctf source_crop;
+ BLI_rctf_init(&source_crop, 0, in->x, 0, in->y);
+
+ IMB_transform(in, out, transform_matrix, &source_crop, IMB_FILTER_NEAREST);
+}
+
static void sequencer_preprocess_transform_crop(
ImBuf *in, ImBuf *out, const SeqRenderData *context, Sequence *seq, const bool is_proxy_image)
{
@@ -1989,7 +1921,164 @@ ImBuf *SEQ_render_give_ibuf_direct(const SeqRenderData *context,
seq_render_state_init(&state);
ImBuf *ibuf = seq_render_strip(context, &state, seq, timeline_frame);
-
return ibuf;
}
+
+/* Gets the direct image from source and scales to thumbnail size. */
+static ImBuf *seq_get_uncached_thumbnail(const SeqRenderData *context,
+ SeqRenderState *state,
+ Sequence *seq,
+ float timeline_frame)
+{
+ bool is_proxy_image = false;
+ ImBuf *ibuf = do_render_strip_uncached(context, state, seq, timeline_frame, &is_proxy_image);
+
+ if (ibuf == NULL) {
+ return NULL;
+ }
+
+ float aspect_ratio = (float)ibuf->x / ibuf->y;
+ int rectx, recty;
+ /* Calculate new dimensions - THUMB_SIZE (256) for x or y. */
+ if (ibuf->x > ibuf->y) {
+ rectx = SEQ_RENDER_THUMB_SIZE;
+ recty = round_fl_to_int(rectx / aspect_ratio);
+ }
+ else {
+ recty = SEQ_RENDER_THUMB_SIZE;
+ rectx = round_fl_to_int(recty * aspect_ratio);
+ }
+
+ /* Scale ibuf to thumbnail size. */
+ ImBuf *scaled_ibuf = IMB_allocImBuf(rectx, recty, 32, ibuf->rect_float ? IB_rectfloat : IB_rect);
+ sequencer_thumbnail_transform(ibuf, scaled_ibuf);
+ seq_imbuf_assign_spaces(context->scene, scaled_ibuf);
+ IMB_freeImBuf(ibuf);
+
+ return scaled_ibuf;
+}
+
+/* Get cached thumbnails. */
+ImBuf *SEQ_get_thumbnail(
+ const SeqRenderData *context, Sequence *seq, float timeline_frame, rcti *crop, bool clipped)
+{
+ ImBuf *ibuf = seq_cache_get(context, seq, roundf(timeline_frame), SEQ_CACHE_STORE_THUMBNAIL);
+
+ if (!clipped || ibuf == NULL) {
+ return ibuf;
+ }
+
+ /* Do clipping. */
+ ImBuf *ibuf_cropped = IMB_dupImBuf(ibuf);
+ if (crop->xmin < 0 || crop->ymin < 0) {
+ crop->xmin = 0;
+ crop->ymin = 0;
+ }
+ if (crop->xmax >= ibuf->x || crop->ymax >= ibuf->y) {
+ crop->xmax = ibuf->x - 1;
+ crop->ymax = ibuf->y - 1;
+ }
+ IMB_rect_crop(ibuf_cropped, crop);
+ IMB_freeImBuf(ibuf);
+ return ibuf_cropped;
+}
+
+/* Render the series of thumbnails and store in cache. */
+void SEQ_render_thumbnails(const SeqRenderData *context,
+ Sequence *seq,
+ Sequence *seq_orig,
+ float start_frame,
+ float frame_step,
+ rctf *view_area,
+ const short *stop)
+{
+ SeqRenderState state;
+ seq_render_state_init(&state);
+
+ /* Adding the hold offset value (seq->anim_startofs) to the start frame. Position of image not
+ * affected, but frame loaded affected. */
+ start_frame = start_frame - frame_step;
+ float upper_thumb_bound = (seq->endstill) ? (seq->start + seq->len) : seq->enddisp;
+ upper_thumb_bound = (upper_thumb_bound > view_area->xmax) ? view_area->xmax + frame_step :
+ upper_thumb_bound;
+
+ while ((start_frame < upper_thumb_bound) & !*stop) {
+ ImBuf *ibuf = seq_cache_get(
+ context, seq_orig, round_fl_to_int(start_frame), SEQ_CACHE_STORE_THUMBNAIL);
+ if (ibuf) {
+ IMB_freeImBuf(ibuf);
+ start_frame += frame_step;
+ continue;
+ }
+
+ ibuf = seq_get_uncached_thumbnail(context, &state, seq, round_fl_to_int(start_frame));
+
+ if (ibuf) {
+ seq_cache_thumbnail_put(context, seq_orig, round_fl_to_int(start_frame), ibuf, view_area);
+ IMB_freeImBuf(ibuf);
+ seq_orig->flag &= ~SEQ_FLAG_SKIP_THUMBNAILS;
+ }
+ else {
+ /* Can not open source file. */
+ seq_orig->flag |= SEQ_FLAG_SKIP_THUMBNAILS;
+ return;
+ }
+
+ start_frame += frame_step;
+ }
+}
+
+/* Get frame step for equally spaced thumbnails. These thumbnails should always be present in
+ * memory, so they can be used when zooming.*/
+int SEQ_render_thumbnails_guaranteed_set_frame_step_get(const Sequence *seq)
+{
+ const int content_len = (seq->enddisp - seq->startdisp - seq->startstill - seq->endstill);
+
+ /* Arbitrary, but due to performance reasons should be as low as possible. */
+ const int thumbnails_base_set_count = min_ii(content_len / 100, 30);
+ if (thumbnails_base_set_count <= 0) {
+ return 0;
+ }
+ return content_len / thumbnails_base_set_count;
+}
+
+/* Render set of evenly spaced thumbnails that are drawn when zooming. */
+void SEQ_render_thumbnails_base_set(
+ const SeqRenderData *context, Sequence *seq, Sequence *seq_orig, rctf *view_area, const short *stop)
+{
+ SeqRenderState state;
+ seq_render_state_init(&state);
+
+ int timeline_frame = seq->startdisp;
+ const int frame_step = SEQ_render_thumbnails_guaranteed_set_frame_step_get(seq);
+
+ while (timeline_frame < seq->enddisp && !*stop) {
+ ImBuf *ibuf = seq_cache_get(
+ context, seq_orig, roundf(timeline_frame), SEQ_CACHE_STORE_THUMBNAIL);
+ if (ibuf) {
+ IMB_freeImBuf(ibuf);
+
+ if (frame_step == 0) {
+ return;
+ }
+
+ timeline_frame += frame_step;
+ continue;
+ }
+
+ ibuf = seq_get_uncached_thumbnail(context, &state, seq, timeline_frame);
+
+ if (ibuf) {
+ seq_cache_thumbnail_put(context, seq_orig, timeline_frame, ibuf, view_area);
+ IMB_freeImBuf(ibuf);
+ }
+
+ if (frame_step == 0) {
+ return;
+ }
+
+ timeline_frame += frame_step;
+ }
+}
+
/** \} */
diff --git a/source/blender/sequencer/intern/sequencer.c b/source/blender/sequencer/intern/sequencer.c
index bf5942090c9..382bd51aae1 100644
--- a/source/blender/sequencer/intern/sequencer.c
+++ b/source/blender/sequencer/intern/sequencer.c
@@ -79,6 +79,8 @@ static Strip *seq_strip_alloc(int type)
strip->transform = MEM_callocN(sizeof(struct StripTransform), "StripTransform");
strip->transform->scale_x = 1;
strip->transform->scale_y = 1;
+ strip->transform->origin[0] = 0.5f;
+ strip->transform->origin[1] = 0.5f;
strip->crop = MEM_callocN(sizeof(struct StripCrop), "StripCrop");
}
@@ -321,6 +323,7 @@ SequencerToolSettings *SEQ_tool_settings_init(void)
SEQ_SNAP_TO_STRIP_HOLD;
tool_settings->snap_distance = 15;
tool_settings->overlap_mode = SEQ_OVERLAP_SHUFFLE;
+ tool_settings->pivot_point = V3D_AROUND_LOCAL_ORIGINS;
return tool_settings;
}
@@ -377,6 +380,12 @@ eSeqOverlapMode SEQ_tool_settings_overlap_mode_get(Scene *scene)
return tool_settings->overlap_mode;
}
+int SEQ_tool_settings_pivot_point_get(Scene *scene)
+{
+ const SequencerToolSettings *tool_settings = SEQ_tool_settings_ensure(scene);
+ return tool_settings->pivot_point;
+}
+
/**
* Get seqbase that is being viewed currently. This can be main seqbase or meta strip seqbase
*
@@ -948,6 +957,8 @@ static bool seq_read_lib_cb(Sequence *seq, void *user_data)
BLI_listbase_clear(&seq->anims);
SEQ_modifier_blend_read_lib(reader, sce, &seq->modifiers);
+
+ seq->flag &= ~SEQ_FLAG_SKIP_THUMBNAILS;
return true;
}
diff --git a/source/blender/sequencer/intern/strip_transform.c b/source/blender/sequencer/intern/strip_transform.c
index 3a5f93a72b0..d5ff455c694 100644
--- a/source/blender/sequencer/intern/strip_transform.c
+++ b/source/blender/sequencer/intern/strip_transform.c
@@ -421,3 +421,101 @@ void SEQ_transform_offset_after_frame(Scene *scene,
}
}
}
+
+void SEQ_image_transform_mirror_factor_get(const Sequence *seq, float r_mirror[2])
+{
+ r_mirror[0] = 1.0f;
+ r_mirror[1] = 1.0f;
+
+ if ((seq->flag & SEQ_FLIPX) != 0) {
+ r_mirror[0] = -1.0f;
+ }
+ if ((seq->flag & SEQ_FLIPY) != 0) {
+ r_mirror[1] = -1.0f;
+ }
+}
+
+/**
+ * Get strip transform origin offset from image center
+ * Note: This function does not apply axis mirror.
+ *
+ * \param scene: Scene in which strips are located
+ * \param seq: Sequence to calculate image transform origin
+ * \param r_origin: return value
+ */
+void SEQ_image_transform_origin_offset_pixelspace_get(const Scene *scene,
+ const Sequence *seq,
+ float r_origin[2])
+{
+ float image_size[2];
+ StripElem *strip_elem = seq->strip->stripdata;
+ if (strip_elem == NULL) {
+ image_size[0] = scene->r.xsch;
+ image_size[1] = scene->r.ysch;
+ }
+ else {
+ image_size[0] = strip_elem->orig_width;
+ image_size[1] = strip_elem->orig_height;
+ }
+
+ const StripTransform *transform = seq->strip->transform;
+ r_origin[0] = (image_size[0] * transform->origin[0]) - (image_size[0] * 0.5f) + transform->xofs;
+ r_origin[1] = (image_size[1] * transform->origin[1]) - (image_size[1] * 0.5f) + transform->yofs;
+
+ float mirror[2];
+ SEQ_image_transform_mirror_factor_get(seq, mirror);
+ mul_v2_v2(r_origin, mirror);
+}
+
+/**
+ * Get strip transform origin offset from image center
+ *
+ * \param scene: Scene in which strips are located
+ * \param seq: Sequence to calculate image transform origin
+ * \param r_origin: return value
+ */
+
+void SEQ_image_transform_final_quad_get(const Scene *scene,
+ const Sequence *seq,
+ float r_quad[4][2])
+{
+ StripTransform *transform = seq->strip->transform;
+ StripCrop *crop = seq->strip->crop;
+
+ int imgage_size[2] = {scene->r.xsch, scene->r.ysch};
+ if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) {
+ imgage_size[0] = seq->strip->stripdata->orig_width;
+ imgage_size[1] = seq->strip->stripdata->orig_height;
+ }
+
+ float transform_matrix[3][3];
+ loc_rot_size_to_mat3(transform_matrix,
+ (const float[]){transform->xofs, transform->yofs},
+ transform->rotation,
+ (const float[]){transform->scale_x, transform->scale_y});
+ const float origin[2] = {imgage_size[0] * transform->origin[0],
+ imgage_size[1] * transform->origin[1]};
+ const float pivot[2] = {origin[0] - (imgage_size[0] / 2), origin[1] - (imgage_size[1] / 2)};
+ transform_pivot_set_m3(transform_matrix, pivot);
+
+ r_quad[0][0] = (imgage_size[0] / 2) - crop->right;
+ r_quad[0][1] = (imgage_size[1] / 2) - crop->top;
+ r_quad[1][0] = (imgage_size[0] / 2) - crop->right;
+ r_quad[1][1] = (-imgage_size[1] / 2) + crop->bottom;
+ r_quad[2][0] = (-imgage_size[0] / 2) + crop->left;
+ r_quad[2][1] = (-imgage_size[1] / 2) + crop->bottom;
+ r_quad[3][0] = (-imgage_size[0] / 2) + crop->left;
+ r_quad[3][1] = (imgage_size[1] / 2) - crop->top;
+
+ mul_m3_v2(transform_matrix, r_quad[0]);
+ mul_m3_v2(transform_matrix, r_quad[1]);
+ mul_m3_v2(transform_matrix, r_quad[2]);
+ mul_m3_v2(transform_matrix, r_quad[3]);
+
+ float mirror[2];
+ SEQ_image_transform_mirror_factor_get(seq, mirror);
+ mul_v2_v2(r_quad[0], mirror);
+ mul_v2_v2(r_quad[1], mirror);
+ mul_v2_v2(r_quad[2], mirror);
+ mul_v2_v2(r_quad[3], mirror);
+}
diff --git a/source/blender/sequencer/intern/utils.c b/source/blender/sequencer/intern/utils.c
index 1d3e7e4a223..8421aab5217 100644
--- a/source/blender/sequencer/intern/utils.c
+++ b/source/blender/sequencer/intern/utils.c
@@ -42,6 +42,7 @@
#include "SEQ_edit.h"
#include "SEQ_iterator.h"
#include "SEQ_relations.h"
+#include "SEQ_render.h"
#include "SEQ_select.h"
#include "SEQ_sequencer.h"
#include "SEQ_time.h"
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 189a231616e..6794b1f4091 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -262,14 +262,21 @@ struct wmEventHandler_Keymap *WM_event_add_keymap_handler_priority(ListBase *han
wmKeyMap *keymap,
int priority);
-typedef struct wmKeyMap *(wmEventHandler_KeymapDynamicFn)(wmWindowManager *wm,
- struct wmEventHandler_Keymap *handler)
- ATTR_WARN_UNUSED_RESULT;
-
-struct wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(
- struct wmWindowManager *wm, struct wmEventHandler_Keymap *handler);
-struct wmKeyMap *WM_event_get_keymap_from_toolsystem(struct wmWindowManager *wm,
- struct wmEventHandler_Keymap *handler);
+typedef struct wmEventHandler_KeymapResult {
+ wmKeyMap *keymaps[3];
+ int keymaps_len;
+} wmEventHandler_KeymapResult;
+
+typedef void(wmEventHandler_KeymapDynamicFn)(wmWindowManager *wm,
+ struct wmEventHandler_Keymap *handler,
+ struct wmEventHandler_KeymapResult *km_result);
+
+void WM_event_get_keymap_from_toolsystem_fallback(struct wmWindowManager *wm,
+ struct wmEventHandler_Keymap *handler,
+ wmEventHandler_KeymapResult *km_result);
+void WM_event_get_keymap_from_toolsystem(struct wmWindowManager *wm,
+ struct wmEventHandler_Keymap *handler,
+ wmEventHandler_KeymapResult *km_result);
struct wmEventHandler_Keymap *WM_event_add_keymap_handler_dynamic(
ListBase *handlers, wmEventHandler_KeymapDynamicFn *keymap_fn, void *user_data);
@@ -281,8 +288,9 @@ void WM_event_set_keymap_handler_post_callback(struct wmEventHandler_Keymap *han
wmKeyMapItem *kmi,
void *user_data),
void *user_data);
-wmKeyMap *WM_event_get_keymap_from_handler(wmWindowManager *wm,
- struct wmEventHandler_Keymap *handler);
+void WM_event_get_keymaps_from_handler(wmWindowManager *wm,
+ struct wmEventHandler_Keymap *handler,
+ struct wmEventHandler_KeymapResult *km_result);
wmKeyMapItem *WM_event_match_keymap_item(struct bContext *C,
wmKeyMap *keymap,
@@ -707,6 +715,8 @@ void WM_event_fileselect_event(struct wmWindowManager *wm, void *ophandle, int e
void WM_operator_region_active_win_set(struct bContext *C);
+int WM_operator_flag_only_pass_through_on_press(int retval, const struct wmEvent *event);
+
/* drag and drop */
struct wmDrag *WM_event_start_drag(
struct bContext *C, int icon, int type, void *poin, double value, unsigned int flags);
@@ -787,6 +797,7 @@ enum {
WM_JOB_TYPE_QUADRIFLOW_REMESH,
WM_JOB_TYPE_TRACE_IMAGE,
WM_JOB_TYPE_LINEART,
+ WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL,
/* add as needed, bake, seq proxy build
* if having hard coded values is a problem */
};
diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
index 213a3c2e342..22bdf65a169 100644
--- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
+++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_group.c
@@ -265,6 +265,8 @@ void WM_gizmogroup_ensure_init(const bContext *C, wmGizmoGroup *gzgroup)
{
/* prepare for first draw */
if (UNLIKELY((gzgroup->init_flag & WM_GIZMOGROUP_INIT_SETUP) == 0)) {
+
+ gzgroup->use_fallback_keymap = true;
gzgroup->type->setup(C, gzgroup);
/* Not ideal, initialize keymap here, needed for RNA runtime generated gizmos. */
diff --git a/source/blender/windowmanager/intern/wm_dragdrop.c b/source/blender/windowmanager/intern/wm_dragdrop.c
index 76bb93b681c..6585349c83c 100644
--- a/source/blender/windowmanager/intern/wm_dragdrop.c
+++ b/source/blender/windowmanager/intern/wm_dragdrop.c
@@ -34,6 +34,7 @@
#include "BLT_translation.h"
#include "BLI_blenlib.h"
+#include "BLI_math_color.h"
#include "BIF_glutil.h"
@@ -50,6 +51,7 @@
#include "UI_interface.h"
#include "UI_interface_icons.h"
+#include "UI_resources.h"
#include "RNA_access.h"
@@ -426,7 +428,7 @@ ID *WM_drag_get_local_ID_or_import_from_asset(const wmDrag *drag, int idcode)
}
/**
- * \brief Free asset ID imported for cancelled drop.
+ * \brief Free asset ID imported for canceled drop.
*
* If the asset was imported (linked/appended) using #WM_drag_get_local_ID_or_import_from_asset()`
* (typically via a #wmDropBox.copy() callback), we want the ID to be removed again if the drop
@@ -463,8 +465,14 @@ void WM_drag_free_imported_drag_ID(struct Main *bmain, wmDrag *drag, wmDropBox *
static void wm_drop_operator_draw(const char *name, int x, int y)
{
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
- const float col_fg[4] = {1.0f, 1.0f, 1.0f, 1.0f};
- const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f};
+
+ /* Use the theme settings from tooltips. */
+ const bTheme *btheme = UI_GetTheme();
+ const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip;
+
+ float col_fg[4], col_bg[4];
+ rgba_uchar_to_float(col_fg, wcol->text);
+ rgba_uchar_to_float(col_bg, wcol->inner);
UI_fontstyle_draw_simple_backdrop(fstyle, x, y, name, col_fg, col_bg);
}
diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c
index ae09786356a..14fcc1d69cc 100644
--- a/source/blender/windowmanager/intern/wm_event_system.c
+++ b/source/blender/windowmanager/intern/wm_event_system.c
@@ -2989,9 +2989,18 @@ static int wm_handlers_do_intern(bContext *C, wmEvent *event, ListBase *handlers
/* Handle all types here. */
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
- wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
- action |= wm_handlers_do_keymap_with_keymap_handler(
- C, event, handlers, handler, keymap, do_debug_handler);
+ wmEventHandler_KeymapResult km_result;
+ WM_event_get_keymaps_from_handler(wm, handler, &km_result);
+ int action_iter = WM_HANDLER_CONTINUE;
+ for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
+ wmKeyMap *keymap = km_result.keymaps[km_index];
+ action_iter |= wm_handlers_do_keymap_with_keymap_handler(
+ C, event, handlers, handler, keymap, do_debug_handler);
+ if (action_iter & WM_HANDLER_BREAK) {
+ break;
+ }
+ }
+ action |= action_iter;
/* Clear the tool-tip whenever a key binding is handled, without this tool-tips
* are kept when a modal operators starts (annoying but otherwise harmless). */
@@ -3905,17 +3914,34 @@ wmEventHandler_Keymap *WM_event_add_keymap_handler(ListBase *handlers, wmKeyMap
*
* Follow #wmEventHandler_KeymapDynamicFn signature.
*/
-wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm,
- wmEventHandler_Keymap *handler)
+void WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm,
+ wmEventHandler_Keymap *handler,
+ wmEventHandler_KeymapResult *km_result)
{
+ memset(km_result, 0x0, sizeof(*km_result));
+
+ const char *keymap_id_list[ARRAY_SIZE(km_result->keymaps)];
+ int keymap_id_list_len = 0;
+
ScrArea *area = handler->dynamic.user_data;
handler->keymap_tool = NULL;
bToolRef_Runtime *tref_rt = area->runtime.tool ? area->runtime.tool->runtime : NULL;
- if (tref_rt && tref_rt->keymap_fallback[0]) {
- const char *keymap_id = NULL;
+ if (tref_rt && tref_rt->keymap[0]) {
+ keymap_id_list[keymap_id_list_len++] = tref_rt->keymap;
+ }
+
+ bool is_gizmo_visible = false;
+ bool is_gizmo_highlight = false;
+
+ if (tref_rt && tref_rt->keymap_fallback[0]) {
+ bool add_keymap = false;
/* Support for the gizmo owning the tool keymap. */
- if (tref_rt->gizmo_group[0] != '\0' && tref_rt->keymap_fallback[0] != '\0') {
+
+ if (tref_rt->flag & TOOLREF_FLAG_FALLBACK_KEYMAP) {
+ add_keymap = true;
+ }
+ if (tref_rt->gizmo_group[0] != '\0') {
wmGizmoMap *gzmap = NULL;
wmGizmoGroup *gzgroup = NULL;
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
@@ -3931,32 +3957,49 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem_fallback(wmWindowManager *wm,
if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP) {
/* If all are hidden, don't override. */
if (gzgroup->use_fallback_keymap) {
+ is_gizmo_visible = true;
wmGizmo *highlight = wm_gizmomap_highlight_get(gzmap);
- if (highlight == NULL) {
- keymap_id = tref_rt->keymap_fallback;
+ if (highlight) {
+ is_gizmo_highlight = true;
}
+ add_keymap = true;
}
}
}
}
+ if (add_keymap) {
+ keymap_id_list[keymap_id_list_len++] = tref_rt->keymap_fallback;
+ }
+ }
- if (keymap_id && keymap_id[0]) {
- wmKeyMap *km = WM_keymap_list_find_spaceid_or_empty(
- &wm->userconf->keymaps, keymap_id, area->spacetype, RGN_TYPE_WINDOW);
- /* We shouldn't use keymaps from unrelated spaces. */
- if (km != NULL) {
- handler->keymap_tool = area->runtime.tool;
- return km;
- }
- printf(
- "Keymap: '%s' not found for tool '%s'\n", tref_rt->keymap, area->runtime.tool->idname);
+ if (is_gizmo_visible && !is_gizmo_highlight) {
+ if (keymap_id_list_len == 2) {
+ SWAP(const char *, keymap_id_list[0], keymap_id_list[1]);
}
}
- return NULL;
+
+ for (int i = 0; i < keymap_id_list_len; i++) {
+ const char *keymap_id = keymap_id_list[i];
+ BLI_assert(keymap_id && keymap_id[0]);
+
+ wmKeyMap *km = WM_keymap_list_find_spaceid_or_empty(
+ &wm->userconf->keymaps, keymap_id, area->spacetype, RGN_TYPE_WINDOW);
+ /* We shouldn't use keymaps from unrelated spaces. */
+ if (km == NULL) {
+ printf("Keymap: '%s' not found for tool '%s'\n", keymap_id, area->runtime.tool->idname);
+ continue;
+ }
+ handler->keymap_tool = area->runtime.tool;
+ km_result->keymaps[km_result->keymaps_len++] = km;
+ }
}
-wmKeyMap *WM_event_get_keymap_from_toolsystem(wmWindowManager *wm, wmEventHandler_Keymap *handler)
+void WM_event_get_keymap_from_toolsystem(wmWindowManager *wm,
+ wmEventHandler_Keymap *handler,
+ wmEventHandler_KeymapResult *km_result)
{
+ memset(km_result, 0x0, sizeof(*km_result));
+
ScrArea *area = handler->dynamic.user_data;
handler->keymap_tool = NULL;
bToolRef_Runtime *tref_rt = area->runtime.tool ? area->runtime.tool->runtime : NULL;
@@ -3968,13 +4011,14 @@ wmKeyMap *WM_event_get_keymap_from_toolsystem(wmWindowManager *wm, wmEventHandle
/* We shouldn't use keymaps from unrelated spaces. */
if (km != NULL) {
handler->keymap_tool = area->runtime.tool;
- return km;
+ km_result->keymaps[km_result->keymaps_len++] = km;
+ }
+ else {
+ printf(
+ "Keymap: '%s' not found for tool '%s'\n", tref_rt->keymap, area->runtime.tool->idname);
}
- printf(
- "Keymap: '%s' not found for tool '%s'\n", tref_rt->keymap, area->runtime.tool->idname);
}
}
- return NULL;
}
struct wmEventHandler_Keymap *WM_event_add_keymap_handler_dynamic(
@@ -5088,18 +5132,22 @@ void WM_set_locked_interface(wmWindowManager *wm, bool lock)
/** \name Event / Keymap Matching API
* \{ */
-wmKeyMap *WM_event_get_keymap_from_handler(wmWindowManager *wm, wmEventHandler_Keymap *handler)
+void WM_event_get_keymaps_from_handler(wmWindowManager *wm,
+ wmEventHandler_Keymap *handler,
+ wmEventHandler_KeymapResult *km_result)
{
- wmKeyMap *keymap;
if (handler->dynamic.keymap_fn != NULL) {
- keymap = handler->dynamic.keymap_fn(wm, handler);
+ handler->dynamic.keymap_fn(wm, handler, km_result);
BLI_assert(handler->keymap == NULL);
}
else {
- keymap = WM_keymap_active(wm, handler->keymap);
+ memset(km_result, 0x0, sizeof(*km_result));
+ wmKeyMap *keymap = WM_keymap_active(wm, handler->keymap);
BLI_assert(keymap != NULL);
+ if (keymap != NULL) {
+ km_result->keymaps[km_result->keymaps_len++] = keymap;
+ }
}
- return keymap;
}
wmKeyMapItem *WM_event_match_keymap_item(bContext *C, wmKeyMap *keymap, const wmEvent *event)
@@ -5128,11 +5176,15 @@ wmKeyMapItem *WM_event_match_keymap_item_from_handlers(bContext *C,
else if (handler_base->poll == NULL || handler_base->poll(CTX_wm_region(C), event)) {
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
- wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
- if (keymap && WM_keymap_poll(C, keymap)) {
- wmKeyMapItem *kmi = WM_event_match_keymap_item(C, keymap, event);
- if (kmi != NULL) {
- return kmi;
+ wmEventHandler_KeymapResult km_result;
+ WM_event_get_keymaps_from_handler(wm, handler, &km_result);
+ for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
+ wmKeyMap *keymap = km_result.keymaps[km_index];
+ if (WM_keymap_poll(C, keymap)) {
+ wmKeyMapItem *kmi = WM_event_match_keymap_item(C, keymap, event);
+ if (kmi != NULL) {
+ return kmi;
+ }
}
}
}
diff --git a/source/blender/windowmanager/intern/wm_jobs.c b/source/blender/windowmanager/intern/wm_jobs.c
index 6494c337c10..2604105896d 100644
--- a/source/blender/windowmanager/intern/wm_jobs.c
+++ b/source/blender/windowmanager/intern/wm_jobs.c
@@ -230,7 +230,7 @@ bool WM_jobs_test(const wmWindowManager *wm, const void *owner, int job_type)
LISTBASE_FOREACH (wmJob *, wm_job, &wm->jobs) {
if (wm_job->owner == owner) {
if (ELEM(job_type, WM_JOB_TYPE_ANY, wm_job->job_type)) {
- if (wm_job->running || wm_job->suspended) {
+ if ((wm_job->flag & WM_JOB_PROGRESS) && (wm_job->running || wm_job->suspended)) {
return true;
}
}
diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c
index f955abaed53..e5aedfc7f47 100644
--- a/source/blender/windowmanager/intern/wm_keymap.c
+++ b/source/blender/windowmanager/intern/wm_keymap.c
@@ -462,7 +462,9 @@ bool WM_keymap_poll(bContext *C, wmKeyMap *keymap)
/* Empty key-maps may be missing more there may be a typo in the name.
* Warn early to avoid losing time investigating each case.
* When developing a customized Blender though you may want empty keymaps. */
- if (!U.app_template[0]) {
+ if (!U.app_template[0] &&
+ /* Fallback key-maps may be intentionally empty, don't flood the output. */
+ !BLI_str_endswith(keymap->idname, " (fallback)")) {
CLOG_WARN(WM_LOG_KEYMAPS, "empty keymap '%s'", keymap->idname);
}
}
@@ -1402,15 +1404,19 @@ static wmKeyMapItem *wm_keymap_item_find_handlers(const bContext *C,
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
- wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
- if (keymap && WM_keymap_poll((bContext *)C, keymap)) {
- wmKeyMapItem *kmi = wm_keymap_item_find_in_keymap(
- keymap, opname, properties, is_strict, params);
- if (kmi != NULL) {
- if (r_keymap) {
- *r_keymap = keymap;
+ wmEventHandler_KeymapResult km_result;
+ WM_event_get_keymaps_from_handler(wm, handler, &km_result);
+ for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
+ wmKeyMap *keymap = km_result.keymaps[km_index];
+ if (WM_keymap_poll((bContext *)C, keymap)) {
+ wmKeyMapItem *kmi = wm_keymap_item_find_in_keymap(
+ keymap, opname, properties, is_strict, params);
+ if (kmi != NULL) {
+ if (r_keymap) {
+ *r_keymap = keymap;
+ }
+ return kmi;
}
- return kmi;
}
}
}
diff --git a/source/blender/windowmanager/intern/wm_operator_utils.c b/source/blender/windowmanager/intern/wm_operator_utils.c
index 81b597f7484..85a0a28de79 100644
--- a/source/blender/windowmanager/intern/wm_operator_utils.c
+++ b/source/blender/windowmanager/intern/wm_operator_utils.c
@@ -41,6 +41,24 @@
#include "ED_screen.h"
/* -------------------------------------------------------------------- */
+/** \name Generic Utilities
+ * \{ */
+
+/**
+ * Only finish + pass through for press events (allowing press-tweak).
+ */
+int WM_operator_flag_only_pass_through_on_press(int retval, const struct wmEvent *event)
+{
+ if ((event->val != KM_PRESS) &&
+ ((retval & OPERATOR_PASS_THROUGH) && (retval & OPERATOR_FINISHED))) {
+ retval &= ~OPERATOR_PASS_THROUGH;
+ }
+ return retval;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Value Interaction Helper
*
* Possible additions (add as needed).
diff --git a/source/blender/windowmanager/intern/wm_toolsystem.c b/source/blender/windowmanager/intern/wm_toolsystem.c
index 5eaf026191f..0c24520d565 100644
--- a/source/blender/windowmanager/intern/wm_toolsystem.c
+++ b/source/blender/windowmanager/intern/wm_toolsystem.c
@@ -326,7 +326,10 @@ void WM_toolsystem_ref_set_from_runtime(struct bContext *C,
bool use_fallback_keymap = false;
if (tref->idname_fallback[0] || tref->runtime->keymap_fallback[0]) {
- if (tref_rt->gizmo_group[0]) {
+ if (tref_rt->flag & TOOLREF_FLAG_FALLBACK_KEYMAP) {
+ use_fallback_keymap = true;
+ }
+ else if (tref_rt->gizmo_group[0]) {
wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(tref_rt->gizmo_group, false);
if (gzgt) {
if (gzgt->flag & WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP) {
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index 887aed7ffc7..8baf4a0e013 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -2426,10 +2426,15 @@ void wm_window_IME_end(wmWindow *win)
void *WM_opengl_context_create(void)
{
- /* On Windows there is a problem creating contexts that share lists
- * from one context that is current in another thread.
- * So we should call this function only on the main thread.
- */
+ /* On Windows there is a problem creating contexts that share resources (almost any object,
+ * including legacy display lists, but also textures) with a context which is current in another
+ * thread. This is a documented and behavior of both `::wglCreateContextAttribsARB()` and
+ * `::wglShareLists()`.
+ *
+ * Other platforms might successfully share resources from context which is active somewhere
+ * else, but to keep our code behave the same on all platform we expect contexts to only be
+ * created from the main thread. */
+
BLI_assert(BLI_thread_is_main());
BLI_assert(GPU_framebuffer_active_get() == GPU_framebuffer_back_get());