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/blenkernel/intern')
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc37
-rw-r--r--source/blender/blenkernel/intern/attribute_math.cc15
-rw-r--r--source/blender/blenkernel/intern/collection.c27
-rw-r--r--source/blender/blenkernel/intern/constraint.c22
-rw-r--r--source/blender/blenkernel/intern/customdata_file.c8
-rw-r--r--source/blender/blenkernel/intern/editmesh.c11
-rw-r--r--source/blender/blenkernel/intern/fcurve.c3
-rw-r--r--source/blender/blenkernel/intern/fluid.c6
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curve.cc224
-rw-r--r--source/blender/blenkernel/intern/geometry_component_mesh.cc56
-rw-r--r--source/blender/blenkernel/intern/geometry_set_instances.cc26
-rw-r--r--source/blender/blenkernel/intern/gpencil.c20
-rw-r--r--source/blender/blenkernel/intern/key.c9
-rw-r--r--source/blender/blenkernel/intern/lib_id.c42
-rw-r--r--source/blender/blenkernel/intern/lib_id_test.cc61
-rw-r--r--source/blender/blenkernel/intern/lib_override.c759
-rw-r--r--source/blender/blenkernel/intern/lib_query.c7
-rw-r--r--source/blender/blenkernel/intern/lib_remap.c7
-rw-r--r--source/blender/blenkernel/intern/material.c30
-rw-r--r--source/blender/blenkernel/intern/node.cc5
-rw-r--r--source/blender/blenkernel/intern/node_ui_storage.cc27
-rw-r--r--source/blender/blenkernel/intern/object.c12
-rw-r--r--source/blender/blenkernel/intern/particle_system.c5
-rw-r--r--source/blender/blenkernel/intern/scene.c6
-rw-r--r--source/blender/blenkernel/intern/spline_bezier.cc24
-rw-r--r--source/blender/blenkernel/intern/spline_nurbs.cc10
-rw-r--r--source/blender/blenkernel/intern/spline_poly.cc7
-rw-r--r--source/blender/blenkernel/intern/unit.c5
-rw-r--r--source/blender/blenkernel/intern/writeffmpeg.c196
29 files changed, 1227 insertions, 440 deletions
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index 62833e10438..f2ad873b10e 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -46,6 +46,8 @@ using blender::StringRef;
using blender::StringRefNull;
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
+using blender::fn::GVArray_For_GSpan;
+using blender::fn::GVArray_For_SingleValue;
namespace blender::bke {
@@ -61,7 +63,7 @@ const blender::fn::CPPType *custom_data_type_to_cpp_type(const CustomDataType ty
case CD_PROP_INT32:
return &CPPType::get<int>();
case CD_PROP_COLOR:
- return &CPPType::get<Color4f>();
+ return &CPPType::get<ColorGeometry4f>();
case CD_PROP_BOOL:
return &CPPType::get<bool>();
default:
@@ -84,7 +86,7 @@ CustomDataType cpp_type_to_custom_data_type(const blender::fn::CPPType &type)
if (type.is<int>()) {
return CD_PROP_INT32;
}
- if (type.is<Color4f>()) {
+ if (type.is<ColorGeometry4f>()) {
return CD_PROP_COLOR;
}
if (type.is<bool>()) {
@@ -355,7 +357,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
case CD_PROP_INT32:
return this->layer_to_read_attribute<int>(layer, domain_size);
case CD_PROP_COLOR:
- return this->layer_to_read_attribute<Color4f>(layer, domain_size);
+ return this->layer_to_read_attribute<ColorGeometry4f>(layer, domain_size);
case CD_PROP_BOOL:
return this->layer_to_read_attribute<bool>(layer, domain_size);
default:
@@ -389,7 +391,7 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
case CD_PROP_INT32:
return this->layer_to_write_attribute<int>(layer, domain_size);
case CD_PROP_COLOR:
- return this->layer_to_write_attribute<Color4f>(layer, domain_size);
+ return this->layer_to_write_attribute<ColorGeometry4f>(layer, domain_size);
case CD_PROP_BOOL:
return this->layer_to_write_attribute<bool>(layer, domain_size);
default:
@@ -628,8 +630,35 @@ std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) co
return {};
}
+/**
+ * Return a virtual array for a stored attribute, or a single value virtual array with the default
+ * value if the attribute doesn't exist. If no default value is provided, the default value for the
+ * type will be used.
+ */
+GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
+ const CustomDataType data_type,
+ const void *default_value) const
+{
+ const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
+
+ std::optional<GSpan> attribute = this->get_for_read(name);
+ if (!attribute) {
+ const int domain_size = this->size_;
+ return std::make_unique<GVArray_For_SingleValue>(
+ *type, domain_size, (default_value == nullptr) ? type->default_value() : default_value);
+ }
+
+ if (attribute->type() == *type) {
+ return std::make_unique<GVArray_For_GSpan>(*attribute);
+ }
+ const blender::nodes::DataTypeConversions &conversions =
+ blender::nodes::get_implicit_type_conversions();
+ return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type);
+}
+
std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name)
{
+ /* If this assert hits, it most likely means that #reallocate was not called at some point. */
BLI_assert(size_ != 0);
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
if (layer.name == name) {
diff --git a/source/blender/blenkernel/intern/attribute_math.cc b/source/blender/blenkernel/intern/attribute_math.cc
index 4ff3a6ceff5..5cdf329effb 100644
--- a/source/blender/blenkernel/intern/attribute_math.cc
+++ b/source/blender/blenkernel/intern/attribute_math.cc
@@ -18,18 +18,21 @@
namespace blender::attribute_math {
-Color4fMixer::Color4fMixer(MutableSpan<Color4f> output_buffer, Color4f default_color)
+ColorGeometryMixer::ColorGeometryMixer(MutableSpan<ColorGeometry4f> output_buffer,
+ ColorGeometry4f default_color)
: buffer_(output_buffer),
default_color_(default_color),
total_weights_(output_buffer.size(), 0.0f)
{
- buffer_.fill(Color4f(0, 0, 0, 0));
+ buffer_.fill(ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
}
-void Color4fMixer::mix_in(const int64_t index, const Color4f &color, const float weight)
+void ColorGeometryMixer::mix_in(const int64_t index,
+ const ColorGeometry4f &color,
+ const float weight)
{
BLI_assert(weight >= 0.0f);
- Color4f &output_color = buffer_[index];
+ ColorGeometry4f &output_color = buffer_[index];
output_color.r += color.r * weight;
output_color.g += color.g * weight;
output_color.b += color.b * weight;
@@ -37,11 +40,11 @@ void Color4fMixer::mix_in(const int64_t index, const Color4f &color, const float
total_weights_[index] += weight;
}
-void Color4fMixer::finalize()
+void ColorGeometryMixer::finalize()
{
for (const int64_t i : buffer_.index_range()) {
const float weight = total_weights_[i];
- Color4f &output_color = buffer_[i];
+ ColorGeometry4f &output_color = buffer_[i];
if (weight > 0.0f) {
const float weight_inv = 1.0f / weight;
output_color.r *= weight_inv;
diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c
index 3170c3aa65c..7b1aaf04640 100644
--- a/source/blender/blenkernel/intern/collection.c
+++ b/source/blender/blenkernel/intern/collection.c
@@ -694,8 +694,7 @@ Collection *BKE_collection_duplicate(Main *bmain,
const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0;
if (!is_subprocess) {
- BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
/* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate
* all expected linked data. */
if (ID_IS_LINKED(collection)) {
@@ -726,8 +725,7 @@ Collection *BKE_collection_duplicate(Main *bmain,
#endif
/* Cleanup. */
- BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
BKE_main_collection_sync(bmain);
}
@@ -1425,7 +1423,8 @@ static bool collection_instance_find_recursive(Collection *collection,
}
LISTBASE_FOREACH (CollectionChild *, collection_child, &collection->children) {
- if (collection_instance_find_recursive(collection_child->collection, instance_collection)) {
+ if (collection_child->collection != NULL &&
+ collection_instance_find_recursive(collection_child->collection, instance_collection)) {
return true;
}
}
@@ -1644,6 +1643,13 @@ void BKE_collection_parent_relations_rebuild(Collection *collection)
continue;
}
+ /* Can happen when remapping data partially out-of-Main (during advanced ID management
+ * operations like liboverride resync e.g.). */
+ if ((child->collection->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE)) != 0) {
+ continue;
+ }
+
+ BLI_assert(collection_find_parent(child->collection, collection) == NULL);
CollectionParent *cparent = MEM_callocN(sizeof(CollectionParent), __func__);
cparent->collection = collection;
BLI_addtail(&child->collection->parents, cparent);
@@ -1652,10 +1658,19 @@ void BKE_collection_parent_relations_rebuild(Collection *collection)
static void collection_parents_rebuild_recursive(Collection *collection)
{
+ /* A same collection may be child of several others, no need to process it more than once. */
+ if ((collection->tag & COLLECTION_TAG_RELATION_REBUILD) == 0) {
+ return;
+ }
+
BKE_collection_parent_relations_rebuild(collection);
collection->tag &= ~COLLECTION_TAG_RELATION_REBUILD;
for (CollectionChild *child = collection->children.first; child != NULL; child = child->next) {
+ /* See comment above in `BKE_collection_parent_relations_rebuild`. */
+ if ((collection->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE)) != 0) {
+ continue;
+ }
collection_parents_rebuild_recursive(child->collection);
}
}
@@ -1679,6 +1694,8 @@ void BKE_main_collections_parent_relations_rebuild(Main *bmain)
/* This function can be called from readfile.c, when this pointer is not guaranteed to be NULL.
*/
if (scene->master_collection != NULL) {
+ BLI_assert(BLI_listbase_is_empty(&scene->master_collection->parents));
+ scene->master_collection->tag |= COLLECTION_TAG_RELATION_REBUILD;
collection_parents_rebuild_recursive(scene->master_collection);
}
}
diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c
index 9293a2b449a..826c79c3764 100644
--- a/source/blender/blenkernel/intern/constraint.c
+++ b/source/blender/blenkernel/intern/constraint.c
@@ -1635,10 +1635,28 @@ static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *UN
float eul[3];
float size[3];
+ /* This constraint is based on euler rotation math, which doesn't work well with shear.
+ * The Y axis is chosen as the main one because constraints are most commonly used on bones.
+ * This also allows using the constraint to simply remove shear. */
+ orthogonalize_m4_stable(cob->matrix, 1, false);
+
+ /* Only do the complex processing if some limits are actually enabled. */
+ if (!(data->flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT))) {
+ return;
+ }
+
+ /* Select the Euler rotation order, defaulting to the owner value. */
+ short rot_order = cob->rotOrder;
+
+ if (data->euler_order != CONSTRAINT_EULER_AUTO) {
+ rot_order = data->euler_order;
+ }
+
+ /* Decompose the matrix using the specified order. */
copy_v3_v3(loc, cob->matrix[3]);
mat4_to_size(size, cob->matrix);
- mat4_to_eulO(eul, cob->rotOrder, cob->matrix);
+ mat4_to_eulO(eul, rot_order, cob->matrix);
/* constraint data uses radians internally */
@@ -1671,7 +1689,7 @@ static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *UN
}
}
- loc_eulO_size_to_mat4(cob->matrix, loc, eul, size, cob->rotOrder);
+ loc_eulO_size_to_mat4(cob->matrix, loc, eul, size, rot_order);
}
static bConstraintTypeInfo CTI_ROTLIMIT = {
diff --git a/source/blender/blenkernel/intern/customdata_file.c b/source/blender/blenkernel/intern/customdata_file.c
index 470c2f2d246..4aaecc26eff 100644
--- a/source/blender/blenkernel/intern/customdata_file.c
+++ b/source/blender/blenkernel/intern/customdata_file.c
@@ -167,7 +167,7 @@ static bool cdf_read_header(CDataFile *cdf)
offset += header->structbytes;
header->structbytes = sizeof(CDataFileHeader);
- if (fseek(f, offset, SEEK_SET) != 0) {
+ if (BLI_fseek(f, offset, SEEK_SET) != 0) {
return false;
}
@@ -201,7 +201,7 @@ static bool cdf_read_header(CDataFile *cdf)
mesh->structbytes = sizeof(CDataFileMeshHeader);
}
- if (fseek(f, offset, SEEK_SET) != 0) {
+ if (BLI_fseek(f, offset, SEEK_SET) != 0) {
return false;
}
@@ -233,7 +233,7 @@ static bool cdf_read_header(CDataFile *cdf)
offset += layer->structbytes;
layer->structbytes = sizeof(CDataFileLayer);
- if (fseek(f, offset, SEEK_SET) != 0) {
+ if (BLI_fseek(f, offset, SEEK_SET) != 0) {
return false;
}
}
@@ -321,7 +321,7 @@ bool cdf_read_layer(CDataFile *cdf, CDataFileLayer *blay)
offset += cdf->layer[a].datasize;
}
- return (fseek(cdf->readf, offset, SEEK_SET) == 0);
+ return (BLI_fseek(cdf->readf, offset, SEEK_SET) == 0);
}
bool cdf_read_data(CDataFile *cdf, unsigned int size, void *data)
diff --git a/source/blender/blenkernel/intern/editmesh.c b/source/blender/blenkernel/intern/editmesh.c
index 0fa3bb29ccd..472de1f3c77 100644
--- a/source/blender/blenkernel/intern/editmesh.c
+++ b/source/blender/blenkernel/intern/editmesh.c
@@ -127,9 +127,10 @@ static void editmesh_tessface_calc_intern(BMEditMesh *em)
}
em->looptris = looptris;
+ em->tottri = looptris_tot;
/* after allocating the em->looptris, we're ready to tessellate */
- BM_mesh_calc_tessellation(em->bm, em->looptris, &em->tottri);
+ BM_mesh_calc_tessellation(em->bm, em->looptris);
}
void BKE_editmesh_looptri_calc(BMEditMesh *em)
@@ -148,6 +149,14 @@ void BKE_editmesh_looptri_calc(BMEditMesh *em)
#endif
}
+void BKE_editmesh_looptri_calc_with_partial(BMEditMesh *em, struct BMPartialUpdate *bmpinfo)
+{
+ BLI_assert(em->tottri == poly_to_tri_count(em->bm->totface, em->bm->totloop));
+ BLI_assert(em->looptris != NULL);
+
+ BM_mesh_calc_tessellation_with_partial(em->bm, em->looptris, bmpinfo);
+}
+
void BKE_editmesh_free_derivedmesh(BMEditMesh *em)
{
if (em->mesh_eval_cage) {
diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c
index 30d5a54b479..68ed3c239ef 100644
--- a/source/blender/blenkernel/intern/fcurve.c
+++ b/source/blender/blenkernel/intern/fcurve.c
@@ -1559,8 +1559,7 @@ void BKE_fcurve_correct_bezpart(const float v1[2], float v2[2], float v3[2], con
}
/**
- .
- * Find roots of cubic equation (c0 x³ + c1 x² + c2 x + c3)
+ * Find roots of cubic equation (c0 x^3 + c1 x^2 + c2 x + c3)
* \return number of roots in `o`.
*
* \note it is up to the caller to allocate enough memory for `o`.
diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c
index 851d8aae378..493a267c2f0 100644
--- a/source/blender/blenkernel/intern/fluid.c
+++ b/source/blender/blenkernel/intern/fluid.c
@@ -623,7 +623,8 @@ static void clamp_bounds_in_domain(FluidDomainSettings *fds,
static bool is_static_object(Object *ob)
{
/* Check if the object has modifiers that might make the object "dynamic". */
- ModifierData *md = ob->modifiers.first;
+ VirtualModifierData virtualModifierData;
+ ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData);
for (; md; md = md->next) {
if (ELEM(md->type,
eModifierType_Cloth,
@@ -631,7 +632,8 @@ static bool is_static_object(Object *ob)
eModifierType_Explode,
eModifierType_Ocean,
eModifierType_ShapeKey,
- eModifierType_Softbody)) {
+ eModifierType_Softbody,
+ eModifierType_Nodes)) {
return false;
}
}
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index d08681da6ec..de8dc355557 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -14,11 +14,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "BKE_spline.hh"
+#include "DNA_ID_enums.h"
+#include "DNA_curve_types.h"
#include "BKE_attribute_access.hh"
#include "BKE_attribute_math.hh"
+#include "BKE_curve.h"
#include "BKE_geometry_set.hh"
+#include "BKE_lib_id.h"
+#include "BKE_spline.hh"
#include "attribute_access_intern.hh"
@@ -26,6 +30,7 @@ using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray_For_GSpan;
using blender::fn::GVArray_GSpan;
+using blender::fn::GVArrayPtr;
using blender::fn::GVMutableArray_For_GMutableSpan;
/* -------------------------------------------------------------------- */
@@ -58,6 +63,11 @@ void CurveComponent::clear()
if (ownership_ == GeometryOwnershipType::Owned) {
delete curve_;
}
+ if (curve_for_render_ != nullptr) {
+ BKE_id_free(nullptr, curve_for_render_);
+ curve_for_render_ = nullptr;
+ }
+
curve_ = nullptr;
}
}
@@ -118,6 +128,29 @@ void CurveComponent::ensure_owns_direct_data()
}
}
+/**
+ * Create empty curve data used for rendering the spline's wire edges.
+ * \note See comment on #curve_for_render_ for further explanation.
+ */
+const Curve *CurveComponent::get_curve_for_render() const
+{
+ if (curve_ == nullptr) {
+ return nullptr;
+ }
+ if (curve_for_render_ != nullptr) {
+ return curve_for_render_;
+ }
+ std::lock_guard lock{curve_for_render_mutex_};
+ if (curve_for_render_ != nullptr) {
+ return curve_for_render_;
+ }
+
+ curve_for_render_ = (Curve *)BKE_id_new_nomain(ID_CU, nullptr);
+ curve_for_render_->curve_eval = curve_;
+
+ return curve_for_render_;
+}
+
/** \} */
/* -------------------------------------------------------------------- */
@@ -142,6 +175,171 @@ int CurveComponent::attribute_domain_size(const AttributeDomain domain) const
return 0;
}
+namespace blender::bke {
+
+namespace {
+struct PointIndices {
+ int spline_index;
+ int point_index;
+};
+} // namespace
+static PointIndices lookup_point_indices(Span<int> offsets, const int index)
+{
+ const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) -
+ offsets.begin() - 1;
+ const int index_in_spline = index - offsets[spline_index];
+ return {spline_index, index_in_spline};
+}
+
+/**
+ * Mix together all of a spline's control point values.
+ *
+ * \note Theoretically this interpolation does not need to compute all values at once.
+ * However, doing that makes the implementation simpler, and this can be optimized in the future if
+ * only some values are required.
+ */
+template<typename T>
+static void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve,
+ const VArray<T> &old_values,
+ MutableSpan<T> r_values)
+{
+ const int splines_len = curve.splines().size();
+ Array<int> offsets = curve.control_point_offsets();
+ BLI_assert(r_values.size() == splines_len);
+ attribute_math::DefaultMixer<T> mixer(r_values);
+
+ for (const int i_spline : IndexRange(splines_len)) {
+ const int spline_offset = offsets[i_spline];
+ const int spline_point_len = offsets[i_spline + 1] - spline_offset;
+ for (const int i_point : IndexRange(spline_point_len)) {
+ const T value = old_values[spline_offset + i_point];
+ mixer.mix_in(i_spline, value);
+ }
+ }
+
+ mixer.finalize();
+}
+
+static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray)
+{
+ GVArrayPtr new_varray;
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
+ Array<T> values(curve.splines().size());
+ adapt_curve_domain_point_to_spline_impl<T>(curve, varray->typed<T>(), values);
+ new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values));
+ }
+ });
+ return new_varray;
+}
+
+/**
+ * A virtual array implementation for the conversion of spline attributes to control point
+ * attributes. The goal is to avoid copying the spline value for every one of its control points
+ * unless it is necessary (in that case the materialize functions will be called).
+ */
+template<typename T> class VArray_For_SplineToPoint final : public VArray<T> {
+ /* Store existing data materialized if it was not already a span. This is expected
+ * to be worth it because a single spline's value will likely be accessed many times. */
+ VArray_Span<T> original_data_;
+ Array<int> offsets_;
+
+ public:
+ VArray_For_SplineToPoint(const VArray<T> &original_varray, Array<int> offsets)
+ : VArray<T>(offsets.last()), original_data_(original_varray), offsets_(std::move(offsets))
+ {
+ }
+
+ T get_impl(const int64_t index) const final
+ {
+ const PointIndices indices = lookup_point_indices(offsets_, index);
+ return original_data_[indices.spline_index];
+ }
+
+ void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final
+ {
+ const int total_size = offsets_.last();
+ if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
+ for (const int spline_index : original_data_.index_range()) {
+ const int offset = offsets_[spline_index];
+ const int next_offset = offsets_[spline_index + 1];
+ r_span.slice(offset, next_offset - offset).fill(original_data_[spline_index]);
+ }
+ }
+ else {
+ int spline_index = 0;
+ for (const int dst_index : mask) {
+ while (offsets_[spline_index] < dst_index) {
+ spline_index++;
+ }
+ r_span[dst_index] = original_data_[spline_index];
+ }
+ }
+ }
+
+ void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final
+ {
+ T *dst = r_span.data();
+ const int total_size = offsets_.last();
+ if (mask.is_range() && mask.as_range() == IndexRange(total_size)) {
+ for (const int spline_index : original_data_.index_range()) {
+ const int offset = offsets_[spline_index];
+ const int next_offset = offsets_[spline_index + 1];
+ uninitialized_fill_n(dst + offset, next_offset - offset, original_data_[spline_index]);
+ }
+ }
+ else {
+ int spline_index = 0;
+ for (const int dst_index : mask) {
+ while (offsets_[spline_index] < dst_index) {
+ spline_index++;
+ }
+ new (dst + dst_index) T(original_data_[spline_index]);
+ }
+ }
+ }
+};
+
+static GVArrayPtr adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArrayPtr varray)
+{
+ GVArrayPtr new_varray;
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
+ using T = decltype(dummy);
+
+ Array<int> offsets = curve.control_point_offsets();
+ new_varray = std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplineToPoint<T>>>(
+ offsets.last(), *varray->typed<T>(), std::move(offsets));
+ });
+ return new_varray;
+}
+
+} // namespace blender::bke
+
+GVArrayPtr CurveComponent::attribute_try_adapt_domain(GVArrayPtr varray,
+ const AttributeDomain from_domain,
+ const AttributeDomain to_domain) const
+{
+ if (!varray) {
+ return {};
+ }
+ if (varray->size() == 0) {
+ return {};
+ }
+ if (from_domain == to_domain) {
+ return varray;
+ }
+
+ if (from_domain == ATTR_DOMAIN_POINT && to_domain == ATTR_DOMAIN_CURVE) {
+ return blender::bke::adapt_curve_domain_point_to_spline(*curve_, std::move(varray));
+ }
+ if (from_domain == ATTR_DOMAIN_CURVE && to_domain == ATTR_DOMAIN_POINT) {
+ return blender::bke::adapt_curve_domain_spline_to_point(*curve_, std::move(varray));
+ }
+
+ return {};
+}
+
static CurveEval *get_curve_from_component_for_write(GeometryComponent &component)
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_CURVE);
@@ -300,20 +498,6 @@ static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve)
* array implementations try to make it workable in common situations.
* \{ */
-namespace {
-struct PointIndices {
- int spline_index;
- int point_index;
-};
-} // namespace
-static PointIndices lookup_point_indices(Span<int> offsets, const int index)
-{
- const int spline_index = std::upper_bound(offsets.begin(), offsets.end(), index) -
- offsets.begin() - 1;
- const int index_in_spline = index - offsets[spline_index];
- return {spline_index, index_in_spline};
-}
-
template<typename T>
static void point_attribute_materialize(Span<Span<T>> data,
Span<int> offsets,
@@ -325,14 +509,12 @@ static void point_attribute_materialize(Span<Span<T>> data,
for (const int spline_index : data.index_range()) {
const int offset = offsets[spline_index];
const int next_offset = offsets[spline_index + 1];
- initialized_copy_n(data[spline_index].data(), next_offset - offset, r_span.data() + offset);
+ r_span.slice(offset, next_offset - offset).copy_from(data[spline_index]);
}
}
else {
int spline_index = 0;
- for (const int i : r_span.index_range()) {
- const int dst_index = mask[i];
-
+ for (const int dst_index : mask) {
while (offsets[spline_index] < dst_index) {
spline_index++;
}
@@ -360,9 +542,7 @@ static void point_attribute_materialize_to_uninitialized(Span<Span<T>> data,
}
else {
int spline_index = 0;
- for (const int i : r_span.index_range()) {
- const int dst_index = mask[i];
-
+ for (const int dst_index : mask) {
while (offsets[spline_index] < dst_index) {
spline_index++;
}
diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc
index 2ecd0e6bd85..42f3a854aec 100644
--- a/source/blender/blenkernel/intern/geometry_component_mesh.cc
+++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc
@@ -219,8 +219,7 @@ static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
/* We compute all interpolated values at once, because for this interpolation, one has to
@@ -249,8 +248,7 @@ static void adapt_mesh_domain_point_to_corner_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
/* It is not strictly necessary to compute the value for all corners here. Instead one could
* lazily lookup the mesh topology when a specific index accessed. This can be more efficient
@@ -290,8 +288,7 @@ static void adapt_mesh_domain_corner_to_face_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_corner_to_face(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totpoly);
@@ -329,8 +326,7 @@ static void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totedge);
@@ -365,8 +361,7 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totvert);
@@ -394,8 +389,7 @@ void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totloop);
@@ -428,8 +422,7 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totedge);
@@ -467,8 +460,7 @@ static void adapt_mesh_domain_point_to_face_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_point_to_face(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totpoly);
@@ -504,8 +496,7 @@ static void adapt_mesh_domain_point_to_edge_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_point_to_edge(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totedge);
@@ -543,8 +534,7 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totloop);
@@ -576,8 +566,7 @@ static void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totvert);
@@ -615,8 +604,7 @@ static void adapt_mesh_domain_edge_to_face_impl(const Mesh &mesh,
static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr varray)
{
GVArrayPtr new_varray;
- const CustomDataType data_type = cpp_type_to_custom_data_type(varray->type());
- attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+ attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
Array<T> values(mesh.totpoly);
@@ -785,18 +773,20 @@ static void set_loop_uv(MLoopUV &uv, float2 co)
copy_v2_v2(uv.uv, co);
}
-static Color4f get_loop_color(const MLoopCol &col)
+static ColorGeometry4f get_loop_color(const MLoopCol &col)
{
- Color4f srgb_color;
- rgba_uchar_to_float(srgb_color, &col.r);
- Color4f linear_color;
- srgb_to_linearrgb_v4(linear_color, srgb_color);
+ ColorGeometry4b encoded_color = ColorGeometry4b(col.r, col.g, col.b, col.a);
+ ColorGeometry4f linear_color = encoded_color.decode();
return linear_color;
}
-static void set_loop_color(MLoopCol &col, Color4f linear_color)
+static void set_loop_color(MLoopCol &col, ColorGeometry4f linear_color)
{
- linearrgb_to_srgb_uchar4(&col.r, linear_color);
+ ColorGeometry4b encoded_color = linear_color.encode();
+ col.r = encoded_color.r;
+ col.g = encoded_color.g;
+ col.b = encoded_color.b;
+ col.a = encoded_color.a;
}
static float get_crease(const MEdge &edge)
@@ -1133,8 +1123,8 @@ static ComponentAttributeProviders create_attribute_providers_for_mesh()
CD_PROP_COLOR,
CD_MLOOPCOL,
corner_access,
- make_derived_read_attribute<MLoopCol, Color4f, get_loop_color>,
- make_derived_write_attribute<MLoopCol, Color4f, get_loop_color, set_loop_color>);
+ make_derived_read_attribute<MLoopCol, ColorGeometry4f, get_loop_color>,
+ make_derived_write_attribute<MLoopCol, ColorGeometry4f, get_loop_color, set_loop_color>);
static VertexGroupsAttributeProvider vertex_groups;
static CustomDataAttributeProvider corner_custom_data(ATTR_DOMAIN_CORNER, corner_access);
diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc
index 9abd00c2b4f..69840ba1612 100644
--- a/source/blender/blenkernel/intern/geometry_set_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_set_instances.cc
@@ -544,9 +544,9 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
}
}
-static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComponent &result)
+static CurveEval *join_curve_splines(Span<GeometryInstanceGroup> set_groups)
{
- CurveEval *new_curve = new CurveEval();
+ Vector<SplinePtr> new_splines;
for (const GeometryInstanceGroup &set_group : set_groups) {
const GeometrySet &set = set_group.geometry_set;
if (!set.has_curve()) {
@@ -558,10 +558,18 @@ static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComp
for (const float4x4 &transform : set_group.transforms) {
SplinePtr new_spline = source_spline->copy();
new_spline->transform(transform);
- new_curve->add_spline(std::move(new_spline));
+ new_splines.append(std::move(new_spline));
}
}
}
+ if (new_splines.is_empty()) {
+ return nullptr;
+ }
+
+ CurveEval *new_curve = new CurveEval();
+ for (SplinePtr &new_spline : new_splines) {
+ new_curve->add_spline(std::move(new_spline));
+ }
for (SplinePtr &spline : new_curve->splines()) {
/* Spline instances should have no custom attributes, since they always come
@@ -573,8 +581,7 @@ static void join_curve_splines(Span<GeometryInstanceGroup> set_groups, CurveComp
}
new_curve->attributes.reallocate(new_curve->splines().size());
-
- result.replace(new_curve);
+ return new_curve;
}
static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups,
@@ -639,14 +646,17 @@ static void join_instance_groups_volume(Span<GeometryInstanceGroup> set_groups,
{
/* Not yet supported. Joining volume grids with the same name requires resampling of at least
* one of the grids. The cell size of the resulting volume has to be determined somehow. */
- VolumeComponent &dst_component = result.get_component_for_write<VolumeComponent>();
- UNUSED_VARS(set_groups, dst_component);
+ UNUSED_VARS(set_groups, result);
}
static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, GeometrySet &result)
{
+ CurveEval *curve = join_curve_splines(set_groups);
+ if (curve == nullptr) {
+ return;
+ }
CurveComponent &dst_component = result.get_component_for_write<CurveComponent>();
- join_curve_splines(set_groups, dst_component);
+ dst_component.replace(curve);
}
GeometrySet geometry_set_realize_mesh_for_modifier(const GeometrySet &geometry_set)
diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c
index 421cb0ac4f1..6d1476485ca 100644
--- a/source/blender/blenkernel/intern/gpencil.c
+++ b/source/blender/blenkernel/intern/gpencil.c
@@ -2624,6 +2624,11 @@ static bool gpencil_is_layer_mask(ViewLayer *view_layer, bGPdata *gpd, bGPDlayer
continue;
}
+ /* Skip if masks are disabled for this view layer. */
+ if (gpl->flag & GP_LAYER_DISABLE_MASKS_IN_VIEWLAYER) {
+ continue;
+ }
+
LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl->mask_layers) {
if (STREQ(gpl_mask->info, mask->name)) {
return true;
@@ -2667,6 +2672,7 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
bGPDframe *act_gpf = gpl->actframe;
bGPDframe *sta_gpf = act_gpf;
bGPDframe *end_gpf = act_gpf ? act_gpf->next : NULL;
+ float prev_opacity = gpl->opacity;
if (gpl->flag & GP_LAYER_HIDE) {
continue;
@@ -2682,9 +2688,12 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
* This is used only in final render and never in Viewport. */
if ((view_layer != NULL) && (gpl->viewlayername[0] != '\0') &&
(!STREQ(view_layer->name, gpl->viewlayername))) {
- /* If the layer is used as mask, cannot be filtered or the masking system
- * will crash because needs the mask layer in the draw pipeline. */
- if (!gpencil_is_layer_mask(view_layer, gpd, gpl)) {
+ /* Do not skip masks when rendering the view-layer so that it can still be used to clip
+ * other layers. Instead set their opacity to zero. */
+ if (gpencil_is_layer_mask(view_layer, gpd, gpl)) {
+ gpl->opacity = 0.0f;
+ }
+ else {
continue;
}
}
@@ -2779,6 +2788,7 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
if (layer_cb) {
layer_cb(gpl, act_gpf, NULL, thunk);
}
+ gpl->opacity = prev_opacity;
continue;
}
@@ -2816,6 +2826,7 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
/* If layer solo mode and Paint mode, only keyframes with data are displayed. */
if (GPENCIL_PAINT_MODE(gpd) && (gpl->flag & GP_LAYER_SOLO_MODE) &&
(act_gpf->framenum != cfra)) {
+ gpl->opacity = prev_opacity;
continue;
}
@@ -2826,6 +2837,9 @@ void BKE_gpencil_visible_stroke_iter(ViewLayer *view_layer,
stroke_cb(gpl, act_gpf, gps, thunk);
}
}
+
+ /* Restore the opacity in case it was overwritten (used to hide masks in render). */
+ gpl->opacity = prev_opacity;
}
}
diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c
index f2893e162cb..073276b7011 100644
--- a/source/blender/blenkernel/intern/key.c
+++ b/source/blender/blenkernel/intern/key.c
@@ -105,6 +105,11 @@ static void shapekey_foreach_id(ID *id, LibraryForeachIDData *data)
BKE_LIB_FOREACHID_PROCESS_ID(data, key->from, IDWALK_CB_LOOPBACK);
}
+static ID *shapekey_owner_get(Main *UNUSED(bmain), ID *id)
+{
+ return ((Key *)id)->from;
+}
+
static void shapekey_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Key *key = (Key *)id;
@@ -216,7 +221,9 @@ IDTypeInfo IDType_ID_KE = {
.make_local = NULL,
.foreach_id = shapekey_foreach_id,
.foreach_cache = NULL,
- .owner_get = NULL, /* Could have one actually? */
+ /* A bit weird, due to shapekeys not being strictly speaking embedded data... But they also
+ * share a lot with those (non linkable, only ever used by one owner ID, etc.). */
+ .owner_get = shapekey_owner_get,
.blend_write = shapekey_blend_write,
.blend_read_data = shapekey_blend_read_data,
diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c
index c2e5006cbc1..f26b85338f0 100644
--- a/source/blender/blenkernel/intern/lib_id.c
+++ b/source/blender/blenkernel/intern/lib_id.c
@@ -164,7 +164,7 @@ static void lib_id_clear_library_data_ex(Main *bmain, ID *id)
id->tag &= ~(LIB_TAG_INDIRECT | LIB_TAG_EXTERN);
id->flag &= ~LIB_INDIRECT_WEAK_LINK;
if (id_in_mainlist) {
- if (BKE_id_new_name_validate(which_libbase(bmain, GS(id->name)), id, NULL)) {
+ if (BKE_id_new_name_validate(which_libbase(bmain, GS(id->name)), id, NULL, false)) {
bmain->is_memfile_undo_written = false;
}
}
@@ -833,7 +833,9 @@ void BKE_libblock_management_main_add(Main *bmain, void *idv)
ListBase *lb = which_libbase(bmain, GS(id->name));
BKE_main_lock(bmain);
BLI_addtail(lb, id);
- BKE_id_new_name_validate(lb, id, NULL);
+ /* We need to allow adding extra datablocks into libraries too, e.g. to support generating new
+ * overrides for recursive resync. */
+ BKE_id_new_name_validate(lb, id, NULL, true);
/* alphabetic insertion: is in new_id */
id->tag &= ~(LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT);
bmain->is_memfile_undo_written = false;
@@ -989,7 +991,7 @@ void BKE_main_id_repair_duplicate_names_listbase(ListBase *lb)
}
for (i = 0; i < lb_len; i++) {
if (!BLI_gset_add(gset, id_array[i]->name + 2)) {
- BKE_id_new_name_validate(lb, id_array[i], NULL);
+ BKE_id_new_name_validate(lb, id_array[i], NULL, false);
}
}
BLI_gset_free(gset, NULL);
@@ -1092,7 +1094,7 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl
BKE_main_lock(bmain);
BLI_addtail(lb, id);
- BKE_id_new_name_validate(lb, id, name);
+ BKE_id_new_name_validate(lb, id, name, false);
bmain->is_memfile_undo_written = false;
/* alphabetic insertion: is in new_id */
BKE_main_unlock(bmain);
@@ -1221,14 +1223,6 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || bmain != NULL);
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_NO_ALLOCATE) == 0);
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_LOCAL) == 0);
- if (!is_private_id_data) {
- /* When we are handling private ID data, we might still want to manage usercounts, even
- * though that ID data-block is actually outside of Main... */
- BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) == 0 ||
- (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) != 0);
- }
- /* Never implicitly copy shapekeys when generating temp data outside of Main database. */
- BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) == 0 || (flag & LIB_ID_COPY_SHAPEKEY) == 0);
/* 'Private ID' data handling. */
if ((bmain != NULL) && is_private_id_data) {
@@ -1565,7 +1559,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_
* and that current one is not. */
bool is_valid = false;
for (id_test = lb->first; id_test; id_test = id_test->next) {
- if (id != id_test && !ID_IS_LINKED(id_test)) {
+ if (id != id_test && id_test->lib == id->lib) {
if (id_test->name[2] == final_name[0] && STREQ(final_name, id_test->name + 2)) {
/* We expect final_name to not be already used, so this is a failure. */
is_valid = false;
@@ -1621,7 +1615,7 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_
for (id_test = lb->first; id_test; id_test = id_test->next) {
char base_name_test[MAX_ID_NAME - 2];
int number_test;
- if ((id != id_test) && !ID_IS_LINKED(id_test) && (name[0] == id_test->name[2]) &&
+ if ((id != id_test) && (id_test->lib == id->lib) && (name[0] == id_test->name[2]) &&
(ELEM(id_test->name[base_name_len + 2], '.', '\0')) &&
STREQLEN(name, id_test->name + 2, base_name_len) &&
(BLI_split_name_num(base_name_test, &number_test, id_test->name + 2, '.') ==
@@ -1710,15 +1704,18 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_
*
* Only for local IDs (linked ones already have a unique ID in their library).
*
+ * \param do_linked_data if true, also ensure a unique name in case the given \a id is linked
+ * (otherwise, just ensure that it is properly sorted).
+ *
* \return true if a new name had to be created.
*/
-bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname)
+bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname, const bool do_linked_data)
{
bool result = false;
char name[MAX_ID_NAME - 2];
- /* If library, don't rename, but do ensure proper sorting. */
- if (ID_IS_LINKED(id)) {
+ /* If library, don't rename (unless explicitly required), but do ensure proper sorting. */
+ if (!do_linked_data && ID_IS_LINKED(id)) {
id_sort_by_name(lb, id, NULL);
return result;
@@ -1762,7 +1759,7 @@ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname)
}
/* next to indirect usage in read/writefile also in editobject.c scene.c */
-void BKE_main_id_clear_newpoins(Main *bmain)
+void BKE_main_id_newptr_and_tag_clear(Main *bmain)
{
ID *id;
@@ -2176,7 +2173,7 @@ void BKE_library_make_local(Main *bmain,
TIMEIT_VALUE_PRINT(make_local);
#endif
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
BLI_memarena_free(linklist_mem);
#ifdef DEBUG_TIME
@@ -2201,9 +2198,9 @@ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name)
/* search for id */
idtest = BLI_findstring(lb, name + 2, offsetof(ID, name) + 2);
- if (idtest != NULL) {
+ if (idtest != NULL && !ID_IS_LINKED(idtest)) {
/* BKE_id_new_name_validate also takes care of sorting. */
- BKE_id_new_name_validate(lb, idtest, NULL);
+ BKE_id_new_name_validate(lb, idtest, NULL, false);
bmain->is_memfile_undo_written = false;
}
}
@@ -2213,8 +2210,9 @@ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name)
*/
void BKE_libblock_rename(Main *bmain, ID *id, const char *name)
{
+ BLI_assert(!ID_IS_LINKED(id));
ListBase *lb = which_libbase(bmain, GS(id->name));
- if (BKE_id_new_name_validate(lb, id, name)) {
+ if (BKE_id_new_name_validate(lb, id, name, false)) {
bmain->is_memfile_undo_written = false;
}
}
diff --git a/source/blender/blenkernel/intern/lib_id_test.cc b/source/blender/blenkernel/intern/lib_id_test.cc
index 9bfd19ae4d6..8e21ae88aa6 100644
--- a/source/blender/blenkernel/intern/lib_id_test.cc
+++ b/source/blender/blenkernel/intern/lib_id_test.cc
@@ -20,6 +20,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
+#include "BLI_string.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
@@ -37,6 +38,7 @@ struct LibIDMainSortTestContext {
static void test_lib_id_main_sort_init(LibIDMainSortTestContext *ctx)
{
+ BKE_idtype_init();
ctx->bmain = BKE_main_new();
}
@@ -109,4 +111,63 @@ TEST(lib_id_main_sort, linked_ids_1)
test_lib_id_main_sort_free(&ctx);
}
+TEST(lib_id_main_unique_name, local_ids_1)
+{
+ LibIDMainSortTestContext ctx = {nullptr};
+ test_lib_id_main_sort_init(&ctx);
+ EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
+
+ ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
+ ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
+ ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
+ test_lib_id_main_sort_check_order({id_a, id_b, id_c});
+
+ BLI_strncpy(id_c->name, id_a->name, sizeof(id_c->name));
+ BKE_id_new_name_validate(&ctx.bmain->objects, id_c, NULL, false);
+ EXPECT_TRUE(strcmp(id_c->name + 2, "OB_A.001") == 0);
+ EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0);
+ EXPECT_TRUE(ctx.bmain->objects.first == id_a);
+ EXPECT_TRUE(ctx.bmain->objects.last == id_b);
+ test_lib_id_main_sort_check_order({id_a, id_c, id_b});
+
+ test_lib_id_main_sort_free(&ctx);
+}
+
+TEST(lib_id_main_unique_name, linked_ids_1)
+{
+ LibIDMainSortTestContext ctx = {nullptr};
+ test_lib_id_main_sort_init(&ctx);
+ EXPECT_TRUE(BLI_listbase_is_empty(&ctx.bmain->libraries));
+
+ Library *lib_a = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_A"));
+ Library *lib_b = static_cast<Library *>(BKE_id_new(ctx.bmain, ID_LI, "LI_B"));
+ ID *id_c = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_C"));
+ ID *id_a = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_A"));
+ ID *id_b = static_cast<ID *>(BKE_id_new(ctx.bmain, ID_OB, "OB_B"));
+
+ id_a->lib = lib_a;
+ id_sort_by_name(&ctx.bmain->objects, id_a, nullptr);
+ id_b->lib = lib_a;
+ id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
+ BLI_strncpy(id_b->name, id_a->name, sizeof(id_b->name));
+ BKE_id_new_name_validate(&ctx.bmain->objects, id_b, NULL, true);
+ EXPECT_TRUE(strcmp(id_b->name + 2, "OB_A.001") == 0);
+ EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0);
+ EXPECT_TRUE(ctx.bmain->objects.first == id_c);
+ EXPECT_TRUE(ctx.bmain->objects.last == id_b);
+ test_lib_id_main_sort_check_order({id_c, id_a, id_b});
+
+ id_b->lib = lib_b;
+ id_sort_by_name(&ctx.bmain->objects, id_b, nullptr);
+ BLI_strncpy(id_b->name, id_a->name, sizeof(id_b->name));
+ BKE_id_new_name_validate(&ctx.bmain->objects, id_b, NULL, true);
+ EXPECT_TRUE(strcmp(id_b->name + 2, "OB_A") == 0);
+ EXPECT_TRUE(strcmp(id_a->name + 2, "OB_A") == 0);
+ EXPECT_TRUE(ctx.bmain->objects.first == id_c);
+ EXPECT_TRUE(ctx.bmain->objects.last == id_b);
+ test_lib_id_main_sort_check_order({id_c, id_a, id_b});
+
+ test_lib_id_main_sort_free(&ctx);
+}
+
} // namespace blender::bke::tests
diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c
index 8341c5b6e78..c93971e7b11 100644
--- a/source/blender/blenkernel/intern/lib_override.c
+++ b/source/blender/blenkernel/intern/lib_override.c
@@ -48,6 +48,7 @@
#include "BKE_lib_query.h"
#include "BKE_lib_remap.h"
#include "BKE_main.h"
+#include "BKE_node.h"
#include "BKE_report.h"
#include "BKE_scene.h"
@@ -80,6 +81,19 @@ static void lib_override_library_property_clear(IDOverrideLibraryProperty *op);
static void lib_override_library_property_operation_clear(
IDOverrideLibraryPropertyOperation *opop);
+/** Get override data for a given ID. Needed because of our beloved shape keys snowflake. */
+BLI_INLINE IDOverrideLibrary *lib_override_get(Main *bmain, ID *id)
+{
+ if (id->flag & LIB_EMBEDDED_DATA_LIB_OVERRIDE) {
+ const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
+ if (id_type->owner_get != NULL) {
+ return id_type->owner_get(bmain, id)->override_library;
+ }
+ BLI_assert(!"IDTypeInfo of liboverride-embedded ID with no owner getter");
+ }
+ return id->override_library;
+}
+
/** Initialize empty overriding of \a reference_id by \a local_id. */
IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id)
{
@@ -194,12 +208,17 @@ void BKE_lib_override_library_free(struct IDOverrideLibrary **override, const bo
*override = NULL;
}
-static ID *lib_override_library_create_from(Main *bmain, ID *reference_id)
+static ID *lib_override_library_create_from(Main *bmain,
+ ID *reference_id,
+ const int lib_id_copy_flags)
{
/* Note: We do not want to copy possible override data from reference here (whether it is an
* override template, or already an override of some other ref data). */
- ID *local_id = BKE_id_copy_ex(
- bmain, reference_id, NULL, LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE);
+ ID *local_id = BKE_id_copy_ex(bmain,
+ reference_id,
+ NULL,
+ LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE |
+ lib_id_copy_flags);
if (local_id == NULL) {
return NULL;
@@ -233,6 +252,13 @@ bool BKE_lib_override_library_is_user_edited(struct ID *id)
return false;
}
+ /* A bit weird, but those embedded IDs are handled by their owner ID anyway, so we can just
+ * assume they are never user-edited, actual proper detection will happen from their owner check.
+ */
+ if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
+ return false;
+ }
+
LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &id->override_library->properties) {
LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
if ((opop->flag & IDOVERRIDE_LIBRARY_FLAG_IDPOINTER_MATCH_REFERENCE) != 0) {
@@ -257,7 +283,7 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain,
BLI_assert(reference_id != NULL);
BLI_assert(reference_id->lib != NULL);
- ID *local_id = lib_override_library_create_from(bmain, reference_id);
+ ID *local_id = lib_override_library_create_from(bmain, reference_id, 0);
if (do_tagged_remap) {
Key *reference_key, *local_key = NULL;
@@ -302,9 +328,17 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain,
* main. You can add more local IDs to be remapped to use new overriding ones by setting their
* LIB_TAG_DOIT tag.
*
+ * \param reference_library the library from which the linked data being overridden come from
+ * (i.e. the library of the linked reference ID).
+ *
+ * \param do_no_main Create the new override data outside of Main database. Used for resyncing of
+ * linked overrides.
+ *
* \return \a true on success, \a false otherwise.
*/
-bool BKE_lib_override_library_create_from_tag(Main *bmain)
+bool BKE_lib_override_library_create_from_tag(Main *bmain,
+ const Library *reference_library,
+ const bool do_no_main)
{
ID *reference_id;
bool success = true;
@@ -314,7 +348,7 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain)
/* Get all IDs we want to override. */
FOREACH_MAIN_ID_BEGIN (bmain, reference_id) {
- if ((reference_id->tag & LIB_TAG_DOIT) != 0 && reference_id->lib != NULL &&
+ if ((reference_id->tag & LIB_TAG_DOIT) != 0 && reference_id->lib == reference_library &&
BKE_idtype_idcode_is_linkable(GS(reference_id->name))) {
todo_id_iter = MEM_callocN(sizeof(*todo_id_iter), __func__);
todo_id_iter->data = reference_id;
@@ -326,10 +360,16 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain)
/* Override the IDs. */
for (todo_id_iter = todo_ids.first; todo_id_iter != NULL; todo_id_iter = todo_id_iter->next) {
reference_id = todo_id_iter->data;
+
+ /* If `newid` is already set, assume it has been handled by calling code.
+ * Only current use case: re-using proxy ID when converting to liboverride. */
if (reference_id->newid == NULL) {
- /* If `newid` is already set, assume it has been handled by calling code.
- * Only current use case: re-using proxy ID when converting to liboverride. */
- if ((reference_id->newid = lib_override_library_create_from(bmain, reference_id)) == NULL) {
+ /* Note: `no main` case is used during resync procedure, to support recursive resync.
+ * This requires extra care further down the resync process,
+ * see: #BKE_lib_override_library_resync. */
+ reference_id->newid = lib_override_library_create_from(
+ bmain, reference_id, do_no_main ? LIB_ID_CREATE_NO_MAIN : 0);
+ if (reference_id->newid == NULL) {
success = false;
break;
}
@@ -369,23 +409,43 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain)
/* Still checking the whole Main, that way we can tag other local IDs as needing to be
* remapped to use newly created overriding IDs, if needed. */
- FOREACH_MAIN_ID_BEGIN (bmain, other_id) {
- if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib == NULL) {
- /* Note that using ID_REMAP_SKIP_INDIRECT_USAGE below is superfluous, as we only remap
- * local IDs usages anyway. */
+ ID *id;
+ FOREACH_MAIN_ID_BEGIN (bmain, id) {
+ /* In case we created new overrides as 'no main', they are not accessible directly in this
+ * loop, but we can get to them through their reference's `newid` pointer. */
+ if (do_no_main && id->lib == reference_id->lib && id->newid != NULL) {
+ other_id = id->newid;
+ /* Otherwise we cannot properly distinguish between IDs that are actually from the
+ * linked library (and should not be remapped), and IDs that are overrides re-generated
+ * from the reference from the linked library, and must therefore be remapped.
+ *
+ * This is reset afterwards at the end of this loop. */
+ other_id->lib = NULL;
+ }
+ else {
+ other_id = id;
+ }
+
+ /* If other ID is a linked one, but not from the same library as our reference, then we
+ * consider we should also remap it, as part of recursive resync. */
+ if ((other_id->tag & LIB_TAG_DOIT) != 0 && other_id->lib != reference_id->lib &&
+ other_id != local_id) {
BKE_libblock_relink_ex(bmain,
other_id,
reference_id,
local_id,
- ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY);
+ ID_REMAP_SKIP_OVERRIDE_LIBRARY | ID_REMAP_FORCE_USER_REFCOUNT);
if (reference_key != NULL) {
BKE_libblock_relink_ex(bmain,
other_id,
&reference_key->id,
&local_key->id,
- ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_OVERRIDE_LIBRARY);
+ ID_REMAP_SKIP_OVERRIDE_LIBRARY | ID_REMAP_FORCE_USER_REFCOUNT);
}
}
+ if (other_id != id) {
+ other_id->lib = reference_id->lib;
+ }
}
FOREACH_MAIN_ID_END;
}
@@ -409,6 +469,8 @@ typedef struct LibOverrideGroupTagData {
ID *id_root;
uint tag;
uint missing_tag;
+ /* Whether we are looping on override data, or their references (linked) one. */
+ bool is_override;
} LibOverrideGroupTagData;
/* Tag all IDs in dependency relationships within an override hierarchy/group.
@@ -421,6 +483,7 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa
{
Main *bmain = data->bmain;
ID *id = data->id_root;
+ const bool is_override = data->is_override;
MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id);
BLI_assert(entry != NULL);
@@ -442,16 +505,16 @@ static bool lib_override_hierarchy_dependencies_recursive_tag(LibOverrideGroupTa
}
/* We only consider IDs from the same library. */
ID *to_id = *to_id_entry->id_pointer.to;
- if (!ID_IS_LINKED(to_id) && !ID_IS_OVERRIDE_LIBRARY(to_id)) {
- /* Pure local data is a barrier of dependency in override cases. */
+ if (to_id == NULL || to_id->lib != id->lib ||
+ (is_override && !ID_IS_OVERRIDE_LIBRARY(to_id))) {
+ /* IDs from different libraries, or non-override IDs in case we are processing overrides, are
+ * both barriers of dependency. */
continue;
}
- if (to_id != NULL && to_id->lib == id->lib) {
- LibOverrideGroupTagData sub_data = *data;
- sub_data.id_root = to_id;
- if (lib_override_hierarchy_dependencies_recursive_tag(&sub_data)) {
- id->tag |= data->tag;
- }
+ LibOverrideGroupTagData sub_data = *data;
+ sub_data.id_root = to_id;
+ if (lib_override_hierarchy_dependencies_recursive_tag(&sub_data)) {
+ id->tag |= data->tag;
}
}
@@ -463,6 +526,7 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat
Main *bmain = data->bmain;
ID *id_owner = data->id_root;
BLI_assert(ID_IS_LINKED(id_owner));
+ BLI_assert(!data->is_override);
const uint tag = data->tag;
const uint missing_tag = data->missing_tag;
@@ -528,6 +592,7 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data)
{
Main *bmain = data->bmain;
ID *id_root = data->id_root;
+ BLI_assert(!data->is_override);
if ((id_root->tag & LIB_TAG_MISSING)) {
id_root->tag |= data->missing_tag;
@@ -554,11 +619,12 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data)
}
}
-static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data)
+static void lib_override_overrides_group_tag_recursive(LibOverrideGroupTagData *data)
{
Main *bmain = data->bmain;
ID *id_owner = data->id_root;
BLI_assert(ID_IS_OVERRIDE_LIBRARY(id_owner));
+ BLI_assert(data->is_override);
const uint tag = data->tag;
const uint missing_tag = data->missing_tag;
@@ -585,45 +651,37 @@ static void lib_override_local_group_tag_recursive(LibOverrideGroupTagData *data
if (ELEM(to_id, NULL, id_owner)) {
continue;
}
- if (!ID_IS_OVERRIDE_LIBRARY(to_id) || ID_IS_LINKED(to_id)) {
+ if (!ID_IS_OVERRIDE_LIBRARY(to_id) || (to_id->lib != id_owner->lib)) {
continue;
}
- /* Do not tag 'virtual' overrides (shape keys here, as we already rejected embedded case
- * above). */
- if (ID_IS_OVERRIDE_LIBRARY_REAL(to_id)) {
- Library *reference_lib = NULL;
- if (GS(id_owner->name) == ID_KE) {
- reference_lib = ((Key *)id_owner)->from->override_library->reference->lib;
- }
- else {
- reference_lib = id_owner->override_library->reference->lib;
- }
- if (to_id->override_library->reference->lib != reference_lib) {
- /* We do not override data-blocks from other libraries, nor do we process them. */
- continue;
- }
+ Library *reference_lib = lib_override_get(bmain, id_owner)->reference->lib;
+ ID *to_id_reference = lib_override_get(bmain, to_id)->reference;
+ if (to_id_reference->lib != reference_lib) {
+ /* We do not override data-blocks from other libraries, nor do we process them. */
+ continue;
+ }
- if (to_id->override_library->reference->tag & LIB_TAG_MISSING) {
- to_id->tag |= missing_tag;
- }
- else {
- to_id->tag |= tag;
- }
+ if (to_id_reference->tag & LIB_TAG_MISSING) {
+ to_id->tag |= missing_tag;
+ }
+ else {
+ to_id->tag |= tag;
}
/* Recursively process the dependencies. */
LibOverrideGroupTagData sub_data = *data;
sub_data.id_root = to_id;
- lib_override_local_group_tag_recursive(&sub_data);
+ lib_override_overrides_group_tag_recursive(&sub_data);
}
}
/* This will tag all override IDs of an override group defined by the given `id_root`. */
-static void lib_override_local_group_tag(LibOverrideGroupTagData *data)
+static void lib_override_overrides_group_tag(LibOverrideGroupTagData *data)
{
ID *id_root = data->id_root;
- BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root) && !ID_IS_LINKED(id_root));
+ BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root));
+ BLI_assert(data->is_override);
if ((id_root->override_library->reference->tag & LIB_TAG_MISSING)) {
id_root->tag |= data->missing_tag;
@@ -633,14 +691,17 @@ static void lib_override_local_group_tag(LibOverrideGroupTagData *data)
}
/* Tag all local overrides in id_root's group. */
- lib_override_local_group_tag_recursive(data);
+ lib_override_overrides_group_tag_recursive(data);
}
static bool lib_override_library_create_do(Main *bmain, ID *id_root)
{
BKE_main_relations_create(bmain, 0);
- LibOverrideGroupTagData data = {
- .bmain = bmain, .id_root = id_root, .tag = LIB_TAG_DOIT, .missing_tag = LIB_TAG_MISSING};
+ LibOverrideGroupTagData data = {.bmain = bmain,
+ .id_root = id_root,
+ .tag = LIB_TAG_DOIT,
+ .missing_tag = LIB_TAG_MISSING,
+ .is_override = false};
lib_override_linked_group_tag(&data);
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
@@ -648,7 +709,7 @@ static bool lib_override_library_create_do(Main *bmain, ID *id_root)
BKE_main_relations_free(bmain);
- return BKE_lib_override_library_create_from_tag(bmain);
+ return BKE_lib_override_library_create_from_tag(bmain, id_root->lib, false);
}
static void lib_override_library_create_post_process(Main *bmain,
@@ -659,6 +720,9 @@ static void lib_override_library_create_post_process(Main *bmain,
Collection *residual_storage,
const bool is_resync)
{
+ /* NOTE: We only care about local IDs here, if a linked object is not instantiated in any way we
+ * do not do anything about it. */
+
BKE_main_collection_sync(bmain);
/* We create a set of all objects referenced into the scene by its hierarchy of collections.
@@ -668,7 +732,7 @@ static void lib_override_library_create_post_process(Main *bmain,
/* Instantiating the root collection or object should never be needed in resync case, since the
* old override would be remapped to the new one. */
- if (!is_resync && id_root != NULL && id_root->newid != NULL) {
+ if (!is_resync && id_root != NULL && id_root->newid != NULL && !ID_IS_LINKED(id_root->newid)) {
switch (GS(id_root->name)) {
case ID_GR: {
Object *ob_reference = id_reference != NULL && GS(id_reference->name) == ID_OB ?
@@ -713,56 +777,58 @@ static void lib_override_library_create_post_process(Main *bmain,
Collection *default_instantiating_collection = residual_storage;
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
Object *ob_new = (Object *)ob->id.newid;
- if (ob_new != NULL) {
- BLI_assert(ob_new->id.override_library != NULL &&
- ob_new->id.override_library->reference == &ob->id);
-
- if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) {
- if (id_root != NULL && default_instantiating_collection == NULL) {
- ID *id_ref = id_root->newid != NULL ? id_root->newid : id_root;
- switch (GS(id_ref->name)) {
- case ID_GR: {
- /* Adding the object to a specific collection outside of the root overridden one is a
- * fairly bad idea (it breaks the override hierarchy concept). But there is no other
- * way to do this currently (we cannot add new collections to overridden root one,
- * this is not currently supported).
- * Since that will be fairly annoying and noisy, only do that in case the override
- * object is not part of any existing collection (i.e. its user count is 0). In
- * practice this should never happen I think. */
- if (ID_REAL_USERS(ob_new) != 0) {
- continue;
- }
- default_instantiating_collection = BKE_collection_add(
- bmain, (Collection *)id_root, "OVERRIDE_HIDDEN");
- /* Hide the collection from viewport and render. */
- default_instantiating_collection->flag |= COLLECTION_RESTRICT_VIEWPORT |
- COLLECTION_RESTRICT_RENDER;
- break;
+ if (ob_new == NULL || ob_new->id.lib != NULL) {
+ continue;
+ }
+
+ BLI_assert(ob_new->id.override_library != NULL &&
+ ob_new->id.override_library->reference == &ob->id);
+
+ if (BLI_gset_lookup(all_objects_in_scene, ob_new) == NULL) {
+ if (id_root != NULL && default_instantiating_collection == NULL) {
+ ID *id_ref = id_root->newid != NULL ? id_root->newid : id_root;
+ switch (GS(id_ref->name)) {
+ case ID_GR: {
+ /* Adding the object to a specific collection outside of the root overridden one is a
+ * fairly bad idea (it breaks the override hierarchy concept). But there is no other
+ * way to do this currently (we cannot add new collections to overridden root one,
+ * this is not currently supported).
+ * Since that will be fairly annoying and noisy, only do that in case the override
+ * object is not part of any existing collection (i.e. its user count is 0). In
+ * practice this should never happen I think. */
+ if (ID_REAL_USERS(ob_new) != 0) {
+ continue;
}
- case ID_OB: {
- /* Add the other objects to one of the collections instantiating the
- * root object, or scene's master collection if none found. */
- 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) &&
- !ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) {
- default_instantiating_collection = collection;
- }
+ default_instantiating_collection = BKE_collection_add(
+ bmain, (Collection *)id_root, "OVERRIDE_HIDDEN");
+ /* Hide the collection from viewport and render. */
+ default_instantiating_collection->flag |= COLLECTION_RESTRICT_VIEWPORT |
+ COLLECTION_RESTRICT_RENDER;
+ break;
+ }
+ case ID_OB: {
+ /* Add the other objects to one of the collections instantiating the
+ * root object, or scene's master collection if none found. */
+ 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) &&
+ !ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY(collection)) {
+ default_instantiating_collection = collection;
}
- break;
}
- default:
- BLI_assert(0);
+ break;
}
+ default:
+ BLI_assert(0);
}
- if (default_instantiating_collection == NULL) {
- default_instantiating_collection = scene->master_collection;
- }
-
- BKE_collection_object_add(bmain, default_instantiating_collection, ob_new);
- DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS);
}
+ if (default_instantiating_collection == NULL) {
+ default_instantiating_collection = scene->master_collection;
+ }
+
+ BKE_collection_object_add(bmain, default_instantiating_collection, ob_new);
+ DEG_id_tag_update_ex(bmain, &ob_new->id, ID_RECALC_TRANSFORM | ID_RECALC_BASE_FLAGS);
}
}
@@ -772,7 +838,7 @@ static void lib_override_library_create_post_process(Main *bmain,
/**
* Advanced 'smart' function to create fully functional overrides.
*
- * \note Currently it only does special things if given \a id_root is an object of collection, more
+ * \note Currently it only does special things if given \a id_root is an object or collection, more
* specific behaviors may be added in the future for other ID types.
*
* \note It will override all IDs tagged with \a LIB_TAG_DOIT, and it does not clear that tag at
@@ -782,22 +848,35 @@ static void lib_override_library_create_post_process(Main *bmain,
* \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
* override, currently.
+ * \param r_id_root_override if not NULL, the override generated for the given \a id_root.
* \return true if override was successfully created.
*/
-bool BKE_lib_override_library_create(
- Main *bmain, Scene *scene, ViewLayer *view_layer, ID *id_root, ID *id_reference)
+bool BKE_lib_override_library_create(Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ ID *id_root,
+ ID *id_reference,
+ ID **r_id_root_override)
{
+ if (r_id_root_override != NULL) {
+ *r_id_root_override = NULL;
+ }
+
const bool success = lib_override_library_create_do(bmain, id_root);
if (!success) {
return success;
}
+ if (r_id_root_override != NULL) {
+ *r_id_root_override = id_root->newid;
+ }
+
lib_override_library_create_post_process(
bmain, scene, view_layer, id_root, id_reference, NULL, false);
/* Cleanup. */
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
/* We need to rebuild some of the deleted override rules (for UI feedback purpose). */
@@ -862,7 +941,7 @@ bool BKE_lib_override_library_proxy_convert(Main *bmain,
DEG_id_tag_update(&ob_proxy->id, ID_RECALC_COPY_ON_WRITE);
- return BKE_lib_override_library_create(bmain, scene, view_layer, id_root, id_reference);
+ return BKE_lib_override_library_create(bmain, scene, view_layer, id_root, id_reference, NULL);
}
/**
@@ -882,17 +961,20 @@ bool BKE_lib_override_library_resync(Main *bmain,
ReportList *reports)
{
BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root));
- BLI_assert(!ID_IS_LINKED(id_root));
ID *id_root_reference = id_root->override_library->reference;
BKE_main_relations_create(bmain, 0);
- LibOverrideGroupTagData data = {
- .bmain = bmain, .id_root = id_root, .tag = LIB_TAG_DOIT, .missing_tag = LIB_TAG_MISSING};
- lib_override_local_group_tag(&data);
+ LibOverrideGroupTagData data = {.bmain = bmain,
+ .id_root = id_root,
+ .tag = LIB_TAG_DOIT,
+ .missing_tag = LIB_TAG_MISSING,
+ .is_override = true};
+ lib_override_overrides_group_tag(&data);
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
data.id_root = id_root_reference;
+ data.is_override = false;
lib_override_linked_group_tag(&data);
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
@@ -916,12 +998,35 @@ bool BKE_lib_override_library_resync(Main *bmain,
id->tag |= LIB_TAG_MISSING;
}
- if (id->tag & LIB_TAG_DOIT && !ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
+ if (id->tag & LIB_TAG_DOIT && (id->lib == id_root->lib) && ID_IS_OVERRIDE_LIBRARY(id)) {
/* While this should not happen in typical cases (and won't be properly supported here), user
* is free to do all kind of very bad things, including having different local overrides of a
* same linked ID in a same hierarchy. */
- if (!BLI_ghash_haskey(linkedref_to_old_override, id->override_library->reference)) {
- BLI_ghash_insert(linkedref_to_old_override, id->override_library->reference, id);
+ IDOverrideLibrary *id_override_library = lib_override_get(bmain, id);
+ ID *reference_id = id_override_library->reference;
+ if (GS(reference_id->name) != GS(id->name)) {
+ switch (GS(id->name)) {
+ case ID_KE:
+ reference_id = (ID *)BKE_key_from_id(reference_id);
+ break;
+ case ID_GR:
+ BLI_assert(GS(reference_id->name) == ID_SCE);
+ reference_id = (ID *)((Scene *)reference_id)->master_collection;
+ break;
+ case ID_NT:
+ reference_id = (ID *)ntreeFromID(id);
+ break;
+ default:
+ break;
+ }
+ }
+ BLI_assert(GS(reference_id->name) == GS(id->name));
+
+ if (!BLI_ghash_haskey(linkedref_to_old_override, reference_id)) {
+ BLI_ghash_insert(linkedref_to_old_override, reference_id, id);
+ if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
+ continue;
+ }
if ((id->override_library->reference->tag & LIB_TAG_DOIT) == 0) {
/* We have an override, but now it does not seem to be necessary to override that ID
* anymore. Check if there are some actual overrides from the user, otherwise assume
@@ -960,7 +1065,8 @@ bool BKE_lib_override_library_resync(Main *bmain,
/* Note that this call also remaps all pointers of tagged IDs from old override IDs to new
* override IDs (including within the old overrides themselves, since those are tagged too
* above). */
- const bool success = BKE_lib_override_library_create_from_tag(bmain);
+ const bool success = BKE_lib_override_library_create_from_tag(
+ bmain, id_root_reference->lib, true);
if (!success) {
return success;
@@ -969,55 +1075,104 @@ bool BKE_lib_override_library_resync(Main *bmain,
ListBase *lb;
FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
- if (id->tag & LIB_TAG_DOIT && id->newid != NULL && ID_IS_LINKED(id)) {
+ if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) {
ID *id_override_new = id->newid;
ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id);
BLI_assert((id_override_new->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0);
+ /* We need to 'move back' newly created override into its proper library (since it was
+ * duplicated from the reference ID with 'no main' option, it should currently be the same
+ * as the reference ID one). */
+ BLI_assert(/*id_override_new->lib == NULL || */ id_override_new->lib == id->lib);
+ BLI_assert(id_override_old == NULL || id_override_old->lib == id_root->lib);
+ id_override_new->lib = id_root->lib;
+ /* Remap step below will tag directly linked ones properly as needed. */
+ if (ID_IS_LINKED(id_override_new)) {
+ id_override_new->tag |= LIB_TAG_INDIRECT;
+ }
+
if (id_override_old != NULL) {
/* Swap the names between old override ID and new one. */
char id_name_buf[MAX_ID_NAME];
memcpy(id_name_buf, id_override_old->name, sizeof(id_name_buf));
memcpy(id_override_old->name, id_override_new->name, sizeof(id_override_old->name));
memcpy(id_override_new->name, id_name_buf, sizeof(id_override_new->name));
- /* Note that this is a very efficient way to keep BMain IDs ordered as expected after
- * swapping their names.
- * However, one has to be very careful with this when iterating over the listbase at the
- * same time. Here it works because we only execute this code when we are in the linked
- * IDs, which are always *after* all local ones, and we only affect local IDs. */
- BLI_listbase_swaplinks(lb, id_override_old, id_override_new);
-
- /* Remap the whole local IDs to use the new override. */
- BKE_libblock_remap(
- bmain, id_override_old, id_override_new, ID_REMAP_SKIP_INDIRECT_USAGE);
-
- /* Copy over overrides rules from old override ID to new one. */
- BLI_duplicatelist(&id_override_new->override_library->properties,
- &id_override_old->override_library->properties);
- for (IDOverrideLibraryProperty *
- op_new = id_override_new->override_library->properties.first,
- *op_old = id_override_old->override_library->properties.first;
- op_new;
- op_new = op_new->next, op_old = op_old->next) {
- lib_override_library_property_copy(op_new, op_old);
+
+ BLI_insertlinkreplace(lb, id_override_old, id_override_new);
+ id_override_old->tag |= LIB_TAG_NO_MAIN;
+ id_override_new->tag &= ~LIB_TAG_NO_MAIN;
+
+ if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new)) {
+ BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old));
+
+ /* Copy over overrides rules from old override ID to new one. */
+ BLI_duplicatelist(&id_override_new->override_library->properties,
+ &id_override_old->override_library->properties);
+ IDOverrideLibraryProperty *op_new =
+ id_override_new->override_library->properties.first;
+ IDOverrideLibraryProperty *op_old =
+ id_override_old->override_library->properties.first;
+ for (; op_new; op_new = op_new->next, op_old = op_old->next) {
+ lib_override_library_property_copy(op_new, op_old);
+ }
}
}
+ else {
+ /* Add to proper main list, ensure unique name for local ID, sort, and clear relevant
+ * tags. */
+ BKE_libblock_management_main_add(bmain, id_override_new);
+ }
}
}
FOREACH_MAIN_LISTBASE_ID_END;
}
FOREACH_MAIN_LISTBASE_END;
+ /* We need to remap old to new override usages in a separate loop, after all new overrides have
+ * been added to Main. */
+ FOREACH_MAIN_ID_BEGIN (bmain, id) {
+ if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) {
+ ID *id_override_new = id->newid;
+ ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id);
+
+ if (id_override_old != NULL) {
+ /* Remap all IDs to use the new override. */
+ BKE_libblock_remap(bmain, id_override_old, id_override_new, 0);
+ /* Remap no-main override IDs we just created too. */
+ GHashIterator linkedref_to_old_override_iter;
+ GHASH_ITER (linkedref_to_old_override_iter, linkedref_to_old_override) {
+ ID *id_override_old_iter = BLI_ghashIterator_getValue(&linkedref_to_old_override_iter);
+ if (id_override_old_iter->tag & LIB_TAG_NO_MAIN) {
+ BKE_libblock_relink_ex(bmain,
+ id_override_old_iter,
+ id_override_old,
+ id_override_new,
+ ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE);
+ }
+ }
+ }
+ }
+ }
+ FOREACH_MAIN_ID_END;
+
+ BKE_main_collection_sync(bmain);
+
/* We need to apply override rules in a separate loop, after all ID pointers have been properly
* remapped, and all new local override IDs have gotten their proper original names, otherwise
* override operations based on those ID names would fail. */
FOREACH_MAIN_ID_BEGIN (bmain, id) {
- if (id->tag & LIB_TAG_DOIT && id->newid != NULL && ID_IS_LINKED(id)) {
+ if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) {
ID *id_override_new = id->newid;
+ if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_override_new)) {
+ continue;
+ }
ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id);
- if (id_override_old != NULL) {
+ if (id_override_old == NULL) {
+ continue;
+ }
+ if (ID_IS_OVERRIDE_LIBRARY_REAL(id_override_old)) {
/* Apply rules on new override ID using old one as 'source' data. */
/* Note that since we already remapped ID pointers in old override IDs to new ones, we
* can also apply ID pointer override rules safely here. */
@@ -1051,30 +1206,44 @@ bool BKE_lib_override_library_resync(Main *bmain,
RNA_OVERRIDE_APPLY_FLAG_IGNORE_ID_POINTERS :
RNA_OVERRIDE_APPLY_FLAG_NOP);
}
+
+ /* Once overrides have been properly 'transferred' from old to new ID, we can clear ID usages
+ * of the old one.
+ * This is necessary in case said old ID is not in Main anymore. */
+ BKE_libblock_relink_ex(bmain,
+ id_override_old,
+ NULL,
+ NULL,
+ ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE);
+ id_override_old->tag |= LIB_TAG_NO_USER_REFCOUNT;
}
}
FOREACH_MAIN_ID_END;
/* Delete old override IDs.
- * Note that we have to use tagged group deletion here, since ID deletion also uses LIB_TAG_DOIT.
- * This improves performances anyway, so everything is fine. */
+ * Note that we have to use tagged group deletion here, since ID deletion also uses
+ * LIB_TAG_DOIT. This improves performances anyway, so everything is fine. */
int user_edited_overrides_deletion_count = 0;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
if (id->tag & LIB_TAG_DOIT) {
- /* Note that this works because linked IDs are always after local ones (including overrides),
- * so we will only ever tag an old override ID after we have already checked it in this loop,
- * hence we cannot untag it later. */
- if (id->newid != NULL && ID_IS_LINKED(id)) {
+ /* Note that this works because linked IDs are always after local ones (including
+ * overrides), so we will only ever tag an old override ID after we have already checked it
+ * in this loop, hence we cannot untag it later. */
+ if (id->newid != NULL && id->lib == id_root_reference->lib) {
ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id);
if (id_override_old != NULL) {
id->newid->tag &= ~LIB_TAG_DOIT;
id_override_old->tag |= LIB_TAG_DOIT;
+ if (id_override_old->tag & LIB_TAG_NO_MAIN) {
+ BKE_id_free(bmain, id_override_old);
+ }
}
}
id->tag &= ~LIB_TAG_DOIT;
}
- /* Also deal with old overrides that went missing in new linked data. */
+ /* Also deal with old overrides that went missing in new linked data - only for real local
+ * overrides for now, not those who are linked. */
else if (id->tag & LIB_TAG_MISSING && !ID_IS_LINKED(id)) {
BLI_assert(ID_IS_OVERRIDE_LIBRARY(id));
if (!BKE_lib_override_library_is_user_edited(id)) {
@@ -1105,6 +1274,10 @@ bool BKE_lib_override_library_resync(Main *bmain,
}
}
FOREACH_MAIN_ID_END;
+
+ /* Cleanup, many pointers in this GHash are already invalid now. */
+ BLI_ghash_free(linkedref_to_old_override, NULL, NULL);
+
BKE_id_multi_tagged_delete(bmain);
/* At this point, `id_root` has very likely been deleted, we need to update it to its new
@@ -1137,50 +1310,136 @@ bool BKE_lib_override_library_resync(Main *bmain,
}
/* Cleanup. */
- BLI_ghash_free(linkedref_to_old_override, NULL, NULL);
-
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); /* That one should not be needed in fact. */
return success;
}
-/**
- * Detect and handle required resync of overrides data, when relations between reference linked IDs
- * have changed.
+/* Also tag ancestors overrides for resync.
*
- * This is a fairly complex and costly operation, typically it should be called after
- * #BKE_lib_override_library_main_update, which would already detect and tag a lot of cases.
+ * WARNING: Expects `bmain` to have valid relation data.
*
- * This function will first detect the remaining cases requiring a resync (namely, either when an
- * existing linked ID that did not require to be overridden before now would be, or when new IDs
- * are added to the hierarchy).
+ * NOTE: Related to `lib_override_library_main_resync_find_root_recurse` below.
*
- * Then it will handle the resync of necessary IDs (through calls to
- * #BKE_lib_override_library_resync).
+ * TODO: This is a sub-optimal, simple solution. At some point, we should rather find a way to
+ * resync a set of 'sub-roots' overrides, instead of having to 'go back' to the real root and
+ * resync the whole hierarchy.
*/
-void BKE_lib_override_library_main_resync(Main *bmain,
- Scene *scene,
- ViewLayer *view_layer,
- ReportList *reports)
+static void lib_override_resync_tagging_finalize_recurse(Main *bmain,
+ ID *id,
+ const int library_indirect_level)
{
- /* We use a specific collection to gather/store all 'orphaned' override collections and objects
- * generated by re-sync-process. This avoids putting them in scene's master collection. */
-#define OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME "OVERRIDE_RESYNC_LEFTOVERS"
- Collection *override_resync_residual_storage = BLI_findstring(
- &bmain->collections, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME, offsetof(ID, name) + 2);
- if (override_resync_residual_storage != NULL &&
- override_resync_residual_storage->id.lib != NULL) {
- override_resync_residual_storage = NULL;
+ if (id->lib != NULL && id->lib->temp_index > library_indirect_level) {
+ CLOG_ERROR(
+ &LOG,
+ "While processing indirect level %d, ID %s from lib %s of indirect level %d detected "
+ "as needing resync.",
+ library_indirect_level,
+ id->name,
+ id->lib->filepath,
+ id->lib->temp_index);
}
- if (override_resync_residual_storage == NULL) {
- override_resync_residual_storage = BKE_collection_add(
- bmain, scene->master_collection, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME);
- /* Hide the collection from viewport and render. */
- override_resync_residual_storage->flag |= COLLECTION_RESTRICT_VIEWPORT |
- COLLECTION_RESTRICT_RENDER;
+
+ MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id);
+ BLI_assert(entry != NULL);
+
+ if (entry->tags & MAINIDRELATIONS_ENTRY_TAGS_PROCESSED) {
+ /* This ID has already been processed. */
+ return;
+ }
+ /* This way we won't process again that ID, should we encounter it again through another
+ * relationship hierarchy. */
+ entry->tags |= MAINIDRELATIONS_ENTRY_TAGS_PROCESSED;
+
+ for (MainIDRelationsEntryItem *entry_item = entry->from_ids; entry_item != NULL;
+ entry_item = entry_item->next) {
+ if (entry_item->usage_flag &
+ (IDWALK_CB_OVERRIDE_LIBRARY_NOT_OVERRIDABLE | IDWALK_CB_LOOPBACK)) {
+ continue;
+ }
+ ID *id_from = entry_item->id_pointer.from;
+
+ /* Case where this ID pointer was to a linked ID, that now needs to be overridden. */
+ if (id_from != id && ID_IS_OVERRIDE_LIBRARY_REAL(id_from) && id_from->lib == id->lib) {
+ id_from->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
+ CLOG_INFO(&LOG,
+ 4,
+ "ID %s (%p) now tagged as needing resync because they use %s (%p) that needs to "
+ "be overridden",
+ id_from->name,
+ id_from->lib,
+ id->name,
+ id->lib);
+ lib_override_resync_tagging_finalize_recurse(bmain, id_from, library_indirect_level);
+ }
+ }
+}
+
+/* Ensures parent collection (or objects) in the same override group are also tagged for resync.
+ *
+ * This is needed since otherwise, some (new) ID added in one sub-collection might be used in
+ * another unrelated sub-collection, if 'root' collection is not resynced separated resync of those
+ * sub-collections would be unaware that this is the same ID, and would re-generate several
+ * overrides for it.
+ *
+ * NOTE: Related to `lib_override_resync_tagging_finalize` above.
+ */
+static ID *lib_override_library_main_resync_find_root_recurse(ID *id, int *level)
+{
+ (*level)++;
+ ID *return_id = id;
+
+ switch (GS(id->name)) {
+ case ID_GR: {
+ /* Find the highest valid collection in the parenting hierarchy.
+ * Note that in practice, in any decent common case there is only one well defined root
+ * collection anyway. */
+ int max_level = *level;
+ Collection *collection = (Collection *)id;
+ LISTBASE_FOREACH (CollectionParent *, collection_parent_iter, &collection->parents) {
+ Collection *collection_parent = collection_parent_iter->collection;
+ if (ID_IS_OVERRIDE_LIBRARY_REAL(collection_parent) &&
+ collection_parent->id.lib == id->lib) {
+ int tmp_level = *level;
+ ID *tmp_id = lib_override_library_main_resync_find_root_recurse(&collection_parent->id,
+ &tmp_level);
+ if (tmp_level > max_level) {
+ max_level = tmp_level;
+ return_id = tmp_id;
+ }
+ }
+ }
+ break;
+ }
+ case ID_OB: {
+ Object *object = (Object *)id;
+ if (object->parent != NULL && ID_IS_OVERRIDE_LIBRARY_REAL(object->parent) &&
+ object->parent->id.lib == id->lib) {
+ return_id = lib_override_library_main_resync_find_root_recurse(&object->parent->id, level);
+ }
+ break;
+ }
+ default:
+ break;
}
+ return return_id;
+}
+
+/* Ensure resync of all overrides at one level of indirect usage.
+ *
+ * We need to handle each level independently, since an override at level n may be affected by
+ * other overrides from level n + 1 etc. (i.e. from linked overrides it may use).
+ */
+static void lib_override_library_main_resync_on_library_indirect_level(
+ Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ Collection *override_resync_residual_storage,
+ const int library_indirect_level,
+ ReportList *reports)
+{
BKE_main_relations_create(bmain, 0);
BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false);
@@ -1192,7 +1451,7 @@ void BKE_lib_override_library_main_resync(Main *bmain,
* those used by current existing overrides. */
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
- if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) {
+ if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
continue;
}
if (id->tag & (LIB_TAG_DOIT | LIB_TAG_MISSING)) {
@@ -1203,7 +1462,8 @@ void BKE_lib_override_library_main_resync(Main *bmain,
LibOverrideGroupTagData data = {.bmain = bmain,
.id_root = id->override_library->reference,
.tag = LIB_TAG_DOIT,
- .missing_tag = LIB_TAG_MISSING};
+ .missing_tag = LIB_TAG_MISSING,
+ .is_override = false};
lib_override_linked_group_tag(&data);
BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
lib_override_hierarchy_dependencies_recursive_tag(&data);
@@ -1213,13 +1473,15 @@ void BKE_lib_override_library_main_resync(Main *bmain,
/* Now check existing overrides, those needing resync will be the one either already tagged as
* such, or the one using linked data that is now tagged as needing override. */
+ BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false);
FOREACH_MAIN_ID_BEGIN (bmain, id) {
- if (!ID_IS_OVERRIDE_LIBRARY_REAL(id) || ID_IS_LINKED(id)) {
+ if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
continue;
}
if (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) {
- CLOG_INFO(&LOG, 4, "ID %s was already tagged as needing resync", id->name);
+ CLOG_INFO(&LOG, 4, "ID %s (%p) was already tagged as needing resync", id->name, id->lib);
+ lib_override_resync_tagging_finalize_recurse(bmain, id, library_indirect_level);
continue;
}
@@ -1234,14 +1496,17 @@ void BKE_lib_override_library_main_resync(Main *bmain,
ID *id_to = *entry_item->id_pointer.to;
/* Case where this ID pointer was to a linked ID, that now needs to be overridden. */
- if (ID_IS_LINKED(id_to) && (id_to->tag & LIB_TAG_DOIT) != 0) {
+ if (ID_IS_LINKED(id_to) && (id_to->lib != id->lib) && (id_to->tag & LIB_TAG_DOIT) != 0) {
id->tag |= LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
CLOG_INFO(&LOG,
3,
- "ID %s now tagged as needing resync because they use linked %s that now needs "
- "to be overridden",
+ "ID %s (%p) now tagged as needing resync because they use linked %s (%p) that "
+ "now needs to be overridden",
id->name,
- id_to->name);
+ id->lib,
+ id_to->name,
+ id_to->lib);
+ lib_override_resync_tagging_finalize_recurse(bmain, id, library_indirect_level);
break;
}
}
@@ -1256,27 +1521,29 @@ void BKE_lib_override_library_main_resync(Main *bmain,
* `FOREACH_MAIN_ID_BEGIN/END` here, and need special multi-loop processing. */
bool do_continue = true;
while (do_continue) {
- ListBase *lb;
do_continue = false;
+ ListBase *lb;
FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
- if ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0) {
- continue;
- }
- BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id));
- if (ID_IS_LINKED(id)) {
+ if ((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0 ||
+ (ID_IS_LINKED(id) && id->lib->temp_index < library_indirect_level) ||
+ (!ID_IS_LINKED(id) && library_indirect_level != 0)) {
continue;
}
- do_continue = true;
+ int level = 0;
/* In complex non-supported cases, with several different override hierarchies sharing
* relations between each-other, we may end up not actually updating/replacing the given
* root id (see e.g. pro/shots/110_rextoria/110_0150_A/110_0150_A.anim.blend of sprites
* project repository, r2687).
* This can lead to infinite loop here, at least avoid this. */
id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
+ id = lib_override_library_main_resync_find_root_recurse(id, &level);
+ id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC;
+ BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id));
+ do_continue = true;
- CLOG_INFO(&LOG, 2, "Resyncing %s...", id->name);
+ CLOG_INFO(&LOG, 2, "Resyncing %s (%p)...", id->name, id->lib);
const bool success = BKE_lib_override_library_resync(
bmain, scene, view_layer, id, override_resync_residual_storage, false, false, reports);
CLOG_INFO(&LOG, 2, "\tSuccess: %d", success);
@@ -1289,6 +1556,113 @@ void BKE_lib_override_library_main_resync(Main *bmain,
}
FOREACH_MAIN_LISTBASE_END;
}
+}
+
+static int lib_override_sort_libraries_func(LibraryIDLinkCallbackData *cb_data)
+{
+ if (cb_data->cb_flag & IDWALK_CB_LOOPBACK) {
+ return IDWALK_RET_NOP;
+ }
+ ID *id_owner = cb_data->id_owner;
+ ID *id = *cb_data->id_pointer;
+ if (id != NULL && ID_IS_LINKED(id) && id->lib != id_owner->lib) {
+ const int owner_library_indirect_level = id_owner->lib != NULL ? id_owner->lib->temp_index : 0;
+ if (owner_library_indirect_level > 10000) {
+ CLOG_ERROR(
+ &LOG,
+ "Levels of indirect usages of libraries is way too high, skipping further building "
+ "loops (Involves at least '%s' and '%s')",
+ id_owner->lib->filepath,
+ id->lib->filepath);
+ BLI_assert(0);
+ return IDWALK_RET_NOP;
+ }
+
+ if (owner_library_indirect_level >= id->lib->temp_index) {
+ id->lib->temp_index = owner_library_indirect_level + 1;
+ *(bool *)cb_data->user_data = true;
+ }
+ }
+ return IDWALK_RET_NOP;
+}
+
+/** Define the `temp_index` of libraries from their highest level of indirect usage.
+ *
+ * E.g. if lib_a uses lib_b, lib_c and lib_d, and lib_b also uses lib_d, then lib_a has an index of
+ * 1, lib_b and lib_c an index of 2, and lib_d an index of 3. */
+static int lib_override_libraries_index_define(Main *bmain)
+{
+ LISTBASE_FOREACH (Library *, library, &bmain->libraries) {
+ /* index 0 is reserved for local data. */
+ library->temp_index = 1;
+ }
+ bool do_continue = true;
+ while (do_continue) {
+ do_continue = false;
+ ID *id;
+ FOREACH_MAIN_ID_BEGIN (bmain, id) {
+ BKE_library_foreach_ID_link(
+ bmain, id, lib_override_sort_libraries_func, &do_continue, IDWALK_READONLY);
+ }
+ FOREACH_MAIN_ID_END;
+ }
+
+ int library_indirect_level_max = 0;
+ LISTBASE_FOREACH (Library *, library, &bmain->libraries) {
+ if (library->temp_index > library_indirect_level_max) {
+ library_indirect_level_max = library->temp_index;
+ }
+ }
+ return library_indirect_level_max;
+}
+
+/**
+ * Detect and handle required resync of overrides data, when relations between reference linked IDs
+ * have changed.
+ *
+ * This is a fairly complex and costly operation, typically it should be called after
+ * #BKE_lib_override_library_main_update, which would already detect and tag a lot of cases.
+ *
+ * This function will first detect the remaining cases requiring a resync (namely, either when an
+ * existing linked ID that did not require to be overridden before now would be, or when new IDs
+ * are added to the hierarchy).
+ *
+ * Then it will handle the resync of necessary IDs (through calls to
+ * #BKE_lib_override_library_resync).
+ */
+void BKE_lib_override_library_main_resync(Main *bmain,
+ Scene *scene,
+ ViewLayer *view_layer,
+ ReportList *reports)
+{
+ /* We use a specific collection to gather/store all 'orphaned' override collections and objects
+ * generated by re-sync-process. This avoids putting them in scene's master collection. */
+#define OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME "OVERRIDE_RESYNC_LEFTOVERS"
+ Collection *override_resync_residual_storage = BLI_findstring(
+ &bmain->collections, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME, offsetof(ID, name) + 2);
+ if (override_resync_residual_storage != NULL &&
+ override_resync_residual_storage->id.lib != NULL) {
+ override_resync_residual_storage = NULL;
+ }
+ if (override_resync_residual_storage == NULL) {
+ override_resync_residual_storage = BKE_collection_add(
+ bmain, scene->master_collection, OVERRIDE_RESYNC_RESIDUAL_STORAGE_NAME);
+ /* Hide the collection from viewport and render. */
+ override_resync_residual_storage->flag |= COLLECTION_RESTRICT_VIEWPORT |
+ COLLECTION_RESTRICT_RENDER;
+ }
+
+ int library_indirect_level = lib_override_libraries_index_define(bmain);
+ while (library_indirect_level >= 0) {
+ /* Update overrides from each indirect level separately. */
+ lib_override_library_main_resync_on_library_indirect_level(bmain,
+ scene,
+ view_layer,
+ override_resync_residual_storage,
+ library_indirect_level,
+ reports);
+ library_indirect_level--;
+ }
/* Essentially ensures that potentially new overrides of new objects will be instantiated. */
lib_override_library_create_post_process(
@@ -1313,9 +1687,12 @@ void BKE_lib_override_library_delete(Main *bmain, ID *id_root)
/* Tag all library overrides in the chains of dependencies from the given root one. */
BKE_main_relations_create(bmain, 0);
- LibOverrideGroupTagData data = {
- .bmain = bmain, .id_root = id_root, .tag = LIB_TAG_DOIT, .missing_tag = LIB_TAG_MISSING};
- lib_override_local_group_tag(&data);
+ LibOverrideGroupTagData data = {.bmain = bmain,
+ .id_root = id_root,
+ .tag = LIB_TAG_DOIT,
+ .missing_tag = LIB_TAG_MISSING,
+ .is_override = true};
+ lib_override_overrides_group_tag(&data);
BKE_main_relations_free(bmain);
@@ -1961,7 +2338,7 @@ bool BKE_lib_override_library_main_operations_create(Main *bmain, const bool for
TaskPool *task_pool = BLI_task_pool_create(&create_pool_data, TASK_PRIORITY_HIGH);
FOREACH_MAIN_ID_BEGIN (bmain, id) {
- if (ID_IS_OVERRIDE_LIBRARY_REAL(id) &&
+ if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY_REAL(id) &&
(force_auto || (id->tag & LIB_TAG_OVERRIDE_LIBRARY_AUTOREFRESH))) {
/* Usual issue with pose, it's quiet rare but sometimes they may not be up to date when this
* function is called. */
diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c
index 3281783d81a..b748061ef8a 100644
--- a/source/blender/blenkernel/intern/lib_query.c
+++ b/source/blender/blenkernel/intern/lib_query.c
@@ -244,9 +244,10 @@ static void library_foreach_ID_link(Main *bmain,
* (the node tree), but re-use those generated for the 'owner' ID (the material). */
if (inherit_data == NULL) {
data.cb_flag = ID_IS_LINKED(id) ? IDWALK_CB_INDIRECT_USAGE : 0;
- /* When an ID is not in Main database, it should never refcount IDs it is using.
- * Exceptions: NodeTrees (yeah!) directly used by Materials. */
- data.cb_flag_clear = (id->tag & LIB_TAG_NO_MAIN) ? IDWALK_CB_USER | IDWALK_CB_USER_ONE : 0;
+ /* When an ID is defined as not refcounting its ID usages, it should never do it. */
+ data.cb_flag_clear = (id->tag & LIB_TAG_NO_USER_REFCOUNT) ?
+ IDWALK_CB_USER | IDWALK_CB_USER_ONE :
+ 0;
}
else {
data.cb_flag = inherit_data->cb_flag;
diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c
index b32b97dc250..2641208897e 100644
--- a/source/blender/blenkernel/intern/lib_remap.c
+++ b/source/blender/blenkernel/intern/lib_remap.c
@@ -137,6 +137,7 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data)
(id_remap_data->flag & ID_REMAP_FORCE_NEVER_NULL_USAGE) == 0);
const bool skip_reference = (id_remap_data->flag & ID_REMAP_SKIP_OVERRIDE_LIBRARY) != 0;
const bool skip_never_null = (id_remap_data->flag & ID_REMAP_SKIP_NEVER_NULL_USAGE) != 0;
+ const bool force_user_refcount = (id_remap_data->flag & ID_REMAP_FORCE_USER_REFCOUNT) != 0;
#ifdef DEBUG_PRINT
printf(
@@ -203,16 +204,16 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data)
}
}
if (cb_flag & IDWALK_CB_USER) {
- /* NOTE: We don't user-count IDs which are not in the main database.
+ /* NOTE: by default we don't user-count IDs which are not in the main database.
* This is because in certain conditions we can have data-blocks in
* the main which are referencing data-blocks outside of it.
* For example, BKE_mesh_new_from_object() called on an evaluated
* object will cause such situation.
*/
- if ((old_id->tag & LIB_TAG_NO_MAIN) == 0) {
+ if (force_user_refcount || (old_id->tag & LIB_TAG_NO_MAIN) == 0) {
id_us_min(old_id);
}
- if (new_id != NULL && (new_id->tag & LIB_TAG_NO_MAIN) == 0) {
+ if (new_id != NULL && (force_user_refcount || (new_id->tag & LIB_TAG_NO_MAIN) == 0)) {
/* We do not want to handle LIB_TAG_INDIRECT/LIB_TAG_EXTERN here. */
new_id->us++;
}
diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c
index 73b64e6efb3..468f735257a 100644
--- a/source/blender/blenkernel/intern/material.c
+++ b/source/blender/blenkernel/intern/material.c
@@ -770,6 +770,7 @@ int BKE_object_material_count_eval(Object *ob)
void BKE_id_material_eval_assign(ID *id, int slot, Material *material)
{
+ BLI_assert(slot >= 1);
Material ***materials_ptr = BKE_id_material_array_p(id);
short *len_ptr = BKE_id_material_len_p(id);
if (ELEM(NULL, materials_ptr, len_ptr)) {
@@ -793,6 +794,21 @@ void BKE_id_material_eval_assign(ID *id, int slot, Material *material)
(*materials_ptr)[slot_index] = material;
}
+/**
+ * Add an empty material slot if the id has no material slots. This material slot allows the
+ * material to be overwritten by object-linked materials.
+ */
+void BKE_id_material_eval_ensure_default_slot(ID *id)
+{
+ short *len_ptr = BKE_id_material_len_p(id);
+ if (len_ptr == NULL) {
+ return;
+ }
+ if (*len_ptr == 0) {
+ BKE_id_material_eval_assign(id, 1, NULL);
+ }
+}
+
Material *BKE_gpencil_material(Object *ob, short act)
{
Material *ma = BKE_object_material_get(ob, act);
@@ -1497,16 +1513,16 @@ void BKE_texpaint_slots_refresh_object(Scene *scene, struct Object *ob)
}
struct FindTexPaintNodeData {
- bNode *node;
- short iter_index;
- short index;
+ Image *ima;
+ bNode *r_node;
};
static bool texpaint_slot_node_find_cb(bNode *node, void *userdata)
{
struct FindTexPaintNodeData *find_data = userdata;
- if (find_data->iter_index++ == find_data->index) {
- find_data->node = node;
+ Image *ima = (Image *)node->id;
+ if (find_data->ima == ima) {
+ find_data->r_node = node;
return false;
}
@@ -1515,10 +1531,10 @@ static bool texpaint_slot_node_find_cb(bNode *node, void *userdata)
bNode *BKE_texpaint_slot_material_find_node(Material *ma, short texpaint_slot)
{
- struct FindTexPaintNodeData find_data = {NULL, 0, texpaint_slot};
+ struct FindTexPaintNodeData find_data = {ma->texpaintslot[texpaint_slot].ima, NULL};
ntree_foreach_texnode_recursive(ma->nodetree, texpaint_slot_node_find_cb, &find_data);
- return find_data.node;
+ return find_data.r_node;
}
/* r_col = current value, col = new value, (fac == 0) is no change */
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index 643dc58af18..2a509fd7df4 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -513,7 +513,7 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
if (node->storage) {
/* could be handlerized at some point, now only 1 exception still */
- if ((ntree->type == NTREE_SHADER) &&
+ if ((ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY)) &&
ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) {
BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage);
}
@@ -5050,8 +5050,10 @@ static void registerGeometryNodes()
register_node_type_geo_boolean();
register_node_type_geo_bounding_box();
register_node_type_geo_collection_info();
+ register_node_type_geo_curve_length();
register_node_type_geo_curve_to_mesh();
register_node_type_geo_curve_resample();
+ register_node_type_geo_delete_geometry();
register_node_type_geo_edge_split();
register_node_type_geo_input_material();
register_node_type_geo_is_viewport();
@@ -5066,6 +5068,7 @@ static void registerGeometryNodes()
register_node_type_geo_mesh_primitive_ico_sphere();
register_node_type_geo_mesh_primitive_line();
register_node_type_geo_mesh_primitive_uv_sphere();
+ register_node_type_geo_mesh_to_curve();
register_node_type_geo_object_info();
register_node_type_geo_point_distribute();
register_node_type_geo_point_instance();
diff --git a/source/blender/blenkernel/intern/node_ui_storage.cc b/source/blender/blenkernel/intern/node_ui_storage.cc
index 7a28fd295fb..e5e9f00c7c3 100644
--- a/source/blender/blenkernel/intern/node_ui_storage.cc
+++ b/source/blender/blenkernel/intern/node_ui_storage.cc
@@ -39,7 +39,7 @@ using blender::Vector;
* bNodeTree struct in DNA. This could change if the node tree had a runtime struct. */
static std::mutex global_ui_storage_mutex;
-static void ui_storage_ensure(bNodeTree &ntree)
+static NodeTreeUIStorage &ui_storage_ensure(bNodeTree &ntree)
{
/* As an optimization, only acquire a lock if the UI storage doesn't exist,
* because it only needs to be allocated once for every node tree. */
@@ -50,6 +50,7 @@ static void ui_storage_ensure(bNodeTree &ntree)
ntree.ui_storage = new NodeTreeUIStorage();
}
}
+ return *ntree.ui_storage;
}
const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C,
@@ -90,7 +91,7 @@ void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
{
NodeTreeUIStorage *ui_storage = ntree.ui_storage;
if (ui_storage != nullptr) {
- std::lock_guard<std::mutex> lock(ui_storage->context_map_mutex);
+ std::lock_guard<std::mutex> lock(ui_storage->mutex);
ui_storage->context_map.remove(context);
}
}
@@ -126,20 +127,14 @@ static void node_error_message_log(bNodeTree &ntree,
}
}
-static NodeUIStorage &node_ui_storage_ensure(bNodeTree &ntree,
+static NodeUIStorage &node_ui_storage_ensure(NodeTreeUIStorage &locked_ui_storage,
const NodeTreeEvaluationContext &context,
const bNode &node)
{
- ui_storage_ensure(ntree);
- NodeTreeUIStorage &ui_storage = *ntree.ui_storage;
-
- std::lock_guard<std::mutex> lock(ui_storage.context_map_mutex);
Map<std::string, NodeUIStorage> &node_tree_ui_storage =
- ui_storage.context_map.lookup_or_add_default(context);
-
+ locked_ui_storage.context_map.lookup_or_add_default(context);
NodeUIStorage &node_ui_storage = node_tree_ui_storage.lookup_or_add_default_as(
StringRef(node.name));
-
return node_ui_storage;
}
@@ -149,10 +144,12 @@ void BKE_nodetree_error_message_add(bNodeTree &ntree,
const NodeWarningType type,
std::string message)
{
+ NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree);
+ std::lock_guard lock{ui_storage.mutex};
+
node_error_message_log(ntree, node, message, type);
- NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node);
- std::lock_guard lock{node_ui_storage.mutex};
+ NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node);
node_ui_storage.warnings.append({type, std::move(message)});
}
@@ -163,8 +160,10 @@ void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
const AttributeDomain domain,
const CustomDataType data_type)
{
- NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ntree, context, node);
- std::lock_guard lock{node_ui_storage.mutex};
+ NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree);
+ std::lock_guard lock{ui_storage.mutex};
+
+ NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node);
node_ui_storage.attribute_hints.add_as(
AvailableAttributeInfo{attribute_name, domain, data_type});
}
diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c
index 034af924ab1..b73f6a5b78c 100644
--- a/source/blender/blenkernel/intern/object.c
+++ b/source/blender/blenkernel/intern/object.c
@@ -2369,7 +2369,7 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int f
BLI_listbase_clear(&psysn->pathcachebufs);
BLI_listbase_clear(&psysn->childcachebufs);
- if (flag & LIB_ID_CREATE_NO_MAIN) {
+ if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
/* XXX Disabled, fails when evaluating depsgraph after copying ID with no main for preview
* creation. */
// BLI_assert((psys->flag & PSYS_SHARED_CACHES) == 0);
@@ -2622,8 +2622,8 @@ void BKE_object_transform_copy(Object *ob_tar, const Object *ob_src)
*
* \note This function does not do any remapping to new IDs, caller must do it
* (\a #BKE_libblock_relink_to_newid()).
- * \note Caller MUST free \a newid pointers itself (#BKE_main_id_clear_newpoins()) and call updates
- * of DEG too (#DAG_relations_tag_update()).
+ * \note Caller MUST free \a newid pointers itself (#BKE_main_id_newptr_and_tag_clear()) and call
+ * updates of DEG too (#DAG_relations_tag_update()).
*/
Object *BKE_object_duplicate(Main *bmain,
Object *ob,
@@ -2633,8 +2633,7 @@ Object *BKE_object_duplicate(Main *bmain,
const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0;
if (!is_subprocess) {
- BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
/* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate
* all expected linked data. */
if (ID_IS_LINKED(ob)) {
@@ -2773,8 +2772,7 @@ Object *BKE_object_duplicate(Main *bmain,
#endif
/* Cleanup. */
- BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
}
if (obn->type == OB_ARMATURE) {
diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c
index c727a144c87..ce4be411c9a 100644
--- a/source/blender/blenkernel/intern/particle_system.c
+++ b/source/blender/blenkernel/intern/particle_system.c
@@ -4912,9 +4912,12 @@ void particle_system_update(struct Depsgraph *depsgraph,
sim.psmd->flag |= eParticleSystemFlag_Pars;
}
+ ParticleTexture ptex;
+
LOOP_EXISTING_PARTICLES
{
- pa->size = part->size;
+ psys_get_texture(&sim, pa, &ptex, PAMAP_SIZE, cfra);
+ pa->size = part->size * ptex.size;
if (part->randsize > 0.0f) {
pa->size *= 1.0f - part->randsize * psys_frand(psys, p + 1);
}
diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c
index a4ab64a8a02..a58db89ad0c 100644
--- a/source/blender/blenkernel/intern/scene.c
+++ b/source/blender/blenkernel/intern/scene.c
@@ -1985,8 +1985,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
const bool is_subprocess = false;
if (!is_subprocess) {
- BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
/* In case root duplicated ID is linked, assume we want to get a local copy of it and
* duplicate all expected linked data. */
if (ID_IS_LINKED(sce)) {
@@ -2027,8 +2026,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
#endif
/* Cleanup. */
- BKE_main_id_tag_all(bmain, LIB_TAG_NEW, false);
- BKE_main_id_clear_newpoins(bmain);
+ BKE_main_id_newptr_and_tag_clear(bmain);
BKE_main_collection_sync(bmain);
}
diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc
index 4be3ba8576e..3c6cf2c78cf 100644
--- a/source/blender/blenkernel/intern/spline_bezier.cc
+++ b/source/blender/blenkernel/intern/spline_bezier.cc
@@ -31,6 +31,14 @@ SplinePtr BezierSpline::copy() const
return std::make_unique<BezierSpline>(*this);
}
+SplinePtr BezierSpline::copy_settings() const
+{
+ std::unique_ptr<BezierSpline> copy = std::make_unique<BezierSpline>();
+ copy_base_settings(*this, *copy);
+ copy->resolution_ = resolution_;
+ return copy;
+}
+
int BezierSpline::size() const
{
const int size = positions_.size();
@@ -59,18 +67,18 @@ void BezierSpline::set_resolution(const int value)
* \warning Call #reallocate on the spline's attributes after adding all points.
*/
void BezierSpline::add_point(const float3 position,
- const HandleType handle_type_start,
- const float3 handle_position_start,
- const HandleType handle_type_end,
- const float3 handle_position_end,
+ const HandleType handle_type_left,
+ const float3 handle_position_left,
+ const HandleType handle_type_right,
+ const float3 handle_position_right,
const float radius,
const float tilt)
{
- handle_types_left_.append(handle_type_start);
- handle_positions_left_.append(handle_position_start);
+ handle_types_left_.append(handle_type_left);
+ handle_positions_left_.append(handle_position_left);
positions_.append(position);
- handle_types_right_.append(handle_type_end);
- handle_positions_right_.append(handle_position_end);
+ handle_types_right_.append(handle_type_right);
+ handle_positions_right_.append(handle_position_right);
radii_.append(radius);
tilts_.append(tilt);
this->mark_cache_invalid();
diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc
index ae691d26cdb..cae206341a0 100644
--- a/source/blender/blenkernel/intern/spline_nurbs.cc
+++ b/source/blender/blenkernel/intern/spline_nurbs.cc
@@ -32,6 +32,16 @@ SplinePtr NURBSpline::copy() const
return std::make_unique<NURBSpline>(*this);
}
+SplinePtr NURBSpline::copy_settings() const
+{
+ std::unique_ptr<NURBSpline> copy = std::make_unique<NURBSpline>();
+ copy_base_settings(*this, *copy);
+ copy->knots_mode = knots_mode;
+ copy->resolution_ = resolution_;
+ copy->order_ = order_;
+ return copy;
+}
+
int NURBSpline::size() const
{
const int size = positions_.size();
diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc
index 5c33b0052fc..5f8e81d5ad0 100644
--- a/source/blender/blenkernel/intern/spline_poly.cc
+++ b/source/blender/blenkernel/intern/spline_poly.cc
@@ -28,6 +28,13 @@ SplinePtr PolySpline::copy() const
return std::make_unique<PolySpline>(*this);
}
+SplinePtr PolySpline::copy_settings() const
+{
+ std::unique_ptr<PolySpline> copy = std::make_unique<PolySpline>();
+ copy_base_settings(*this, *copy);
+ return copy;
+}
+
int PolySpline::size() const
{
const int size = positions_.size();
diff --git a/source/blender/blenkernel/intern/unit.c b/source/blender/blenkernel/intern/unit.c
index 9ae1c754846..3612a26315c 100644
--- a/source/blender/blenkernel/intern/unit.c
+++ b/source/blender/blenkernel/intern/unit.c
@@ -943,7 +943,7 @@ static int unit_scale_str(char *str,
/* Add the addition sign, the bias, and the close parenthesis after the value. */
int value_end_ofs = find_end_of_value_chars(str, len_max, prev_op_ofs + 2);
- int len_bias_num = BLI_snprintf(str_tmp, TEMP_STR_SIZE, "+%.9g)", unit->bias);
+ int len_bias_num = BLI_snprintf_rlen(str_tmp, TEMP_STR_SIZE, "+%.9g)", unit->bias);
if (value_end_ofs + len_bias_num < len_max) {
memmove(str + value_end_ofs + len_bias_num, str + value_end_ofs, len - value_end_ofs + 1);
memcpy(str + value_end_ofs, str_tmp, len_bias_num);
@@ -957,7 +957,8 @@ static int unit_scale_str(char *str,
int len_move = (len - (found_ofs + len_name)) + 1; /* 1+ to copy the string terminator. */
/* "#" Removed later */
- int len_num = BLI_snprintf(str_tmp, TEMP_STR_SIZE, "*%.9g" SEP_STR, unit->scalar / scale_pref);
+ int len_num = BLI_snprintf_rlen(
+ str_tmp, TEMP_STR_SIZE, "*%.9g" SEP_STR, unit->scalar / scale_pref);
if (len_num > len_max) {
len_num = len_max;
diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c
index 39f65d76e3c..92d19f18a93 100644
--- a/source/blender/blenkernel/intern/writeffmpeg.c
+++ b/source/blender/blenkernel/intern/writeffmpeg.c
@@ -205,12 +205,11 @@ static int write_audio_frame(FFMpegContext *context)
success = -1;
}
- av_packet_rescale_ts(pkt, c->time_base, context->audio_stream->time_base);
- if (pkt->duration > 0) {
- pkt->duration = av_rescale_q(pkt->duration, c->time_base, context->audio_stream->time_base);
- }
-
pkt->stream_index = context->audio_stream->index;
+ av_packet_rescale_ts(pkt, c->time_base, context->audio_stream->time_base);
+# ifdef FFMPEG_USE_DURATION_WORKAROUND
+ my_guess_pkt_duration(context->outfile, context->audio_stream, pkt);
+# endif
pkt->flags |= AV_PKT_FLAG_KEY;
@@ -349,6 +348,10 @@ static int write_video_frame(FFMpegContext *context, int cfra, AVFrame *frame, R
packet->stream_index = context->video_stream->index;
av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base);
+# ifdef FFMPEG_USE_DURATION_WORKAROUND
+ my_guess_pkt_duration(context->outfile, context->video_stream, packet);
+# endif
+
if (av_interleaved_write_frame(context->outfile, packet) != 0) {
success = -1;
break;
@@ -515,6 +518,48 @@ static void set_ffmpeg_properties(RenderData *rd,
}
}
+static AVRational calc_time_base(uint den, double num, int codec_id)
+{
+ /* Convert the input 'num' to an integer. Simply shift the decimal places until we get an integer
+ * (within a floating point error range).
+ * For example if we have `den = 3` and `num = 0.1` then the fps is: `den/num = 30` fps.
+ * When converting this to a FFMPEG time base, we want num to be an integer.
+ * So we simply move the decimal places of both numbers. i.e. `den = 30`, `num = 1`. */
+ float eps = FLT_EPSILON;
+ const uint DENUM_MAX = (codec_id == AV_CODEC_ID_MPEG4) ? (1UL << 16) - 1 : (1UL << 31) - 1;
+
+ /* Calculate the precision of the initial floating point number. */
+ if (num > 1.0) {
+ const uint num_integer_bits = log2_floor_u((unsigned int)num);
+
+ /* Formula for calculating the epsilon value: (power of two range) / (pow mantissa bits)
+ * For example, a float has 23 mantissa bits and the float value 3.5f as a pow2 range of
+ * (4-2=2):
+ * (2) / pow2(23) = floating point precision for 3.5f
+ */
+ eps = (float)(1 << num_integer_bits) * FLT_EPSILON;
+ }
+
+ /* Calculate how many decimal shifts we can do until we run out of precision. */
+ const int max_num_shift = fabsf(log10f(eps));
+ /* Calculate how many times we can shift the denominator. */
+ const int max_den_shift = log10f(DENUM_MAX) - log10f(den);
+ const int max_iter = min_ii(max_num_shift, max_den_shift);
+
+ for (int i = 0; i < max_iter && fabs(num - round(num)) > eps; i++) {
+ /* Increase the number and denominator until both are integers. */
+ num *= 10;
+ den *= 10;
+ eps *= 10;
+ }
+
+ AVRational time_base;
+ time_base.den = den;
+ time_base.num = (int)num;
+
+ return time_base;
+}
+
/* prepare a video stream for the output file */
static AVStream *alloc_video_stream(FFMpegContext *context,
@@ -545,13 +590,24 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->codec_id = codec_id;
c->codec_type = AVMEDIA_TYPE_VIDEO;
+ codec = avcodec_find_encoder(c->codec_id);
+ if (!codec) {
+ fprintf(stderr, "Couldn't find valid video codec\n");
+ avcodec_free_context(&c);
+ context->video_codec = NULL;
+ return NULL;
+ }
+
+ /* Load codec defaults into 'c'. */
+ avcodec_get_context_defaults3(c, codec);
+
/* Get some values from the current render settings */
c->width = rectx;
c->height = recty;
- /* FIXME: Really bad hack (tm) for NTSC support */
if (context->ffmpeg_type == FFMPEG_DV && rd->frs_sec != 25) {
+ /* FIXME: Really bad hack (tm) for NTSC support */
c->time_base.den = 2997;
c->time_base.num = 100;
}
@@ -559,21 +615,23 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->time_base.den = rd->frs_sec;
c->time_base.num = (int)rd->frs_sec_base;
}
- else if (compare_ff(rd->frs_sec_base, 1.001f, 0.000001f)) {
- /* This converts xx/1.001 (which is used in presets) to xx000/1001 (which is used in the rest
- * of the world, including FFmpeg). */
- c->time_base.den = (int)(rd->frs_sec * 1000);
- c->time_base.num = (int)(rd->frs_sec_base * 1000);
- }
else {
- /* This calculates a fraction (DENUM_MAX / num) which approximates the scene frame rate
- * (frs_sec / frs_sec_base). It uses the maximum denominator allowed by FFmpeg.
- */
- const double DENUM_MAX = (codec_id == AV_CODEC_ID_MPEG4) ? (1UL << 16) - 1 : (1UL << 31) - 1;
- const double num = (DENUM_MAX / (double)rd->frs_sec) * rd->frs_sec_base;
+ c->time_base = calc_time_base(rd->frs_sec, rd->frs_sec_base, codec_id);
+ }
- c->time_base.den = (int)DENUM_MAX;
- c->time_base.num = (int)num;
+ /* As per the time-base documentation here:
+ * https://www.ffmpeg.org/ffmpeg-codecs.html#Codec-Options
+ * We want to set the time base to (1 / fps) for fixed frame rate video.
+ * If it is not possible, we want to set the time-base numbers to something as
+ * small as possible.
+ */
+ if (c->time_base.num != 1) {
+ AVRational new_time_base;
+ if (av_reduce(
+ &new_time_base.num, &new_time_base.den, c->time_base.num, c->time_base.den, INT_MAX)) {
+ /* Exact reduction was possible. Use the new value. */
+ c->time_base = new_time_base;
+ }
}
st->time_base = c->time_base;
@@ -585,6 +643,11 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
ffmpeg_dict_set_int(&opts, "lossless", 1);
}
else if (context->ffmpeg_crf >= 0) {
+ /* As per https://trac.ffmpeg.org/wiki/Encode/VP9 we must set the bit rate to zero when
+ * encoding with vp9 in crf mode.
+ * Set this to always be zero for other codecs as well.
+ * We don't care about bit rate in crf mode. */
+ c->bit_rate = 0;
ffmpeg_dict_set_int(&opts, "crf", context->ffmpeg_crf);
}
else {
@@ -624,12 +687,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
}
}
- codec = avcodec_find_encoder(c->codec_id);
- if (!codec) {
- avcodec_free_context(&c);
- return NULL;
- }
-
/* Be sure to use the correct pixel format(e.g. RGB, YUV) */
if (codec->pix_fmts) {
@@ -646,12 +703,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->codec_tag = (('D' << 24) + ('I' << 16) + ('V' << 8) + 'X');
}
- if (codec_id == AV_CODEC_ID_H264) {
- /* correct wrong default ffmpeg param which crash x264 */
- c->qmin = 10;
- c->qmax = 51;
- }
-
/* Keep lossless encodes in the RGB domain. */
if (codec_id == AV_CODEC_ID_HUFFYUV) {
if (rd->im_format.planes == R_IMF_PLANES_RGBA) {
@@ -678,6 +729,11 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
}
}
+ /* Use 4:4:4 instead of 4:2:0 pixel format for lossless rendering. */
+ if ((codec_id == AV_CODEC_ID_H264 || codec_id == AV_CODEC_ID_VP9) && context->ffmpeg_crf == 0) {
+ c->pix_fmt = AV_PIX_FMT_YUV444P;
+ }
+
if (codec_id == AV_CODEC_ID_PNG) {
if (rd->im_format.planes == R_IMF_PLANES_RGBA) {
c->pix_fmt = AV_PIX_FMT_RGBA;
@@ -711,10 +767,14 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->thread_type = FF_THREAD_SLICE;
}
- if (avcodec_open2(c, codec, &opts) < 0) {
+ int ret = avcodec_open2(c, codec, &opts);
+
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't initialize video codec: %s\n", av_err2str(ret));
BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size);
av_dict_free(&opts);
avcodec_free_context(&c);
+ context->video_codec = NULL;
return NULL;
}
av_dict_free(&opts);
@@ -774,6 +834,17 @@ static AVStream *alloc_audio_stream(FFMpegContext *context,
c->codec_id = codec_id;
c->codec_type = AVMEDIA_TYPE_AUDIO;
+ codec = avcodec_find_encoder(c->codec_id);
+ if (!codec) {
+ fprintf(stderr, "Couldn't find valid audio codec\n");
+ avcodec_free_context(&c);
+ context->audio_codec = NULL;
+ return NULL;
+ }
+
+ /* Load codec defaults into 'c'. */
+ avcodec_get_context_defaults3(c, codec);
+
c->sample_rate = rd->ffcodecdata.audio_mixrate;
c->bit_rate = context->ffmpeg_audio_bitrate * 1000;
c->sample_fmt = AV_SAMPLE_FMT_S16;
@@ -803,13 +874,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context,
c->sample_fmt = AV_SAMPLE_FMT_FLT;
}
- codec = avcodec_find_encoder(c->codec_id);
- if (!codec) {
- // XXX error("Couldn't find a valid audio codec");
- avcodec_free_context(&c);
- return NULL;
- }
-
if (codec->sample_fmts) {
/* Check if the preferred sample format for this codec is supported.
* this is because, depending on the version of libav,
@@ -849,11 +913,14 @@ static AVStream *alloc_audio_stream(FFMpegContext *context,
set_ffmpeg_properties(rd, c, "audio", &opts);
- if (avcodec_open2(c, codec, &opts) < 0) {
- // XXX error("Couldn't initialize audio codec");
+ int ret = avcodec_open2(c, codec, &opts);
+
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't initialize audio codec: %s\n", av_err2str(ret));
BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size);
av_dict_free(&opts);
avcodec_free_context(&c);
+ context->audio_codec = NULL;
return NULL;
}
av_dict_free(&opts);
@@ -1181,6 +1248,9 @@ static void flush_ffmpeg(FFMpegContext *context)
packet->stream_index = context->video_stream->index;
av_packet_rescale_ts(packet, c->time_base, context->video_stream->time_base);
+# ifdef FFMPEG_USE_DURATION_WORKAROUND
+ my_guess_pkt_duration(context->outfile, context->video_stream, packet);
+# endif
int write_ret = av_interleaved_write_frame(context->outfile, packet);
if (write_ret != 0) {
@@ -1630,49 +1700,7 @@ static void ffmpeg_set_expert_options(RenderData *rd)
IDP_FreePropertyContent(rd->ffcodecdata.properties);
}
- if (codec_id == AV_CODEC_ID_H264) {
- /*
- * All options here are for x264, but must be set via ffmpeg.
- * The names are therefore different - Search for "x264 to FFmpeg option mapping"
- * to get a list.
- */
-
- /*
- * Use CABAC coder. Using "coder:1", which should be equivalent,
- * crashes Blender for some reason. Either way - this is no big deal.
- */
- BKE_ffmpeg_property_add_string(rd, "video", "coder:vlc");
-
- /*
- * The other options were taken from the libx264-default.preset
- * included in the ffmpeg distribution.
- */
-
- /* This breaks compatibility for QT. */
- // BKE_ffmpeg_property_add_string(rd, "video", "flags:loop");
- BKE_ffmpeg_property_add_string(rd, "video", "cmp:chroma");
- BKE_ffmpeg_property_add_string(rd, "video", "partitions:parti4x4"); /* Deprecated. */
- BKE_ffmpeg_property_add_string(rd, "video", "partitions:partp8x8"); /* Deprecated. */
- BKE_ffmpeg_property_add_string(rd, "video", "partitions:partb8x8"); /* Deprecated. */
- BKE_ffmpeg_property_add_string(rd, "video", "me:hex");
- BKE_ffmpeg_property_add_string(rd, "video", "subq:6");
- BKE_ffmpeg_property_add_string(rd, "video", "me_range:16");
- BKE_ffmpeg_property_add_string(rd, "video", "qdiff:4");
- BKE_ffmpeg_property_add_string(rd, "video", "keyint_min:25");
- BKE_ffmpeg_property_add_string(rd, "video", "sc_threshold:40");
- BKE_ffmpeg_property_add_string(rd, "video", "i_qfactor:0.71");
- BKE_ffmpeg_property_add_string(rd, "video", "b_strategy:1");
- BKE_ffmpeg_property_add_string(rd, "video", "bf:3");
- BKE_ffmpeg_property_add_string(rd, "video", "refs:2");
- BKE_ffmpeg_property_add_string(rd, "video", "qcomp:0.6");
-
- BKE_ffmpeg_property_add_string(rd, "video", "trellis:0");
- BKE_ffmpeg_property_add_string(rd, "video", "weightb:1");
- BKE_ffmpeg_property_add_string(rd, "video", "8x8dct:1");
- BKE_ffmpeg_property_add_string(rd, "video", "fast-pskip:1");
- BKE_ffmpeg_property_add_string(rd, "video", "wpredp:2");
- }
- else if (codec_id == AV_CODEC_ID_DNXHD) {
+ if (codec_id == AV_CODEC_ID_DNXHD) {
if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) {
BKE_ffmpeg_property_add_string(rd, "video", "mbd:rd");
}