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:
-rw-r--r--build_files/cmake/platform/platform_apple.cmake2
-rw-r--r--doc/python_api/rst/info_gotcha.rst4
-rw-r--r--doc/python_api/sphinx_doc_gen.py40
-rw-r--r--extern/fast_float/LICENSE-MIT27
-rw-r--r--extern/fast_float/README.blender7
-rw-r--r--extern/fast_float/README.md218
-rw-r--r--extern/fast_float/fast_float.h2979
-rw-r--r--intern/cycles/device/metal/kernel.mm2
-rw-r--r--intern/cycles/integrator/pass_accessor_cpu.cpp4
-rw-r--r--intern/cycles/integrator/path_trace.cpp39
-rw-r--r--intern/cycles/integrator/path_trace_work_cpu.cpp8
-rw-r--r--intern/cycles/integrator/shader_eval.cpp2
-rw-r--r--intern/cycles/kernel/svm/blackbody.h2
-rw-r--r--intern/cycles/kernel/svm/closure.h3
-rw-r--r--intern/cycles/kernel/svm/math_util.h4
-rw-r--r--intern/cycles/kernel/types.h7
-rw-r--r--intern/cycles/kernel/util/color.h9
-rw-r--r--intern/cycles/scene/shader.cpp39
-rw-r--r--intern/cycles/scene/shader.h5
-rw-r--r--intern/cycles/scene/shader_nodes.cpp4
-rw-r--r--intern/cycles/util/tbb.h11
-rw-r--r--intern/cycles/util/transform.h15
-rw-r--r--intern/ghost/GHOST_C-api.h4
-rw-r--r--intern/ghost/GHOST_ISystem.h3
-rw-r--r--intern/ghost/GHOST_Types.h10
-rw-r--r--intern/ghost/intern/GHOST_C-api.cpp4
-rw-r--r--intern/ghost/intern/GHOST_System.cpp4
-rw-r--r--intern/ghost/intern/GHOST_System.h3
-rw-r--r--intern/ghost/intern/GHOST_SystemWin32.cpp71
-rw-r--r--intern/ghost/intern/GHOST_SystemWin32.h10
-rw-r--r--intern/ghost/intern/GHOST_WindowWin32.cpp2
-rw-r--r--intern/ghost/intern/GHOST_Wintab.cpp160
-rw-r--r--intern/ghost/intern/GHOST_Wintab.h28
-rw-r--r--release/datafiles/icons/brush.sculpt.multiplane_scrape.datbin1916 -> 2060 bytes
-rw-r--r--release/datafiles/icons/ops.sculpt.color_filter.datbin1700 -> 1700 bytes
-rw-r--r--release/datafiles/icons/ops.sculpt.mask_by_color.datbin3716 -> 3716 bytes
m---------release/datafiles/locale0
m---------release/scripts/addons0
-rw-r--r--release/scripts/modules/bpy_types.py18
-rw-r--r--release/scripts/modules/rna_info.py6
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py2
-rw-r--r--release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py1
-rw-r--r--release/scripts/startup/bl_operators/constraint.py4
-rw-r--r--release/scripts/startup/bl_operators/file.py2
-rw-r--r--release/scripts/startup/bl_operators/geometry_nodes.py26
-rw-r--r--release/scripts/startup/bl_operators/object.py2
-rw-r--r--release/scripts/startup/bl_operators/wm.py8
-rw-r--r--release/scripts/startup/bl_ui/__init__.py1
-rw-r--r--release/scripts/startup/bl_ui/properties_constraint.py1
-rw-r--r--release/scripts/startup/bl_ui/properties_data_mesh.py6
-rw-r--r--release/scripts/startup/bl_ui/space_filebrowser.py1
-rw-r--r--release/scripts/startup/bl_ui/space_graph.py1
-rw-r--r--release/scripts/startup/bl_ui/space_nla.py13
-rw-r--r--release/scripts/startup/bl_ui/space_node.py5
-rw-r--r--release/scripts/startup/bl_ui/space_sequencer.py1
-rw-r--r--release/scripts/startup/bl_ui/space_toolsystem_toolbar.py5
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py2
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py2
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py19
-rw-r--r--release/scripts/startup/keyingsets_builtins.py18
-rw-r--r--release/scripts/startup/nodeitems_builtins.py21
-rw-r--r--source/blender/blenkernel/BKE_curve_to_mesh.hh8
-rw-r--r--source/blender/blenkernel/BKE_curves.hh36
-rw-r--r--source/blender/blenkernel/BKE_fcurve.h2
-rw-r--r--source/blender/blenkernel/BKE_global.h5
-rw-r--r--source/blender/blenkernel/BKE_layer.h4
-rw-r--r--source/blender/blenkernel/BKE_modifier.h1
-rw-r--r--source/blender/blenkernel/BKE_paint.h19
-rw-r--r--source/blender/blenkernel/BKE_pbvh.h21
-rw-r--r--source/blender/blenkernel/BKE_pbvh_pixels.hh184
-rw-r--r--source/blender/blenkernel/CMakeLists.txt1
-rw-r--r--source/blender/blenkernel/intern/action_mirror.c24
-rw-r--r--source/blender/blenkernel/intern/armature.c2
-rw-r--r--source/blender/blenkernel/intern/curve.cc2
-rw-r--r--source/blender/blenkernel/intern/curve_poly.cc20
-rw-r--r--source/blender/blenkernel/intern/curve_to_mesh_convert.cc1092
-rw-r--r--source/blender/blenkernel/intern/curves_geometry.cc88
-rw-r--r--source/blender/blenkernel/intern/fcurve_cache.c2
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.cc69
-rw-r--r--source/blender/blenkernel/intern/image.cc9
-rw-r--r--source/blender/blenkernel/intern/material.c8
-rw-r--r--source/blender/blenkernel/intern/mesh_convert.cc4
-rw-r--r--source/blender/blenkernel/intern/mesh_iterators.c7
-rw-r--r--source/blender/blenkernel/intern/mesh_runtime.cc3
-rw-r--r--source/blender/blenkernel/intern/modifier.c35
-rw-r--r--source/blender/blenkernel/intern/paint.c24
-rw-r--r--source/blender/blenkernel/intern/paint_canvas.cc51
-rw-r--r--source/blender/blenkernel/intern/pbvh.c14
-rw-r--r--source/blender/blenkernel/intern/pbvh_intern.h13
-rw-r--r--source/blender/blenkernel/intern/pbvh_pixels.cc393
-rw-r--r--source/blender/blenkernel/intern/subdiv_mesh.c15
-rw-r--r--source/blender/blenkernel/intern/tracking_stabilize.c2
-rw-r--r--source/blender/blenkernel/nla_private.h2
-rw-r--r--source/blender/blenlib/BLI_math_rotation.hh18
-rw-r--r--source/blender/blenlib/BLI_vector.hh10
-rw-r--r--source/blender/blenlib/CMakeLists.txt2
-rw-r--r--source/blender/blenlib/intern/math_rotation.cc26
-rw-r--r--source/blender/blenlib/tests/BLI_math_rotation_test.cc22
-rw-r--r--source/blender/draw/engines/eevee/eevee_shaders_extra.cc2
-rw-r--r--source/blender/draw/intern/draw_cache_impl_mesh.c3
-rw-r--r--source/blender/draw/intern/draw_manager.c4
-rw-r--r--source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc10
-rw-r--r--source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc10
-rw-r--r--source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc8
-rw-r--r--source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc7
-rw-r--r--source/blender/editors/animation/anim_channels_defines.c2
-rw-r--r--source/blender/editors/animation/keyframing.c9
-rw-r--r--source/blender/editors/include/ED_armature.h2
-rw-r--r--source/blender/editors/include/ED_sculpt.h3
-rw-r--r--source/blender/editors/io/io_obj.c13
-rw-r--r--source/blender/editors/io/io_usd.c36
-rw-r--r--source/blender/editors/object/CMakeLists.txt2
-rw-r--r--source/blender/editors/object/object_intern.h1
-rw-r--r--source/blender/editors/object/object_modifier.cc (renamed from source/blender/editors/object/object_modifier.c)303
-rw-r--r--source/blender/editors/object/object_ops.c1
-rw-r--r--source/blender/editors/object/object_transform.cc6
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c127
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h32
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_ops.c15
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_paint_color.c8
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_paint_image.cc430
-rw-r--r--source/blender/editors/space_node/node_draw.cc2
-rw-r--r--source/blender/editors/space_node/node_edit.cc5
-rw-r--r--source/blender/editors/space_outliner/outliner_tools.cc14
-rw-r--r--source/blender/editors/space_sequencer/sequencer_draw.c2
-rw-r--r--source/blender/editors/space_view3d/view3d_iterators.c2
-rw-r--r--source/blender/editors/transform/transform_convert_action.c2
-rw-r--r--source/blender/editors/transform/transform_snap_object.cc36
-rw-r--r--source/blender/geometry/intern/mesh_merge_by_distance.cc235
-rw-r--r--source/blender/gpu/intern/gpu_shader_dependency.cc40
-rw-r--r--source/blender/io/collada/AnimationImporter.cpp2
-rw-r--r--source/blender/io/common/CMakeLists.txt5
-rw-r--r--source/blender/io/common/IO_string_utils.hh69
-rw-r--r--source/blender/io/common/intern/string_utils.cc99
-rw-r--r--source/blender/io/common/intern/string_utils_test.cc118
-rw-r--r--source/blender/io/usd/CMakeLists.txt28
-rw-r--r--source/blender/io/usd/tests/usd_imaging_test.cc71
-rw-r--r--source/blender/io/usd/tests/usd_stage_creation_test.cc19
-rw-r--r--source/blender/io/usd/tests/usd_tests_common.cc45
-rw-r--r--source/blender/io/usd/tests/usd_tests_common.h20
-rw-r--r--source/blender/io/wavefront_obj/CMakeLists.txt5
-rw-r--r--source/blender/io/wavefront_obj/IO_wavefront_obj.h1
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc11
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc58
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh13
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc6
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_exporter.cc39
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc792
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh114
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_mesh.cc95
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_mesh.hh7
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_mtl.cc10
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_mtl.hh25
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_import_objects.hh25
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_importer.cc37
-rw-r--r--source/blender/io/wavefront_obj/importer/obj_importer.hh3
-rw-r--r--source/blender/io/wavefront_obj/importer/parser_string_utils.cc174
-rw-r--r--source/blender/io/wavefront_obj/importer/parser_string_utils.hh54
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc95
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh110
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_importer_tests.cc4
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc172
-rw-r--r--source/blender/makesdna/DNA_material_types.h2
-rw-r--r--source/blender/makesdna/DNA_mesh_types.h6
-rw-r--r--source/blender/makesdna/DNA_meshdata_types.h1
-rw-r--r--source/blender/makesdna/DNA_scene_types.h1
-rw-r--r--source/blender/makesdna/DNA_space_types.h2
-rw-r--r--source/blender/makesrna/intern/rna_action.c2
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c2
-rw-r--r--source/blender/nodes/NOD_node_tree_ref.hh16
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc13
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc1
-rw-r--r--source/blender/nodes/intern/node_common.cc5
-rw-r--r--source/blender/nodes/intern/node_tree_ref.cc17
-rw-r--r--source/blender/nodes/shader/node_shader_tree.cc11
-rw-r--r--source/blender/sequencer/SEQ_animation.h6
-rw-r--r--source/blender/sequencer/intern/strip_edit.c2
-rw-r--r--source/blender/windowmanager/intern/wm_draw.c51
-rw-r--r--source/blender/windowmanager/intern/wm_window.c10
-rw-r--r--source/creator/creator_args.c14
m---------source/tools0
-rw-r--r--tests/performance/api/config.py13
-rw-r--r--tests/performance/api/environment.py11
-rw-r--r--tests/performance/api/test.py2
-rw-r--r--tests/python/bevel_operator.py308
-rw-r--r--tests/python/bl_animation_fcurves.py4
-rw-r--r--tests/python/bl_blendfile_liblink.py8
-rw-r--r--tests/python/bl_blendfile_library_overrides.py10
-rw-r--r--tests/python/bl_keymap_validate.py3
-rw-r--r--tests/python/bl_pyapi_idprop.py2
-rw-r--r--tests/python/bl_rigging_symmetrize.py12
-rw-r--r--tests/python/bl_usd_import_test.py3
-rw-r--r--tests/python/boolean_operator.py34
-rw-r--r--tests/python/curve_to_mesh.py64
-rw-r--r--tests/python/deform_modifiers.py64
-rw-r--r--tests/python/modifiers.py212
-rw-r--r--tests/python/modules/mesh_test.py6
-rwxr-xr-xtests/python/modules/test_utils.py2
-rw-r--r--tests/python/operators.py654
-rw-r--r--tests/python/physics_cloth.py4
-rw-r--r--tests/python/physics_dynamic_paint.py8
-rw-r--r--tests/python/physics_ocean.py2
-rw-r--r--tests/python/physics_particle_instance.py4
-rw-r--r--tests/python/physics_particle_system.py4
-rw-r--r--tests/python/physics_softbody.py6
206 files changed, 8544 insertions, 2890 deletions
diff --git a/build_files/cmake/platform/platform_apple.cmake b/build_files/cmake/platform/platform_apple.cmake
index 43ce23081af..cdc9aa91a53 100644
--- a/build_files/cmake/platform/platform_apple.cmake
+++ b/build_files/cmake/platform/platform_apple.cmake
@@ -214,7 +214,7 @@ if(WITH_SDL)
find_package(SDL2)
set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS})
set(SDL_LIBRARY ${SDL2_LIBRARIES})
- string(APPEND PLATFORM_LINKFLAGS " -framework ForceFeedback")
+ string(APPEND PLATFORM_LINKFLAGS " -framework ForceFeedback -framework GameController -framework CoreHaptics")
endif()
set(PNG_ROOT ${LIBDIR}/png)
diff --git a/doc/python_api/rst/info_gotcha.rst b/doc/python_api/rst/info_gotcha.rst
index bef76a5e479..d3067eb0518 100644
--- a/doc/python_api/rst/info_gotcha.rst
+++ b/doc/python_api/rst/info_gotcha.rst
@@ -93,7 +93,7 @@ Consider the calculations that might contribute to the object's final transforma
- Animation function curves.
- Drivers and their Python expressions.
- Constraints
-- Parent objects and all of their F-curves, constraints, etc.
+- Parent objects and all of their F-Curves, constraints, etc.
To avoid expensive recalculations every time a property is modified,
Blender defers the evaluation until the results are needed.
@@ -802,7 +802,7 @@ Removing Data
-------------
**Any** data that you remove shouldn't be modified or accessed afterwards,
-this includes: F-curves, drivers, render layers, timeline markers, modifiers, constraints
+this includes: F-Curves, drivers, render layers, timeline markers, modifiers, constraints
along with objects, scenes, collections, bones, etc.
The ``remove()`` API calls will invalidate the data they free to prevent common mistakes.
diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py
index b074ce77a39..477d2196666 100644
--- a/doc/python_api/sphinx_doc_gen.py
+++ b/doc/python_api/sphinx_doc_gen.py
@@ -573,7 +573,7 @@ def example_extract_docstring(filepath):
line_no += 1
file.close()
- return "\n".join(text), line_no, line_no_has_content
+ return "\n".join(text).rstrip("\n"), line_no, line_no_has_content
def title_string(text, heading_char, double=False):
@@ -593,9 +593,13 @@ def write_example_ref(ident, fw, example_id, ext="py"):
filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
text, line_no, line_no_has_content = example_extract_docstring(filepath_full)
+ if text:
+ # Ensure a blank line, needed since in some cases the indentation doesn't match the previous line.
+ # which causes Sphinx not to warn about bad indentation.
+ fw("\n")
+ for line in text.split("\n"):
+ fw("%s\n" % (ident + line).rstrip())
- for line in text.split("\n"):
- fw("%s\n" % (ident + line).rstrip())
fw("\n")
# Some files only contain a doc-string.
@@ -1147,6 +1151,9 @@ def pycontext2sphinx(basepath):
fw("Note that all context values are readonly,\n")
fw("but may be modified through the data API or by running operators\n\n")
+ # Track all unique properties to properly use `noindex`.
+ unique = set()
+
def write_contex_cls():
fw(title_string("Global Context", "-"))
@@ -1164,9 +1171,11 @@ def pycontext2sphinx(basepath):
# First write RNA
for prop in sorted_struct_properties:
- # support blacklisting props
+ # Support blacklisting props.
if prop.identifier in struct_blacklist:
continue
+ # No need to check if there are duplicates yet as it's known there wont be.
+ unique.add(prop.identifier)
type_descr = prop.get_type_description(
class_fmt=":class:`bpy.types.%s`", collection_id=_BPY_PROP_COLLECTION_ID)
@@ -1204,7 +1213,8 @@ def pycontext2sphinx(basepath):
"file_context_dir",
)
- unique = set()
+ # Track unique for `context_strings` to validate `context_type_map`.
+ unique_context_strings = set()
blend_cdll = ctypes.CDLL("")
for ctx_str in context_strings:
subsection = "%s Context" % ctx_str.split("_")[0].title()
@@ -1216,22 +1226,32 @@ def pycontext2sphinx(basepath):
i = 0
while char_array[i] is not None:
member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
- fw(".. data:: %s\n\n" % member)
+ unique_all_len = len(unique)
+ unique.add(member)
+ member_visited = unique_all_len == len(unique)
+
+ unique_context_strings.add(member)
+
+ fw(".. data:: %s\n" % member)
+ # Avoid warnings about the member being included multiple times.
+ if member_visited:
+ fw(" :noindex:\n")
+ fw("\n")
+
try:
member_type, is_seq = context_type_map[member]
except KeyError:
raise SystemExit("Error: context key %r not found in context_type_map; update %s" % (member, __file__)) from None
fw(" :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
- unique.add(member)
i += 1
# generate typemap...
- # for member in sorted(unique):
+ # for member in sorted(unique_context_strings):
# print(' "%s": ("", False),' % member)
- if len(context_type_map) > len(unique):
+ if len(context_type_map) > len(unique_context_strings):
warnings.warn(
"Some types are not used: %s" %
- str([member for member in context_type_map if member not in unique]))
+ str([member for member in context_type_map if member not in unique_context_strings]))
else:
pass # will have raised an error above
diff --git a/extern/fast_float/LICENSE-MIT b/extern/fast_float/LICENSE-MIT
new file mode 100644
index 00000000000..2fb2a37ad7f
--- /dev/null
+++ b/extern/fast_float/LICENSE-MIT
@@ -0,0 +1,27 @@
+MIT License
+
+Copyright (c) 2021 The fast_float authors
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/extern/fast_float/README.blender b/extern/fast_float/README.blender
new file mode 100644
index 00000000000..a584a0511ee
--- /dev/null
+++ b/extern/fast_float/README.blender
@@ -0,0 +1,7 @@
+Project: fast_float
+URL: https://github.com/fastfloat/fast_float
+License: MIT
+Upstream version: 3.4.0 (b7f9d6c)
+Local modifications:
+
+- Took only the fast_float.h header and the license/readme files
diff --git a/extern/fast_float/README.md b/extern/fast_float/README.md
new file mode 100644
index 00000000000..1e1c06d0a3e
--- /dev/null
+++ b/extern/fast_float/README.md
@@ -0,0 +1,218 @@
+## fast_float number parsing library: 4x faster than strtod
+
+![Ubuntu 20.04 CI (GCC 9)](https://github.com/lemire/fast_float/workflows/Ubuntu%2020.04%20CI%20(GCC%209)/badge.svg)
+![Ubuntu 18.04 CI (GCC 7)](https://github.com/lemire/fast_float/workflows/Ubuntu%2018.04%20CI%20(GCC%207)/badge.svg)
+![Alpine Linux](https://github.com/lemire/fast_float/workflows/Alpine%20Linux/badge.svg)
+![MSYS2-CI](https://github.com/lemire/fast_float/workflows/MSYS2-CI/badge.svg)
+![VS16-CLANG-CI](https://github.com/lemire/fast_float/workflows/VS16-CLANG-CI/badge.svg)
+[![VS16-CI](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.yml)
+
+The fast_float library provides fast header-only implementations for the C++ from_chars
+functions for `float` and `double` types. These functions convert ASCII strings representing
+decimal values (e.g., `1.3e10`) into binary types. We provide exact rounding (including
+round to even). In our experience, these `fast_float` functions many times faster than comparable number-parsing functions from existing C++ standard libraries.
+
+Specifically, `fast_float` provides the following two functions with a C++17-like syntax (the library itself only requires C++11):
+
+```C++
+from_chars_result from_chars(const char* first, const char* last, float& value, ...);
+from_chars_result from_chars(const char* first, const char* last, double& value, ...);
+```
+
+The return type (`from_chars_result`) is defined as the struct:
+```C++
+struct from_chars_result {
+ const char* ptr;
+ std::errc ec;
+};
+```
+
+It parses the character sequence [first,last) for a number. It parses floating-point numbers expecting
+a locale-independent format equivalent to the C++17 from_chars function.
+The resulting floating-point value is the closest floating-point values (using either float or double),
+using the "round to even" convention for values that would otherwise fall right in-between two values.
+That is, we provide exact parsing according to the IEEE standard.
+
+
+Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the
+parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned
+`ec` contains a representative error, otherwise the default (`std::errc()`) value is stored.
+
+The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`).
+
+It will parse infinity and nan values.
+
+Example:
+
+``` C++
+#include "fast_float/fast_float.h"
+#include <iostream>
+
+int main() {
+ const std::string input = "3.1416 xyz ";
+ double result;
+ auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
+ if(answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
+ std::cout << "parsed the number " << result << std::endl;
+ return EXIT_SUCCESS;
+}
+```
+
+
+Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of
+the type `fast_float::chars_format`. It is a bitset value: we check whether
+`fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set
+to determine whether we allow the fixed point and scientific notation respectively.
+The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
+
+The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification.
+* The `from_chars` function does not skip leading white-space characters.
+* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is forbidden.
+* It is generally impossible to represent a decimal value exactly as binary floating-point number (`float` and `double` types). We seek the nearest value. We round to an even mantissa when we are in-between two binary floating-point numbers.
+
+Furthermore, we have the following restrictions:
+* We only support `float` and `double` types at this time.
+* We only support the decimal format: we do not support hexadecimal strings.
+* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value.
+
+We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems.
+
+
+
+## Using commas as decimal separator
+
+
+The C++ standard stipulate that `from_chars` has to be locale-independent. In
+particular, the decimal separator has to be the period (`.`). However,
+some users still want to use the `fast_float` library with in a locale-dependent
+manner. Using a separate function called `from_chars_advanced`, we allow the users
+to pass a `parse_options` instance which contains a custom decimal separator (e.g.,
+the comma). You may use it as follows.
+
+```C++
+#include "fast_float/fast_float.h"
+#include <iostream>
+
+int main() {
+ const std::string input = "3,1416 xyz ";
+ double result;
+ fast_float::parse_options options{fast_float::chars_format::general, ','};
+ auto answer = fast_float::from_chars_advanced(input.data(), input.data()+input.size(), result, options);
+ if((answer.ec != std::errc()) || ((result != 3.1416))) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
+ std::cout << "parsed the number " << result << std::endl;
+ return EXIT_SUCCESS;
+}
+```
+
+
+## Reference
+
+- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Pratice and Experience 51 (8), 2021.
+
+## Other programming languages
+
+- [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called `rcppfastfloat`.
+- [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`.
+- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`.
+- [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`.
+
+
+## Relation With Other Work
+
+The fastfloat algorithm is part of the [LLVM standard libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba).
+
+The fast_float library provides a performance similar to that of the [fast_double_parser](https://github.com/lemire/fast_double_parser) library but using an updated algorithm reworked from the ground up, and while offering an API more in line with the expectations of C++ programmers. The fast_double_parser library is part of the [Microsoft LightGBM machine-learning framework](https://github.com/microsoft/LightGBM).
+
+## Users
+
+The fast_float library is used by [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied the number parsing speed by two or three times. It is also used by [Yandex ClickHouse](https://github.com/ClickHouse/ClickHouse) and by [Google Jsonnet](https://github.com/google/jsonnet).
+
+
+## How fast is it?
+
+It can parse random floating-point numbers at a speed of 1 GB/s on some systems. We find that it is often twice as fast as the best available competitor, and many times faster than many standard-library implementations.
+
+<img src="http://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png" width="400">
+
+```
+$ ./build/benchmarks/benchmark
+# parsing random integers in the range [0,1)
+volume = 2.09808 MB
+netlib : 271.18 MB/s (+/- 1.2 %) 12.93 Mfloat/s
+doubleconversion : 225.35 MB/s (+/- 1.2 %) 10.74 Mfloat/s
+strtod : 190.94 MB/s (+/- 1.6 %) 9.10 Mfloat/s
+abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfloat/s
+fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s
+```
+
+See https://github.com/lemire/simple_fastfloat_benchmark for our benchmarking code.
+
+
+## Video
+
+[![Go Systems 2020](http://img.youtube.com/vi/AVXgvlMeIm4/0.jpg)](http://www.youtube.com/watch?v=AVXgvlMeIm4)<br />
+
+## Using as a CMake dependency
+
+This library is header-only by design. The CMake file provides the `fast_float` target
+which is merely a pointer to the `include` directory.
+
+If you drop the `fast_float` repository in your CMake project, you should be able to use
+it in this manner:
+
+```cmake
+add_subdirectory(fast_float)
+target_link_libraries(myprogram PUBLIC fast_float)
+```
+
+Or you may want to retrieve the dependency automatically if you have a sufficiently recent version of CMake (3.11 or better at least):
+
+```cmake
+FetchContent_Declare(
+ fast_float
+ GIT_REPOSITORY https://github.com/lemire/fast_float.git
+ GIT_TAG tags/v1.1.2
+ GIT_SHALLOW TRUE)
+
+FetchContent_MakeAvailable(fast_float)
+target_link_libraries(myprogram PUBLIC fast_float)
+
+```
+
+You should change the `GIT_TAG` line so that you recover the version you wish to use.
+
+## Using as single header
+
+The script `script/amalgamate.py` may be used to generate a single header
+version of the library if so desired.
+Just run the script from the root directory of this repository.
+You can customize the license type and output file if desired as described in
+the command line help.
+
+You may directly download automatically generated single-header files:
+
+https://github.com/fastfloat/fast_float/releases/download/v1.1.2/fast_float.h
+
+## Credit
+
+Though this work is inspired by many different people, this work benefited especially from exchanges with
+Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided
+invaluable feedback. Rémy Oudompheng first implemented a fast path we use in the case of long digits.
+
+The library includes code adapted from Google Wuffs (written by Nigel Tao) which was originally published
+under the Apache 2.0 license.
+
+## License
+
+<sup>
+Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
+2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
+</sup>
+
+<br>
+
+<sub>
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in this repository by you, as defined in the Apache-2.0 license,
+shall be dual licensed as above, without any additional terms or conditions.
+</sub>
diff --git a/extern/fast_float/fast_float.h b/extern/fast_float/fast_float.h
new file mode 100644
index 00000000000..2482dfdbd6c
--- /dev/null
+++ b/extern/fast_float/fast_float.h
@@ -0,0 +1,2979 @@
+// fast_float by Daniel Lemire
+// fast_float by João Paulo Magalhaes
+//
+// with contributions from Eugene Golushkov
+// with contributions from Maksim Kita
+// with contributions from Marcin Wojdyr
+// with contributions from Neal Richardson
+// with contributions from Tim Paine
+// with contributions from Fabio Pellacini
+//
+// Licensed under the Apache License, Version 2.0, or the
+// MIT License at your option. This file may not be copied,
+// modified, or distributed except according to those terms.
+//
+// MIT License Notice
+//
+// MIT License
+//
+// Copyright (c) 2021 The fast_float authors
+//
+// Permission is hereby granted, free of charge, to any
+// person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the
+// Software without restriction, including without
+// limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice
+// shall be included in all copies or substantial portions
+// of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+// Apache License (Version 2.0) Notice
+//
+// Copyright 2021 The fast_float authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+//
+
+#ifndef FASTFLOAT_FAST_FLOAT_H
+#define FASTFLOAT_FAST_FLOAT_H
+
+#include <system_error>
+
+namespace fast_float {
+enum chars_format {
+ scientific = 1<<0,
+ fixed = 1<<2,
+ hex = 1<<3,
+ general = fixed | scientific
+};
+
+
+struct from_chars_result {
+ const char *ptr;
+ std::errc ec;
+};
+
+struct parse_options {
+ constexpr explicit parse_options(chars_format fmt = chars_format::general,
+ char dot = '.')
+ : format(fmt), decimal_point(dot) {}
+
+ /** Which number formats are accepted */
+ chars_format format;
+ /** The character used as decimal point */
+ char decimal_point;
+};
+
+/**
+ * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting
+ * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale.
+ * The resulting floating-point value is the closest floating-point values (using either float or double),
+ * using the "round to even" convention for values that would otherwise fall right in-between two values.
+ * That is, we provide exact parsing according to the IEEE standard.
+ *
+ * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the
+ * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned
+ * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored.
+ *
+ * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`).
+ *
+ * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of
+ * the type `fast_float::chars_format`. It is a bitset value: we check whether
+ * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set
+ * to determine whether we allowe the fixed point and scientific notation respectively.
+ * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
+ */
+template<typename T>
+from_chars_result from_chars(const char *first, const char *last,
+ T &value, chars_format fmt = chars_format::general) noexcept;
+
+/**
+ * Like from_chars, but accepts an `options` argument to govern number parsing.
+ */
+template<typename T>
+from_chars_result from_chars_advanced(const char *first, const char *last,
+ T &value, parse_options options) noexcept;
+
+}
+#endif // FASTFLOAT_FAST_FLOAT_H
+
+#ifndef FASTFLOAT_FLOAT_COMMON_H
+#define FASTFLOAT_FLOAT_COMMON_H
+
+#include <cfloat>
+#include <cstdint>
+#include <cassert>
+#include <cstring>
+#include <type_traits>
+
+#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \
+ || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \
+ || defined(__MINGW64__) \
+ || defined(__s390x__) \
+ || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \
+ || defined(__EMSCRIPTEN__))
+#define FASTFLOAT_64BIT
+#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \
+ || defined(__arm__) || defined(_M_ARM) \
+ || defined(__MINGW32__))
+#define FASTFLOAT_32BIT
+#else
+ // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow.
+ // We can never tell the register width, but the SIZE_MAX is a good approximation.
+ // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability.
+ #if SIZE_MAX == 0xffff
+ #error Unknown platform (16-bit, unsupported)
+ #elif SIZE_MAX == 0xffffffff
+ #define FASTFLOAT_32BIT
+ #elif SIZE_MAX == 0xffffffffffffffff
+ #define FASTFLOAT_64BIT
+ #else
+ #error Unknown platform (not 32-bit, not 64-bit?)
+ #endif
+#endif
+
+#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__))
+#include <intrin.h>
+#endif
+
+#if defined(_MSC_VER) && !defined(__clang__)
+#define FASTFLOAT_VISUAL_STUDIO 1
+#endif
+
+#ifdef _WIN32
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#else
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#include <machine/endian.h>
+#elif defined(sun) || defined(__sun)
+#include <sys/byteorder.h>
+#else
+#include <endian.h>
+#endif
+#
+#ifndef __BYTE_ORDER__
+// safe choice
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#endif
+#
+#ifndef __ORDER_LITTLE_ENDIAN__
+// safe choice
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#endif
+#
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define FASTFLOAT_IS_BIG_ENDIAN 0
+#else
+#define FASTFLOAT_IS_BIG_ENDIAN 1
+#endif
+#endif
+
+#ifdef FASTFLOAT_VISUAL_STUDIO
+#define fastfloat_really_inline __forceinline
+#else
+#define fastfloat_really_inline inline __attribute__((always_inline))
+#endif
+
+#ifndef FASTFLOAT_ASSERT
+#define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); }
+#endif
+
+#ifndef FASTFLOAT_DEBUG_ASSERT
+#include <cassert>
+#define FASTFLOAT_DEBUG_ASSERT(x) assert(x)
+#endif
+
+// rust style `try!()` macro, or `?` operator
+#define FASTFLOAT_TRY(x) { if (!(x)) return false; }
+
+namespace fast_float {
+
+// Compares two ASCII strings in a case insensitive manner.
+inline bool fastfloat_strncasecmp(const char *input1, const char *input2,
+ size_t length) {
+ char running_diff{0};
+ for (size_t i = 0; i < length; i++) {
+ running_diff |= (input1[i] ^ input2[i]);
+ }
+ return (running_diff == 0) || (running_diff == 32);
+}
+
+#ifndef FLT_EVAL_METHOD
+#error "FLT_EVAL_METHOD should be defined, please include cfloat."
+#endif
+
+// a pointer and a length to a contiguous block of memory
+template <typename T>
+struct span {
+ const T* ptr;
+ size_t length;
+ span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {}
+ span() : ptr(nullptr), length(0) {}
+
+ constexpr size_t len() const noexcept {
+ return length;
+ }
+
+ const T& operator[](size_t index) const noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return ptr[index];
+ }
+};
+
+struct value128 {
+ uint64_t low;
+ uint64_t high;
+ value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {}
+ value128() : low(0), high(0) {}
+};
+
+/* result might be undefined when input_num is zero */
+fastfloat_really_inline int leading_zeroes(uint64_t input_num) {
+ assert(input_num > 0);
+#ifdef FASTFLOAT_VISUAL_STUDIO
+ #if defined(_M_X64) || defined(_M_ARM64)
+ unsigned long leading_zero = 0;
+ // Search the mask data from most significant bit (MSB)
+ // to least significant bit (LSB) for a set bit (1).
+ _BitScanReverse64(&leading_zero, input_num);
+ return (int)(63 - leading_zero);
+ #else
+ int last_bit = 0;
+ if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32;
+ if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16;
+ if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8;
+ if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4;
+ if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2;
+ if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1;
+ return 63 - last_bit;
+ #endif
+#else
+ return __builtin_clzll(input_num);
+#endif
+}
+
+#ifdef FASTFLOAT_32BIT
+
+// slow emulation routine for 32-bit
+fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) {
+ return x * (uint64_t)y;
+}
+
+// slow emulation routine for 32-bit
+#if !defined(__MINGW64__)
+fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd,
+ uint64_t *hi) {
+ uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd);
+ uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd);
+ uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32));
+ uint64_t adbc_carry = !!(adbc < ad);
+ uint64_t lo = bd + (adbc << 32);
+ *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) +
+ (adbc_carry << 32) + !!(lo < bd);
+ return lo;
+}
+#endif // !__MINGW64__
+
+#endif // FASTFLOAT_32BIT
+
+
+// compute 64-bit a*b
+fastfloat_really_inline value128 full_multiplication(uint64_t a,
+ uint64_t b) {
+ value128 answer;
+#ifdef _M_ARM64
+ // ARM64 has native support for 64-bit multiplications, no need to emulate
+ answer.high = __umulh(a, b);
+ answer.low = a * b;
+#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__))
+ answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64
+#elif defined(FASTFLOAT_64BIT)
+ __uint128_t r = ((__uint128_t)a) * b;
+ answer.low = uint64_t(r);
+ answer.high = uint64_t(r >> 64);
+#else
+ #error Not implemented
+#endif
+ return answer;
+}
+
+struct adjusted_mantissa {
+ uint64_t mantissa{0};
+ int32_t power2{0}; // a negative value indicates an invalid result
+ adjusted_mantissa() = default;
+ bool operator==(const adjusted_mantissa &o) const {
+ return mantissa == o.mantissa && power2 == o.power2;
+ }
+ bool operator!=(const adjusted_mantissa &o) const {
+ return mantissa != o.mantissa || power2 != o.power2;
+ }
+};
+
+// Bias so we can get the real exponent with an invalid adjusted_mantissa.
+constexpr static int32_t invalid_am_bias = -0x8000;
+
+constexpr static double powers_of_ten_double[] = {
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
+ 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
+constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5,
+ 1e6, 1e7, 1e8, 1e9, 1e10};
+
+template <typename T> struct binary_format {
+ using equiv_uint = typename std::conditional<sizeof(T) == 4, uint32_t, uint64_t>::type;
+
+ static inline constexpr int mantissa_explicit_bits();
+ static inline constexpr int minimum_exponent();
+ static inline constexpr int infinite_power();
+ static inline constexpr int sign_index();
+ static inline constexpr int min_exponent_fast_path();
+ static inline constexpr int max_exponent_fast_path();
+ static inline constexpr int max_exponent_round_to_even();
+ static inline constexpr int min_exponent_round_to_even();
+ static inline constexpr uint64_t max_mantissa_fast_path();
+ static inline constexpr int largest_power_of_ten();
+ static inline constexpr int smallest_power_of_ten();
+ static inline constexpr T exact_power_of_ten(int64_t power);
+ static inline constexpr size_t max_digits();
+ static inline constexpr equiv_uint exponent_mask();
+ static inline constexpr equiv_uint mantissa_mask();
+ static inline constexpr equiv_uint hidden_bit_mask();
+};
+
+template <> inline constexpr int binary_format<double>::mantissa_explicit_bits() {
+ return 52;
+}
+template <> inline constexpr int binary_format<float>::mantissa_explicit_bits() {
+ return 23;
+}
+
+template <> inline constexpr int binary_format<double>::max_exponent_round_to_even() {
+ return 23;
+}
+
+template <> inline constexpr int binary_format<float>::max_exponent_round_to_even() {
+ return 10;
+}
+
+template <> inline constexpr int binary_format<double>::min_exponent_round_to_even() {
+ return -4;
+}
+
+template <> inline constexpr int binary_format<float>::min_exponent_round_to_even() {
+ return -17;
+}
+
+template <> inline constexpr int binary_format<double>::minimum_exponent() {
+ return -1023;
+}
+template <> inline constexpr int binary_format<float>::minimum_exponent() {
+ return -127;
+}
+
+template <> inline constexpr int binary_format<double>::infinite_power() {
+ return 0x7FF;
+}
+template <> inline constexpr int binary_format<float>::infinite_power() {
+ return 0xFF;
+}
+
+template <> inline constexpr int binary_format<double>::sign_index() { return 63; }
+template <> inline constexpr int binary_format<float>::sign_index() { return 31; }
+
+template <> inline constexpr int binary_format<double>::min_exponent_fast_path() {
+#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
+ return 0;
+#else
+ return -22;
+#endif
+}
+template <> inline constexpr int binary_format<float>::min_exponent_fast_path() {
+#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
+ return 0;
+#else
+ return -10;
+#endif
+}
+
+template <> inline constexpr int binary_format<double>::max_exponent_fast_path() {
+ return 22;
+}
+template <> inline constexpr int binary_format<float>::max_exponent_fast_path() {
+ return 10;
+}
+
+template <> inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() {
+ return uint64_t(2) << mantissa_explicit_bits();
+}
+template <> inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() {
+ return uint64_t(2) << mantissa_explicit_bits();
+}
+
+template <>
+inline constexpr double binary_format<double>::exact_power_of_ten(int64_t power) {
+ return powers_of_ten_double[power];
+}
+template <>
+inline constexpr float binary_format<float>::exact_power_of_ten(int64_t power) {
+
+ return powers_of_ten_float[power];
+}
+
+
+template <>
+inline constexpr int binary_format<double>::largest_power_of_ten() {
+ return 308;
+}
+template <>
+inline constexpr int binary_format<float>::largest_power_of_ten() {
+ return 38;
+}
+
+template <>
+inline constexpr int binary_format<double>::smallest_power_of_ten() {
+ return -342;
+}
+template <>
+inline constexpr int binary_format<float>::smallest_power_of_ten() {
+ return -65;
+}
+
+template <> inline constexpr size_t binary_format<double>::max_digits() {
+ return 769;
+}
+template <> inline constexpr size_t binary_format<float>::max_digits() {
+ return 114;
+}
+
+template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::exponent_mask() {
+ return 0x7F800000;
+}
+template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::exponent_mask() {
+ return 0x7FF0000000000000;
+}
+
+template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::mantissa_mask() {
+ return 0x007FFFFF;
+}
+template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::mantissa_mask() {
+ return 0x000FFFFFFFFFFFFF;
+}
+
+template <> inline constexpr binary_format<float>::equiv_uint
+ binary_format<float>::hidden_bit_mask() {
+ return 0x00800000;
+}
+template <> inline constexpr binary_format<double>::equiv_uint
+ binary_format<double>::hidden_bit_mask() {
+ return 0x0010000000000000;
+}
+
+template<typename T>
+fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) {
+ uint64_t word = am.mantissa;
+ word |= uint64_t(am.power2) << binary_format<T>::mantissa_explicit_bits();
+ word = negative
+ ? word | (uint64_t(1) << binary_format<T>::sign_index()) : word;
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ if (std::is_same<T, float>::value) {
+ ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian
+ } else {
+ ::memcpy(&value, &word, sizeof(T));
+ }
+#else
+ // For little-endian systems:
+ ::memcpy(&value, &word, sizeof(T));
+#endif
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_ASCII_NUMBER_H
+#define FASTFLOAT_ASCII_NUMBER_H
+
+#include <cctype>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+
+namespace fast_float {
+
+// Next function can be micro-optimized, but compilers are entirely
+// able to optimize it well.
+fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; }
+
+fastfloat_really_inline uint64_t byteswap(uint64_t val) {
+ return (val & 0xFF00000000000000) >> 56
+ | (val & 0x00FF000000000000) >> 40
+ | (val & 0x0000FF0000000000) >> 24
+ | (val & 0x000000FF00000000) >> 8
+ | (val & 0x00000000FF000000) << 8
+ | (val & 0x0000000000FF0000) << 24
+ | (val & 0x000000000000FF00) << 40
+ | (val & 0x00000000000000FF) << 56;
+}
+
+fastfloat_really_inline uint64_t read_u64(const char *chars) {
+ uint64_t val;
+ ::memcpy(&val, chars, sizeof(uint64_t));
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ return val;
+}
+
+fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) {
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ ::memcpy(chars, &val, sizeof(uint64_t));
+}
+
+// credit @aqrit
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) {
+ const uint64_t mask = 0x000000FF000000FF;
+ const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+ const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
+ val -= 0x3030303030303030;
+ val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
+ val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
+ return uint32_t(val);
+}
+
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept {
+ return parse_eight_digits_unrolled(read_u64(chars));
+}
+
+// credit @aqrit
+fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept {
+ return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+ 0x8080808080808080));
+}
+
+fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept {
+ return is_made_of_eight_digits_fast(read_u64(chars));
+}
+
+typedef span<const char> byte_span;
+
+struct parsed_number_string {
+ int64_t exponent{0};
+ uint64_t mantissa{0};
+ const char *lastmatch{nullptr};
+ bool negative{false};
+ bool valid{false};
+ bool too_many_digits{false};
+ // contains the range of the significant digits
+ byte_span integer{}; // non-nullable
+ byte_span fraction{}; // nullable
+};
+
+// Assuming that you use no more than 19 digits, this will
+// parse an ASCII string.
+fastfloat_really_inline
+parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept {
+ const chars_format fmt = options.format;
+ const char decimal_point = options.decimal_point;
+
+ parsed_number_string answer;
+ answer.valid = false;
+ answer.too_many_digits = false;
+ answer.negative = (*p == '-');
+ if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+ ++p;
+ if (p == pend) {
+ return answer;
+ }
+ if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot
+ return answer;
+ }
+ }
+ const char *const start_digits = p;
+
+ uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ // a multiplication by 10 is cheaper than an arbitrary integer
+ // multiplication
+ i = 10 * i +
+ uint64_t(*p - '0'); // might overflow, we will handle the overflow later
+ ++p;
+ }
+ const char *const end_of_integer_part = p;
+ int64_t digit_count = int64_t(end_of_integer_part - start_digits);
+ answer.integer = byte_span(start_digits, size_t(digit_count));
+ int64_t exponent = 0;
+ if ((p != pend) && (*p == decimal_point)) {
+ ++p;
+ const char* before = p;
+ // can occur at most twice without overflowing, but let it occur more, since
+ // for integers with many digits, digit parsing is the primary bottleneck.
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ ++p;
+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+ }
+ exponent = before - p;
+ answer.fraction = byte_span(before, size_t(p - before));
+ digit_count -= exponent;
+ }
+ // we must have encountered at least one integer!
+ if (digit_count == 0) {
+ return answer;
+ }
+ int64_t exp_number = 0; // explicit exponential part
+ if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) {
+ const char * location_of_e = p;
+ ++p;
+ bool neg_exp = false;
+ if ((p != pend) && ('-' == *p)) {
+ neg_exp = true;
+ ++p;
+ } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+ ++p;
+ }
+ if ((p == pend) || !is_integer(*p)) {
+ if(!(fmt & chars_format::fixed)) {
+ // We are in error.
+ return answer;
+ }
+ // Otherwise, we will be ignoring the 'e'.
+ p = location_of_e;
+ } else {
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ if (exp_number < 0x10000000) {
+ exp_number = 10 * exp_number + digit;
+ }
+ ++p;
+ }
+ if(neg_exp) { exp_number = - exp_number; }
+ exponent += exp_number;
+ }
+ } else {
+ // If it scientific and not fixed, we have to bail out.
+ if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; }
+ }
+ answer.lastmatch = p;
+ answer.valid = true;
+
+ // If we frequently had to deal with long strings of digits,
+ // we could extend our code by using a 128-bit integer instead
+ // of a 64-bit integer. However, this is uncommon.
+ //
+ // We can deal with up to 19 digits.
+ if (digit_count > 19) { // this is uncommon
+ // It is possible that the integer had an overflow.
+ // We have to handle the case where we have 0.0000somenumber.
+ // We need to be mindful of the case where we only have zeroes...
+ // E.g., 0.000000000...000.
+ const char *start = start_digits;
+ while ((start != pend) && (*start == '0' || *start == decimal_point)) {
+ if(*start == '0') { digit_count --; }
+ start++;
+ }
+ if (digit_count > 19) {
+ answer.too_many_digits = true;
+ // Let us start again, this time, avoiding overflows.
+ // We don't need to check if is_integer, since we use the
+ // pre-tokenized spans from above.
+ i = 0;
+ p = answer.integer.ptr;
+ const char* int_end = p + answer.integer.len();
+ const uint64_t minimal_nineteen_digit_integer{1000000000000000000};
+ while((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+ i = i * 10 + uint64_t(*p - '0');
+ ++p;
+ }
+ if (i >= minimal_nineteen_digit_integer) { // We have a big integers
+ exponent = end_of_integer_part - p + exp_number;
+ } else { // We have a value with a fractional component.
+ p = answer.fraction.ptr;
+ const char* frac_end = p + answer.fraction.len();
+ while((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+ i = i * 10 + uint64_t(*p - '0');
+ ++p;
+ }
+ exponent = answer.fraction.ptr - p + exp_number;
+ }
+ // We have now corrected both exponent and i, to a truncated value
+ }
+ }
+ answer.exponent = exponent;
+ answer.mantissa = i;
+ return answer;
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_FAST_TABLE_H
+#define FASTFLOAT_FAST_TABLE_H
+
+#include <cstdint>
+
+namespace fast_float {
+
+/**
+ * When mapping numbers from decimal to binary,
+ * we go from w * 10^q to m * 2^p but we have
+ * 10^q = 5^q * 2^q, so effectively
+ * we are trying to match
+ * w * 2^q * 5^q to m * 2^p. Thus the powers of two
+ * are not a concern since they can be represented
+ * exactly using the binary notation, only the powers of five
+ * affect the binary significand.
+ */
+
+/**
+ * The smallest non-zero float (binary64) is 2^−1074.
+ * We take as input numbers of the form w x 10^q where w < 2^64.
+ * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076.
+ * However, we have that
+ * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074.
+ * Thus it is possible for a number of the form w * 10^-342 where
+ * w is a 64-bit value to be a non-zero floating-point number.
+ *********
+ * Any number of form w * 10^309 where w>= 1 is going to be
+ * infinite in binary64 so we never need to worry about powers
+ * of 5 greater than 308.
+ */
+template <class unused = void>
+struct powers_template {
+
+constexpr static int smallest_power_of_five = binary_format<double>::smallest_power_of_ten();
+constexpr static int largest_power_of_five = binary_format<double>::largest_power_of_ten();
+constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1);
+// Powers of five from 5^-342 all the way to 5^308 rounded toward one.
+static const uint64_t power_of_five_128[number_of_entries];
+};
+
+template <class unused>
+const uint64_t powers_template<unused>::power_of_five_128[number_of_entries] = {
+ 0xeef453d6923bd65a,0x113faa2906a13b3f,
+ 0x9558b4661b6565f8,0x4ac7ca59a424c507,
+ 0xbaaee17fa23ebf76,0x5d79bcf00d2df649,
+ 0xe95a99df8ace6f53,0xf4d82c2c107973dc,
+ 0x91d8a02bb6c10594,0x79071b9b8a4be869,
+ 0xb64ec836a47146f9,0x9748e2826cdee284,
+ 0xe3e27a444d8d98b7,0xfd1b1b2308169b25,
+ 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7,
+ 0xb208ef855c969f4f,0xbdbd2d335e51a935,
+ 0xde8b2b66b3bc4723,0xad2c788035e61382,
+ 0x8b16fb203055ac76,0x4c3bcb5021afcc31,
+ 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d,
+ 0xd953e8624b85dd78,0xd71d6dad34a2af0d,
+ 0x87d4713d6f33aa6b,0x8672648c40e5ad68,
+ 0xa9c98d8ccb009506,0x680efdaf511f18c2,
+ 0xd43bf0effdc0ba48,0x212bd1b2566def2,
+ 0x84a57695fe98746d,0x14bb630f7604b57,
+ 0xa5ced43b7e3e9188,0x419ea3bd35385e2d,
+ 0xcf42894a5dce35ea,0x52064cac828675b9,
+ 0x818995ce7aa0e1b2,0x7343efebd1940993,
+ 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8,
+ 0xca66fa129f9b60a6,0xd41a26e077774ef6,
+ 0xfd00b897478238d0,0x8920b098955522b4,
+ 0x9e20735e8cb16382,0x55b46e5f5d5535b0,
+ 0xc5a890362fddbc62,0xeb2189f734aa831d,
+ 0xf712b443bbd52b7b,0xa5e9ec7501d523e4,
+ 0x9a6bb0aa55653b2d,0x47b233c92125366e,
+ 0xc1069cd4eabe89f8,0x999ec0bb696e840a,
+ 0xf148440a256e2c76,0xc00670ea43ca250d,
+ 0x96cd2a865764dbca,0x380406926a5e5728,
+ 0xbc807527ed3e12bc,0xc605083704f5ecf2,
+ 0xeba09271e88d976b,0xf7864a44c633682e,
+ 0x93445b8731587ea3,0x7ab3ee6afbe0211d,
+ 0xb8157268fdae9e4c,0x5960ea05bad82964,
+ 0xe61acf033d1a45df,0x6fb92487298e33bd,
+ 0x8fd0c16206306bab,0xa5d3b6d479f8e056,
+ 0xb3c4f1ba87bc8696,0x8f48a4899877186c,
+ 0xe0b62e2929aba83c,0x331acdabfe94de87,
+ 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14,
+ 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9,
+ 0xdb71e91432b1a24a,0xc9e82cd9f69d6150,
+ 0x892731ac9faf056e,0xbe311c083a225cd2,
+ 0xab70fe17c79ac6ca,0x6dbd630a48aaf406,
+ 0xd64d3d9db981787d,0x92cbbccdad5b108,
+ 0x85f0468293f0eb4e,0x25bbf56008c58ea5,
+ 0xa76c582338ed2621,0xaf2af2b80af6f24e,
+ 0xd1476e2c07286faa,0x1af5af660db4aee1,
+ 0x82cca4db847945ca,0x50d98d9fc890ed4d,
+ 0xa37fce126597973c,0xe50ff107bab528a0,
+ 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8,
+ 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a,
+ 0x9faacf3df73609b1,0x77b191618c54e9ac,
+ 0xc795830d75038c1d,0xd59df5b9ef6a2417,
+ 0xf97ae3d0d2446f25,0x4b0573286b44ad1d,
+ 0x9becce62836ac577,0x4ee367f9430aec32,
+ 0xc2e801fb244576d5,0x229c41f793cda73f,
+ 0xf3a20279ed56d48a,0x6b43527578c1110f,
+ 0x9845418c345644d6,0x830a13896b78aaa9,
+ 0xbe5691ef416bd60c,0x23cc986bc656d553,
+ 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8,
+ 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9,
+ 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53,
+ 0xe858ad248f5c22c9,0xd1b3400f8f9cff68,
+ 0x91376c36d99995be,0x23100809b9c21fa1,
+ 0xb58547448ffffb2d,0xabd40a0c2832a78a,
+ 0xe2e69915b3fff9f9,0x16c90c8f323f516c,
+ 0x8dd01fad907ffc3b,0xae3da7d97f6792e3,
+ 0xb1442798f49ffb4a,0x99cd11cfdf41779c,
+ 0xdd95317f31c7fa1d,0x40405643d711d583,
+ 0x8a7d3eef7f1cfc52,0x482835ea666b2572,
+ 0xad1c8eab5ee43b66,0xda3243650005eecf,
+ 0xd863b256369d4a40,0x90bed43e40076a82,
+ 0x873e4f75e2224e68,0x5a7744a6e804a291,
+ 0xa90de3535aaae202,0x711515d0a205cb36,
+ 0xd3515c2831559a83,0xd5a5b44ca873e03,
+ 0x8412d9991ed58091,0xe858790afe9486c2,
+ 0xa5178fff668ae0b6,0x626e974dbe39a872,
+ 0xce5d73ff402d98e3,0xfb0a3d212dc8128f,
+ 0x80fa687f881c7f8e,0x7ce66634bc9d0b99,
+ 0xa139029f6a239f72,0x1c1fffc1ebc44e80,
+ 0xc987434744ac874e,0xa327ffb266b56220,
+ 0xfbe9141915d7a922,0x4bf1ff9f0062baa8,
+ 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9,
+ 0xc4ce17b399107c22,0xcb550fb4384d21d3,
+ 0xf6019da07f549b2b,0x7e2a53a146606a48,
+ 0x99c102844f94e0fb,0x2eda7444cbfc426d,
+ 0xc0314325637a1939,0xfa911155fefb5308,
+ 0xf03d93eebc589f88,0x793555ab7eba27ca,
+ 0x96267c7535b763b5,0x4bc1558b2f3458de,
+ 0xbbb01b9283253ca2,0x9eb1aaedfb016f16,
+ 0xea9c227723ee8bcb,0x465e15a979c1cadc,
+ 0x92a1958a7675175f,0xbfacd89ec191ec9,
+ 0xb749faed14125d36,0xcef980ec671f667b,
+ 0xe51c79a85916f484,0x82b7e12780e7401a,
+ 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810,
+ 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15,
+ 0xdfbdcece67006ac9,0x67a791e093e1d49a,
+ 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0,
+ 0xaecc49914078536d,0x58fae9f773886e18,
+ 0xda7f5bf590966848,0xaf39a475506a899e,
+ 0x888f99797a5e012d,0x6d8406c952429603,
+ 0xaab37fd7d8f58178,0xc8e5087ba6d33b83,
+ 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64,
+ 0x855c3be0a17fcd26,0x5cf2eea09a55067f,
+ 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e,
+ 0xd0601d8efc57b08b,0xf13b94daf124da26,
+ 0x823c12795db6ce57,0x76c53d08d6b70858,
+ 0xa2cb1717b52481ed,0x54768c4b0c64ca6e,
+ 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09,
+ 0xfe5d54150b090b02,0xd3f93b35435d7c4c,
+ 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf,
+ 0xc6b8e9b0709f109a,0x359ab6419ca1091b,
+ 0xf867241c8cc6d4c0,0xc30163d203c94b62,
+ 0x9b407691d7fc44f8,0x79e0de63425dcf1d,
+ 0xc21094364dfb5636,0x985915fc12f542e4,
+ 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d,
+ 0x979cf3ca6cec5b5a,0xa705992ceecf9c42,
+ 0xbd8430bd08277231,0x50c6ff782a838353,
+ 0xece53cec4a314ebd,0xa4f8bf5635246428,
+ 0x940f4613ae5ed136,0x871b7795e136be99,
+ 0xb913179899f68584,0x28e2557b59846e3f,
+ 0xe757dd7ec07426e5,0x331aeada2fe589cf,
+ 0x9096ea6f3848984f,0x3ff0d2c85def7621,
+ 0xb4bca50b065abe63,0xfed077a756b53a9,
+ 0xe1ebce4dc7f16dfb,0xd3e8495912c62894,
+ 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c,
+ 0xb080392cc4349dec,0xbd8d794d96aacfb3,
+ 0xdca04777f541c567,0xecf0d7a0fc5583a0,
+ 0x89e42caaf9491b60,0xf41686c49db57244,
+ 0xac5d37d5b79b6239,0x311c2875c522ced5,
+ 0xd77485cb25823ac7,0x7d633293366b828b,
+ 0x86a8d39ef77164bc,0xae5dff9c02033197,
+ 0xa8530886b54dbdeb,0xd9f57f830283fdfc,
+ 0xd267caa862a12d66,0xd072df63c324fd7b,
+ 0x8380dea93da4bc60,0x4247cb9e59f71e6d,
+ 0xa46116538d0deb78,0x52d9be85f074e608,
+ 0xcd795be870516656,0x67902e276c921f8b,
+ 0x806bd9714632dff6,0xba1cd8a3db53b6,
+ 0xa086cfcd97bf97f3,0x80e8a40eccd228a4,
+ 0xc8a883c0fdaf7df0,0x6122cd128006b2cd,
+ 0xfad2a4b13d1b5d6c,0x796b805720085f81,
+ 0x9cc3a6eec6311a63,0xcbe3303674053bb0,
+ 0xc3f490aa77bd60fc,0xbedbfc4411068a9c,
+ 0xf4f1b4d515acb93b,0xee92fb5515482d44,
+ 0x991711052d8bf3c5,0x751bdd152d4d1c4a,
+ 0xbf5cd54678eef0b6,0xd262d45a78a0635d,
+ 0xef340a98172aace4,0x86fb897116c87c34,
+ 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0,
+ 0xbae0a846d2195712,0x8974836059cca109,
+ 0xe998d258869facd7,0x2bd1a438703fc94b,
+ 0x91ff83775423cc06,0x7b6306a34627ddcf,
+ 0xb67f6455292cbf08,0x1a3bc84c17b1d542,
+ 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93,
+ 0x8e938662882af53e,0x547eb47b7282ee9c,
+ 0xb23867fb2a35b28d,0xe99e619a4f23aa43,
+ 0xdec681f9f4c31f31,0x6405fa00e2ec94d4,
+ 0x8b3c113c38f9f37e,0xde83bc408dd3dd04,
+ 0xae0b158b4738705e,0x9624ab50b148d445,
+ 0xd98ddaee19068c76,0x3badd624dd9b0957,
+ 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6,
+ 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c,
+ 0xd47487cc8470652b,0x7647c3200069671f,
+ 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073,
+ 0xa5fb0a17c777cf09,0xf468107100525890,
+ 0xcf79cc9db955c2cc,0x7182148d4066eeb4,
+ 0x81ac1fe293d599bf,0xc6f14cd848405530,
+ 0xa21727db38cb002f,0xb8ada00e5a506a7c,
+ 0xca9cf1d206fdc03b,0xa6d90811f0e4851c,
+ 0xfd442e4688bd304a,0x908f4a166d1da663,
+ 0x9e4a9cec15763e2e,0x9a598e4e043287fe,
+ 0xc5dd44271ad3cdba,0x40eff1e1853f29fd,
+ 0xf7549530e188c128,0xd12bee59e68ef47c,
+ 0x9a94dd3e8cf578b9,0x82bb74f8301958ce,
+ 0xc13a148e3032d6e7,0xe36a52363c1faf01,
+ 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1,
+ 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9,
+ 0xbcb2b812db11a5de,0x7415d448f6b6f0e7,
+ 0xebdf661791d60f56,0x111b495b3464ad21,
+ 0x936b9fcebb25c995,0xcab10dd900beec34,
+ 0xb84687c269ef3bfb,0x3d5d514f40eea742,
+ 0xe65829b3046b0afa,0xcb4a5a3112a5112,
+ 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab,
+ 0xb3f4e093db73a093,0x59ed216765690f56,
+ 0xe0f218b8d25088b8,0x306869c13ec3532c,
+ 0x8c974f7383725573,0x1e414218c73a13fb,
+ 0xafbd2350644eeacf,0xe5d1929ef90898fa,
+ 0xdbac6c247d62a583,0xdf45f746b74abf39,
+ 0x894bc396ce5da772,0x6b8bba8c328eb783,
+ 0xab9eb47c81f5114f,0x66ea92f3f326564,
+ 0xd686619ba27255a2,0xc80a537b0efefebd,
+ 0x8613fd0145877585,0xbd06742ce95f5f36,
+ 0xa798fc4196e952e7,0x2c48113823b73704,
+ 0xd17f3b51fca3a7a0,0xf75a15862ca504c5,
+ 0x82ef85133de648c4,0x9a984d73dbe722fb,
+ 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba,
+ 0xcc963fee10b7d1b3,0x318df905079926a8,
+ 0xffbbcfe994e5c61f,0xfdf17746497f7052,
+ 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633,
+ 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0,
+ 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0,
+ 0x9c1661a651213e2d,0x6bea10ca65c084e,
+ 0xc31bfa0fe5698db8,0x486e494fcff30a62,
+ 0xf3e2f893dec3f126,0x5a89dba3c3efccfa,
+ 0x986ddb5c6b3a76b7,0xf89629465a75e01c,
+ 0xbe89523386091465,0xf6bbb397f1135823,
+ 0xee2ba6c0678b597f,0x746aa07ded582e2c,
+ 0x94db483840b717ef,0xa8c2a44eb4571cdc,
+ 0xba121a4650e4ddeb,0x92f34d62616ce413,
+ 0xe896a0d7e51e1566,0x77b020baf9c81d17,
+ 0x915e2486ef32cd60,0xace1474dc1d122e,
+ 0xb5b5ada8aaff80b8,0xd819992132456ba,
+ 0xe3231912d5bf60e6,0x10e1fff697ed6c69,
+ 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1,
+ 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2,
+ 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde,
+ 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b,
+ 0xad4ab7112eb3929d,0x86c16c98d2c953c6,
+ 0xd89d64d57a607744,0xe871c7bf077ba8b7,
+ 0x87625f056c7c4a8b,0x11471cd764ad4972,
+ 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf,
+ 0xd389b47879823479,0x4aff1d108d4ec2c3,
+ 0x843610cb4bf160cb,0xcedf722a585139ba,
+ 0xa54394fe1eedb8fe,0xc2974eb4ee658828,
+ 0xce947a3da6a9273e,0x733d226229feea32,
+ 0x811ccc668829b887,0x806357d5a3f525f,
+ 0xa163ff802a3426a8,0xca07c2dcb0cf26f7,
+ 0xc9bcff6034c13052,0xfc89b393dd02f0b5,
+ 0xfc2c3f3841f17c67,0xbbac2078d443ace2,
+ 0x9d9ba7832936edc0,0xd54b944b84aa4c0d,
+ 0xc5029163f384a931,0xa9e795e65d4df11,
+ 0xf64335bcf065d37d,0x4d4617b5ff4a16d5,
+ 0x99ea0196163fa42e,0x504bced1bf8e4e45,
+ 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6,
+ 0xf07da27a82c37088,0x5d767327bb4e5a4c,
+ 0x964e858c91ba2655,0x3a6a07f8d510f86f,
+ 0xbbe226efb628afea,0x890489f70a55368b,
+ 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e,
+ 0x92c8ae6b464fc96f,0x3b0b8bc90012929d,
+ 0xb77ada0617e3bbcb,0x9ce6ebb40173744,
+ 0xe55990879ddcaabd,0xcc420a6a101d0515,
+ 0x8f57fa54c2a9eab6,0x9fa946824a12232d,
+ 0xb32df8e9f3546564,0x47939822dc96abf9,
+ 0xdff9772470297ebd,0x59787e2b93bc56f7,
+ 0x8bfbea76c619ef36,0x57eb4edb3c55b65a,
+ 0xaefae51477a06b03,0xede622920b6b23f1,
+ 0xdab99e59958885c4,0xe95fab368e45eced,
+ 0x88b402f7fd75539b,0x11dbcb0218ebb414,
+ 0xaae103b5fcd2a881,0xd652bdc29f26a119,
+ 0xd59944a37c0752a2,0x4be76d3346f0495f,
+ 0x857fcae62d8493a5,0x6f70a4400c562ddb,
+ 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952,
+ 0xd097ad07a71f26b2,0x7e2000a41346a7a7,
+ 0x825ecc24c873782f,0x8ed400668c0c28c8,
+ 0xa2f67f2dfa90563b,0x728900802f0f32fa,
+ 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9,
+ 0xfea126b7d78186bc,0xe2f610c84987bfa8,
+ 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9,
+ 0xc6ede63fa05d3143,0x91503d1c79720dbb,
+ 0xf8a95fcf88747d94,0x75a44c6397ce912a,
+ 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba,
+ 0xc24452da229b021b,0xfbe85badce996168,
+ 0xf2d56790ab41c2a2,0xfae27299423fb9c3,
+ 0x97c560ba6b0919a5,0xdccd879fc967d41a,
+ 0xbdb6b8e905cb600f,0x5400e987bbc1c920,
+ 0xed246723473e3813,0x290123e9aab23b68,
+ 0x9436c0760c86e30b,0xf9a0b6720aaf6521,
+ 0xb94470938fa89bce,0xf808e40e8d5b3e69,
+ 0xe7958cb87392c2c2,0xb60b1d1230b20e04,
+ 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2,
+ 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3,
+ 0xe2280b6c20dd5232,0x25c6da63c38de1b0,
+ 0x8d590723948a535f,0x579c487e5a38ad0e,
+ 0xb0af48ec79ace837,0x2d835a9df0c6d851,
+ 0xdcdb1b2798182244,0xf8e431456cf88e65,
+ 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff,
+ 0xac8b2d36eed2dac5,0xe272467e3d222f3f,
+ 0xd7adf884aa879177,0x5b0ed81dcc6abb0f,
+ 0x86ccbb52ea94baea,0x98e947129fc2b4e9,
+ 0xa87fea27a539e9a5,0x3f2398d747b36224,
+ 0xd29fe4b18e88640e,0x8eec7f0d19a03aad,
+ 0x83a3eeeef9153e89,0x1953cf68300424ac,
+ 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7,
+ 0xcdb02555653131b6,0x3792f412cb06794d,
+ 0x808e17555f3ebf11,0xe2bbd88bbee40bd0,
+ 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4,
+ 0xc8de047564d20a8b,0xf245825a5a445275,
+ 0xfb158592be068d2e,0xeed6e2f0f0d56712,
+ 0x9ced737bb6c4183d,0x55464dd69685606b,
+ 0xc428d05aa4751e4c,0xaa97e14c3c26b886,
+ 0xf53304714d9265df,0xd53dd99f4b3066a8,
+ 0x993fe2c6d07b7fab,0xe546a8038efe4029,
+ 0xbf8fdb78849a5f96,0xde98520472bdd033,
+ 0xef73d256a5c0f77c,0x963e66858f6d4440,
+ 0x95a8637627989aad,0xdde7001379a44aa8,
+ 0xbb127c53b17ec159,0x5560c018580d5d52,
+ 0xe9d71b689dde71af,0xaab8f01e6e10b4a6,
+ 0x9226712162ab070d,0xcab3961304ca70e8,
+ 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22,
+ 0xe45c10c42a2b3b05,0x8cb89a7db77c506a,
+ 0x8eb98a7a9a5b04e3,0x77f3608e92adb242,
+ 0xb267ed1940f1c61c,0x55f038b237591ed3,
+ 0xdf01e85f912e37a3,0x6b6c46dec52f6688,
+ 0x8b61313bbabce2c6,0x2323ac4b3b3da015,
+ 0xae397d8aa96c1b77,0xabec975e0a0d081a,
+ 0xd9c7dced53c72255,0x96e7bd358c904a21,
+ 0x881cea14545c7575,0x7e50d64177da2e54,
+ 0xaa242499697392d2,0xdde50bd1d5d0b9e9,
+ 0xd4ad2dbfc3d07787,0x955e4ec64b44e864,
+ 0x84ec3c97da624ab4,0xbd5af13bef0b113e,
+ 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e,
+ 0xcfb11ead453994ba,0x67de18eda5814af2,
+ 0x81ceb32c4b43fcf4,0x80eacf948770ced7,
+ 0xa2425ff75e14fc31,0xa1258379a94d028d,
+ 0xcad2f7f5359a3b3e,0x96ee45813a04330,
+ 0xfd87b5f28300ca0d,0x8bca9d6e188853fc,
+ 0x9e74d1b791e07e48,0x775ea264cf55347e,
+ 0xc612062576589dda,0x95364afe032a819e,
+ 0xf79687aed3eec551,0x3a83ddbd83f52205,
+ 0x9abe14cd44753b52,0xc4926a9672793543,
+ 0xc16d9a0095928a27,0x75b7053c0f178294,
+ 0xf1c90080baf72cb1,0x5324c68b12dd6339,
+ 0x971da05074da7bee,0xd3f6fc16ebca5e04,
+ 0xbce5086492111aea,0x88f4bb1ca6bcf585,
+ 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6,
+ 0x9392ee8e921d5d07,0x3aff322e62439fd0,
+ 0xb877aa3236a4b449,0x9befeb9fad487c3,
+ 0xe69594bec44de15b,0x4c2ebe687989a9b4,
+ 0x901d7cf73ab0acd9,0xf9d37014bf60a11,
+ 0xb424dc35095cd80f,0x538484c19ef38c95,
+ 0xe12e13424bb40e13,0x2865a5f206b06fba,
+ 0x8cbccc096f5088cb,0xf93f87b7442e45d4,
+ 0xafebff0bcb24aafe,0xf78f69a51539d749,
+ 0xdbe6fecebdedd5be,0xb573440e5a884d1c,
+ 0x89705f4136b4a597,0x31680a88f8953031,
+ 0xabcc77118461cefc,0xfdc20d2b36ba7c3e,
+ 0xd6bf94d5e57a42bc,0x3d32907604691b4d,
+ 0x8637bd05af6c69b5,0xa63f9a49c2c1b110,
+ 0xa7c5ac471b478423,0xfcf80dc33721d54,
+ 0xd1b71758e219652b,0xd3c36113404ea4a9,
+ 0x83126e978d4fdf3b,0x645a1cac083126ea,
+ 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4,
+ 0xcccccccccccccccc,0xcccccccccccccccd,
+ 0x8000000000000000,0x0,
+ 0xa000000000000000,0x0,
+ 0xc800000000000000,0x0,
+ 0xfa00000000000000,0x0,
+ 0x9c40000000000000,0x0,
+ 0xc350000000000000,0x0,
+ 0xf424000000000000,0x0,
+ 0x9896800000000000,0x0,
+ 0xbebc200000000000,0x0,
+ 0xee6b280000000000,0x0,
+ 0x9502f90000000000,0x0,
+ 0xba43b74000000000,0x0,
+ 0xe8d4a51000000000,0x0,
+ 0x9184e72a00000000,0x0,
+ 0xb5e620f480000000,0x0,
+ 0xe35fa931a0000000,0x0,
+ 0x8e1bc9bf04000000,0x0,
+ 0xb1a2bc2ec5000000,0x0,
+ 0xde0b6b3a76400000,0x0,
+ 0x8ac7230489e80000,0x0,
+ 0xad78ebc5ac620000,0x0,
+ 0xd8d726b7177a8000,0x0,
+ 0x878678326eac9000,0x0,
+ 0xa968163f0a57b400,0x0,
+ 0xd3c21bcecceda100,0x0,
+ 0x84595161401484a0,0x0,
+ 0xa56fa5b99019a5c8,0x0,
+ 0xcecb8f27f4200f3a,0x0,
+ 0x813f3978f8940984,0x4000000000000000,
+ 0xa18f07d736b90be5,0x5000000000000000,
+ 0xc9f2c9cd04674ede,0xa400000000000000,
+ 0xfc6f7c4045812296,0x4d00000000000000,
+ 0x9dc5ada82b70b59d,0xf020000000000000,
+ 0xc5371912364ce305,0x6c28000000000000,
+ 0xf684df56c3e01bc6,0xc732000000000000,
+ 0x9a130b963a6c115c,0x3c7f400000000000,
+ 0xc097ce7bc90715b3,0x4b9f100000000000,
+ 0xf0bdc21abb48db20,0x1e86d40000000000,
+ 0x96769950b50d88f4,0x1314448000000000,
+ 0xbc143fa4e250eb31,0x17d955a000000000,
+ 0xeb194f8e1ae525fd,0x5dcfab0800000000,
+ 0x92efd1b8d0cf37be,0x5aa1cae500000000,
+ 0xb7abc627050305ad,0xf14a3d9e40000000,
+ 0xe596b7b0c643c719,0x6d9ccd05d0000000,
+ 0x8f7e32ce7bea5c6f,0xe4820023a2000000,
+ 0xb35dbf821ae4f38b,0xdda2802c8a800000,
+ 0xe0352f62a19e306e,0xd50b2037ad200000,
+ 0x8c213d9da502de45,0x4526f422cc340000,
+ 0xaf298d050e4395d6,0x9670b12b7f410000,
+ 0xdaf3f04651d47b4c,0x3c0cdd765f114000,
+ 0x88d8762bf324cd0f,0xa5880a69fb6ac800,
+ 0xab0e93b6efee0053,0x8eea0d047a457a00,
+ 0xd5d238a4abe98068,0x72a4904598d6d880,
+ 0x85a36366eb71f041,0x47a6da2b7f864750,
+ 0xa70c3c40a64e6c51,0x999090b65f67d924,
+ 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d,
+ 0x82818f1281ed449f,0xbff8f10e7a8921a4,
+ 0xa321f2d7226895c7,0xaff72d52192b6a0d,
+ 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490,
+ 0xfee50b7025c36a08,0x2f236d04753d5b4,
+ 0x9f4f2726179a2245,0x1d762422c946590,
+ 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5,
+ 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2,
+ 0x9b934c3b330c8577,0x63cc55f49f88eb2f,
+ 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb,
+ 0xf316271c7fc3908a,0x8bef464e3945ef7a,
+ 0x97edd871cfda3a56,0x97758bf0e3cbb5ac,
+ 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317,
+ 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd,
+ 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a,
+ 0xb975d6b6ee39e436,0xb3e2fd538e122b44,
+ 0xe7d34c64a9c85d44,0x60dbbca87196b616,
+ 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd,
+ 0xb51d13aea4a488dd,0x6babab6398bdbe41,
+ 0xe264589a4dcdab14,0xc696963c7eed2dd1,
+ 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2,
+ 0xb0de65388cc8ada8,0x3b25a55f43294bcb,
+ 0xdd15fe86affad912,0x49ef0eb713f39ebe,
+ 0x8a2dbf142dfcc7ab,0x6e3569326c784337,
+ 0xacb92ed9397bf996,0x49c2c37f07965404,
+ 0xd7e77a8f87daf7fb,0xdc33745ec97be906,
+ 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3,
+ 0xa8acd7c0222311bc,0xc40832ea0d68ce0c,
+ 0xd2d80db02aabd62b,0xf50a3fa490c30190,
+ 0x83c7088e1aab65db,0x792667c6da79e0fa,
+ 0xa4b8cab1a1563f52,0x577001b891185938,
+ 0xcde6fd5e09abcf26,0xed4c0226b55e6f86,
+ 0x80b05e5ac60b6178,0x544f8158315b05b4,
+ 0xa0dc75f1778e39d6,0x696361ae3db1c721,
+ 0xc913936dd571c84c,0x3bc3a19cd1e38e9,
+ 0xfb5878494ace3a5f,0x4ab48a04065c723,
+ 0x9d174b2dcec0e47b,0x62eb0d64283f9c76,
+ 0xc45d1df942711d9a,0x3ba5d0bd324f8394,
+ 0xf5746577930d6500,0xca8f44ec7ee36479,
+ 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb,
+ 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e,
+ 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e,
+ 0x95d04aee3b80ece5,0xbba1f1d158724a12,
+ 0xbb445da9ca61281f,0x2a8a6e45ae8edc97,
+ 0xea1575143cf97226,0xf52d09d71a3293bd,
+ 0x924d692ca61be758,0x593c2626705f9c56,
+ 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c,
+ 0xe498f455c38b997a,0xb6dfb9c0f956447,
+ 0x8edf98b59a373fec,0x4724bd4189bd5eac,
+ 0xb2977ee300c50fe7,0x58edec91ec2cb657,
+ 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed,
+ 0x8b865b215899f46c,0xbd79e0d20082ee74,
+ 0xae67f1e9aec07187,0xecd8590680a3aa11,
+ 0xda01ee641a708de9,0xe80e6f4820cc9495,
+ 0x884134fe908658b2,0x3109058d147fdcdd,
+ 0xaa51823e34a7eede,0xbd4b46f0599fd415,
+ 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a,
+ 0x850fadc09923329e,0x3e2cf6bc604ddb0,
+ 0xa6539930bf6bff45,0x84db8346b786151c,
+ 0xcfe87f7cef46ff16,0xe612641865679a63,
+ 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e,
+ 0xa26da3999aef7749,0xe3be5e330f38f09d,
+ 0xcb090c8001ab551c,0x5cadf5bfd3072cc5,
+ 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6,
+ 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa,
+ 0xc646d63501a1511d,0xb281e1fd541501b8,
+ 0xf7d88bc24209a565,0x1f225a7ca91a4226,
+ 0x9ae757596946075f,0x3375788de9b06958,
+ 0xc1a12d2fc3978937,0x52d6b1641c83ae,
+ 0xf209787bb47d6b84,0xc0678c5dbd23a49a,
+ 0x9745eb4d50ce6332,0xf840b7ba963646e0,
+ 0xbd176620a501fbff,0xb650e5a93bc3d898,
+ 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe,
+ 0x93ba47c980e98cdf,0xc66f336c36b10137,
+ 0xb8a8d9bbe123f017,0xb80b0047445d4184,
+ 0xe6d3102ad96cec1d,0xa60dc059157491e5,
+ 0x9043ea1ac7e41392,0x87c89837ad68db2f,
+ 0xb454e4a179dd1877,0x29babe4598c311fb,
+ 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a,
+ 0x8ce2529e2734bb1d,0x1899e4a65f58660c,
+ 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f,
+ 0xdc21a1171d42645d,0x76707543f4fa1f73,
+ 0x899504ae72497eba,0x6a06494a791c53a8,
+ 0xabfa45da0edbde69,0x487db9d17636892,
+ 0xd6f8d7509292d603,0x45a9d2845d3c42b6,
+ 0x865b86925b9bc5c2,0xb8a2392ba45a9b2,
+ 0xa7f26836f282b732,0x8e6cac7768d7141e,
+ 0xd1ef0244af2364ff,0x3207d795430cd926,
+ 0x8335616aed761f1f,0x7f44e6bd49e807b8,
+ 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6,
+ 0xcd036837130890a1,0x36dba887c37a8c0f,
+ 0x802221226be55a64,0xc2494954da2c9789,
+ 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c,
+ 0xc83553c5c8965d3d,0x6f92829494e5acc7,
+ 0xfa42a8b73abbf48c,0xcb772339ba1f17f9,
+ 0x9c69a97284b578d7,0xff2a760414536efb,
+ 0xc38413cf25e2d70d,0xfef5138519684aba,
+ 0xf46518c2ef5b8cd1,0x7eb258665fc25d69,
+ 0x98bf2f79d5993802,0xef2f773ffbd97a61,
+ 0xbeeefb584aff8603,0xaafb550ffacfd8fa,
+ 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38,
+ 0x952ab45cfa97a0b2,0xdd945a747bf26183,
+ 0xba756174393d88df,0x94f971119aeef9e4,
+ 0xe912b9d1478ceb17,0x7a37cd5601aab85d,
+ 0x91abb422ccb812ee,0xac62e055c10ab33a,
+ 0xb616a12b7fe617aa,0x577b986b314d6009,
+ 0xe39c49765fdf9d94,0xed5a7e85fda0b80b,
+ 0x8e41ade9fbebc27d,0x14588f13be847307,
+ 0xb1d219647ae6b31c,0x596eb2d8ae258fc8,
+ 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb,
+ 0x8aec23d680043bee,0x25de7bb9480d5854,
+ 0xada72ccc20054ae9,0xaf561aa79a10ae6a,
+ 0xd910f7ff28069da4,0x1b2ba1518094da04,
+ 0x87aa9aff79042286,0x90fb44d2f05d0842,
+ 0xa99541bf57452b28,0x353a1607ac744a53,
+ 0xd3fa922f2d1675f2,0x42889b8997915ce8,
+ 0x847c9b5d7c2e09b7,0x69956135febada11,
+ 0xa59bc234db398c25,0x43fab9837e699095,
+ 0xcf02b2c21207ef2e,0x94f967e45e03f4bb,
+ 0x8161afb94b44f57d,0x1d1be0eebac278f5,
+ 0xa1ba1ba79e1632dc,0x6462d92a69731732,
+ 0xca28a291859bbf93,0x7d7b8f7503cfdcfe,
+ 0xfcb2cb35e702af78,0x5cda735244c3d43e,
+ 0x9defbf01b061adab,0x3a0888136afa64a7,
+ 0xc56baec21c7a1916,0x88aaa1845b8fdd0,
+ 0xf6c69a72a3989f5b,0x8aad549e57273d45,
+ 0x9a3c2087a63f6399,0x36ac54e2f678864b,
+ 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd,
+ 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5,
+ 0x969eb7c47859e743,0x9f644ae5a4b1b325,
+ 0xbc4665b596706114,0x873d5d9f0dde1fee,
+ 0xeb57ff22fc0c7959,0xa90cb506d155a7ea,
+ 0x9316ff75dd87cbd8,0x9a7f12442d588f2,
+ 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f,
+ 0xe5d3ef282a242e81,0x8f1668c8a86da5fa,
+ 0x8fa475791a569d10,0xf96e017d694487bc,
+ 0xb38d92d760ec4455,0x37c981dcc395a9ac,
+ 0xe070f78d3927556a,0x85bbe253f47b1417,
+ 0x8c469ab843b89562,0x93956d7478ccec8e,
+ 0xaf58416654a6babb,0x387ac8d1970027b2,
+ 0xdb2e51bfe9d0696a,0x6997b05fcc0319e,
+ 0x88fcf317f22241e2,0x441fece3bdf81f03,
+ 0xab3c2fddeeaad25a,0xd527e81cad7626c3,
+ 0xd60b3bd56a5586f1,0x8a71e223d8d3b074,
+ 0x85c7056562757456,0xf6872d5667844e49,
+ 0xa738c6bebb12d16c,0xb428f8ac016561db,
+ 0xd106f86e69d785c7,0xe13336d701beba52,
+ 0x82a45b450226b39c,0xecc0024661173473,
+ 0xa34d721642b06084,0x27f002d7f95d0190,
+ 0xcc20ce9bd35c78a5,0x31ec038df7b441f4,
+ 0xff290242c83396ce,0x7e67047175a15271,
+ 0x9f79a169bd203e41,0xf0062c6e984d386,
+ 0xc75809c42c684dd1,0x52c07b78a3e60868,
+ 0xf92e0c3537826145,0xa7709a56ccdf8a82,
+ 0x9bbcc7a142b17ccb,0x88a66076400bb691,
+ 0xc2abf989935ddbfe,0x6acff893d00ea435,
+ 0xf356f7ebf83552fe,0x583f6b8c4124d43,
+ 0x98165af37b2153de,0xc3727a337a8b704a,
+ 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c,
+ 0xeda2ee1c7064130c,0x1162def06f79df73,
+ 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8,
+ 0xb9a74a0637ce2ee1,0x6d953e2bd7173692,
+ 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437,
+ 0x910ab1d4db9914a0,0x1d9c9892400a22a2,
+ 0xb54d5e4a127f59c8,0x2503beb6d00cab4b,
+ 0xe2a0b5dc971f303a,0x2e44ae64840fd61d,
+ 0x8da471a9de737e24,0x5ceaecfed289e5d2,
+ 0xb10d8e1456105dad,0x7425a83e872c5f47,
+ 0xdd50f1996b947518,0xd12f124e28f77719,
+ 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f,
+ 0xace73cbfdc0bfb7b,0x636cc64d1001550b,
+ 0xd8210befd30efa5a,0x3c47f7e05401aa4e,
+ 0x8714a775e3e95c78,0x65acfaec34810a71,
+ 0xa8d9d1535ce3b396,0x7f1839a741a14d0d,
+ 0xd31045a8341ca07c,0x1ede48111209a050,
+ 0x83ea2b892091e44d,0x934aed0aab460432,
+ 0xa4e4b66b68b65d60,0xf81da84d5617853f,
+ 0xce1de40642e3f4b9,0x36251260ab9d668e,
+ 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019,
+ 0xa1075a24e4421730,0xb24cf65b8612f81f,
+ 0xc94930ae1d529cfc,0xdee033f26797b627,
+ 0xfb9b7cd9a4a7443c,0x169840ef017da3b1,
+ 0x9d412e0806e88aa5,0x8e1f289560ee864e,
+ 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2,
+ 0xf5b5d7ec8acb58a2,0xae10af696774b1db,
+ 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29,
+ 0xbff610b0cc6edd3f,0x17fd090a58d32af3,
+ 0xeff394dcff8a948e,0xddfc4b4cef07f5b0,
+ 0x95f83d0a1fb69cd9,0x4abdaf101564f98e,
+ 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1,
+ 0xea53df5fd18d5513,0x84c86189216dc5ed,
+ 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4,
+ 0xb7118682dbb66a77,0x3fbc8c33221dc2a1,
+ 0xe4d5e82392a40515,0xfabaf3feaa5334a,
+ 0x8f05b1163ba6832d,0x29cb4d87f2a7400e,
+ 0xb2c71d5bca9023f8,0x743e20e9ef511012,
+ 0xdf78e4b2bd342cf6,0x914da9246b255416,
+ 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e,
+ 0xae9672aba3d0c320,0xa184ac2473b529b1,
+ 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e,
+ 0x8865899617fb1871,0x7e2fa67c7a658892,
+ 0xaa7eebfb9df9de8d,0xddbb901b98feeab7,
+ 0xd51ea6fa85785631,0x552a74227f3ea565,
+ 0x8533285c936b35de,0xd53a88958f87275f,
+ 0xa67ff273b8460356,0x8a892abaf368f137,
+ 0xd01fef10a657842c,0x2d2b7569b0432d85,
+ 0x8213f56a67f6b29b,0x9c3b29620e29fc73,
+ 0xa298f2c501f45f42,0x8349f3ba91b47b8f,
+ 0xcb3f2f7642717713,0x241c70a936219a73,
+ 0xfe0efb53d30dd4d7,0xed238cd383aa0110,
+ 0x9ec95d1463e8a506,0xf4363804324a40aa,
+ 0xc67bb4597ce2ce48,0xb143c6053edcd0d5,
+ 0xf81aa16fdc1b81da,0xdd94b7868e94050a,
+ 0x9b10a4e5e9913128,0xca7cf2b4191c8326,
+ 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0,
+ 0xf24a01a73cf2dccf,0xbc633b39673c8cec,
+ 0x976e41088617ca01,0xd5be0503e085d813,
+ 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18,
+ 0xec9c459d51852ba2,0xddf8e7d60ed1219e,
+ 0x93e1ab8252f33b45,0xcabb90e5c942b503,
+ 0xb8da1662e7b00a17,0x3d6a751f3b936243,
+ 0xe7109bfba19c0c9d,0xcc512670a783ad4,
+ 0x906a617d450187e2,0x27fb2b80668b24c5,
+ 0xb484f9dc9641e9da,0xb1f9f660802dedf6,
+ 0xe1a63853bbd26451,0x5e7873f8a0396973,
+ 0x8d07e33455637eb2,0xdb0b487b6423e1e8,
+ 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62,
+ 0xdc5c5301c56b75f7,0x7641a140cc7810fb,
+ 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d,
+ 0xac2820d9623bf429,0x546345fa9fbdcd44,
+ 0xd732290fbacaf133,0xa97c177947ad4095,
+ 0x867f59a9d4bed6c0,0x49ed8eabcccc485d,
+ 0xa81f301449ee8c70,0x5c68f256bfff5a74,
+ 0xd226fc195c6a2f8c,0x73832eec6fff3111,
+ 0x83585d8fd9c25db7,0xc831fd53c5ff7eab,
+ 0xa42e74f3d032f525,0xba3e7ca8b77f5e55,
+ 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb,
+ 0x80444b5e7aa7cf85,0x7980d163cf5b81b3,
+ 0xa0555e361951c366,0xd7e105bcc332621f,
+ 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7,
+ 0xfa856334878fc150,0xb14f98f6f0feb951,
+ 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3,
+ 0xc3b8358109e84f07,0xa862f80ec4700c8,
+ 0xf4a642e14c6262c8,0xcd27bb612758c0fa,
+ 0x98e7e9cccfbd7dbd,0x8038d51cb897789c,
+ 0xbf21e44003acdd2c,0xe0470a63e6bd56c3,
+ 0xeeea5d5004981478,0x1858ccfce06cac74,
+ 0x95527a5202df0ccb,0xf37801e0c43ebc8,
+ 0xbaa718e68396cffd,0xd30560258f54e6ba,
+ 0xe950df20247c83fd,0x47c6b82ef32a2069,
+ 0x91d28b7416cdd27e,0x4cdc331d57fa5441,
+ 0xb6472e511c81471d,0xe0133fe4adf8e952,
+ 0xe3d8f9e563a198e5,0x58180fddd97723a6,
+ 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,};
+using powers = powers_template<>;
+
+}
+
+#endif
+
+#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H
+#define FASTFLOAT_DECIMAL_TO_BINARY_H
+
+#include <cfloat>
+#include <cinttypes>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+
+namespace fast_float {
+
+// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating
+// the result, with the "high" part corresponding to the most significant bits and the
+// low part corresponding to the least significant bits.
+//
+template <int bit_precision>
+fastfloat_really_inline
+value128 compute_product_approximation(int64_t q, uint64_t w) {
+ const int index = 2 * int(q - powers::smallest_power_of_five);
+ // For small values of q, e.g., q in [0,27], the answer is always exact because
+ // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]);
+ // gives the exact answer.
+ value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]);
+ static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]");
+ constexpr uint64_t precision_mask = (bit_precision < 64) ?
+ (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision)
+ : uint64_t(0xFFFFFFFFFFFFFFFF);
+ if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower)
+ // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed.
+ value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]);
+ firstproduct.low += secondproduct.high;
+ if(secondproduct.high > firstproduct.low) {
+ firstproduct.high++;
+ }
+ }
+ return firstproduct;
+}
+
+namespace detail {
+/**
+ * For q in (0,350), we have that
+ * f = (((152170 + 65536) * q ) >> 16);
+ * is equal to
+ * floor(p) + q
+ * where
+ * p = log(5**q)/log(2) = q * log(5)/log(2)
+ *
+ * For negative values of q in (-400,0), we have that
+ * f = (((152170 + 65536) * q ) >> 16);
+ * is equal to
+ * -ceil(p) + q
+ * where
+ * p = log(5**-q)/log(2) = -q * log(5)/log(2)
+ */
+ constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept {
+ return (((152170 + 65536) * q) >> 16) + 63;
+ }
+} // namespace detail
+
+// create an adjusted mantissa, biased by the invalid power2
+// for significant digits already multiplied by 10 ** q.
+template <typename binary>
+fastfloat_really_inline
+adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept {
+ int hilz = int(w >> 63) ^ 1;
+ adjusted_mantissa answer;
+ answer.mantissa = w << hilz;
+ int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent();
+ answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias);
+ return answer;
+}
+
+// w * 10 ** q, without rounding the representation up.
+// the power2 in the exponent will be adjusted by invalid_am_bias.
+template <typename binary>
+fastfloat_really_inline
+adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept {
+ int lz = leading_zeroes(w);
+ w <<= lz;
+ value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
+ return compute_error_scaled<binary>(q, product.high, lz);
+}
+
+// w * 10 ** q
+// The returned value should be a valid ieee64 number that simply need to be packed.
+// However, in some very rare cases, the computation will fail. In such cases, we
+// return an adjusted_mantissa with a negative power of 2: the caller should recompute
+// in such cases.
+template <typename binary>
+fastfloat_really_inline
+adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept {
+ adjusted_mantissa answer;
+ if ((w == 0) || (q < binary::smallest_power_of_ten())) {
+ answer.power2 = 0;
+ answer.mantissa = 0;
+ // result should be zero
+ return answer;
+ }
+ if (q > binary::largest_power_of_ten()) {
+ // we want to get infinity:
+ answer.power2 = binary::infinite_power();
+ answer.mantissa = 0;
+ return answer;
+ }
+ // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five].
+
+ // We want the most significant bit of i to be 1. Shift if needed.
+ int lz = leading_zeroes(w);
+ w <<= lz;
+
+ // The required precision is binary::mantissa_explicit_bits() + 3 because
+ // 1. We need the implicit bit
+ // 2. We need an extra bit for rounding purposes
+ // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift)
+
+ value128 product = compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w);
+ if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further
+ // In some very rare cases, this could happen, in which case we might need a more accurate
+ // computation that what we can provide cheaply. This is very, very unlikely.
+ //
+ const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0,
+ // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation.
+ if(!inside_safe_exponent) {
+ return compute_error_scaled<binary>(q, product.high, lz);
+ }
+ }
+ // The "compute_product_approximation" function can be slightly slower than a branchless approach:
+ // value128 product = compute_product(q, w);
+ // but in practice, we can win big with the compute_product_approximation if its additional branch
+ // is easily predicted. Which is best is data specific.
+ int upperbit = int(product.high >> 63);
+
+ answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3);
+
+ answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent());
+ if (answer.power2 <= 0) { // we have a subnormal?
+ // Here have that answer.power2 <= 0 so -answer.power2 >= 0
+ if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure.
+ answer.power2 = 0;
+ answer.mantissa = 0;
+ // result should be zero
+ return answer;
+ }
+ // next line is safe because -answer.power2 + 1 < 64
+ answer.mantissa >>= -answer.power2 + 1;
+ // Thankfully, we can't have both "round-to-even" and subnormals because
+ // "round-to-even" only occurs for powers close to 0.
+ answer.mantissa += (answer.mantissa & 1); // round up
+ answer.mantissa >>= 1;
+ // There is a weird scenario where we don't have a subnormal but just.
+ // Suppose we start with 2.2250738585072013e-308, we end up
+ // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal
+ // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round
+ // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer
+ // subnormal, but we can only know this after rounding.
+ // So we only declare a subnormal if we are smaller than the threshold.
+ answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1;
+ return answer;
+ }
+
+ // usually, we round *up*, but if we fall right in between and and we have an
+ // even basis, we need to round down
+ // We are only concerned with the cases where 5**q fits in single 64-bit word.
+ if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) &&
+ ((answer.mantissa & 3) == 1) ) { // we may fall between two floats!
+ // To be in-between two floats we need that in doing
+ // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3);
+ // ... we dropped out only zeroes. But if this happened, then we can go back!!!
+ if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) {
+ answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up
+ }
+ }
+
+ answer.mantissa += (answer.mantissa & 1); // round up
+ answer.mantissa >>= 1;
+ if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) {
+ answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits());
+ answer.power2++; // undo previous addition
+ }
+
+ answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits());
+ if (answer.power2 >= binary::infinite_power()) { // infinity
+ answer.power2 = binary::infinite_power();
+ answer.mantissa = 0;
+ }
+ return answer;
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_BIGINT_H
+#define FASTFLOAT_BIGINT_H
+
+#include <algorithm>
+#include <cstdint>
+#include <climits>
+#include <cstring>
+
+
+namespace fast_float {
+
+// the limb width: we want efficient multiplication of double the bits in
+// limb, or for 64-bit limbs, at least 64-bit multiplication where we can
+// extract the high and low parts efficiently. this is every 64-bit
+// architecture except for sparc, which emulates 128-bit multiplication.
+// we might have platforms where `CHAR_BIT` is not 8, so let's avoid
+// doing `8 * sizeof(limb)`.
+#if defined(FASTFLOAT_64BIT) && !defined(__sparc)
+#define FASTFLOAT_64BIT_LIMB
+typedef uint64_t limb;
+constexpr size_t limb_bits = 64;
+#else
+#define FASTFLOAT_32BIT_LIMB
+typedef uint32_t limb;
+constexpr size_t limb_bits = 32;
+#endif
+
+typedef span<limb> limb_span;
+
+// number of bits in a bigint. this needs to be at least the number
+// of bits required to store the largest bigint, which is
+// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or
+// ~3600 bits, so we round to 4000.
+constexpr size_t bigint_bits = 4000;
+constexpr size_t bigint_limbs = bigint_bits / limb_bits;
+
+// vector-like type that is allocated on the stack. the entire
+// buffer is pre-allocated, and only the length changes.
+template <uint16_t size>
+struct stackvec {
+ limb data[size];
+ // we never need more than 150 limbs
+ uint16_t length{0};
+
+ stackvec() = default;
+ stackvec(const stackvec &) = delete;
+ stackvec &operator=(const stackvec &) = delete;
+ stackvec(stackvec &&) = delete;
+ stackvec &operator=(stackvec &&other) = delete;
+
+ // create stack vector from existing limb span.
+ stackvec(limb_span s) {
+ FASTFLOAT_ASSERT(try_extend(s));
+ }
+
+ limb& operator[](size_t index) noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
+ const limb& operator[](size_t index) const noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
+ // index from the end of the container
+ const limb& rindex(size_t index) const noexcept {
+ FASTFLOAT_DEBUG_ASSERT(index < length);
+ size_t rindex = length - index - 1;
+ return data[rindex];
+ }
+
+ // set the length, without bounds checking.
+ void set_len(size_t len) noexcept {
+ length = uint16_t(len);
+ }
+ constexpr size_t len() const noexcept {
+ return length;
+ }
+ constexpr bool is_empty() const noexcept {
+ return length == 0;
+ }
+ constexpr size_t capacity() const noexcept {
+ return size;
+ }
+ // append item to vector, without bounds checking
+ void push_unchecked(limb value) noexcept {
+ data[length] = value;
+ length++;
+ }
+ // append item to vector, returning if item was added
+ bool try_push(limb value) noexcept {
+ if (len() < capacity()) {
+ push_unchecked(value);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // add items to the vector, from a span, without bounds checking
+ void extend_unchecked(limb_span s) noexcept {
+ limb* ptr = data + length;
+ ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len());
+ set_len(len() + s.len());
+ }
+ // try to add items to the vector, returning if items were added
+ bool try_extend(limb_span s) noexcept {
+ if (len() + s.len() <= capacity()) {
+ extend_unchecked(s);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // resize the vector, without bounds checking
+ // if the new size is longer than the vector, assign value to each
+ // appended item.
+ void resize_unchecked(size_t new_len, limb value) noexcept {
+ if (new_len > len()) {
+ size_t count = new_len - len();
+ limb* first = data + len();
+ limb* last = first + count;
+ ::std::fill(first, last, value);
+ set_len(new_len);
+ } else {
+ set_len(new_len);
+ }
+ }
+ // try to resize the vector, returning if the vector was resized.
+ bool try_resize(size_t new_len, limb value) noexcept {
+ if (new_len > capacity()) {
+ return false;
+ } else {
+ resize_unchecked(new_len, value);
+ return true;
+ }
+ }
+ // check if any limbs are non-zero after the given index.
+ // this needs to be done in reverse order, since the index
+ // is relative to the most significant limbs.
+ bool nonzero(size_t index) const noexcept {
+ while (index < len()) {
+ if (rindex(index) != 0) {
+ return true;
+ }
+ index++;
+ }
+ return false;
+ }
+ // normalize the big integer, so most-significant zero limbs are removed.
+ void normalize() noexcept {
+ while (len() > 0 && rindex(0) == 0) {
+ length--;
+ }
+ }
+};
+
+fastfloat_really_inline
+uint64_t empty_hi64(bool& truncated) noexcept {
+ truncated = false;
+ return 0;
+}
+
+fastfloat_really_inline
+uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept {
+ truncated = false;
+ int shl = leading_zeroes(r0);
+ return r0 << shl;
+}
+
+fastfloat_really_inline
+uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept {
+ int shl = leading_zeroes(r0);
+ if (shl == 0) {
+ truncated = r1 != 0;
+ return r0;
+ } else {
+ int shr = 64 - shl;
+ truncated = (r1 << shl) != 0;
+ return (r0 << shl) | (r1 >> shr);
+ }
+}
+
+fastfloat_really_inline
+uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept {
+ return uint64_hi64(r0, truncated);
+}
+
+fastfloat_really_inline
+uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept {
+ uint64_t x0 = r0;
+ uint64_t x1 = r1;
+ return uint64_hi64((x0 << 32) | x1, truncated);
+}
+
+fastfloat_really_inline
+uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept {
+ uint64_t x0 = r0;
+ uint64_t x1 = r1;
+ uint64_t x2 = r2;
+ return uint64_hi64(x0, (x1 << 32) | x2, truncated);
+}
+
+// add two small integers, checking for overflow.
+// we want an efficient operation. for msvc, where
+// we don't have built-in intrinsics, this is still
+// pretty fast.
+fastfloat_really_inline
+limb scalar_add(limb x, limb y, bool& overflow) noexcept {
+ limb z;
+
+// gcc and clang
+#if defined(__has_builtin)
+ #if __has_builtin(__builtin_add_overflow)
+ overflow = __builtin_add_overflow(x, y, &z);
+ return z;
+ #endif
+#endif
+
+ // generic, this still optimizes correctly on MSVC.
+ z = x + y;
+ overflow = z < x;
+ return z;
+}
+
+// multiply two small integers, getting both the high and low bits.
+fastfloat_really_inline
+limb scalar_mul(limb x, limb y, limb& carry) noexcept {
+#ifdef FASTFLOAT_64BIT_LIMB
+ #if defined(__SIZEOF_INT128__)
+ // GCC and clang both define it as an extension.
+ __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry);
+ carry = limb(z >> limb_bits);
+ return limb(z);
+ #else
+ // fallback, no native 128-bit integer multiplication with carry.
+ // on msvc, this optimizes identically, somehow.
+ value128 z = full_multiplication(x, y);
+ bool overflow;
+ z.low = scalar_add(z.low, carry, overflow);
+ z.high += uint64_t(overflow); // cannot overflow
+ carry = z.high;
+ return z.low;
+ #endif
+#else
+ uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry);
+ carry = limb(z >> limb_bits);
+ return limb(z);
+#endif
+}
+
+// add scalar value to bigint starting from offset.
+// used in grade school multiplication
+template <uint16_t size>
+inline bool small_add_from(stackvec<size>& vec, limb y, size_t start) noexcept {
+ size_t index = start;
+ limb carry = y;
+ bool overflow;
+ while (carry != 0 && index < vec.len()) {
+ vec[index] = scalar_add(vec[index], carry, overflow);
+ carry = limb(overflow);
+ index += 1;
+ }
+ if (carry != 0) {
+ FASTFLOAT_TRY(vec.try_push(carry));
+ }
+ return true;
+}
+
+// add scalar value to bigint.
+template <uint16_t size>
+fastfloat_really_inline bool small_add(stackvec<size>& vec, limb y) noexcept {
+ return small_add_from(vec, y, 0);
+}
+
+// multiply bigint by scalar value.
+template <uint16_t size>
+inline bool small_mul(stackvec<size>& vec, limb y) noexcept {
+ limb carry = 0;
+ for (size_t index = 0; index < vec.len(); index++) {
+ vec[index] = scalar_mul(vec[index], y, carry);
+ }
+ if (carry != 0) {
+ FASTFLOAT_TRY(vec.try_push(carry));
+ }
+ return true;
+}
+
+// add bigint to bigint starting from index.
+// used in grade school multiplication
+template <uint16_t size>
+bool large_add_from(stackvec<size>& x, limb_span y, size_t start) noexcept {
+ // the effective x buffer is from `xstart..x.len()`, so exit early
+ // if we can't get that current range.
+ if (x.len() < start || y.len() > x.len() - start) {
+ FASTFLOAT_TRY(x.try_resize(y.len() + start, 0));
+ }
+
+ bool carry = false;
+ for (size_t index = 0; index < y.len(); index++) {
+ limb xi = x[index + start];
+ limb yi = y[index];
+ bool c1 = false;
+ bool c2 = false;
+ xi = scalar_add(xi, yi, c1);
+ if (carry) {
+ xi = scalar_add(xi, 1, c2);
+ }
+ x[index + start] = xi;
+ carry = c1 | c2;
+ }
+
+ // handle overflow
+ if (carry) {
+ FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start));
+ }
+ return true;
+}
+
+// add bigint to bigint.
+template <uint16_t size>
+fastfloat_really_inline bool large_add_from(stackvec<size>& x, limb_span y) noexcept {
+ return large_add_from(x, y, 0);
+}
+
+// grade-school multiplication algorithm
+template <uint16_t size>
+bool long_mul(stackvec<size>& x, limb_span y) noexcept {
+ limb_span xs = limb_span(x.data, x.len());
+ stackvec<size> z(xs);
+ limb_span zs = limb_span(z.data, z.len());
+
+ if (y.len() != 0) {
+ limb y0 = y[0];
+ FASTFLOAT_TRY(small_mul(x, y0));
+ for (size_t index = 1; index < y.len(); index++) {
+ limb yi = y[index];
+ stackvec<size> zi;
+ if (yi != 0) {
+ // re-use the same buffer throughout
+ zi.set_len(0);
+ FASTFLOAT_TRY(zi.try_extend(zs));
+ FASTFLOAT_TRY(small_mul(zi, yi));
+ limb_span zis = limb_span(zi.data, zi.len());
+ FASTFLOAT_TRY(large_add_from(x, zis, index));
+ }
+ }
+ }
+
+ x.normalize();
+ return true;
+}
+
+// grade-school multiplication algorithm
+template <uint16_t size>
+bool large_mul(stackvec<size>& x, limb_span y) noexcept {
+ if (y.len() == 1) {
+ FASTFLOAT_TRY(small_mul(x, y[0]));
+ } else {
+ FASTFLOAT_TRY(long_mul(x, y));
+ }
+ return true;
+}
+
+// big integer type. implements a small subset of big integer
+// arithmetic, using simple algorithms since asymptotically
+// faster algorithms are slower for a small number of limbs.
+// all operations assume the big-integer is normalized.
+struct bigint {
+ // storage of the limbs, in little-endian order.
+ stackvec<bigint_limbs> vec;
+
+ bigint(): vec() {}
+ bigint(const bigint &) = delete;
+ bigint &operator=(const bigint &) = delete;
+ bigint(bigint &&) = delete;
+ bigint &operator=(bigint &&other) = delete;
+
+ bigint(uint64_t value): vec() {
+#ifdef FASTFLOAT_64BIT_LIMB
+ vec.push_unchecked(value);
+#else
+ vec.push_unchecked(uint32_t(value));
+ vec.push_unchecked(uint32_t(value >> 32));
+#endif
+ vec.normalize();
+ }
+
+ // get the high 64 bits from the vector, and if bits were truncated.
+ // this is to get the significant digits for the float.
+ uint64_t hi64(bool& truncated) const noexcept {
+#ifdef FASTFLOAT_64BIT_LIMB
+ if (vec.len() == 0) {
+ return empty_hi64(truncated);
+ } else if (vec.len() == 1) {
+ return uint64_hi64(vec.rindex(0), truncated);
+ } else {
+ uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated);
+ truncated |= vec.nonzero(2);
+ return result;
+ }
+#else
+ if (vec.len() == 0) {
+ return empty_hi64(truncated);
+ } else if (vec.len() == 1) {
+ return uint32_hi64(vec.rindex(0), truncated);
+ } else if (vec.len() == 2) {
+ return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated);
+ } else {
+ uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated);
+ truncated |= vec.nonzero(3);
+ return result;
+ }
+#endif
+ }
+
+ // compare two big integers, returning the large value.
+ // assumes both are normalized. if the return value is
+ // negative, other is larger, if the return value is
+ // positive, this is larger, otherwise they are equal.
+ // the limbs are stored in little-endian order, so we
+ // must compare the limbs in ever order.
+ int compare(const bigint& other) const noexcept {
+ if (vec.len() > other.vec.len()) {
+ return 1;
+ } else if (vec.len() < other.vec.len()) {
+ return -1;
+ } else {
+ for (size_t index = vec.len(); index > 0; index--) {
+ limb xi = vec[index - 1];
+ limb yi = other.vec[index - 1];
+ if (xi > yi) {
+ return 1;
+ } else if (xi < yi) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+ }
+
+ // shift left each limb n bits, carrying over to the new limb
+ // returns true if we were able to shift all the digits.
+ bool shl_bits(size_t n) noexcept {
+ // Internally, for each item, we shift left by n, and add the previous
+ // right shifted limb-bits.
+ // For example, we transform (for u8) shifted left 2, to:
+ // b10100100 b01000010
+ // b10 b10010001 b00001000
+ FASTFLOAT_DEBUG_ASSERT(n != 0);
+ FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8);
+
+ size_t shl = n;
+ size_t shr = limb_bits - shl;
+ limb prev = 0;
+ for (size_t index = 0; index < vec.len(); index++) {
+ limb xi = vec[index];
+ vec[index] = (xi << shl) | (prev >> shr);
+ prev = xi;
+ }
+
+ limb carry = prev >> shr;
+ if (carry != 0) {
+ return vec.try_push(carry);
+ }
+ return true;
+ }
+
+ // move the limbs left by `n` limbs.
+ bool shl_limbs(size_t n) noexcept {
+ FASTFLOAT_DEBUG_ASSERT(n != 0);
+ if (n + vec.len() > vec.capacity()) {
+ return false;
+ } else if (!vec.is_empty()) {
+ // move limbs
+ limb* dst = vec.data + n;
+ const limb* src = vec.data;
+ ::memmove(dst, src, sizeof(limb) * vec.len());
+ // fill in empty limbs
+ limb* first = vec.data;
+ limb* last = first + n;
+ ::std::fill(first, last, 0);
+ vec.set_len(n + vec.len());
+ return true;
+ } else {
+ return true;
+ }
+ }
+
+ // move the limbs left by `n` bits.
+ bool shl(size_t n) noexcept {
+ size_t rem = n % limb_bits;
+ size_t div = n / limb_bits;
+ if (rem != 0) {
+ FASTFLOAT_TRY(shl_bits(rem));
+ }
+ if (div != 0) {
+ FASTFLOAT_TRY(shl_limbs(div));
+ }
+ return true;
+ }
+
+ // get the number of leading zeros in the bigint.
+ int ctlz() const noexcept {
+ if (vec.is_empty()) {
+ return 0;
+ } else {
+#ifdef FASTFLOAT_64BIT_LIMB
+ return leading_zeroes(vec.rindex(0));
+#else
+ // no use defining a specialized leading_zeroes for a 32-bit type.
+ uint64_t r0 = vec.rindex(0);
+ return leading_zeroes(r0 << 32);
+#endif
+ }
+ }
+
+ // get the number of bits in the bigint.
+ int bit_length() const noexcept {
+ int lz = ctlz();
+ return int(limb_bits * vec.len()) - lz;
+ }
+
+ bool mul(limb y) noexcept {
+ return small_mul(vec, y);
+ }
+
+ bool add(limb y) noexcept {
+ return small_add(vec, y);
+ }
+
+ // multiply as if by 2 raised to a power.
+ bool pow2(uint32_t exp) noexcept {
+ return shl(exp);
+ }
+
+ // multiply as if by 5 raised to a power.
+ bool pow5(uint32_t exp) noexcept {
+ // multiply by a power of 5
+ static constexpr uint32_t large_step = 135;
+ static constexpr uint64_t small_power_of_5[] = {
+ 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL,
+ 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL,
+ 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL,
+ 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL,
+ 2384185791015625UL, 11920928955078125UL, 59604644775390625UL,
+ 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL,
+ };
+#ifdef FASTFLOAT_64BIT_LIMB
+ constexpr static limb large_power_of_5[] = {
+ 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL,
+ 10482974169319127550UL, 198276706040285095UL};
+#else
+ constexpr static limb large_power_of_5[] = {
+ 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U,
+ 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U};
+#endif
+ size_t large_length = sizeof(large_power_of_5) / sizeof(limb);
+ limb_span large = limb_span(large_power_of_5, large_length);
+ while (exp >= large_step) {
+ FASTFLOAT_TRY(large_mul(vec, large));
+ exp -= large_step;
+ }
+#ifdef FASTFLOAT_64BIT_LIMB
+ uint32_t small_step = 27;
+ limb max_native = 7450580596923828125UL;
+#else
+ uint32_t small_step = 13;
+ limb max_native = 1220703125U;
+#endif
+ while (exp >= small_step) {
+ FASTFLOAT_TRY(small_mul(vec, max_native));
+ exp -= small_step;
+ }
+ if (exp != 0) {
+ FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp])));
+ }
+
+ return true;
+ }
+
+ // multiply as if by 10 raised to a power.
+ bool pow10(uint32_t exp) noexcept {
+ FASTFLOAT_TRY(pow5(exp));
+ return pow2(exp);
+ }
+};
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_ASCII_NUMBER_H
+#define FASTFLOAT_ASCII_NUMBER_H
+
+#include <cctype>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+
+namespace fast_float {
+
+// Next function can be micro-optimized, but compilers are entirely
+// able to optimize it well.
+fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; }
+
+fastfloat_really_inline uint64_t byteswap(uint64_t val) {
+ return (val & 0xFF00000000000000) >> 56
+ | (val & 0x00FF000000000000) >> 40
+ | (val & 0x0000FF0000000000) >> 24
+ | (val & 0x000000FF00000000) >> 8
+ | (val & 0x00000000FF000000) << 8
+ | (val & 0x0000000000FF0000) << 24
+ | (val & 0x000000000000FF00) << 40
+ | (val & 0x00000000000000FF) << 56;
+}
+
+fastfloat_really_inline uint64_t read_u64(const char *chars) {
+ uint64_t val;
+ ::memcpy(&val, chars, sizeof(uint64_t));
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ return val;
+}
+
+fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) {
+#if FASTFLOAT_IS_BIG_ENDIAN == 1
+ // Need to read as-if the number was in little-endian order.
+ val = byteswap(val);
+#endif
+ ::memcpy(chars, &val, sizeof(uint64_t));
+}
+
+// credit @aqrit
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) {
+ const uint64_t mask = 0x000000FF000000FF;
+ const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+ const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
+ val -= 0x3030303030303030;
+ val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
+ val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
+ return uint32_t(val);
+}
+
+fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept {
+ return parse_eight_digits_unrolled(read_u64(chars));
+}
+
+// credit @aqrit
+fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept {
+ return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+ 0x8080808080808080));
+}
+
+fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept {
+ return is_made_of_eight_digits_fast(read_u64(chars));
+}
+
+typedef span<const char> byte_span;
+
+struct parsed_number_string {
+ int64_t exponent{0};
+ uint64_t mantissa{0};
+ const char *lastmatch{nullptr};
+ bool negative{false};
+ bool valid{false};
+ bool too_many_digits{false};
+ // contains the range of the significant digits
+ byte_span integer{}; // non-nullable
+ byte_span fraction{}; // nullable
+};
+
+// Assuming that you use no more than 19 digits, this will
+// parse an ASCII string.
+fastfloat_really_inline
+parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept {
+ const chars_format fmt = options.format;
+ const char decimal_point = options.decimal_point;
+
+ parsed_number_string answer;
+ answer.valid = false;
+ answer.too_many_digits = false;
+ answer.negative = (*p == '-');
+ if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+ ++p;
+ if (p == pend) {
+ return answer;
+ }
+ if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot
+ return answer;
+ }
+ }
+ const char *const start_digits = p;
+
+ uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ // a multiplication by 10 is cheaper than an arbitrary integer
+ // multiplication
+ i = 10 * i +
+ uint64_t(*p - '0'); // might overflow, we will handle the overflow later
+ ++p;
+ }
+ const char *const end_of_integer_part = p;
+ int64_t digit_count = int64_t(end_of_integer_part - start_digits);
+ answer.integer = byte_span(start_digits, size_t(digit_count));
+ int64_t exponent = 0;
+ if ((p != pend) && (*p == decimal_point)) {
+ ++p;
+ const char* before = p;
+ // can occur at most twice without overflowing, but let it occur more, since
+ // for integers with many digits, digit parsing is the primary bottleneck.
+ while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) {
+ i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok
+ p += 8;
+ }
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ ++p;
+ i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+ }
+ exponent = before - p;
+ answer.fraction = byte_span(before, size_t(p - before));
+ digit_count -= exponent;
+ }
+ // we must have encountered at least one integer!
+ if (digit_count == 0) {
+ return answer;
+ }
+ int64_t exp_number = 0; // explicit exponential part
+ if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) {
+ const char * location_of_e = p;
+ ++p;
+ bool neg_exp = false;
+ if ((p != pend) && ('-' == *p)) {
+ neg_exp = true;
+ ++p;
+ } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+ ++p;
+ }
+ if ((p == pend) || !is_integer(*p)) {
+ if(!(fmt & chars_format::fixed)) {
+ // We are in error.
+ return answer;
+ }
+ // Otherwise, we will be ignoring the 'e'.
+ p = location_of_e;
+ } else {
+ while ((p != pend) && is_integer(*p)) {
+ uint8_t digit = uint8_t(*p - '0');
+ if (exp_number < 0x10000000) {
+ exp_number = 10 * exp_number + digit;
+ }
+ ++p;
+ }
+ if(neg_exp) { exp_number = - exp_number; }
+ exponent += exp_number;
+ }
+ } else {
+ // If it scientific and not fixed, we have to bail out.
+ if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; }
+ }
+ answer.lastmatch = p;
+ answer.valid = true;
+
+ // If we frequently had to deal with long strings of digits,
+ // we could extend our code by using a 128-bit integer instead
+ // of a 64-bit integer. However, this is uncommon.
+ //
+ // We can deal with up to 19 digits.
+ if (digit_count > 19) { // this is uncommon
+ // It is possible that the integer had an overflow.
+ // We have to handle the case where we have 0.0000somenumber.
+ // We need to be mindful of the case where we only have zeroes...
+ // E.g., 0.000000000...000.
+ const char *start = start_digits;
+ while ((start != pend) && (*start == '0' || *start == decimal_point)) {
+ if(*start == '0') { digit_count --; }
+ start++;
+ }
+ if (digit_count > 19) {
+ answer.too_many_digits = true;
+ // Let us start again, this time, avoiding overflows.
+ // We don't need to check if is_integer, since we use the
+ // pre-tokenized spans from above.
+ i = 0;
+ p = answer.integer.ptr;
+ const char* int_end = p + answer.integer.len();
+ const uint64_t minimal_nineteen_digit_integer{1000000000000000000};
+ while((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+ i = i * 10 + uint64_t(*p - '0');
+ ++p;
+ }
+ if (i >= minimal_nineteen_digit_integer) { // We have a big integers
+ exponent = end_of_integer_part - p + exp_number;
+ } else { // We have a value with a fractional component.
+ p = answer.fraction.ptr;
+ const char* frac_end = p + answer.fraction.len();
+ while((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+ i = i * 10 + uint64_t(*p - '0');
+ ++p;
+ }
+ exponent = answer.fraction.ptr - p + exp_number;
+ }
+ // We have now corrected both exponent and i, to a truncated value
+ }
+ }
+ answer.exponent = exponent;
+ answer.mantissa = i;
+ return answer;
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_DIGIT_COMPARISON_H
+#define FASTFLOAT_DIGIT_COMPARISON_H
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+
+
+namespace fast_float {
+
+// 1e0 to 1e19
+constexpr static uint64_t powers_of_ten_uint64[] = {
+ 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL,
+ 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL,
+ 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL,
+ 1000000000000000000UL, 10000000000000000000UL};
+
+// calculate the exponent, in scientific notation, of the number.
+// this algorithm is not even close to optimized, but it has no practical
+// effect on performance: in order to have a faster algorithm, we'd need
+// to slow down performance for faster algorithms, and this is still fast.
+fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) noexcept {
+ uint64_t mantissa = num.mantissa;
+ int32_t exponent = int32_t(num.exponent);
+ while (mantissa >= 10000) {
+ mantissa /= 10000;
+ exponent += 4;
+ }
+ while (mantissa >= 100) {
+ mantissa /= 100;
+ exponent += 2;
+ }
+ while (mantissa >= 10) {
+ mantissa /= 10;
+ exponent += 1;
+ }
+ return exponent;
+}
+
+// this converts a native floating-point number to an extended-precision float.
+template <typename T>
+fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept {
+ using equiv_uint = typename binary_format<T>::equiv_uint;
+ constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask();
+ constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask();
+ constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask();
+
+ adjusted_mantissa am;
+ int32_t bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
+ equiv_uint bits;
+ ::memcpy(&bits, &value, sizeof(T));
+ if ((bits & exponent_mask) == 0) {
+ // denormal
+ am.power2 = 1 - bias;
+ am.mantissa = bits & mantissa_mask;
+ } else {
+ // normal
+ am.power2 = int32_t((bits & exponent_mask) >> binary_format<T>::mantissa_explicit_bits());
+ am.power2 -= bias;
+ am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
+ }
+
+ return am;
+}
+
+// get the extended precision value of the halfway point between b and b+u.
+// we are given a native float that represents b, so we need to adjust it
+// halfway between b and b+u.
+template <typename T>
+fastfloat_really_inline adjusted_mantissa to_extended_halfway(T value) noexcept {
+ adjusted_mantissa am = to_extended(value);
+ am.mantissa <<= 1;
+ am.mantissa += 1;
+ am.power2 -= 1;
+ return am;
+}
+
+// round an extended-precision float to the nearest machine float.
+template <typename T, typename callback>
+fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept {
+ int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1;
+ if (-am.power2 >= mantissa_shift) {
+ // have a denormal float
+ int32_t shift = -am.power2 + 1;
+ cb(am, std::min(shift, 64));
+ // check for round-up: if rounding-nearest carried us to the hidden bit.
+ am.power2 = (am.mantissa < (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) ? 0 : 1;
+ return;
+ }
+
+ // have a normal float, use the default shift.
+ cb(am, mantissa_shift);
+
+ // check for carry
+ if (am.mantissa >= (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) {
+ am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+ am.power2++;
+ }
+
+ // check for infinite: we could have carried to an infinite power
+ am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits());
+ if (am.power2 >= binary_format<T>::infinite_power()) {
+ am.power2 = binary_format<T>::infinite_power();
+ am.mantissa = 0;
+ }
+}
+
+template <typename callback>
+fastfloat_really_inline
+void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept {
+ uint64_t mask;
+ uint64_t halfway;
+ if (shift == 64) {
+ mask = UINT64_MAX;
+ } else {
+ mask = (uint64_t(1) << shift) - 1;
+ }
+ if (shift == 0) {
+ halfway = 0;
+ } else {
+ halfway = uint64_t(1) << (shift - 1);
+ }
+ uint64_t truncated_bits = am.mantissa & mask;
+ uint64_t is_above = truncated_bits > halfway;
+ uint64_t is_halfway = truncated_bits == halfway;
+
+ // shift digits into position
+ if (shift == 64) {
+ am.mantissa = 0;
+ } else {
+ am.mantissa >>= shift;
+ }
+ am.power2 += shift;
+
+ bool is_odd = (am.mantissa & 1) == 1;
+ am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above));
+}
+
+fastfloat_really_inline void round_down(adjusted_mantissa& am, int32_t shift) noexcept {
+ if (shift == 64) {
+ am.mantissa = 0;
+ } else {
+ am.mantissa >>= shift;
+ }
+ am.power2 += shift;
+}
+
+fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept {
+ uint64_t val;
+ while (std::distance(first, last) >= 8) {
+ ::memcpy(&val, first, sizeof(uint64_t));
+ if (val != 0x3030303030303030) {
+ break;
+ }
+ first += 8;
+ }
+ while (first != last) {
+ if (*first != '0') {
+ break;
+ }
+ first++;
+ }
+}
+
+// determine if any non-zero digits were truncated.
+// all characters must be valid digits.
+fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept {
+ // do 8-bit optimizations, can just compare to 8 literal 0s.
+ uint64_t val;
+ while (std::distance(first, last) >= 8) {
+ ::memcpy(&val, first, sizeof(uint64_t));
+ if (val != 0x3030303030303030) {
+ return true;
+ }
+ first += 8;
+ }
+ while (first != last) {
+ if (*first != '0') {
+ return true;
+ }
+ first++;
+ }
+ return false;
+}
+
+fastfloat_really_inline bool is_truncated(byte_span s) noexcept {
+ return is_truncated(s.ptr, s.ptr + s.len());
+}
+
+fastfloat_really_inline
+void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept {
+ value = value * 100000000 + parse_eight_digits_unrolled(p);
+ p += 8;
+ counter += 8;
+ count += 8;
+}
+
+fastfloat_really_inline
+void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept {
+ value = value * 10 + limb(*p - '0');
+ p++;
+ counter++;
+ count++;
+}
+
+fastfloat_really_inline
+void add_native(bigint& big, limb power, limb value) noexcept {
+ big.mul(power);
+ big.add(value);
+}
+
+fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept {
+ // need to round-up the digits, but need to avoid rounding
+ // ....9999 to ...10000, which could cause a false halfway point.
+ add_native(big, 10, 1);
+ count++;
+}
+
+// parse the significant digits into a big integer
+inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept {
+ // try to minimize the number of big integer and scalar multiplication.
+ // therefore, try to parse 8 digits at a time, and multiply by the largest
+ // scalar value (9 or 19 digits) for each step.
+ size_t counter = 0;
+ digits = 0;
+ limb value = 0;
+#ifdef FASTFLOAT_64BIT_LIMB
+ size_t step = 19;
+#else
+ size_t step = 9;
+#endif
+
+ // process all integer digits.
+ const char* p = num.integer.ptr;
+ const char* pend = p + num.integer.len();
+ skip_zeros(p, pend);
+ // process all digits, in increments of step per loop
+ while (p != pend) {
+ while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) {
+ parse_eight_digits(p, value, counter, digits);
+ }
+ while (counter < step && p != pend && digits < max_digits) {
+ parse_one_digit(p, value, counter, digits);
+ }
+ if (digits == max_digits) {
+ // add the temporary value, then check if we've truncated any digits
+ add_native(result, limb(powers_of_ten_uint64[counter]), value);
+ bool truncated = is_truncated(p, pend);
+ if (num.fraction.ptr != nullptr) {
+ truncated |= is_truncated(num.fraction);
+ }
+ if (truncated) {
+ round_up_bigint(result, digits);
+ }
+ return;
+ } else {
+ add_native(result, limb(powers_of_ten_uint64[counter]), value);
+ counter = 0;
+ value = 0;
+ }
+ }
+
+ // add our fraction digits, if they're available.
+ if (num.fraction.ptr != nullptr) {
+ p = num.fraction.ptr;
+ pend = p + num.fraction.len();
+ if (digits == 0) {
+ skip_zeros(p, pend);
+ }
+ // process all digits, in increments of step per loop
+ while (p != pend) {
+ while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) {
+ parse_eight_digits(p, value, counter, digits);
+ }
+ while (counter < step && p != pend && digits < max_digits) {
+ parse_one_digit(p, value, counter, digits);
+ }
+ if (digits == max_digits) {
+ // add the temporary value, then check if we've truncated any digits
+ add_native(result, limb(powers_of_ten_uint64[counter]), value);
+ bool truncated = is_truncated(p, pend);
+ if (truncated) {
+ round_up_bigint(result, digits);
+ }
+ return;
+ } else {
+ add_native(result, limb(powers_of_ten_uint64[counter]), value);
+ counter = 0;
+ value = 0;
+ }
+ }
+ }
+
+ if (counter != 0) {
+ add_native(result, limb(powers_of_ten_uint64[counter]), value);
+ }
+}
+
+template <typename T>
+inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept {
+ FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent)));
+ adjusted_mantissa answer;
+ bool truncated;
+ answer.mantissa = bigmant.hi64(truncated);
+ int bias = binary_format<T>::mantissa_explicit_bits() - binary_format<T>::minimum_exponent();
+ answer.power2 = bigmant.bit_length() - 64 + bias;
+
+ round<T>(answer, [truncated](adjusted_mantissa& a, int32_t shift) {
+ round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool {
+ return is_above || (is_halfway && truncated) || (is_odd && is_halfway);
+ });
+ });
+
+ return answer;
+}
+
+// the scaling here is quite simple: we have, for the real digits `m * 10^e`,
+// and for the theoretical digits `n * 2^f`. Since `e` is always negative,
+// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`.
+// we then need to scale by `2^(f- e)`, and then the two significant digits
+// are of the same magnitude.
+template <typename T>
+inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept {
+ bigint& real_digits = bigmant;
+ int32_t real_exp = exponent;
+
+ // get the value of `b`, rounded down, and get a bigint representation of b+h
+ adjusted_mantissa am_b = am;
+ // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type.
+ round<T>(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); });
+ T b;
+ to_float(false, am_b, b);
+ adjusted_mantissa theor = to_extended_halfway(b);
+ bigint theor_digits(theor.mantissa);
+ int32_t theor_exp = theor.power2;
+
+ // scale real digits and theor digits to be same power.
+ int32_t pow2_exp = theor_exp - real_exp;
+ uint32_t pow5_exp = uint32_t(-real_exp);
+ if (pow5_exp != 0) {
+ FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp));
+ }
+ if (pow2_exp > 0) {
+ FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp)));
+ } else if (pow2_exp < 0) {
+ FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp)));
+ }
+
+ // compare digits, and use it to director rounding
+ int ord = real_digits.compare(theor_digits);
+ adjusted_mantissa answer = am;
+ round<T>(answer, [ord](adjusted_mantissa& a, int32_t shift) {
+ round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool {
+ (void)_; // not needed, since we've done our comparison
+ (void)__; // not needed, since we've done our comparison
+ if (ord > 0) {
+ return true;
+ } else if (ord < 0) {
+ return false;
+ } else {
+ return is_odd;
+ }
+ });
+ });
+
+ return answer;
+}
+
+// parse the significant digits as a big integer to unambiguously round the
+// the significant digits. here, we are trying to determine how to round
+// an extended float representation close to `b+h`, halfway between `b`
+// (the float rounded-down) and `b+u`, the next positive float. this
+// algorithm is always correct, and uses one of two approaches. when
+// the exponent is positive relative to the significant digits (such as
+// 1234), we create a big-integer representation, get the high 64-bits,
+// determine if any lower bits are truncated, and use that to direct
+// rounding. in case of a negative exponent relative to the significant
+// digits (such as 1.2345), we create a theoretical representation of
+// `b` as a big-integer type, scaled to the same binary exponent as
+// the actual digits. we then compare the big integer representations
+// of both, and use that to direct rounding.
+template <typename T>
+inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept {
+ // remove the invalid exponent bias
+ am.power2 -= invalid_am_bias;
+
+ int32_t sci_exp = scientific_exponent(num);
+ size_t max_digits = binary_format<T>::max_digits();
+ size_t digits = 0;
+ bigint bigmant;
+ parse_mantissa(bigmant, num, max_digits, digits);
+ // can't underflow, since digits is at most max_digits.
+ int32_t exponent = sci_exp + 1 - int32_t(digits);
+ if (exponent >= 0) {
+ return positive_digit_comp<T>(bigmant, exponent);
+ } else {
+ return negative_digit_comp<T>(bigmant, am, exponent);
+ }
+}
+
+} // namespace fast_float
+
+#endif
+
+#ifndef FASTFLOAT_PARSE_NUMBER_H
+#define FASTFLOAT_PARSE_NUMBER_H
+
+
+#include <cmath>
+#include <cstring>
+#include <limits>
+#include <system_error>
+
+namespace fast_float {
+
+
+namespace detail {
+/**
+ * Special case +inf, -inf, nan, infinity, -infinity.
+ * The case comparisons could be made much faster given that we know that the
+ * strings a null-free and fixed.
+ **/
+template <typename T>
+from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept {
+ from_chars_result answer;
+ answer.ptr = first;
+ answer.ec = std::errc(); // be optimistic
+ bool minusSign = false;
+ if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here
+ minusSign = true;
+ ++first;
+ }
+ if (last - first >= 3) {
+ if (fastfloat_strncasecmp(first, "nan", 3)) {
+ answer.ptr = (first += 3);
+ value = minusSign ? -std::numeric_limits<T>::quiet_NaN() : std::numeric_limits<T>::quiet_NaN();
+ // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan).
+ if(first != last && *first == '(') {
+ for(const char* ptr = first + 1; ptr != last; ++ptr) {
+ if (*ptr == ')') {
+ answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
+ break;
+ }
+ else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_'))
+ break; // forbidden char, not nan(n-char-seq-opt)
+ }
+ }
+ return answer;
+ }
+ if (fastfloat_strncasecmp(first, "inf", 3)) {
+ if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) {
+ answer.ptr = first + 8;
+ } else {
+ answer.ptr = first + 3;
+ }
+ value = minusSign ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity();
+ return answer;
+ }
+ }
+ answer.ec = std::errc::invalid_argument;
+ return answer;
+}
+
+} // namespace detail
+
+template<typename T>
+from_chars_result from_chars(const char *first, const char *last,
+ T &value, chars_format fmt /*= chars_format::general*/) noexcept {
+ return from_chars_advanced(first, last, value, parse_options{fmt});
+}
+
+template<typename T>
+from_chars_result from_chars_advanced(const char *first, const char *last,
+ T &value, parse_options options) noexcept {
+
+ static_assert (std::is_same<T, double>::value || std::is_same<T, float>::value, "only float and double are supported");
+
+
+ from_chars_result answer;
+ if (first == last) {
+ answer.ec = std::errc::invalid_argument;
+ answer.ptr = first;
+ return answer;
+ }
+ parsed_number_string pns = parse_number_string(first, last, options);
+ if (!pns.valid) {
+ return detail::parse_infnan(first, last, value);
+ }
+ answer.ec = std::errc(); // be optimistic
+ answer.ptr = pns.lastmatch;
+ // Next is Clinger's fast path.
+ if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format<T>::max_exponent_fast_path() && pns.mantissa <=binary_format<T>::max_mantissa_fast_path() && !pns.too_many_digits) {
+ value = T(pns.mantissa);
+ if (pns.exponent < 0) { value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); }
+ else { value = value * binary_format<T>::exact_power_of_ten(pns.exponent); }
+ if (pns.negative) { value = -value; }
+ return answer;
+ }
+ adjusted_mantissa am = compute_float<binary_format<T>>(pns.exponent, pns.mantissa);
+ if(pns.too_many_digits && am.power2 >= 0) {
+ if(am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) {
+ am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa);
+ }
+ }
+ // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0),
+ // then we need to go the long way around again. This is very uncommon.
+ if(am.power2 < 0) { am = digit_comp<T>(pns, am); }
+ to_float(pns.negative, am, value);
+ return answer;
+}
+
+} // namespace fast_float
+
+#endif
+
diff --git a/intern/cycles/device/metal/kernel.mm b/intern/cycles/device/metal/kernel.mm
index 1434b297ddd..9555ca03c8e 100644
--- a/intern/cycles/device/metal/kernel.mm
+++ b/intern/cycles/device/metal/kernel.mm
@@ -459,7 +459,7 @@ bool MetalDeviceKernels::load(MetalDevice *device, int kernel_type)
tbb::task_arena local_arena(max_mtlcompiler_threads);
local_arena.execute([&]() {
- tbb::parallel_for(int(0), int(DEVICE_KERNEL_NUM), [&](int i) {
+ parallel_for(int(0), int(DEVICE_KERNEL_NUM), [&](int i) {
/* skip megakernel */
if (i == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) {
return;
diff --git a/intern/cycles/integrator/pass_accessor_cpu.cpp b/intern/cycles/integrator/pass_accessor_cpu.cpp
index 509190c8a7e..02260a54bf4 100644
--- a/intern/cycles/integrator/pass_accessor_cpu.cpp
+++ b/intern/cycles/integrator/pass_accessor_cpu.cpp
@@ -44,7 +44,7 @@ inline void PassAccessorCPU::run_get_pass_kernel_processor_float(
const int pixel_stride = destination.pixel_stride ? destination.pixel_stride :
destination.num_components;
- tbb::parallel_for(0, buffer_params.window_height, [&](int64_t y) {
+ parallel_for(0, buffer_params.window_height, [&](int64_t y) {
const float *buffer = window_data + y * buffer_row_stride;
float *pixel = destination.pixels +
(y * buffer_params.width + destination.offset) * pixel_stride;
@@ -69,7 +69,7 @@ inline void PassAccessorCPU::run_get_pass_kernel_processor_half_rgba(
const int destination_stride = destination.stride != 0 ? destination.stride :
buffer_params.width;
- tbb::parallel_for(0, buffer_params.window_height, [&](int64_t y) {
+ parallel_for(0, buffer_params.window_height, [&](int64_t y) {
const float *buffer = window_data + y * buffer_row_stride;
half4 *pixel = dst_start + y * destination_stride;
func(kfilm_convert, buffer, pixel, buffer_params.window_width, pass_stride);
diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp
index f1e70b7f28f..4ecd3b829e8 100644
--- a/intern/cycles/integrator/path_trace.cpp
+++ b/intern/cycles/integrator/path_trace.cpp
@@ -334,7 +334,7 @@ void PathTrace::init_render_buffers(const RenderWork &render_work)
/* Handle initialization scheduled by the render scheduler. */
if (render_work.init_render_buffers) {
- tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
path_trace_work->zero_render_buffers();
});
@@ -355,10 +355,9 @@ void PathTrace::path_trace(RenderWork &render_work)
const int num_works = path_trace_works_.size();
- tbb::task_group_context *tbb_ctx = tbb::task::self().group();
- tbb_ctx->capture_fp_settings();
+ thread_capture_fp_settings();
- tbb::parallel_for(0, num_works, [&](int i) {
+ parallel_for(0, num_works, [&](int i) {
const double work_start_time = time_dt();
const int num_samples = render_work.path_trace.num_samples;
@@ -408,7 +407,7 @@ void PathTrace::adaptive_sample(RenderWork &render_work)
const double start_time = time_dt();
uint num_active_pixels = 0;
- tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
const uint num_active_pixels_in_work =
path_trace_work->adaptive_sampling_converge_filter_count_active(
render_work.adaptive_sampling.threshold, render_work.adaptive_sampling.reset);
@@ -486,7 +485,7 @@ void PathTrace::cryptomatte_postprocess(const RenderWork &render_work)
}
VLOG(3) << "Perform cryptomatte work.";
- tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
path_trace_work->cryptomatte_postproces();
});
}
@@ -539,7 +538,7 @@ void PathTrace::denoise(const RenderWork &render_work)
if (multi_device_buffers) {
multi_device_buffers->copy_from_device();
- tbb::parallel_for_each(
+ parallel_for_each(
path_trace_works_, [&multi_device_buffers](unique_ptr<PathTraceWork> &path_trace_work) {
path_trace_work->copy_from_denoised_render_buffers(multi_device_buffers.get());
});
@@ -809,7 +808,7 @@ void PathTrace::tile_buffer_read()
}
/* Read buffers back from device. */
- tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
path_trace_work->copy_render_buffers_from_device();
});
@@ -817,7 +816,7 @@ void PathTrace::tile_buffer_read()
PathTraceTile tile(*this);
if (output_driver_->read_render_tile(tile)) {
/* Copy buffers to device again. */
- tbb::parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) {
path_trace_work->copy_render_buffers_to_device();
});
}
@@ -881,20 +880,20 @@ void PathTrace::progress_set_status(const string &status, const string &substatu
void PathTrace::copy_to_render_buffers(RenderBuffers *render_buffers)
{
- tbb::parallel_for_each(path_trace_works_,
- [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) {
- path_trace_work->copy_to_render_buffers(render_buffers);
- });
+ parallel_for_each(path_trace_works_,
+ [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) {
+ path_trace_work->copy_to_render_buffers(render_buffers);
+ });
render_buffers->copy_to_device();
}
void PathTrace::copy_from_render_buffers(RenderBuffers *render_buffers)
{
render_buffers->copy_from_device();
- tbb::parallel_for_each(path_trace_works_,
- [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) {
- path_trace_work->copy_from_render_buffers(render_buffers);
- });
+ parallel_for_each(path_trace_works_,
+ [&render_buffers](unique_ptr<PathTraceWork> &path_trace_work) {
+ path_trace_work->copy_from_render_buffers(render_buffers);
+ });
}
bool PathTrace::copy_render_tile_from_device()
@@ -906,7 +905,7 @@ bool PathTrace::copy_render_tile_from_device()
bool success = true;
- tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
if (!success) {
return;
}
@@ -1007,7 +1006,7 @@ bool PathTrace::get_render_tile_pixels(const PassAccessor &pass_accessor,
bool success = true;
- tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
if (!success) {
return;
}
@@ -1024,7 +1023,7 @@ bool PathTrace::set_render_tile_pixels(PassAccessor &pass_accessor,
{
bool success = true;
- tbb::parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
+ parallel_for_each(path_trace_works_, [&](unique_ptr<PathTraceWork> &path_trace_work) {
if (!success) {
return;
}
diff --git a/intern/cycles/integrator/path_trace_work_cpu.cpp b/intern/cycles/integrator/path_trace_work_cpu.cpp
index 147e273284b..518ef3185f9 100644
--- a/intern/cycles/integrator/path_trace_work_cpu.cpp
+++ b/intern/cycles/integrator/path_trace_work_cpu.cpp
@@ -73,7 +73,7 @@ void PathTraceWorkCPU::render_samples(RenderStatistics &statistics,
tbb::task_arena local_arena = local_tbb_arena_create(device_);
local_arena.execute([&]() {
- tbb::parallel_for(int64_t(0), total_pixels_num, [&](int64_t work_index) {
+ parallel_for(int64_t(0), total_pixels_num, [&](int64_t work_index) {
if (is_cancel_requested()) {
return;
}
@@ -219,7 +219,7 @@ int PathTraceWorkCPU::adaptive_sampling_converge_filter_count_active(float thres
/* Check convergency and do x-filter in a single `parallel_for`, to reduce threading overhead. */
local_arena.execute([&]() {
- tbb::parallel_for(full_y, full_y + height, [&](int y) {
+ parallel_for(full_y, full_y + height, [&](int y) {
CPUKernelThreadGlobals *kernel_globals = &kernel_thread_globals_[0];
bool row_converged = true;
@@ -243,7 +243,7 @@ int PathTraceWorkCPU::adaptive_sampling_converge_filter_count_active(float thres
if (num_active_pixels) {
local_arena.execute([&]() {
- tbb::parallel_for(full_x, full_x + width, [&](int x) {
+ parallel_for(full_x, full_x + width, [&](int x) {
CPUKernelThreadGlobals *kernel_globals = &kernel_thread_globals_[0];
kernels_.adaptive_sampling_filter_y(
kernel_globals, render_buffer, x, full_y, height, offset, stride);
@@ -265,7 +265,7 @@ void PathTraceWorkCPU::cryptomatte_postproces()
/* Check convergency and do x-filter in a single `parallel_for`, to reduce threading overhead. */
local_arena.execute([&]() {
- tbb::parallel_for(0, height, [&](int y) {
+ parallel_for(0, height, [&](int y) {
CPUKernelThreadGlobals *kernel_globals = &kernel_thread_globals_[0];
int pixel_index = y * width;
diff --git a/intern/cycles/integrator/shader_eval.cpp b/intern/cycles/integrator/shader_eval.cpp
index f5036b4020d..92b9d1c662d 100644
--- a/intern/cycles/integrator/shader_eval.cpp
+++ b/intern/cycles/integrator/shader_eval.cpp
@@ -92,7 +92,7 @@ bool ShaderEval::eval_cpu(Device *device,
tbb::task_arena local_arena(device->info.cpu_threads);
local_arena.execute([&]() {
- tbb::parallel_for(int64_t(0), work_size, [&](int64_t work_index) {
+ parallel_for(int64_t(0), work_size, [&](int64_t work_index) {
/* TODO: is this fast enough? */
if (progress_.get_cancel()) {
success = false;
diff --git a/intern/cycles/kernel/svm/blackbody.h b/intern/cycles/kernel/svm/blackbody.h
index 1618341b655..af59c2fe747 100644
--- a/intern/cycles/kernel/svm/blackbody.h
+++ b/intern/cycles/kernel/svm/blackbody.h
@@ -23,7 +23,7 @@ ccl_device_noinline void svm_node_blackbody(KernelGlobals kg,
/* Input */
float temperature = stack_load_float(stack, temperature_offset);
- float3 color_rgb = svm_math_blackbody_color(temperature);
+ float3 color_rgb = rec709_to_rgb(kg, svm_math_blackbody_color_rec709(temperature));
stack_store_float3(stack, col_offset, color_rgb);
}
diff --git a/intern/cycles/kernel/svm/closure.h b/intern/cycles/kernel/svm/closure.h
index 88b44cdbacf..305bd404d27 100644
--- a/intern/cycles/kernel/svm/closure.h
+++ b/intern/cycles/kernel/svm/closure.h
@@ -1111,7 +1111,8 @@ ccl_device_noinline int svm_node_principled_volume(KernelGlobals kg,
if (intensity > CLOSURE_WEIGHT_CUTOFF) {
float3 blackbody_tint = stack_load_float3(stack, node.w);
- float3 bb = blackbody_tint * intensity * svm_math_blackbody_color(T);
+ float3 bb = blackbody_tint * intensity *
+ rec709_to_rgb(kg, svm_math_blackbody_color_rec709(T));
emission_setup(sd, bb);
}
}
diff --git a/intern/cycles/kernel/svm/math_util.h b/intern/cycles/kernel/svm/math_util.h
index 2a496aee1e1..9f2d9561e26 100644
--- a/intern/cycles/kernel/svm/math_util.h
+++ b/intern/cycles/kernel/svm/math_util.h
@@ -189,10 +189,8 @@ ccl_device float svm_math(NodeMathType type, float a, float b, float c)
}
}
-ccl_device float3 svm_math_blackbody_color(float t)
+ccl_device float3 svm_math_blackbody_color_rec709(float t)
{
- /* TODO(lukas): Reimplement in XYZ. */
-
/* Calculate color in range 800..12000 using an approximation
* a/x+bx+c for R and G and ((at + b)t + c)t + d) for B
* Max absolute error for RGB is (0.00095, 0.00077, 0.00057),
diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h
index db499a1e1bc..422285cd346 100644
--- a/intern/cycles/kernel/types.h
+++ b/intern/cycles/kernel/types.h
@@ -1117,13 +1117,18 @@ typedef struct KernelFilm {
float4 xyz_to_g;
float4 xyz_to_b;
float4 rgb_to_y;
+ /* Rec709 to rendering color space. */
+ float4 rec709_to_r;
+ float4 rec709_to_g;
+ float4 rec709_to_b;
+ int is_rec709;
int pass_bake_primitive;
int pass_bake_differential;
int use_approximate_shadow_catcher;
- int pad1, pad2;
+ int pad1;
} KernelFilm;
static_assert_align(KernelFilm, 16);
diff --git a/intern/cycles/kernel/util/color.h b/intern/cycles/kernel/util/color.h
index 95b6b33795d..28978d873d6 100644
--- a/intern/cycles/kernel/util/color.h
+++ b/intern/cycles/kernel/util/color.h
@@ -14,6 +14,15 @@ ccl_device float3 xyz_to_rgb(KernelGlobals kg, float3 xyz)
dot(float4_to_float3(kernel_data.film.xyz_to_b), xyz));
}
+ccl_device float3 rec709_to_rgb(KernelGlobals kg, float3 rec709)
+{
+ return (kernel_data.film.is_rec709) ?
+ rec709 :
+ make_float3(dot(float4_to_float3(kernel_data.film.rec709_to_r), rec709),
+ dot(float4_to_float3(kernel_data.film.rec709_to_g), rec709),
+ dot(float4_to_float3(kernel_data.film.rec709_to_b), rec709));
+}
+
ccl_device float linear_rgb_to_gray(KernelGlobals kg, float3 c)
{
return dot(c, float4_to_float3(kernel_data.film.rgb_to_y));
diff --git a/intern/cycles/scene/shader.cpp b/intern/cycles/scene/shader.cpp
index 8a08f2a5be9..e1af92ea8cf 100644
--- a/intern/cycles/scene/shader.cpp
+++ b/intern/cycles/scene/shader.cpp
@@ -579,6 +579,10 @@ void ShaderManager::device_update_common(Device * /*device*/,
kfilm->xyz_to_g = float3_to_float4(xyz_to_g);
kfilm->xyz_to_b = float3_to_float4(xyz_to_b);
kfilm->rgb_to_y = float3_to_float4(rgb_to_y);
+ kfilm->rec709_to_r = float3_to_float4(rec709_to_r);
+ kfilm->rec709_to_g = float3_to_float4(rec709_to_g);
+ kfilm->rec709_to_b = float3_to_float4(rec709_to_b);
+ kfilm->is_rec709 = is_rec709;
}
void ShaderManager::device_free_common(Device *, DeviceScene *dscene, Scene *scene)
@@ -740,6 +744,11 @@ float ShaderManager::linear_rgb_to_gray(float3 c)
return dot(c, rgb_to_y);
}
+float3 ShaderManager::rec709_to_scene_linear(float3 c)
+{
+ return make_float3(dot(rec709_to_r, c), dot(rec709_to_g, c), dot(rec709_to_b, c));
+}
+
string ShaderManager::get_cryptomatte_materials(Scene *scene)
{
string manifest = "{";
@@ -802,11 +811,29 @@ void ShaderManager::init_xyz_transforms()
{
/* Default to ITU-BT.709 in case no appropriate transform found.
* Note XYZ here is defined as having a D65 white point. */
- xyz_to_r = make_float3(3.2404542f, -1.5371385f, -0.4985314f);
- xyz_to_g = make_float3(-0.9692660f, 1.8760108f, 0.0415560f);
- xyz_to_b = make_float3(0.0556434f, -0.2040259f, 1.0572252f);
+ const Transform xyz_to_rec709 = make_transform(3.2404542f,
+ -1.5371385f,
+ -0.4985314f,
+ 0.0f,
+ -0.9692660f,
+ 1.8760108f,
+ 0.0415560f,
+ 0.0f,
+ 0.0556434f,
+ -0.2040259f,
+ 1.0572252f,
+ 0.0f);
+
+ xyz_to_r = float4_to_float3(xyz_to_rec709.x);
+ xyz_to_g = float4_to_float3(xyz_to_rec709.y);
+ xyz_to_b = float4_to_float3(xyz_to_rec709.z);
rgb_to_y = make_float3(0.2126729f, 0.7151522f, 0.0721750f);
+ rec709_to_r = make_float3(1.0f, 0.0f, 0.0f);
+ rec709_to_g = make_float3(0.0f, 1.0f, 0.0f);
+ rec709_to_b = make_float3(0.0f, 0.0f, 1.0f);
+ is_rec709 = true;
+
#ifdef WITH_OCIO
/* Get from OpenColorO config if it has the required roles. */
OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
@@ -857,6 +884,12 @@ void ShaderManager::init_xyz_transforms()
const Transform rgb_to_xyz = transform_inverse(xyz_to_rgb);
rgb_to_y = float4_to_float3(rgb_to_xyz.y);
+
+ const Transform rec709_to_rgb = xyz_to_rgb * transform_inverse(xyz_to_rec709);
+ rec709_to_r = float4_to_float3(rec709_to_rgb.x);
+ rec709_to_g = float4_to_float3(rec709_to_rgb.y);
+ rec709_to_b = float4_to_float3(rec709_to_rgb.z);
+ is_rec709 = transform_equal_threshold(xyz_to_rgb, xyz_to_rec709, 0.0001f);
#endif
}
diff --git a/intern/cycles/scene/shader.h b/intern/cycles/scene/shader.h
index cbe331d8ec2..274bb9b4fa1 100644
--- a/intern/cycles/scene/shader.h
+++ b/intern/cycles/scene/shader.h
@@ -208,6 +208,7 @@ class ShaderManager {
static void free_memory();
float linear_rgb_to_gray(float3 c);
+ float3 rec709_to_scene_linear(float3 c);
string get_cryptomatte_materials(Scene *scene);
@@ -239,6 +240,10 @@ class ShaderManager {
float3 xyz_to_g;
float3 xyz_to_b;
float3 rgb_to_y;
+ float3 rec709_to_r;
+ float3 rec709_to_g;
+ float3 rec709_to_b;
+ bool is_rec709;
};
CCL_NAMESPACE_END
diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp
index a951a558731..95fccf725f3 100644
--- a/intern/cycles/scene/shader_nodes.cpp
+++ b/intern/cycles/scene/shader_nodes.cpp
@@ -5763,7 +5763,9 @@ BlackbodyNode::BlackbodyNode() : ShaderNode(get_node_type())
void BlackbodyNode::constant_fold(const ConstantFolder &folder)
{
if (folder.all_inputs_constant()) {
- folder.make_constant(svm_math_blackbody_color(temperature));
+ const float3 rgb_rec709 = svm_math_blackbody_color_rec709(temperature);
+ const float3 rgb = folder.scene->shader_manager->rec709_to_scene_linear(rgb_rec709);
+ folder.make_constant(rgb);
}
}
diff --git a/intern/cycles/util/tbb.h b/intern/cycles/util/tbb.h
index 7105ddda0f8..948bf2b3e0e 100644
--- a/intern/cycles/util/tbb.h
+++ b/intern/cycles/util/tbb.h
@@ -25,6 +25,17 @@ CCL_NAMESPACE_BEGIN
using tbb::blocked_range;
using tbb::enumerable_thread_specific;
using tbb::parallel_for;
+using tbb::parallel_for_each;
+
+static inline void thread_capture_fp_settings()
+{
+#if TBB_INTERFACE_VERSION_MAJOR >= 12
+ tbb::task_group_context *ctx = tbb::task::current_context();
+#else
+ tbb::task_group_context *ctx = tbb::task::self().group();
+#endif
+ ctx->capture_fp_settings();
+}
static inline void parallel_for_cancel()
{
diff --git a/intern/cycles/util/transform.h b/intern/cycles/util/transform.h
index 371dbb0f4aa..477272f0ba6 100644
--- a/intern/cycles/util/transform.h
+++ b/intern/cycles/util/transform.h
@@ -285,6 +285,21 @@ ccl_device_inline bool operator!=(const Transform &A, const Transform &B)
return !(A == B);
}
+ccl_device_inline bool transform_equal_threshold(const Transform &A,
+ const Transform &B,
+ const float threshold)
+{
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 4; y++) {
+ if (fabsf(A[x][y] - B[x][y]) > threshold) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
ccl_device_inline float3 transform_get_column(const Transform *t, int column)
{
return make_float3(t->x[column], t->y[column], t->z[column]);
diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h
index a82f634183d..ec641938f1f 100644
--- a/intern/ghost/GHOST_C-api.h
+++ b/intern/ghost/GHOST_C-api.h
@@ -30,8 +30,10 @@ extern GHOST_SystemHandle GHOST_CreateSystem(void);
/**
* Specifies whether debug messages are to be enabled for the specific system handle.
+ * \param systemhandle: The handle to the system.
+ * \param debug: Flag for systems to debug.
*/
-extern void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, int is_debug_enabled);
+extern void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, GHOST_Debug debug);
/**
* Disposes the one and only system.
diff --git a/intern/ghost/GHOST_ISystem.h b/intern/ghost/GHOST_ISystem.h
index ed193ee7e5d..bb91abbadec 100644
--- a/intern/ghost/GHOST_ISystem.h
+++ b/intern/ghost/GHOST_ISystem.h
@@ -452,8 +452,9 @@ class GHOST_ISystem {
/**
* Specify whether debug messages are to be shown.
+ * \param debug: Flag for systems to debug.
*/
- virtual void initDebug(bool is_debug_enabled) = 0;
+ virtual void initDebug(GHOST_Debug debug) = 0;
/**
* Check whether debug messages are to be shown.
diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h
index c654367072f..85913fbd10c 100644
--- a/intern/ghost/GHOST_Types.h
+++ b/intern/ghost/GHOST_Types.h
@@ -573,6 +573,16 @@ typedef struct {
uint32_t frequency;
} GHOST_DisplaySetting;
+typedef enum {
+ /** Axis that cursor grab will wrap. */
+ GHOST_kDebugDefault = (1 << 1),
+ GHOST_kDebugWintab = (1 << 2),
+} GHOST_TDebugFlags;
+
+typedef struct {
+ int flags;
+} GHOST_Debug;
+
#ifdef _WIN32
typedef void *GHOST_TEmbedderWindowID;
#endif // _WIN32
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp
index e3d01c24283..93e94893162 100644
--- a/intern/ghost/intern/GHOST_C-api.cpp
+++ b/intern/ghost/intern/GHOST_C-api.cpp
@@ -30,11 +30,11 @@ GHOST_SystemHandle GHOST_CreateSystem(void)
return (GHOST_SystemHandle)system;
}
-void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, int is_debug_enabled)
+void GHOST_SystemInitDebug(GHOST_SystemHandle systemhandle, GHOST_Debug debug)
{
GHOST_ISystem *system = (GHOST_ISystem *)systemhandle;
- system->initDebug(is_debug_enabled);
+ system->initDebug(debug);
}
GHOST_TSuccess GHOST_DisposeSystem(GHOST_SystemHandle systemhandle)
diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp
index 3df85e18bc7..0d0d41972fd 100644
--- a/intern/ghost/intern/GHOST_System.cpp
+++ b/intern/ghost/intern/GHOST_System.cpp
@@ -390,9 +390,9 @@ void GHOST_System::useWindowFocus(const bool use_focus)
m_windowFocus = use_focus;
}
-void GHOST_System::initDebug(bool is_debug_enabled)
+void GHOST_System::initDebug(GHOST_Debug debug)
{
- m_is_debug_enabled = is_debug_enabled;
+ m_is_debug_enabled = debug.flags & GHOST_kDebugDefault;
}
bool GHOST_System::isDebugEnabled()
diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h
index 0e1e3f734ae..4a3cded1fbd 100644
--- a/intern/ghost/intern/GHOST_System.h
+++ b/intern/ghost/intern/GHOST_System.h
@@ -334,8 +334,9 @@ class GHOST_System : public GHOST_ISystem {
/**
* Specify whether debug messages are to be shown.
+ * \param debug: Flag for systems to debug.
*/
- virtual void initDebug(bool is_debug_enabled);
+ virtual void initDebug(GHOST_Debug debug);
/**
* Check whether debug messages are to be shown.
diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp
index e588c7485b4..83869188b65 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemWin32.cpp
@@ -872,6 +872,13 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(GHOST_TEventType type,
int msgPosY = GET_Y_LPARAM(msgPos);
system->pushEvent(new GHOST_EventCursor(
::GetMessageTime(), GHOST_kEventCursorMove, window, msgPosX, msgPosY, td));
+
+ if (type == GHOST_kEventButtonDown) {
+ WINTAB_PRINTF("HWND %p OS button down\n", window->getHWND());
+ }
+ else if (type == GHOST_kEventButtonUp) {
+ WINTAB_PRINTF("HWND %p OS button up\n", window->getHWND());
+ }
}
window->updateMouseCapture(type == GHOST_kEventButtonDown ? MousePressed : MouseReleased);
@@ -914,6 +921,8 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window)
break;
}
case GHOST_kEventButtonDown: {
+ WINTAB_PRINTF("HWND %p Wintab button down", window->getHWND());
+
UINT message;
switch (info.button) {
case GHOST_kButtonMaskLeft:
@@ -939,9 +948,12 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window)
/* Test for Win32/Wintab button down match. */
useWintabPos = wt->testCoordinates(msg.pt.x, msg.pt.y, info.x, info.y);
if (!useWintabPos) {
+ WINTAB_PRINTF(" ... but associated system button mismatched position\n");
continue;
}
+ WINTAB_PRINTF(" ... associated to system button\n");
+
/* Steal the Win32 event which was previously peeked. */
PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD);
@@ -958,9 +970,14 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window)
mouseMoveHandled = true;
break;
}
+ else {
+ WINTAB_PRINTF(" ... but no system button\n");
+ }
}
case GHOST_kEventButtonUp: {
+ WINTAB_PRINTF("HWND %p Wintab button up", window->getHWND());
if (!useWintabPos) {
+ WINTAB_PRINTF(" ... but Wintab position isn't trusted\n");
continue;
}
@@ -986,10 +1003,14 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window)
if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) &&
msg.message != WM_QUIT) {
+ WINTAB_PRINTF(" ... associated to system button\n");
window->updateMouseCapture(MouseReleased);
system->pushEvent(
new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
}
+ else {
+ WINTAB_PRINTF(" ... but no system button\n");
+ }
break;
}
default:
@@ -1318,6 +1339,12 @@ void GHOST_SystemWin32::setTabletAPI(GHOST_TTabletAPI api)
}
}
+void GHOST_SystemWin32::initDebug(GHOST_Debug debug)
+{
+ GHOST_System::initDebug(debug);
+ GHOST_Wintab::setDebug(debug.flags & GHOST_kDebugWintab);
+}
+
void GHOST_SystemWin32::processMinMaxInfo(MINMAXINFO *minmax)
{
minmax->ptMinTrackSize.x = 320;
@@ -1593,6 +1620,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
// Wintab events, processed
////////////////////////////////////////////////////////////////////////
case WT_CSRCHANGE: {
+ WINTAB_PRINTF("HWND %p HCTX %p WT_CSRCHANGE\n", window->getHWND(), (void *)lParam);
GHOST_Wintab *wt = window->getWintab();
if (wt) {
wt->updateCursorInfo();
@@ -1601,6 +1629,20 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
break;
}
case WT_PROXIMITY: {
+ WINTAB_PRINTF("HWND %p HCTX %p WT_PROXIMITY\n", window->getHWND(), (void *)wParam);
+ if (LOWORD(lParam)) {
+ WINTAB_PRINTF(" Cursor entering context.\n");
+ }
+ else {
+ WINTAB_PRINTF(" Cursor leaving context.\n");
+ }
+ if (HIWORD(lParam)) {
+ WINTAB_PRINTF(" Cursor entering or leaving hardware proximity.\n");
+ }
+ else {
+ WINTAB_PRINTF(" Cursor neither entering nor leaving hardware proximity.\n");
+ }
+
GHOST_Wintab *wt = window->getWintab();
if (wt) {
bool inRange = LOWORD(lParam);
@@ -1616,6 +1658,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
break;
}
case WT_INFOCHANGE: {
+ WINTAB_PRINTF("HWND %p HCTX %p WT_INFOCHANGE\n", window->getHWND(), (void *)wParam);
GHOST_Wintab *wt = window->getWintab();
if (wt) {
wt->processInfoChange(lParam);
@@ -1632,6 +1675,32 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
eventHandled = true;
break;
////////////////////////////////////////////////////////////////////////
+ // Wintab events, debug
+ ////////////////////////////////////////////////////////////////////////
+ case WT_CTXOPEN:
+ WINTAB_PRINTF("HWND %p HCTX %p WT_CTXOPEN\n", window->getHWND(), (void *)wParam);
+ break;
+ case WT_CTXCLOSE:
+ WINTAB_PRINTF("HWND %p HCTX %p WT_CTXCLOSE\n", window->getHWND(), (void *)wParam);
+ break;
+ case WT_CTXUPDATE:
+ WINTAB_PRINTF("HWND %p HCTX %p WT_CTXUPDATE\n", window->getHWND(), (void *)wParam);
+ break;
+ case WT_CTXOVERLAP:
+ WINTAB_PRINTF("HWND %p HCTX %p WT_CTXOVERLAP", window->getHWND(), (void *)wParam);
+ switch (lParam) {
+ case CXS_DISABLED:
+ WINTAB_PRINTF(" CXS_DISABLED\n");
+ break;
+ case CXS_OBSCURED:
+ WINTAB_PRINTF(" CXS_OBSCURED\n");
+ break;
+ case CXS_ONTOP:
+ WINTAB_PRINTF(" CXS_ONTOP\n");
+ break;
+ }
+ break;
+ ////////////////////////////////////////////////////////////////////////
// Pointer events, processed
////////////////////////////////////////////////////////////////////////
case WM_POINTERUPDATE:
@@ -1692,6 +1761,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
break;
case WM_MOUSEMOVE:
if (!window->m_mousePresent) {
+ WINTAB_PRINTF("HWND %p mouse enter\n", window->getHWND());
TRACKMOUSEEVENT tme = {sizeof(tme)};
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hwnd;
@@ -1740,6 +1810,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
}
break;
case WM_MOUSELEAVE: {
+ WINTAB_PRINTF("HWND %p mouse leave\n", window->getHWND());
window->m_mousePresent = false;
if (window->getTabletData().Active == GHOST_kTabletModeNone) {
event = processCursorEvent(window);
diff --git a/intern/ghost/intern/GHOST_SystemWin32.h b/intern/ghost/intern/GHOST_SystemWin32.h
index 16ad5f041ca..9f8d52f9ca3 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.h
+++ b/intern/ghost/intern/GHOST_SystemWin32.h
@@ -259,6 +259,16 @@ class GHOST_SystemWin32 : public GHOST_System {
*/
void setTabletAPI(GHOST_TTabletAPI api) override;
+ /***************************************************************************************
+ ** Debug Info
+ ***************************************************************************************/
+
+ /**
+ * Specify which debug messages are to be shown.
+ * \param debug: Flag for systems to debug.
+ */
+ void initDebug(GHOST_Debug debug) override;
+
protected:
/**
* Initializes the system.
diff --git a/intern/ghost/intern/GHOST_WindowWin32.cpp b/intern/ghost/intern/GHOST_WindowWin32.cpp
index 11a3c097958..2ce224b666b 100644
--- a/intern/ghost/intern/GHOST_WindowWin32.cpp
+++ b/intern/ghost/intern/GHOST_WindowWin32.cpp
@@ -960,6 +960,7 @@ GHOST_Wintab *GHOST_WindowWin32::getWintab() const
void GHOST_WindowWin32::loadWintab(bool enable)
{
if (!m_wintab) {
+ WINTAB_PRINTF("Loading Wintab for window %p\n", m_hWnd);
if (m_wintab = GHOST_Wintab::loadWintab(m_hWnd)) {
if (enable) {
m_wintab->enable();
@@ -982,6 +983,7 @@ void GHOST_WindowWin32::loadWintab(bool enable)
void GHOST_WindowWin32::closeWintab()
{
+ WINTAB_PRINTF("Closing Wintab for window %p\n", m_hWnd);
delete m_wintab;
m_wintab = NULL;
}
diff --git a/intern/ghost/intern/GHOST_Wintab.cpp b/intern/ghost/intern/GHOST_Wintab.cpp
index 2547a38c0d1..be1a0a4b314 100644
--- a/intern/ghost/intern/GHOST_Wintab.cpp
+++ b/intern/ghost/intern/GHOST_Wintab.cpp
@@ -11,7 +11,6 @@
GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd)
{
/* Load Wintab library if available. */
-
auto handle = unique_hmodule(::LoadLibrary("Wintab32.dll"), &::FreeLibrary);
if (!handle) {
return nullptr;
@@ -116,6 +115,11 @@ GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd)
}
}
+ int sanityQueueSize = queueSizeGet(hctx.get());
+ WINTAB_PRINTF("HCTX %p %s queueSize: %d, queueSizeGet: %d\n", hctx.get(), __func__, queueSize, sanityQueueSize);
+
+ WINTAB_PRINTF("Loaded Wintab context %p\n", hctx.get());
+
return new GHOST_Wintab(std::move(handle),
info,
get,
@@ -183,7 +187,17 @@ GHOST_Wintab::GHOST_Wintab(unique_hmodule handle,
m_pkts{queueSize}
{
m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
+ WINTAB_PRINTF("Wintab Devices: %d\n", m_numDevices);
+
updateCursorInfo();
+
+ /* Debug info. */
+ printContextDebugInfo();
+}
+
+GHOST_Wintab::~GHOST_Wintab()
+{
+ WINTAB_PRINTF("Closing Wintab context %p\n", m_context.get());
}
void GHOST_Wintab::enable()
@@ -249,6 +263,7 @@ void GHOST_Wintab::updateCursorInfo()
BOOL pressureSupport = m_fpInfo(WTI_DEVICES, DVC_NPRESSURE, &Pressure);
m_maxPressure = pressureSupport ? Pressure.axMax : 0;
+ WINTAB_PRINTF("HCTX %p %s maxPressure: %d\n", m_context.get(), __func__, m_maxPressure);
BOOL tiltSupport = m_fpInfo(WTI_DEVICES, DVC_ORIENTATION, &Orientation);
/* Check if tablet supports azimuth [0] and altitude [1], encoded in axResolution. */
@@ -259,6 +274,7 @@ void GHOST_Wintab::updateCursorInfo()
else {
m_maxAzimuth = m_maxAltitude = 0;
}
+ WINTAB_PRINTF("HCTX %p %s maxAzimuth: %d, maxAltitude: %d\n", m_context.get(), __func__, m_maxAzimuth, m_maxAltitude);
}
void GHOST_Wintab::processInfoChange(LPARAM lParam)
@@ -266,6 +282,7 @@ void GHOST_Wintab::processInfoChange(LPARAM lParam)
/* Update number of connected Wintab digitizers. */
if (LOWORD(lParam) == WTI_INTERFACE && HIWORD(lParam) == IFC_NDEVICES) {
m_fpInfo(WTI_INTERFACE, IFC_NDEVICES, &m_numDevices);
+ WINTAB_PRINTF("HCTX %p %s numDevices: %d\n", m_context.get(), __func__, m_numDevices);
}
}
@@ -456,3 +473,144 @@ bool GHOST_Wintab::testCoordinates(int sysX, int sysY, int wtX, int wtY)
return false;
}
}
+
+bool GHOST_Wintab::m_debug = false;
+
+void GHOST_Wintab::setDebug(bool debug)
+{
+ m_debug = debug;
+}
+
+bool GHOST_Wintab::getDebug()
+{
+ return m_debug;
+}
+
+void GHOST_Wintab::printContextDebugInfo()
+{
+ if (!m_debug) {
+ return;
+ }
+
+ /* Print button maps. */
+ BYTE logicalButtons[32] = {0};
+ BYTE systemButtons[32] = {0};
+ for (int i = 0; i < 3; i++) {
+ printf("initializeWintab cursor %d buttons\n", i);
+ UINT lbut = m_fpInfo(WTI_CURSORS + i, CSR_BUTTONMAP, &logicalButtons);
+ if (lbut) {
+ printf("%d", logicalButtons[0]);
+ for (int j = 1; j < lbut; j++) {
+ printf(", %d", logicalButtons[j]);
+ }
+ printf("\n");
+ }
+ else {
+ printf("logical button error\n");
+ }
+ UINT sbut = m_fpInfo(WTI_CURSORS + i, CSR_SYSBTNMAP, &systemButtons);
+ if (sbut) {
+ printf("%d", systemButtons[0]);
+ for (int j = 1; j < sbut; j++) {
+ printf(", %d", systemButtons[j]);
+ }
+ printf("\n");
+ }
+ else {
+ printf("system button error\n");
+ }
+ }
+
+ /* Print context information. */
+
+ /* Print open context constraints. */
+ UINT maxcontexts, opencontexts;
+ m_fpInfo(WTI_INTERFACE, IFC_NCONTEXTS, &maxcontexts);
+ m_fpInfo(WTI_STATUS, STA_CONTEXTS, &opencontexts);
+ printf("%u max contexts, %u open contexts\n", maxcontexts, opencontexts);
+
+ /* Print system information. */
+ printf("left: %d, top: %d, width: %d, height: %d\n",
+ ::GetSystemMetrics(SM_XVIRTUALSCREEN),
+ ::GetSystemMetrics(SM_YVIRTUALSCREEN),
+ ::GetSystemMetrics(SM_CXVIRTUALSCREEN),
+ ::GetSystemMetrics(SM_CYVIRTUALSCREEN));
+
+ auto printContextRanges = [](LOGCONTEXT &lc) {
+ printf("lcInOrgX: %d, lcInOrgY: %d, lcInExtX: %d, lcInExtY: %d\n",
+ lc.lcInOrgX,
+ lc.lcInOrgY,
+ lc.lcInExtX,
+ lc.lcInExtY);
+ printf("lcOutOrgX: %d, lcOutOrgY: %d, lcOutExtX: %d, lcOutExtY: %d\n",
+ lc.lcOutOrgX,
+ lc.lcOutOrgY,
+ lc.lcOutExtX,
+ lc.lcOutExtY);
+ printf("lcSysOrgX: %d, lcSysOrgY: %d, lcSysExtX: %d, lcSysExtY: %d\n",
+ lc.lcSysOrgX,
+ lc.lcSysOrgY,
+ lc.lcSysExtX,
+ lc.lcSysExtY);
+ };
+
+ LOGCONTEXT lc;
+
+ /* Print system context. */
+ m_fpInfo(WTI_DEFSYSCTX, 0, &lc);
+ printf("WTI_DEFSYSCTX\n");
+ printContextRanges(lc);
+
+ /* Print system context, manually populated. */
+ m_fpInfo(WTI_DEFSYSCTX, CTX_INORGX, &lc.lcInOrgX);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_INORGY, &lc.lcInOrgY);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTX, &lc.lcInExtX);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_INEXTY, &lc.lcInExtY);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGX, &lc.lcOutOrgX);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_OUTORGY, &lc.lcOutOrgY);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTX, &lc.lcOutExtX);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_OUTEXTY, &lc.lcOutExtY);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGX, &lc.lcSysOrgX);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_SYSORGY, &lc.lcSysOrgY);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTX, &lc.lcSysExtX);
+ m_fpInfo(WTI_DEFSYSCTX, CTX_SYSEXTY, &lc.lcSysExtY);
+ printf("WTI_DEFSYSCTX CTX_*\n");
+ printContextRanges(lc);
+
+ for (unsigned int i = 0; i < m_numDevices; i++) {
+ /* Print individual device system context. */
+ m_fpInfo(WTI_DSCTXS + i, 0, &lc);
+ printf("WTI_DSCTXS %u\n", i);
+ printContextRanges(lc);
+
+ /* Print individual device system context, manually populated. */
+ m_fpInfo(WTI_DSCTXS + i, CTX_INORGX, &lc.lcInOrgX);
+ m_fpInfo(WTI_DSCTXS + i, CTX_INORGY, &lc.lcInOrgY);
+ m_fpInfo(WTI_DSCTXS + i, CTX_INEXTX, &lc.lcInExtX);
+ m_fpInfo(WTI_DSCTXS + i, CTX_INEXTY, &lc.lcInExtY);
+ m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGX, &lc.lcOutOrgX);
+ m_fpInfo(WTI_DSCTXS + i, CTX_OUTORGY, &lc.lcOutOrgY);
+ m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTX, &lc.lcOutExtX);
+ m_fpInfo(WTI_DSCTXS + i, CTX_OUTEXTY, &lc.lcOutExtY);
+ m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGX, &lc.lcSysOrgX);
+ m_fpInfo(WTI_DSCTXS + i, CTX_SYSORGY, &lc.lcSysOrgY);
+ m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTX, &lc.lcSysExtX);
+ m_fpInfo(WTI_DSCTXS + i, CTX_SYSEXTY, &lc.lcSysExtY);
+ printf("WTI_DSCTX %u CTX_*\n", i);
+ printContextRanges(lc);
+
+ /* Print device axis. */
+ AXIS axis_x, axis_y;
+ m_fpInfo(WTI_DEVICES + i, DVC_X, &axis_x);
+ m_fpInfo(WTI_DEVICES + i, DVC_Y, &axis_y);
+ printf("WTI_DEVICES %u axis_x org: %d, axis_y org: %d axis_x ext: %d, axis_y ext: %d\n",
+ i,
+ axis_x.axMin,
+ axis_y.axMin,
+ axis_x.axMax - axis_x.axMin + 1,
+ axis_y.axMax - axis_y.axMin + 1);
+ }
+
+ /* Other stuff while we have a logcontext. */
+ printf("sysmode %d\n", lc.lcSysMode);
+} \ No newline at end of file
diff --git a/intern/ghost/intern/GHOST_Wintab.h b/intern/ghost/intern/GHOST_Wintab.h
index a793d2d8f63..86a0143ecc0 100644
--- a/intern/ghost/intern/GHOST_Wintab.h
+++ b/intern/ghost/intern/GHOST_Wintab.h
@@ -13,6 +13,7 @@
#pragma once
#include <memory>
+#include <stdio.h>
#include <vector>
#include <wtypes.h>
@@ -25,6 +26,14 @@
#define PACKETMODE 0
#include <pktdef.h>
+#define WINTAB_PRINTF(x, ...) \
+ { \
+ if (GHOST_Wintab::getDebug()) { \
+ printf(x, __VA_ARGS__); \
+ } \
+ } \
+ (void)0
+
/* Typedefs for Wintab functions to allow dynamic loading. */
typedef UINT(API *GHOST_WIN32_WTInfo)(UINT, UINT, LPVOID);
typedef BOOL(API *GHOST_WIN32_WTGet)(HCTX, LPLOGCONTEXTA);
@@ -55,9 +64,12 @@ class GHOST_Wintab {
/**
* Loads Wintab if available.
* \param hwnd: Window to attach Wintab context to.
+ * \return Pointer to the initialized GHOST_Wintab object, or null if initialization failed.
*/
static GHOST_Wintab *loadWintab(HWND hwnd);
+ ~GHOST_Wintab();
+
/**
* Enables Wintab context.
*/
@@ -146,6 +158,16 @@ class GHOST_Wintab {
*/
GHOST_TabletData getLastTabletData();
+ /* Sets Wintab debugging.
+ * \param debug: True to enable Wintab debugging.
+ */
+ static void setDebug(bool debug);
+
+ /* Returns whether Wintab logging should occur.
+ * \return True if Wintab logging should occur.
+ */
+ static bool getDebug();
+
private:
/** Wintab DLL handle. */
unique_hmodule m_handle;
@@ -200,6 +222,9 @@ class GHOST_Wintab {
/** Most recently received tablet data, or none if pen is not in range. */
GHOST_TabletData m_lastTabletData = GHOST_TABLET_DATA_NONE;
+ /** Whether Wintab logging is enabled. */
+ static bool m_debug;
+
GHOST_Wintab(unique_hmodule handle,
GHOST_WIN32_WTInfo info,
GHOST_WIN32_WTGet get,
@@ -233,4 +258,7 @@ class GHOST_Wintab {
* \param system: System coordinates.
*/
static void extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &system);
+
+ /* Prints Wintab Context information. */
+ void printContextDebugInfo();
};
diff --git a/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat b/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat
index b785bb51431..6e17f520282 100644
--- a/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat
+++ b/release/datafiles/icons/brush.sculpt.multiplane_scrape.dat
Binary files differ
diff --git a/release/datafiles/icons/ops.sculpt.color_filter.dat b/release/datafiles/icons/ops.sculpt.color_filter.dat
index d589b15a124..8a65043eb5f 100644
--- a/release/datafiles/icons/ops.sculpt.color_filter.dat
+++ b/release/datafiles/icons/ops.sculpt.color_filter.dat
Binary files differ
diff --git a/release/datafiles/icons/ops.sculpt.mask_by_color.dat b/release/datafiles/icons/ops.sculpt.mask_by_color.dat
index 637c47d2d84..6194ce172d5 100644
--- a/release/datafiles/icons/ops.sculpt.mask_by_color.dat
+++ b/release/datafiles/icons/ops.sculpt.mask_by_color.dat
Binary files differ
diff --git a/release/datafiles/locale b/release/datafiles/locale
-Subproject 716dc02ec30c0810513f7b4adc4ae865ae50c4e
+Subproject 63699f968344db7dc853d2c5972325beea44900
diff --git a/release/scripts/addons b/release/scripts/addons
-Subproject 787ea78f7fa6f0373d80ba1247768402df93f8a
+Subproject baa581415c7ed23d7c45ef87363174813567268
diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py
index be60a1711aa..b3f0c055769 100644
--- a/release/scripts/modules/bpy_types.py
+++ b/release/scripts/modules/bpy_types.py
@@ -371,13 +371,8 @@ class _GenericBone:
"""
return (self.tail - self.head)
- @property
- def children(self):
- """A list of all the bones children.
-
- .. note:: Takes ``O(len(bones))`` time."""
- return [child for child in self._other_bones if child.parent == self]
-
+ # NOTE: each bone type is responsible for implementing `children`.
+ # This is done since `Bone` has direct access to this data in RNA.
@property
def children_recursive(self):
"""A list of all children from this bone.
@@ -457,10 +452,19 @@ class PoseBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
class Bone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
__slots__ = ()
+ # NOTE: `children` is implemented in RNA.
+
class EditBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
__slots__ = ()
+ @property
+ def children(self):
+ """A list of all the bones children.
+
+ .. note:: Takes ``O(len(bones))`` time."""
+ return [child for child in self._other_bones if child.parent == self]
+
def align_orientation(self, other):
"""
Align this bone to another by moving its tail and settings its roll
diff --git a/release/scripts/modules/rna_info.py b/release/scripts/modules/rna_info.py
index a8814759840..b009cc4fefe 100644
--- a/release/scripts/modules/rna_info.py
+++ b/release/scripts/modules/rna_info.py
@@ -198,7 +198,11 @@ class InfoStructRNA:
for identifier, attr in self._get_py_visible_attrs():
# methods may be python wrappers to C functions
attr_func = getattr(attr, "__func__", attr)
- if type(attr_func) in {types.BuiltinMethodType, types.BuiltinFunctionType}:
+ if (
+ (type(attr_func) in {types.BuiltinMethodType, types.BuiltinFunctionType}) or
+ # Without the `objclass` check, many inherited methods are included.
+ (type(attr_func) == types.MethodDescriptorType and attr_func.__objclass__ == self.py_class)
+ ):
functions.append((identifier, attr))
return functions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index fc86d02b83e..6408873f13e 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -7370,7 +7370,7 @@ def km_3d_view_tool_sculpt_mask_by_color(params):
"3D View Tool: Sculpt, Mask by Color",
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
{"items": [
- ("sculpt.mask_by_color", {"type": params.tool_mouse, "value": 'CLICK'}, None)
+ ("sculpt.mask_by_color", {"type": params.tool_mouse, "value": 'PRESS'}, None)
]},
)
diff --git a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py
index 544b32700a3..4863a78c07a 100644
--- a/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py
+++ b/release/scripts/startup/bl_app_templates_system/2D_Animation/__init__.py
@@ -5,6 +5,7 @@
import bpy
from bpy.app.handlers import persistent
+
def update_factory_startup_screens():
# 2D Animation.
screen = bpy.data.screens["2D Animation"]
diff --git a/release/scripts/startup/bl_operators/constraint.py b/release/scripts/startup/bl_operators/constraint.py
index a05936c875c..444452ad2c5 100644
--- a/release/scripts/startup/bl_operators/constraint.py
+++ b/release/scripts/startup/bl_operators/constraint.py
@@ -70,8 +70,8 @@ class CONSTRAINT_OT_normalize_target_weights(Operator):
class CONSTRAINT_OT_disable_keep_transform(Operator):
"""Set the influence of this constraint to zero while """ \
- """trying to maintain the object's transformation. Other active """ \
- """constraints can still influence the final transformation"""
+ """trying to maintain the object's transformation. Other active """ \
+ """constraints can still influence the final transformation"""
bl_idname = "constraint.disable_keep_transform"
bl_label = "Disable and Keep Transform"
diff --git a/release/scripts/startup/bl_operators/file.py b/release/scripts/startup/bl_operators/file.py
index cfe03b4760c..0fafb09f672 100644
--- a/release/scripts/startup/bl_operators/file.py
+++ b/release/scripts/startup/bl_operators/file.py
@@ -247,7 +247,7 @@ class WM_OT_previews_batch_clear(Operator):
class WM_OT_blend_strings_utf8_validate(Operator):
"""Check and fix all strings in current .blend file to be valid UTF-8 Unicode """ \
- """(needed for some old, 2.4x area files)"""
+ """(needed for some old, 2.4x area files)"""
bl_idname = "wm.blend_strings_utf8_validate"
bl_label = "Validate .blend strings"
bl_options = {'REGISTER'}
diff --git a/release/scripts/startup/bl_operators/geometry_nodes.py b/release/scripts/startup/bl_operators/geometry_nodes.py
index f62fed79438..ea4d40bb778 100644
--- a/release/scripts/startup/bl_operators/geometry_nodes.py
+++ b/release/scripts/startup/bl_operators/geometry_nodes.py
@@ -26,7 +26,6 @@ def geometry_node_group_empty_new():
def geometry_modifier_poll(context):
ob = context.object
-
# Test object support for geometry node modifier
if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME', 'CURVE', 'FONT', 'CURVES'}:
return False
@@ -83,32 +82,7 @@ class NewGeometryNodeTreeAssign(Operator):
return {'FINISHED'}
-class CopyGeometryNodeTreeAssign(Operator):
- """Copy the active geometry node group and assign it to the active modifier"""
-
- bl_idname = "node.copy_geometry_node_group_assign"
- bl_label = "Copy Geometry Node Group"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(cls, context):
- return geometry_modifier_poll(context)
-
- def execute(self, context):
- modifier = context.object.modifiers.active
- if modifier is None:
- return {'CANCELLED'}
-
- group = modifier.node_group
- if group is None:
- return {'CANCELLED'}
-
- modifier.node_group = group.copy()
- return {'FINISHED'}
-
-
classes = (
NewGeometryNodesModifier,
NewGeometryNodeTreeAssign,
- CopyGeometryNodeTreeAssign,
)
diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py
index f5ea2d0f85b..6857670c31d 100644
--- a/release/scripts/startup/bl_operators/object.py
+++ b/release/scripts/startup/bl_operators/object.py
@@ -937,7 +937,7 @@ class LoadReferenceImage(LoadImageAsEmpty, Operator):
class OBJECT_OT_assign_property_defaults(Operator):
"""Assign the current values of custom properties as their defaults, """ \
- """for use as part of the rest pose state in NLA track mixing"""
+ """for use as part of the rest pose state in NLA track mixing"""
bl_idname = "object.assign_property_defaults"
bl_label = "Assign Custom Property Values as Default"
bl_options = {'UNDO', 'REGISTER'}
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py
index d6e5604fde0..0f063da40fb 100644
--- a/release/scripts/startup/bl_operators/wm.py
+++ b/release/scripts/startup/bl_operators/wm.py
@@ -1317,7 +1317,7 @@ class WM_OT_properties_edit(Operator):
name="Array Length",
default=3,
min=1,
- max=32, # 32 is the maximum size for RNA array properties.
+ max=32, # 32 is the maximum size for RNA array properties.
)
# Integer properties.
@@ -1511,7 +1511,7 @@ class WM_OT_properties_edit(Operator):
elif self.property_type == 'STRING':
self.default_string = rna_data["default"]
- if self.property_type in { 'FLOAT_ARRAY', 'INT_ARRAY'}:
+ if self.property_type in {'FLOAT_ARRAY', 'INT_ARRAY'}:
self.array_length = len(item[name])
# The dictionary does not contain the description if it was empty.
@@ -2940,9 +2940,9 @@ class WM_MT_splash_quick_setup(Menu):
layout.label(text="Quick Setup")
- split = layout.split(factor=0.14) # Left margin.
+ split = layout.split(factor=0.14) # Left margin.
split.label()
- split = split.split(factor=0.73) # Content width.
+ split = split.split(factor=0.73) # Content width.
col = split.column()
diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py
index 63e7e838cf4..b3b4ab8e52c 100644
--- a/release/scripts/startup/bl_ui/__init__.py
+++ b/release/scripts/startup/bl_ui/__init__.py
@@ -239,4 +239,5 @@ class UI_MT_list_item_context_menu(bpy.types.Menu):
# context menu items.
pass
+
bpy.utils.register_class(UI_MT_list_item_context_menu)
diff --git a/release/scripts/startup/bl_ui/properties_constraint.py b/release/scripts/startup/bl_ui/properties_constraint.py
index 77f454362a4..bd63368bdfd 100644
--- a/release/scripts/startup/bl_ui/properties_constraint.py
+++ b/release/scripts/startup/bl_ui/properties_constraint.py
@@ -1162,6 +1162,7 @@ class ConstraintButtonsSubPanel:
# Child Of Constraint
+
class OBJECT_PT_bChildOfConstraint(ObjectConstraintPanel, ConstraintButtonsPanel, Panel):
def draw(self, context):
self.draw_childof(context)
diff --git a/release/scripts/startup/bl_ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py
index 1ba7a4a5413..d7c885cf870 100644
--- a/release/scripts/startup/bl_ui/properties_data_mesh.py
+++ b/release/scripts/startup/bl_ui/properties_data_mesh.py
@@ -65,6 +65,7 @@ class MESH_MT_shape_key_context_menu(Menu):
layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move to Top").type = 'TOP'
layout.operator("object.shape_key_move", icon='TRIA_DOWN_BAR', text="Move to Bottom").type = 'BOTTOM'
+
class MESH_MT_attribute_context_menu(Menu):
bl_label = "Attribute Specials"
@@ -134,6 +135,7 @@ class MESH_UL_uvmaps(UIList):
layout.alignment = 'CENTER'
layout.label(text="", icon_value=icon)
+
class MeshButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
@@ -419,6 +421,7 @@ class DATA_PT_uv_texture(MeshButtonsPanel, Panel):
col.operator("mesh.uv_texture_add", icon='ADD', text="")
col.operator("mesh.uv_texture_remove", icon='REMOVE', text="")
+
class DATA_PT_remesh(MeshButtonsPanel, Panel):
bl_label = "Remesh"
bl_options = {'DEFAULT_CLOSED'}
@@ -605,7 +608,7 @@ class MESH_UL_color_attributes(UIList, ColorAttributesListBase):
sub.label(text="%s â–¶ %s" % (domain_name, data_type.name))
active_render = _index == data.color_attributes.render_color_index
-
+
row = layout.row()
row.emboss = 'NONE'
prop = row.operator(
@@ -650,6 +653,7 @@ class DATA_PT_vertex_colors(DATA_PT_mesh_attributes, Panel):
self.draw_attribute_warnings(context, layout)
+
classes = (
MESH_MT_vertex_group_context_menu,
MESH_MT_shape_key_context_menu,
diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py
index 4144cf8c8f8..07ec0cd85a6 100644
--- a/release/scripts/startup/bl_ui/space_filebrowser.py
+++ b/release/scripts/startup/bl_ui/space_filebrowser.py
@@ -838,6 +838,7 @@ classes = (
ASSETBROWSER_MT_context_menu,
)
+
def asset_path_str_get(_self):
asset_file_handle = bpy.context.asset_file_handle
if asset_file_handle is None:
diff --git a/release/scripts/startup/bl_ui/space_graph.py b/release/scripts/startup/bl_ui/space_graph.py
index 706e228c5d9..a78ad72cada 100644
--- a/release/scripts/startup/bl_ui/space_graph.py
+++ b/release/scripts/startup/bl_ui/space_graph.py
@@ -324,6 +324,7 @@ class GRAPH_MT_key_snap(Menu):
layout.operator("graph.frame_jump", text="Cursor to Selection")
layout.operator("graph.snap_cursor_value", text="Cursor Value to Selection")
+
class GRAPH_MT_slider(Menu):
bl_label = "Slider Operators"
diff --git a/release/scripts/startup/bl_ui/space_nla.py b/release/scripts/startup/bl_ui/space_nla.py
index f0e991c768d..27c8cb754a7 100644
--- a/release/scripts/startup/bl_ui/space_nla.py
+++ b/release/scripts/startup/bl_ui/space_nla.py
@@ -166,7 +166,6 @@ class NLA_MT_marker_select(Menu):
layout.operator("marker.select_leftright", text="After Current Frame").mode = 'RIGHT'
-
class NLA_MT_edit(Menu):
bl_label = "Edit"
@@ -214,8 +213,10 @@ class NLA_MT_edit(Menu):
layout.operator("nla.tweakmode_exit", text="Stop Tweaking Strip Actions")
else:
layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True
- layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True
- layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False
+ layout.operator("nla.tweakmode_enter",
+ text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True
+ layout.operator("nla.tweakmode_enter",
+ text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False
class NLA_MT_add(Menu):
@@ -289,8 +290,10 @@ class NLA_MT_context_menu(Menu):
layout.operator("nla.tweakmode_exit", text="Stop Tweaking Strip Actions")
else:
layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True
- layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True
- layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False
+ layout.operator("nla.tweakmode_enter",
+ text="Start Tweaking Strip Actions (Full Stack)").use_upper_stack_evaluation = True
+ layout.operator("nla.tweakmode_enter",
+ text="Start Tweaking Strip Actions (Lower Stack)").use_upper_stack_evaluation = False
layout.separator()
diff --git a/release/scripts/startup/bl_ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py
index 1a6e9f57e75..cc673a8bc39 100644
--- a/release/scripts/startup/bl_ui/space_node.py
+++ b/release/scripts/startup/bl_ui/space_node.py
@@ -149,7 +149,7 @@ class NODE_HT_header(Header):
active_modifier = ob.modifiers.active
if active_modifier and active_modifier.type == 'NODES':
if active_modifier.node_group:
- row.template_ID(active_modifier, "node_group", new="node.copy_geometry_node_group_assign")
+ row.template_ID(active_modifier, "node_group", new="object.geometry_node_tree_copy_assign")
else:
row.template_ID(active_modifier, "node_group", new="node.new_geometry_node_group_assign")
else:
@@ -776,7 +776,7 @@ class NodeTreeInterfacePanel:
"node.tree_socket_change_type",
"socket_type",
text=active_socket.bl_label if active_socket.bl_label else active_socket.bl_idname
- )
+ )
props.in_out = in_out
layout.use_property_split = True
@@ -816,6 +816,7 @@ class NODE_PT_node_tree_interface_inputs(NodeTreeInterfacePanel, Panel):
def draw(self, context):
self.draw_socket_list(context, "IN", "inputs", "active_input")
+
class NODE_PT_node_tree_interface_outputs(NodeTreeInterfacePanel, Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
diff --git a/release/scripts/startup/bl_ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py
index 56a63215dcb..bbf9548a973 100644
--- a/release/scripts/startup/bl_ui/space_sequencer.py
+++ b/release/scripts/startup/bl_ui/space_sequencer.py
@@ -1959,7 +1959,6 @@ class SEQUENCER_PT_adjust_sound(SequencerButtonsPanel, Panel):
split.prop(strip, "show_waveform")
-
class SEQUENCER_PT_adjust_comp(SequencerButtonsPanel, Panel):
bl_label = "Compositing"
bl_category = "Strip"
diff --git a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
index 158e07659bc..e00bd2edef9 100644
--- a/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
@@ -2322,7 +2322,7 @@ class _defs_curves_sculpt:
context,
idname_prefix="builtin_brush.",
icon_prefix="ops.curves.sculpt_",
- type= bpy.types.Brush,
+ type=bpy.types.Brush,
attr="curves_sculpt_tool",
)
@@ -2527,7 +2527,8 @@ class _defs_sequencer_generic:
icon="ops.transform.transform",
widget="SEQUENCER_GGT_gizmo2d",
# No keymap default action, only for gizmo!
- )
+ )
+
class _defs_sequencer_select:
@ToolDef.from_fn
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index b954d726ca3..eaa0fd87bce 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -2281,6 +2281,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
),
)
+
# Keep this as tweaks can be useful to restore.
"""
class USERPREF_PT_experimental_tweaks(ExperimentalPanel, Panel):
@@ -2295,6 +2296,7 @@ class USERPREF_PT_experimental_tweaks(ExperimentalPanel, Panel):
"""
+
class USERPREF_PT_experimental_debugging(ExperimentalPanel, Panel):
bl_label = "Debugging"
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 74f20aca072..fc518e929d9 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -2835,6 +2835,7 @@ class VIEW3D_MT_object_cleanup(Menu):
layout.operator("object.material_slot_remove_unused", text="Remove Unused Material Slots")
+
class VIEW3D_MT_object_asset(Menu):
bl_label = "Asset"
@@ -6500,7 +6501,6 @@ class VIEW3D_PT_overlay_edit_mesh_normals(Panel):
row.prop(overlay, "use_normals_constant_screen_size", text="", icon='FIXED_SIZE')
-
class VIEW3D_PT_overlay_edit_mesh_freestyle(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index 332933be68a..10dfd182836 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -483,15 +483,19 @@ class SelectPaintSlotHelper:
match getattr(mode_settings, self.canvas_source_attr_name):
case 'MATERIAL':
if len(ob.material_slots) > 1:
- layout.template_list("MATERIAL_UL_matslots", "layers",
- ob, "material_slots",
- ob, "active_material_index", rows=2)
+ layout.template_list(
+ "MATERIAL_UL_matslots", "layers",
+ ob, "material_slots",
+ ob, "active_material_index", rows=2,
+ )
mat = ob.active_material
if mat and mat.texture_paint_images:
row = layout.row()
- row.template_list("TEXTURE_UL_texpaintslots", "",
- mat, "texture_paint_slots",
- mat, "paint_active_slot", rows=2)
+ row.template_list(
+ "TEXTURE_UL_texpaintslots", "",
+ mat, "texture_paint_slots",
+ mat, "paint_active_slot", rows=2,
+ )
if mat.texture_paint_slots:
slot = mat.texture_paint_slots[mat.paint_active_slot]
@@ -517,7 +521,7 @@ class SelectPaintSlotHelper:
else:
layout.menu("VIEW3D_MT_tools_projectpaint_uvlayer", text=uv_text, translate=False)
have_image = getattr(settings, self.canvas_image_attr_name) is not None
-
+
self.draw_image_interpolation(layout=layout, mode_settings=mode_settings)
case 'COLOR_ATTRIBUTE':
@@ -562,7 +566,6 @@ class VIEW3D_PT_slots_projectpaint(SelectPaintSlotHelper, View3DPanel, Panel):
layout.prop(mode_settings, "interpolation", text="")
-
class VIEW3D_PT_slots_paint_canvas(SelectPaintSlotHelper, View3DPanel, Panel):
bl_category = "Tool"
bl_context = ".sculpt_mode" # dot on purpose (access from topbar)
diff --git a/release/scripts/startup/keyingsets_builtins.py b/release/scripts/startup/keyingsets_builtins.py
index 486137f4247..fb287183c99 100644
--- a/release/scripts/startup/keyingsets_builtins.py
+++ b/release/scripts/startup/keyingsets_builtins.py
@@ -201,7 +201,7 @@ class BUILTIN_KSI_BendyBones(KeyingSetInfo):
# VisualLocation
class BUILTIN_KSI_VisualLoc(KeyingSetInfo):
"""Insert a keyframe on each of the location channels, """ \
- """taking into account effects of constraints and relationships"""
+ """taking into account effects of constraints and relationships"""
bl_label = "Visual Location"
bl_options = {'INSERTKEY_VISUAL'}
@@ -219,7 +219,7 @@ class BUILTIN_KSI_VisualLoc(KeyingSetInfo):
# VisualRotation
class BUILTIN_KSI_VisualRot(KeyingSetInfo):
"""Insert a keyframe on each of the rotation channels, """ \
- """taking into account effects of constraints and relationships"""
+ """taking into account effects of constraints and relationships"""
bl_label = "Visual Rotation"
bl_options = {'INSERTKEY_VISUAL'}
@@ -237,7 +237,7 @@ class BUILTIN_KSI_VisualRot(KeyingSetInfo):
# VisualScaling
class BUILTIN_KSI_VisualScaling(KeyingSetInfo):
"""Insert a keyframe on each of the scale channels, """ \
- """taking into account effects of constraints and relationships"""
+ """taking into account effects of constraints and relationships"""
bl_label = "Visual Scale"
bl_options = {'INSERTKEY_VISUAL'}
@@ -255,7 +255,7 @@ class BUILTIN_KSI_VisualScaling(KeyingSetInfo):
# VisualLocRot
class BUILTIN_KSI_VisualLocRot(KeyingSetInfo):
"""Insert a keyframe on each of the location and rotation channels, """ \
- """taking into account effects of constraints and relationships"""
+ """taking into account effects of constraints and relationships"""
bl_label = "Visual Location & Rotation"
bl_options = {'INSERTKEY_VISUAL'}
@@ -277,7 +277,7 @@ class BUILTIN_KSI_VisualLocRot(KeyingSetInfo):
# VisualLocScale
class BUILTIN_KSI_VisualLocScale(KeyingSetInfo):
"""Insert a keyframe on each of the location and scale channels, """ \
- """taking into account effects of constraints and relationships"""
+ """taking into account effects of constraints and relationships"""
bl_label = "Visual Location & Scale"
bl_options = {'INSERTKEY_VISUAL'}
@@ -299,7 +299,7 @@ class BUILTIN_KSI_VisualLocScale(KeyingSetInfo):
# VisualLocRotScale
class BUILTIN_KSI_VisualLocRotScale(KeyingSetInfo):
"""Insert a keyframe on each of the location, """ \
- """rotation and scale channels, taking into account effects of constraints and relationships"""
+ """rotation and scale channels, taking into account effects of constraints and relationships"""
bl_label = "Visual Location, Rotation & Scale"
bl_options = {'INSERTKEY_VISUAL'}
@@ -323,7 +323,7 @@ class BUILTIN_KSI_VisualLocRotScale(KeyingSetInfo):
# VisualRotScale
class BUILTIN_KSI_VisualRotScale(KeyingSetInfo):
"""Insert a keyframe on each of the rotation and scale channels, """ \
- """taking into account effects of constraints and relationships"""
+ """taking into account effects of constraints and relationships"""
bl_label = "Visual Rotation & Scale"
bl_options = {'INSERTKEY_VISUAL'}
@@ -525,14 +525,14 @@ class WholeCharacterMixin:
class BUILTIN_KSI_WholeCharacter(WholeCharacterMixin, KeyingSetInfo):
"""Insert a keyframe for all properties that are likely to get animated in a character rig """ \
- """(useful when blocking out a shot)"""
+ """(useful when blocking out a shot)"""
bl_idname = ANIM_KS_WHOLE_CHARACTER_ID
bl_label = "Whole Character"
class BUILTIN_KSI_WholeCharacterSelected(WholeCharacterMixin, KeyingSetInfo):
"""Insert a keyframe for all properties that are likely to get animated in a character rig """ \
- """(only selected bones)"""
+ """(only selected bones)"""
bl_idname = ANIM_KS_WHOLE_CHARACTER_SELECTED_ID
bl_label = "Whole Character (Selected Bones Only)"
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index e7c00f4915e..737ea9350b3 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -64,7 +64,8 @@ node_tree_group_type = {
'GeometryNodeTree': 'GeometryNodeGroup',
}
-# Custom Menu for Geometry Node Curves
+
+# Custom Menu for Geometry Node Curves.
def curve_node_items(context):
if context is None:
return
@@ -100,7 +101,8 @@ def curve_node_items(context):
yield NodeItem("GeometryNodeSetSplineResolution")
yield NodeItem("GeometryNodeCurveSplineType")
-# Custom Menu for Geometry Node Mesh
+
+# Custom Menu for Geometry Node Mesh.
def mesh_node_items(context):
if context is None:
return
@@ -131,7 +133,8 @@ def mesh_node_items(context):
yield NodeItemCustom(draw=lambda self, layout, context: layout.separator())
yield NodeItem("GeometryNodeSetShadeSmooth")
-# Custom Menu for Geometry Nodes "Geometry" category
+
+# Custom Menu for Geometry Nodes "Geometry" category.
def geometry_node_items(context):
if context is None:
return
@@ -154,7 +157,8 @@ def geometry_node_items(context):
yield NodeItem("GeometryNodeSetID")
yield NodeItem("GeometryNodeSetPosition")
-# Custom Menu for Geometry Node Input Nodes
+
+# Custom Menu for Geometry Node Input Nodes.
def geometry_input_node_items(context):
if context is None:
return
@@ -181,7 +185,8 @@ def geometry_input_node_items(context):
yield NodeItem("GeometryNodeInputRadius")
yield NodeItem("GeometryNodeInputSceneTime")
-# Custom Menu for Material Nodes
+
+# Custom Menu for Material Nodes.
def geometry_material_node_items(context):
if context is None:
return
@@ -196,7 +201,8 @@ def geometry_material_node_items(context):
yield NodeItem("GeometryNodeSetMaterial")
yield NodeItem("GeometryNodeSetMaterialIndex")
-# Custom Menu for Geometry Node Points
+
+# Custom Menu for Geometry Node Points.
def point_node_items(context):
if context is None:
return
@@ -210,7 +216,8 @@ def point_node_items(context):
yield NodeItemCustom(draw=lambda self, layout, context: layout.separator())
yield NodeItem("GeometryNodeSetPointRadius")
-# generic node group items generator for shader, compositor, geometry and texture node groups
+
+# Generic node group items generator for shader, compositor, geometry and texture node groups.
def node_group_items(context):
if context is None:
return
diff --git a/source/blender/blenkernel/BKE_curve_to_mesh.hh b/source/blender/blenkernel/BKE_curve_to_mesh.hh
index a49cb6eb7f5..6e657542e0f 100644
--- a/source/blender/blenkernel/BKE_curve_to_mesh.hh
+++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh
@@ -2,7 +2,7 @@
#pragma once
-struct CurveEval;
+struct CurvesGeometry;
struct Mesh;
/** \file
@@ -21,11 +21,13 @@ namespace blender::bke {
* changed anyway in a way that affects the normals. So currently this code uses the safer /
* simpler solution of deferring normal calculation to the rest of Blender.
*/
-Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, bool fill_caps);
+Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
+ const CurvesGeometry &profile,
+ bool fill_caps);
/**
* Create a loose-edge mesh based on the evaluated path of the curve's splines.
* Transfer curve attributes to the mesh.
*/
-Mesh *curve_to_wire_mesh(const CurveEval &curve);
+Mesh *curve_to_wire_mesh(const CurvesGeometry &curve);
} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh
index 06971a2243a..282e2a40bd0 100644
--- a/source/blender/blenkernel/BKE_curves.hh
+++ b/source/blender/blenkernel/BKE_curves.hh
@@ -183,6 +183,13 @@ class CurvesGeometry : public ::CurvesGeometry {
MutableSpan<int> resolution_for_write();
/**
+ * The angle used to rotate evaluated normals around the tangents after their calculation.
+ * Call #tag_normals_changed after changes.
+ */
+ VArray<float> tilt() const;
+ MutableSpan<float> tilt_for_write();
+
+ /**
* Which method to use for calculating the normals of evaluated points (#NormalMode).
* Call #tag_normals_changed after changes.
*/
@@ -321,6 +328,10 @@ class CurvesGeometry : public ::CurvesGeometry {
* calculated. That can be ensured with #ensure_evaluated_offsets.
*/
void interpolate_to_evaluated(int curve_index, GSpan src, GMutableSpan dst) const;
+ /**
+ * Evaluate generic data for curve control points to the standard evaluated points of the curves.
+ */
+ void interpolate_to_evaluated(GSpan src, GMutableSpan dst) const;
private:
/**
@@ -439,6 +450,13 @@ bool segment_is_vector(Span<int8_t> handle_types_left,
bool last_cylic_segment_is_vector(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right);
/**
+ * Return true if the handle types at the index are free (#BEZIER_HANDLE_FREE) or vector
+ * (#BEZIER_HANDLE_VECTOR). In these cases, directional continuitity from the previous and next
+ * evaluated segments is assumed not to be desired.
+ */
+bool point_is_sharp(Span<int8_t> handle_types_left, Span<int8_t> handle_types_right, int index);
+
+/**
* Calculate offsets into the curve's evaluated points for each control point. While most control
* point edges generate the number of edges specified by the resolution, vector segments only
* generate one edge.
@@ -708,4 +726,22 @@ inline float CurvesGeometry::evaluated_length_total_for_curve(const int curve_in
/** \} */
+/* -------------------------------------------------------------------- */
+/** \name Bezier Inline Methods
+ * \{ */
+
+namespace curves::bezier {
+
+inline bool point_is_sharp(const Span<int8_t> handle_types_left,
+ const Span<int8_t> handle_types_right,
+ const int index)
+{
+ return ELEM(handle_types_left[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE) ||
+ ELEM(handle_types_right[index], BEZIER_HANDLE_VECTOR, BEZIER_HANDLE_FREE);
+}
+
+} // namespace curves::bezier
+
+/** \} */
+
} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h
index 382bfd5b7db..6784a1296e9 100644
--- a/source/blender/blenkernel/BKE_fcurve.h
+++ b/source/blender/blenkernel/BKE_fcurve.h
@@ -264,7 +264,7 @@ struct FCurve *id_data_find_fcurve(
ID *id, void *data, struct StructRNA *type, const char *prop_name, int index, bool *r_driven);
/**
- * Get list of LinkData's containing pointers to the F-curves
+ * Get list of LinkData's containing pointers to the F-Curves
* which control the types of data indicated.
* e.g. `numMatches = BKE_fcurves_filter(matches, &act->curves, "pose.bones[", "MyFancyBone");`
*
diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h
index d82e7460071..2070584a8a0 100644
--- a/source/blender/blenkernel/BKE_global.h
+++ b/source/blender/blenkernel/BKE_global.h
@@ -195,12 +195,13 @@ enum {
G_DEBUG_XR = (1 << 19), /* XR/OpenXR messages */
G_DEBUG_XR_TIME = (1 << 20), /* XR/OpenXR timing messages */
- G_DEBUG_GHOST = (1 << 21), /* Debug GHOST module. */
+ G_DEBUG_GHOST = (1 << 21), /* Debug GHOST module. */
+ G_DEBUG_WINTAB = (1 << 22), /* Debug Wintab. */
};
#define G_DEBUG_ALL \
(G_DEBUG | G_DEBUG_FFMPEG | G_DEBUG_PYTHON | G_DEBUG_EVENTS | G_DEBUG_WM | G_DEBUG_JOBS | \
- G_DEBUG_FREESTYLE | G_DEBUG_DEPSGRAPH | G_DEBUG_IO | G_DEBUG_GHOST)
+ G_DEBUG_FREESTYLE | G_DEBUG_DEPSGRAPH | G_DEBUG_IO | G_DEBUG_GHOST | G_DEBUG_WINTAB)
/** #Global.fileflags */
enum {
diff --git a/source/blender/blenkernel/BKE_layer.h b/source/blender/blenkernel/BKE_layer.h
index cad6f6d6645..3e4f2fe154e 100644
--- a/source/blender/blenkernel/BKE_layer.h
+++ b/source/blender/blenkernel/BKE_layer.h
@@ -594,9 +594,9 @@ void BKE_view_layer_rename_lightgroup(ViewLayer *view_layer,
ViewLayerLightgroup *lightgroup,
const char *name);
-void BKE_lightgroup_membership_get(struct LightgroupMembership *lgm, char *value);
+void BKE_lightgroup_membership_get(struct LightgroupMembership *lgm, char *name);
int BKE_lightgroup_membership_length(struct LightgroupMembership *lgm);
-void BKE_lightgroup_membership_set(struct LightgroupMembership **lgm, const char *value);
+void BKE_lightgroup_membership_set(struct LightgroupMembership **lgm, const char *name);
#ifdef __cplusplus
}
diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h
index 41a02545591..881e86ccc54 100644
--- a/source/blender/blenkernel/BKE_modifier.h
+++ b/source/blender/blenkernel/BKE_modifier.h
@@ -491,7 +491,6 @@ struct Object *BKE_modifiers_is_deformed_by_lattice(struct Object *ob);
struct Object *BKE_modifiers_is_deformed_by_curve(struct Object *ob);
bool BKE_modifiers_uses_multires(struct Object *ob);
bool BKE_modifiers_uses_armature(struct Object *ob, struct bArmature *arm);
-bool BKE_modifiers_uses_subsurf_facedots(const struct Scene *scene, struct Object *ob);
bool BKE_modifiers_is_correctable_deformed(const struct Scene *scene, struct Object *ob);
void BKE_modifier_free_temporary_data(struct ModifierData *md);
diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h
index db773d34cdc..4ae37095411 100644
--- a/source/blender/blenkernel/BKE_paint.h
+++ b/source/blender/blenkernel/BKE_paint.h
@@ -30,7 +30,9 @@ struct EdgeSet;
struct EnumPropertyItem;
struct GHash;
struct GridPaintMask;
+struct Image;
struct ImagePool;
+struct ImageUser;
struct ListBase;
struct MLoop;
struct MLoopTri;
@@ -650,6 +652,11 @@ typedef struct SculptSession {
*/
bool sticky_shading_color;
+ /**
+ * Last used painting canvas key.
+ */
+ char *last_paint_canvas_key;
+
} SculptSession;
void BKE_sculptsession_free(struct Object *ob);
@@ -727,8 +734,16 @@ enum {
};
/* paint_canvas.cc */
-struct Image *BKE_paint_canvas_image_get(const struct PaintModeSettings *settings,
- struct Object *ob);
+/**
+ * Create a key that can be used to compare with previous ones to identify changes.
+ * The resulting 'string' is owned by the caller.
+ */
+char *BKE_paint_canvas_key_get(struct PaintModeSettings *settings, struct Object *ob);
+
+bool BKE_paint_canvas_image_get(struct PaintModeSettings *settings,
+ struct Object *ob,
+ struct Image **r_image,
+ struct ImageUser **r_image_user);
int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings,
struct Object *ob);
diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h
index 775847f27f8..bb918fcfdcb 100644
--- a/source/blender/blenkernel/BKE_pbvh.h
+++ b/source/blender/blenkernel/BKE_pbvh.h
@@ -31,10 +31,13 @@ struct MLoopTri;
struct MPoly;
struct MVert;
struct Mesh;
+struct MeshElemMap;
struct PBVH;
struct PBVHNode;
struct SubdivCCG;
struct TaskParallelSettings;
+struct Image;
+struct ImageUser;
struct MeshElemMap;
typedef struct PBVH PBVH;
@@ -48,6 +51,15 @@ typedef struct {
float (*color)[4];
} PBVHColorBufferNode;
+typedef struct PBVHPixelsNode {
+ /**
+ * Contains triangle/pixel data used during texture painting.
+ *
+ * Contains #blender::bke::pbvh::pixels::NodeData.
+ */
+ void *node_data;
+} PBVHPixelsNode;
+
typedef enum {
PBVH_Leaf = 1 << 0,
@@ -66,6 +78,8 @@ typedef enum {
PBVH_UpdateTopology = 1 << 13,
PBVH_UpdateColor = 1 << 14,
+ PBVH_RebuildPixels = 1 << 15,
+
} PBVHNodeFlags;
typedef struct PBVHFrustumPlanes {
@@ -127,6 +141,12 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh,
struct BMLog *log,
int cd_vert_node_offset,
int cd_face_node_offset);
+
+void BKE_pbvh_build_pixels(PBVH *pbvh,
+ const struct MLoop *mloop,
+ struct CustomData *ldata,
+ struct Image *image,
+ struct ImageUser *image_user);
void BKE_pbvh_free(PBVH *pbvh);
/* Hierarchical Search in the BVH, two methods:
@@ -287,6 +307,7 @@ bool BKE_pbvh_node_fully_masked_get(PBVHNode *node);
void BKE_pbvh_node_fully_unmasked_set(PBVHNode *node, int fully_masked);
bool BKE_pbvh_node_fully_unmasked_get(PBVHNode *node);
+void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh);
void BKE_pbvh_vert_mark_update(PBVH *pbvh, int index);
void BKE_pbvh_node_get_grids(PBVH *pbvh,
diff --git a/source/blender/blenkernel/BKE_pbvh_pixels.hh b/source/blender/blenkernel/BKE_pbvh_pixels.hh
new file mode 100644
index 00000000000..35eb340d0a1
--- /dev/null
+++ b/source/blender/blenkernel/BKE_pbvh_pixels.hh
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#pragma once
+
+#include "BLI_math.h"
+#include "BLI_math_vec_types.hh"
+#include "BLI_rect.h"
+#include "BLI_vector.hh"
+
+#include "DNA_image_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_image.h"
+#include "BKE_image_wrappers.hh"
+
+#include "IMB_imbuf_types.h"
+
+namespace blender::bke::pbvh::pixels {
+
+struct TrianglePaintInput {
+ int3 vert_indices;
+ /**
+ * Delta barycentric coordinates between 2 neighbouring UV's in the U direction.
+ *
+ * Only the first two coordinates are stored. The third should be recalculated
+ */
+ float2 delta_barycentric_coord_u;
+
+ /**
+ * Initially only the vert indices are known.
+ *
+ * delta_barycentric_coord_u is initialized in a later stage as it requires image tile
+ * dimensions.
+ */
+ TrianglePaintInput(const int3 vert_indices)
+ : vert_indices(vert_indices), delta_barycentric_coord_u(0.0f, 0.0f)
+ {
+ }
+};
+
+/**
+ * Data shared between pixels that belong to the same triangle.
+ *
+ * Data is stored as a list of structs, grouped by usage to improve performance (improves CPU
+ * cache prefetching).
+ */
+struct Triangles {
+ /** Data accessed by the inner loop of the painting brush. */
+ Vector<TrianglePaintInput> paint_input;
+
+ public:
+ void append(const int3 vert_indices)
+ {
+ this->paint_input.append(TrianglePaintInput(vert_indices));
+ }
+
+ TrianglePaintInput &get_paint_input(const int index)
+ {
+ return paint_input[index];
+ }
+
+ const TrianglePaintInput &get_paint_input(const int index) const
+ {
+ return paint_input[index];
+ }
+
+ void clear()
+ {
+ paint_input.clear();
+ }
+
+ uint64_t size() const
+ {
+ return paint_input.size();
+ }
+
+ uint64_t mem_size() const
+ {
+ return paint_input.size() * sizeof(TrianglePaintInput);
+ }
+};
+
+/**
+ * Encode sequential pixels to reduce memory footprint.
+ */
+struct PackedPixelRow {
+ /** Barycentric coordinate of the first pixel. */
+ float2 start_barycentric_coord;
+ /** Image coordinate starting of the first pixel. */
+ ushort2 start_image_coordinate;
+ /** Number of sequential pixels encoded in this package. */
+ ushort num_pixels;
+ /** Reference to the pbvh triangle index. */
+ ushort triangle_index;
+};
+
+/**
+ * Node pixel data containing the pixels for a single UDIM tile.
+ */
+struct UDIMTilePixels {
+ /** UDIM Tile number. */
+ short tile_number;
+
+ struct {
+ bool dirty : 1;
+ } flags;
+
+ /* Dirty region of the tile in image space. */
+ rcti dirty_region;
+
+ Vector<PackedPixelRow> pixel_rows;
+
+ UDIMTilePixels()
+ {
+ flags.dirty = false;
+ BLI_rcti_init_minmax(&dirty_region);
+ }
+
+ void mark_dirty(const PackedPixelRow &pixel_row)
+ {
+ int2 start_image_coord(pixel_row.start_image_coordinate.x, pixel_row.start_image_coordinate.y);
+ BLI_rcti_do_minmax_v(&dirty_region, start_image_coord);
+ BLI_rcti_do_minmax_v(&dirty_region, start_image_coord + int2(pixel_row.num_pixels + 1, 0));
+ flags.dirty = true;
+ }
+
+ void clear_dirty()
+ {
+ BLI_rcti_init_minmax(&dirty_region);
+ flags.dirty = false;
+ }
+};
+
+struct NodeData {
+ struct {
+ bool dirty : 1;
+ } flags;
+
+ Vector<UDIMTilePixels> tiles;
+ Triangles triangles;
+
+ NodeData()
+ {
+ flags.dirty = false;
+ }
+
+ UDIMTilePixels *find_tile_data(const image::ImageTileWrapper &image_tile)
+ {
+ for (UDIMTilePixels &tile : tiles) {
+ if (tile.tile_number == image_tile.get_tile_number()) {
+ return &tile;
+ }
+ }
+ return nullptr;
+ }
+
+ void mark_region(Image &image, const image::ImageTileWrapper &image_tile, ImBuf &image_buffer)
+ {
+ UDIMTilePixels *tile = find_tile_data(image_tile);
+ if (tile && tile->flags.dirty) {
+ BKE_image_partial_update_mark_region(
+ &image, image_tile.image_tile, &image_buffer, &tile->dirty_region);
+ tile->clear_dirty();
+ }
+ }
+
+ void clear_data()
+ {
+ tiles.clear();
+ triangles.clear();
+ }
+
+ static void free_func(void *instance)
+ {
+ NodeData *node_data = static_cast<NodeData *>(instance);
+ MEM_delete(node_data);
+ }
+};
+
+NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node);
+void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user);
+
+} // namespace blender::bke::pbvh::pixels
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index ce4131a0627..710e2900d78 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -244,6 +244,7 @@ set(SRC
intern/pbvh.cc
intern/pbvh.c
intern/pbvh_bmesh.c
+ intern/pbvh_pixels.cc
intern/pointcache.c
intern/pointcloud.cc
intern/preferences.c
diff --git a/source/blender/blenkernel/intern/action_mirror.c b/source/blender/blenkernel/intern/action_mirror.c
index bb988605923..7ef561c8216 100644
--- a/source/blender/blenkernel/intern/action_mirror.c
+++ b/source/blender/blenkernel/intern/action_mirror.c
@@ -37,21 +37,21 @@
* That could be useful if a user for example only has 2x rotation channels set.
* In practice users typically keyframe all rotation channels or none.
*
- * - F-curve modifiers are disabled for evaluation,
+ * - F-Curve modifiers are disabled for evaluation,
* so the values written back to the keyframes don't include modifier offsets.
*
* - Sub-frame key-frames aren't supported,
* this could be added if needed without much trouble.
*
- * - F-curves must have a #FCurve.bezt array (sampled curves aren't supported).
+ * - F-Curves must have a #FCurve.bezt array (sampled curves aren't supported).
* \{ */
/**
- * This structure is created for each pose channels F-curve,
+ * This structure is created for each pose channels F-Curve,
* an action be evaluated and stored in `fcurve_eval`,
* with the mirrored values written into `bezt_array`.
*
- * Store F-curve evaluated values, constructed with a sorted array of rounded keyed-frames,
+ * Store F-Curve evaluated values, constructed with a sorted array of rounded keyed-frames,
* passed to #action_flip_pchan_cache_init.
*/
struct FCurve_KeyCache {
@@ -60,7 +60,7 @@ struct FCurve_KeyCache {
*/
FCurve *fcurve;
/**
- * Cached evaluated F-curve values (without modifiers).
+ * Cached evaluated F-Curve values (without modifiers).
*/
float *fcurve_eval;
/**
@@ -117,7 +117,7 @@ static void action_flip_pchan_cache_init(struct FCurve_KeyCache *fkc,
{
BLI_assert(fkc->fcurve != NULL);
- /* Cache the F-curve values for `keyed_frames`. */
+ /* Cache the F-Curve values for `keyed_frames`. */
const int fcurve_flag = fkc->fcurve->flag;
fkc->fcurve->flag |= FCURVE_MOD_OFF;
fkc->fcurve_eval = MEM_mallocN(sizeof(float) * keyed_frames_len, __func__);
@@ -175,7 +175,7 @@ static void action_flip_pchan(Object *ob_arm,
* unavailable channels are left NULL. */
/**
- * Structure to store transformation F-curves corresponding to a pose bones transformation.
+ * Structure to store transformation F-Curves corresponding to a pose bones transformation.
* Match struct member names from #bPoseChannel so macros avoid repetition.
*
* \note There is no need to read values unless they influence the 4x4 transform matrix,
@@ -210,7 +210,7 @@ static void action_flip_pchan(Object *ob_arm,
#undef FCURVE_ASSIGN_VALUE
#undef FCURVE_ASSIGN_ARRAY
- /* Array of F-curves, for convenient access. */
+ /* Array of F-Curves, for convenient access. */
#define FCURVE_CHANNEL_LEN (sizeof(fkc_pchan) / sizeof(struct FCurve_KeyCache))
FCurve *fcurve_array[FCURVE_CHANNEL_LEN];
int fcurve_array_len = 0;
@@ -232,7 +232,7 @@ static void action_flip_pchan(Object *ob_arm,
const float *keyed_frames = BKE_fcurves_calc_keyed_frames(
fcurve_array, fcurve_array_len, &keyed_frames_len);
- /* Initialize the pose channel curve cache from the F-curve. */
+ /* Initialize the pose channel curve cache from the F-Curve. */
for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) {
struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan;
if (fkc->fcurve == NULL) {
@@ -256,7 +256,7 @@ static void action_flip_pchan(Object *ob_arm,
float arm_mat_inv[4][4];
invert_m4_m4(arm_mat_inv, pchan_flip ? pchan_flip->bone->arm_mat : pchan->bone->arm_mat);
- /* Now flip the transformation & write it back to the F-curves in `fkc_pchan`. */
+ /* Now flip the transformation & write it back to the F-Curves in `fkc_pchan`. */
for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) {
@@ -329,7 +329,7 @@ static void action_flip_pchan(Object *ob_arm,
BKE_pchan_apply_mat4(&pchan_temp, chan_mat, false);
- /* Write the values back to the F-curves. */
+ /* Write the values back to the F-Curves. */
#define WRITE_VALUE_FLT(id) \
if (fkc_pchan.id.fcurve_eval != NULL) { \
BezTriple *bezt = fkc_pchan.id.bezt_array[frame_index]; \
@@ -348,7 +348,7 @@ static void action_flip_pchan(Object *ob_arm,
} \
((void)0)
- /* Write the values back the F-curves. */
+ /* Write the values back the F-Curves. */
WRITE_ARRAY_FLT(loc);
WRITE_ARRAY_FLT(eul);
WRITE_ARRAY_FLT(quat);
diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c
index 0a6ef8f0f01..0ed322d386c 100644
--- a/source/blender/blenkernel/intern/armature.c
+++ b/source/blender/blenkernel/intern/armature.c
@@ -2480,7 +2480,7 @@ void BKE_pose_where_is_bone(struct Depsgraph *depsgraph,
float ctime,
bool do_extra)
{
- /* This gives a chan_mat with actions (F-curve) results. */
+ /* This gives a chan_mat with actions (F-Curve) results. */
if (do_extra) {
BKE_pchan_calc_mat(pchan);
}
diff --git a/source/blender/blenkernel/intern/curve.cc b/source/blender/blenkernel/intern/curve.cc
index 6815b4d7486..bad70d4ccc6 100644
--- a/source/blender/blenkernel/intern/curve.cc
+++ b/source/blender/blenkernel/intern/curve.cc
@@ -3182,7 +3182,7 @@ static void calchandleNurb_intern(BezTriple *bezt,
len *= 2.5614f;
if (len != 0.0f) {
- /* Only for F-curves. */
+ /* Only for F-Curves. */
bool leftviolate = false, rightviolate = false;
if (!is_fcurve || fcurve_smoothing == FCURVE_SMOOTH_NONE) {
diff --git a/source/blender/blenkernel/intern/curve_poly.cc b/source/blender/blenkernel/intern/curve_poly.cc
index b0ed62d38dd..2db7cd71ad3 100644
--- a/source/blender/blenkernel/intern/curve_poly.cc
+++ b/source/blender/blenkernel/intern/curve_poly.cc
@@ -6,7 +6,7 @@
#include <algorithm>
-#include "BLI_math_vector.h"
+#include "BLI_math_rotation.hh"
#include "BLI_math_vector.hh"
#include "BKE_curves.hh"
@@ -54,20 +54,6 @@ void calculate_tangents(const Span<float3> positions,
}
}
-static float3 rotate_direction_around_axis(const float3 &direction,
- const float3 &axis,
- const float angle)
-{
- BLI_ASSERT_UNIT_V3(direction);
- BLI_ASSERT_UNIT_V3(axis);
-
- const float3 axis_scaled = axis * math::dot(direction, axis);
- const float3 diff = direction - axis_scaled;
- const float3 cross = math::cross(axis, diff);
-
- return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle);
-}
-
void calculate_normals_z_up(const Span<float3> tangents, MutableSpan<float3> normals)
{
BLI_assert(normals.size() == tangents.size());
@@ -98,7 +84,7 @@ static float3 calculate_next_normal(const float3 &last_normal,
const float angle = angle_normalized_v3v3(last_tangent, current_tangent);
if (angle != 0.0) {
const float3 axis = math::normalize(math::cross(last_tangent, current_tangent));
- return rotate_direction_around_axis(last_normal, axis, angle);
+ return math::rotate_direction_around_axis(last_normal, axis, angle);
}
return last_normal;
}
@@ -147,7 +133,7 @@ void calculate_normals_minimum(const Span<float3> tangents,
const float angle_step = correction_angle / normals.size();
for (const int i : normals.index_range()) {
const float angle = angle_step * i;
- normals[i] = rotate_direction_around_axis(normals[i], tangents[i], angle);
+ normals[i] = math::rotate_direction_around_axis(normals[i], tangents[i], angle);
}
}
diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
index 9b22a4c9726..c48d155f5ce 100644
--- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
+++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc
@@ -9,65 +9,15 @@
#include "BKE_attribute_access.hh"
#include "BKE_attribute_math.hh"
+#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_material.h"
#include "BKE_mesh.h"
-#include "BKE_spline.hh"
#include "BKE_curve_to_mesh.hh"
namespace blender::bke {
-/** Information about the creation of one curve spline and profile spline combination. */
-struct ResultInfo {
- const Spline &spline;
- const Spline &profile;
- int vert_offset;
- int edge_offset;
- int loop_offset;
- int poly_offset;
- int spline_vert_len;
- int spline_edge_len;
- int profile_vert_len;
- int profile_edge_len;
-};
-
-static void vert_extrude_to_mesh_data(const Spline &spline,
- const float3 profile_vert,
- MutableSpan<MVert> r_verts,
- MutableSpan<MEdge> r_edges,
- const int vert_offset,
- const int edge_offset)
-{
- const int eval_size = spline.evaluated_points_size();
- for (const int i : IndexRange(eval_size - 1)) {
- MEdge &edge = r_edges[edge_offset + i];
- edge.v1 = vert_offset + i;
- edge.v2 = vert_offset + i + 1;
- edge.flag = ME_LOOSEEDGE;
- }
-
- if (spline.is_cyclic() && spline.evaluated_edges_size() > 1) {
- MEdge &edge = r_edges[edge_offset + spline.evaluated_edges_size() - 1];
- edge.v1 = vert_offset + eval_size - 1;
- edge.v2 = vert_offset;
- edge.flag = ME_LOOSEEDGE;
- }
-
- Span<float3> positions = spline.evaluated_positions();
- Span<float3> tangents = spline.evaluated_tangents();
- Span<float3> normals = spline.evaluated_normals();
- VArray<float> radii = spline.interpolate_to_evaluated(spline.radii());
- for (const int i : IndexRange(eval_size)) {
- float4x4 point_matrix = float4x4::from_normalized_axis_data(
- positions[i], normals[i], tangents[i]);
- point_matrix.apply_scale(radii[i]);
-
- MVert &vert = r_verts[vert_offset + i];
- copy_v3_v3(vert.co, point_matrix * profile_vert);
- }
-}
-
static void mark_edges_sharp(MutableSpan<MEdge> edges)
{
for (MEdge &edge : edges) {
@@ -75,36 +25,50 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges)
}
}
-static void spline_extrude_to_mesh_data(const ResultInfo &info,
- const bool fill_caps,
- MutableSpan<MVert> r_verts,
- MutableSpan<MEdge> r_edges,
- MutableSpan<MLoop> r_loops,
- MutableSpan<MPoly> r_polys)
+static void fill_mesh_topology(const int vert_offset,
+ const int edge_offset,
+ const int poly_offset,
+ const int loop_offset,
+ const int main_point_num,
+ const int profile_point_num,
+ const bool main_cyclic,
+ const bool profile_cyclic,
+ const bool fill_caps,
+ MutableSpan<MEdge> edges,
+ MutableSpan<MLoop> loops,
+ MutableSpan<MPoly> polys)
{
- const Spline &spline = info.spline;
- const Spline &profile = info.profile;
- if (info.profile_vert_len == 1) {
- vert_extrude_to_mesh_data(spline,
- profile.evaluated_positions()[0],
- r_verts,
- r_edges,
- info.vert_offset,
- info.edge_offset);
+ const int main_segment_num = curves::curve_segment_size(main_point_num, main_cyclic);
+ const int profile_segment_num = curves::curve_segment_size(profile_point_num, profile_cyclic);
+
+ if (profile_point_num == 1) {
+ for (const int i : IndexRange(main_point_num - 1)) {
+ MEdge &edge = edges[edge_offset + i];
+ edge.v1 = vert_offset + i;
+ edge.v2 = vert_offset + i + 1;
+ edge.flag = ME_LOOSEEDGE;
+ }
+
+ if (main_cyclic && main_segment_num > 1) {
+ MEdge &edge = edges[edge_offset + main_segment_num - 1];
+ edge.v1 = vert_offset + main_point_num - 1;
+ edge.v2 = vert_offset;
+ edge.flag = ME_LOOSEEDGE;
+ }
return;
}
/* Add the edges running along the length of the curve, starting at each profile vertex. */
- const int spline_edges_start = info.edge_offset;
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
- const int profile_edge_offset = spline_edges_start + i_profile * info.spline_edge_len;
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
+ const int main_edges_start = edge_offset;
+ for (const int i_profile : IndexRange(profile_point_num)) {
+ const int profile_edge_offset = main_edges_start + i_profile * main_segment_num;
+ for (const int i_ring : IndexRange(main_segment_num)) {
+ const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1;
- const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
- const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
+ const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
+ const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring;
- MEdge &edge = r_edges[profile_edge_offset + i_ring];
+ MEdge &edge = edges[profile_edge_offset + i_ring];
edge.v1 = ring_vert_offset + i_profile;
edge.v2 = next_ring_vert_offset + i_profile;
edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
@@ -112,16 +76,15 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info,
}
/* Add the edges running along each profile ring. */
- const int profile_edges_start = spline_edges_start +
- info.profile_vert_len * info.spline_edge_len;
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
+ const int profile_edges_start = main_edges_start + profile_point_num * main_segment_num;
+ for (const int i_ring : IndexRange(main_point_num)) {
+ const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
- const int ring_edge_offset = profile_edges_start + i_ring * info.profile_edge_len;
- for (const int i_profile : IndexRange(info.profile_edge_len)) {
- const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
+ const int ring_edge_offset = profile_edges_start + i_ring * profile_segment_num;
+ for (const int i_profile : IndexRange(profile_segment_num)) {
+ const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1;
- MEdge &edge = r_edges[ring_edge_offset + i_profile];
+ MEdge &edge = edges[ring_edge_offset + i_profile];
edge.v1 = ring_vert_offset + i_profile;
edge.v2 = ring_vert_offset + i_next_profile;
edge.flag = ME_EDGEDRAW | ME_EDGERENDER;
@@ -129,368 +92,410 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info,
}
/* Calculate poly and corner indices. */
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int i_next_ring = (i_ring == info.spline_vert_len - 1) ? 0 : i_ring + 1;
+ for (const int i_ring : IndexRange(main_segment_num)) {
+ const int i_next_ring = (i_ring == main_point_num - 1) ? 0 : i_ring + 1;
- const int ring_vert_offset = info.vert_offset + info.profile_vert_len * i_ring;
- const int next_ring_vert_offset = info.vert_offset + info.profile_vert_len * i_next_ring;
+ const int ring_vert_offset = vert_offset + profile_point_num * i_ring;
+ const int next_ring_vert_offset = vert_offset + profile_point_num * i_next_ring;
- const int ring_edge_start = profile_edges_start + info.profile_edge_len * i_ring;
- const int next_ring_edge_offset = profile_edges_start + info.profile_edge_len * i_next_ring;
+ const int ring_edge_start = profile_edges_start + profile_segment_num * i_ring;
+ const int next_ring_edge_offset = profile_edges_start + profile_segment_num * i_next_ring;
- const int ring_poly_offset = info.poly_offset + i_ring * info.profile_edge_len;
- const int ring_loop_offset = info.loop_offset + i_ring * info.profile_edge_len * 4;
+ const int ring_poly_offset = poly_offset + i_ring * profile_segment_num;
+ const int ring_loop_offset = loop_offset + i_ring * profile_segment_num * 4;
- for (const int i_profile : IndexRange(info.profile_edge_len)) {
+ for (const int i_profile : IndexRange(profile_segment_num)) {
const int ring_segment_loop_offset = ring_loop_offset + i_profile * 4;
- const int i_next_profile = (i_profile == info.profile_vert_len - 1) ? 0 : i_profile + 1;
+ const int i_next_profile = (i_profile == profile_point_num - 1) ? 0 : i_profile + 1;
- const int spline_edge_start = spline_edges_start + info.spline_edge_len * i_profile;
- const int next_spline_edge_start = spline_edges_start +
- info.spline_edge_len * i_next_profile;
+ const int main_edge_start = main_edges_start + main_segment_num * i_profile;
+ const int next_main_edge_start = main_edges_start + main_segment_num * i_next_profile;
- MPoly &poly = r_polys[ring_poly_offset + i_profile];
+ MPoly &poly = polys[ring_poly_offset + i_profile];
poly.loopstart = ring_segment_loop_offset;
poly.totloop = 4;
poly.flag = ME_SMOOTH;
- MLoop &loop_a = r_loops[ring_segment_loop_offset];
+ MLoop &loop_a = loops[ring_segment_loop_offset];
loop_a.v = ring_vert_offset + i_profile;
loop_a.e = ring_edge_start + i_profile;
- MLoop &loop_b = r_loops[ring_segment_loop_offset + 1];
+ MLoop &loop_b = loops[ring_segment_loop_offset + 1];
loop_b.v = ring_vert_offset + i_next_profile;
- loop_b.e = next_spline_edge_start + i_ring;
- MLoop &loop_c = r_loops[ring_segment_loop_offset + 2];
+ loop_b.e = next_main_edge_start + i_ring;
+ MLoop &loop_c = loops[ring_segment_loop_offset + 2];
loop_c.v = next_ring_vert_offset + i_next_profile;
loop_c.e = next_ring_edge_offset + i_profile;
- MLoop &loop_d = r_loops[ring_segment_loop_offset + 3];
+ MLoop &loop_d = loops[ring_segment_loop_offset + 3];
loop_d.v = next_ring_vert_offset + i_profile;
- loop_d.e = spline_edge_start + i_ring;
+ loop_d.e = main_edge_start + i_ring;
}
}
- const bool has_caps = fill_caps && profile.is_cyclic() && !spline.is_cyclic();
+ const bool has_caps = fill_caps && !main_cyclic && profile_cyclic;
if (has_caps) {
- const int poly_size = info.spline_edge_len * info.profile_edge_len;
- const int cap_loop_offset = info.loop_offset + poly_size * 4;
- const int cap_poly_offset = info.poly_offset + poly_size;
+ const int poly_size = main_segment_num * profile_segment_num;
+ const int cap_loop_offset = loop_offset + poly_size * 4;
+ const int cap_poly_offset = poly_offset + poly_size;
- MPoly &poly_start = r_polys[cap_poly_offset];
+ MPoly &poly_start = polys[cap_poly_offset];
poly_start.loopstart = cap_loop_offset;
- poly_start.totloop = info.profile_edge_len;
- MPoly &poly_end = r_polys[cap_poly_offset + 1];
- poly_end.loopstart = cap_loop_offset + info.profile_edge_len;
- poly_end.totloop = info.profile_edge_len;
-
- const int last_ring_index = info.spline_vert_len - 1;
- const int last_ring_vert_offset = info.vert_offset + info.profile_vert_len * last_ring_index;
- const int last_ring_edge_offset = profile_edges_start +
- info.profile_edge_len * last_ring_index;
-
- for (const int i : IndexRange(info.profile_edge_len)) {
- const int i_inv = info.profile_edge_len - i - 1;
- MLoop &loop_start = r_loops[cap_loop_offset + i];
- loop_start.v = info.vert_offset + i_inv;
- loop_start.e = profile_edges_start + ((i == (info.profile_edge_len - 1)) ?
- (info.profile_edge_len - 1) :
- (i_inv - 1));
- MLoop &loop_end = r_loops[cap_loop_offset + info.profile_edge_len + i];
+ poly_start.totloop = profile_segment_num;
+ MPoly &poly_end = polys[cap_poly_offset + 1];
+ poly_end.loopstart = cap_loop_offset + profile_segment_num;
+ poly_end.totloop = profile_segment_num;
+
+ const int last_ring_index = main_point_num - 1;
+ const int last_ring_vert_offset = vert_offset + profile_point_num * last_ring_index;
+ const int last_ring_edge_offset = profile_edges_start + profile_segment_num * last_ring_index;
+
+ for (const int i : IndexRange(profile_segment_num)) {
+ const int i_inv = profile_segment_num - i - 1;
+ MLoop &loop_start = loops[cap_loop_offset + i];
+ loop_start.v = vert_offset + i_inv;
+ loop_start.e = profile_edges_start +
+ ((i == (profile_segment_num - 1)) ? (profile_segment_num - 1) : (i_inv - 1));
+ MLoop &loop_end = loops[cap_loop_offset + profile_segment_num + i];
loop_end.v = last_ring_vert_offset + i;
loop_end.e = last_ring_edge_offset + i;
}
- mark_edges_sharp(r_edges.slice(profile_edges_start, info.profile_edge_len));
- mark_edges_sharp(r_edges.slice(last_ring_edge_offset, info.profile_edge_len));
+ mark_edges_sharp(edges.slice(profile_edges_start, profile_segment_num));
+ mark_edges_sharp(edges.slice(last_ring_edge_offset, profile_segment_num));
}
+}
- /* Calculate the positions of each profile ring profile along the spline. */
- Span<float3> positions = spline.evaluated_positions();
- Span<float3> tangents = spline.evaluated_tangents();
- Span<float3> normals = spline.evaluated_normals();
- Span<float3> profile_positions = profile.evaluated_positions();
-
- VArray<float> radii = spline.interpolate_to_evaluated(spline.radii());
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- float4x4 point_matrix = float4x4::from_normalized_axis_data(
- positions[i_ring], normals[i_ring], tangents[i_ring]);
- point_matrix.apply_scale(radii[i_ring]);
-
- const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
- MVert &vert = r_verts[ring_vert_start + i_profile];
- copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]);
- }
+static void mark_bezier_vector_edges_sharp(const int profile_point_num,
+ const int main_segment_num,
+ const Span<int> control_point_offsets,
+ const Span<int8_t> handle_types_left,
+ const Span<int8_t> handle_types_right,
+ MutableSpan<MEdge> edges)
+{
+ const int main_edges_start = 0;
+ if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, 0)) {
+ mark_edges_sharp(edges.slice(main_edges_start, main_segment_num));
}
- /* Mark edge loops from sharp vector control points sharp. */
- if (profile.type() == CURVE_TYPE_BEZIER) {
- const BezierSpline &bezier_spline = static_cast<const BezierSpline &>(profile);
- Span<int> control_point_offsets = bezier_spline.control_point_offsets();
- for (const int i : IndexRange(bezier_spline.size())) {
- if (bezier_spline.point_is_sharp(i)) {
- mark_edges_sharp(
- r_edges.slice(spline_edges_start + info.spline_edge_len * control_point_offsets[i],
- info.spline_edge_len));
- }
+ for (const int i : IndexRange(profile_point_num).drop_front(1)) {
+ if (curves::bezier::point_is_sharp(handle_types_left, handle_types_right, i)) {
+ mark_edges_sharp(edges.slice(
+ main_edges_start + main_segment_num * control_point_offsets[i - 1], main_segment_num));
}
}
}
-static inline int spline_extrude_vert_size(const Spline &curve, const Spline &profile)
+static void fill_mesh_positions(const int main_point_num,
+ const int profile_point_num,
+ const Span<float3> main_positions,
+ const Span<float3> profile_positions,
+ const Span<float3> tangents,
+ const Span<float3> normals,
+ const Span<float> radii,
+ MutableSpan<MVert> mesh_positions)
{
- return curve.evaluated_points_size() * profile.evaluated_points_size();
-}
+ if (profile_point_num == 1) {
+ for (const int i_ring : IndexRange(main_point_num)) {
+ float4x4 point_matrix = float4x4::from_normalized_axis_data(
+ main_positions[i_ring], normals[i_ring], tangents[i_ring]);
+ if (!radii.is_empty()) {
+ point_matrix.apply_scale(radii[i_ring]);
+ }
-static inline int spline_extrude_edge_size(const Spline &curve, const Spline &profile)
-{
- /* Add the ring edges, with one ring for every curve vertex, and the edge loops
- * that run along the length of the curve, starting on the first profile. */
- return curve.evaluated_points_size() * profile.evaluated_edges_size() +
- curve.evaluated_edges_size() * profile.evaluated_points_size();
-}
+ MVert &vert = mesh_positions[i_ring];
+ copy_v3_v3(vert.co, point_matrix * profile_positions.first());
+ }
+ }
+ else {
+ for (const int i_ring : IndexRange(main_point_num)) {
+ float4x4 point_matrix = float4x4::from_normalized_axis_data(
+ main_positions[i_ring], normals[i_ring], tangents[i_ring]);
+ if (!radii.is_empty()) {
+ point_matrix.apply_scale(radii[i_ring]);
+ }
-static inline int spline_extrude_loop_size(const Spline &curve,
- const Spline &profile,
- const bool fill_caps)
-{
- const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4;
- const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic();
- const int caps = has_caps ? profile.evaluated_edges_size() * 2 : 0;
- return tube + caps;
+ const int ring_vert_start = i_ring * profile_point_num;
+ for (const int i_profile : IndexRange(profile_point_num)) {
+ MVert &vert = mesh_positions[ring_vert_start + i_profile];
+ copy_v3_v3(vert.co, point_matrix * profile_positions[i_profile]);
+ }
+ }
+ }
}
-static inline int spline_extrude_poly_size(const Spline &curve,
- const Spline &profile,
- const bool fill_caps)
+struct CurvesInfo {
+ const CurvesGeometry &main;
+ const CurvesGeometry &profile;
+
+ /* Make sure these are spans because they are potentially accessed many times. */
+ VArray_Span<bool> main_cyclic;
+ VArray_Span<bool> profile_cyclic;
+
+ /* TODO: Remove once these are cached on #CurvesGeometry. */
+ std::array<int, CURVE_TYPES_NUM> main_type_counts;
+ std::array<int, CURVE_TYPES_NUM> profile_type_counts;
+};
+static CurvesInfo get_curves_info(const CurvesGeometry &main, const CurvesGeometry &profile)
{
- const int tube = curve.evaluated_edges_size() * profile.evaluated_edges_size();
- const bool has_caps = fill_caps && profile.is_cyclic() && !curve.is_cyclic();
- const int caps = has_caps ? 2 : 0;
- return tube + caps;
+ return {main,
+ profile,
+ main.cyclic(),
+ profile.cyclic(),
+ main.count_curve_types(),
+ profile.count_curve_types()};
}
struct ResultOffsets {
+ /** The total number of curve combinations. */
+ int total;
+
+ /** Offsets into the result mesh for each combination. */
Array<int> vert;
Array<int> edge;
Array<int> loop;
Array<int> poly;
+
+ /* The indices of the main and profile curves that form each combination. */
+ Array<int> main_indices;
+ Array<int> profile_indices;
};
-static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles,
- Span<SplinePtr> curves,
- const bool fill_caps)
+static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps)
{
- const int total = profiles.size() * curves.size();
- Array<int> vert(total + 1);
- Array<int> edge(total + 1);
- Array<int> loop(total + 1);
- Array<int> poly(total + 1);
+ ResultOffsets result;
+ result.total = info.main.curves_num() * info.profile.curves_num();
+ result.vert.reinitialize(result.total + 1);
+ result.edge.reinitialize(result.total + 1);
+ result.loop.reinitialize(result.total + 1);
+ result.poly.reinitialize(result.total + 1);
+
+ result.main_indices.reinitialize(result.total);
+ result.profile_indices.reinitialize(result.total);
+
+ info.main.ensure_evaluated_offsets();
+ info.profile.ensure_evaluated_offsets();
int mesh_index = 0;
int vert_offset = 0;
int edge_offset = 0;
int loop_offset = 0;
int poly_offset = 0;
- for (const int i_spline : curves.index_range()) {
- for (const int i_profile : profiles.index_range()) {
- vert[mesh_index] = vert_offset;
- edge[mesh_index] = edge_offset;
- loop[mesh_index] = loop_offset;
- poly[mesh_index] = poly_offset;
- vert_offset += spline_extrude_vert_size(*curves[i_spline], *profiles[i_profile]);
- edge_offset += spline_extrude_edge_size(*curves[i_spline], *profiles[i_profile]);
- loop_offset += spline_extrude_loop_size(*curves[i_spline], *profiles[i_profile], fill_caps);
- poly_offset += spline_extrude_poly_size(*curves[i_spline], *profiles[i_profile], fill_caps);
+ for (const int i_main : info.main.curves_range()) {
+ const bool main_cyclic = info.main_cyclic[i_main];
+ const int main_point_num = info.main.evaluated_points_for_curve(i_main).size();
+ const int main_segment_num = curves::curve_segment_size(main_point_num, main_cyclic);
+ for (const int i_profile : info.profile.curves_range()) {
+ result.vert[mesh_index] = vert_offset;
+ result.edge[mesh_index] = edge_offset;
+ result.loop[mesh_index] = loop_offset;
+ result.poly[mesh_index] = poly_offset;
+
+ result.main_indices[mesh_index] = i_main;
+ result.profile_indices[mesh_index] = i_profile;
+
+ const bool profile_cyclic = info.profile_cyclic[i_profile];
+ const int profile_point_num = info.profile.evaluated_points_for_curve(i_profile).size();
+ const int profile_segment_num = curves::curve_segment_size(profile_point_num,
+ profile_cyclic);
+
+ const bool has_caps = fill_caps && !main_cyclic && profile_cyclic;
+ const int tube_face_num = main_segment_num * profile_segment_num;
+
+ vert_offset += main_point_num * profile_point_num;
+
+ /* Add the ring edges, with one ring for every curve vertex, and the edge loops
+ * that run along the length of the curve, starting on the first profile. */
+ edge_offset += main_point_num * profile_segment_num + main_segment_num * profile_point_num;
+
+ /* Add two cap N-gons for every ending. */
+ poly_offset += tube_face_num + (has_caps ? 2 : 0);
+
+ /* All faces on the tube are quads, and all cap faces are N-gons with an edge for each
+ * profile edge. */
+ loop_offset += tube_face_num * 4 + (has_caps ? profile_segment_num * 2 : 0);
+
mesh_index++;
}
}
- vert.last() = vert_offset;
- edge.last() = edge_offset;
- loop.last() = loop_offset;
- poly.last() = poly_offset;
- return {std::move(vert), std::move(edge), std::move(loop), std::move(poly)};
+ result.vert.last() = vert_offset;
+ result.edge.last() = edge_offset;
+ result.loop.last() = loop_offset;
+ result.poly.last() = poly_offset;
+
+ return result;
}
-static AttributeDomain get_result_attribute_domain(const MeshComponent &component,
- const AttributeIDRef &attribute_id)
+static AttributeDomain get_attribute_domain_for_mesh(const MeshComponent &mesh,
+ const AttributeIDRef &attribute_id)
{
/* Only use a different domain if it is builtin and must only exist on one domain. */
- if (!component.attribute_is_builtin(attribute_id)) {
+ if (!mesh.attribute_is_builtin(attribute_id)) {
return ATTR_DOMAIN_POINT;
}
- std::optional<AttributeMetaData> meta_data = component.attribute_get_meta_data(attribute_id);
+ std::optional<AttributeMetaData> meta_data = mesh.attribute_get_meta_data(attribute_id);
if (!meta_data) {
- /* This function has to return something in this case, but it shouldn't be used,
- * so return an output that will assert later if the code attempts to handle it. */
- return ATTR_DOMAIN_AUTO;
+ return ATTR_DOMAIN_POINT;
}
return meta_data->domain;
}
-/**
- * The data stored in the attribute and its domain from #OutputAttribute, to avoid calling
- * `as_span()` for every single profile and curve spline combination, and for readability.
- */
-struct ResultAttributeData {
- GMutableSpan data;
- AttributeDomain domain;
-};
-
-static std::optional<ResultAttributeData> create_attribute_and_get_span(
- MeshComponent &component,
- const AttributeIDRef &attribute_id,
- AttributeMetaData meta_data,
- Vector<OutputAttribute> &r_attributes)
+static bool should_add_attribute_to_mesh(const CurveComponent &curve_component,
+ const MeshComponent &mesh_component,
+ const AttributeIDRef &id)
{
- const AttributeDomain domain = get_result_attribute_domain(component, attribute_id);
- OutputAttribute attribute = component.attribute_try_get_for_output_only(
- attribute_id, domain, meta_data.data_type);
- if (!attribute) {
- return std::nullopt;
+ /* The position attribute has special non-generic evaluation. */
+ if (id.is_named() && id.name() == "position") {
+ return false;
+ }
+ /* Don't propagate built-in curves attributes that are not built-in on meshes. */
+ if (curve_component.attribute_is_builtin(id) && !mesh_component.attribute_is_builtin(id)) {
+ return false;
}
+ if (!id.should_be_kept()) {
+ return false;
+ }
+ return true;
+}
- GMutableSpan span = attribute.as_span();
- r_attributes.append(std::move(attribute));
- return std::make_optional<ResultAttributeData>({span, domain});
+static GSpan evaluated_attribute_if_necessary(const GVArray &src,
+ const CurvesGeometry &curves,
+ const Span<int> type_counts,
+ Vector<std::byte> &buffer)
+{
+ if (type_counts[CURVE_TYPE_POLY] == curves.curves_num() && src.is_span()) {
+ return src.get_internal_span();
+ }
+ buffer.reinitialize(curves.evaluated_points_num() * src.type().size());
+ GMutableSpan eval{src.type(), buffer.data(), curves.evaluated_points_num()};
+ curves.interpolate_to_evaluated(src.get_internal_span(), eval);
+ return eval;
}
-/**
- * Store the references to the attribute data from the curve and profile inputs. Here we rely on
- * the invariants of the storage of curve attributes, that the order will be consistent between
- * splines, and all splines will have the same attributes.
- */
-struct ResultAttributes {
- /**
- * Result attributes on the mesh corresponding to each attribute on the curve input, in the same
- * order. The data is optional only in case the attribute does not exist on the mesh for some
- * reason, like "shade_smooth" when the result has no faces.
- */
- Vector<std::optional<ResultAttributeData>> curve_point_attributes;
- Vector<std::optional<ResultAttributeData>> curve_spline_attributes;
-
- /**
- * Result attributes corresponding the attributes on the profile input, in the same order. The
- * attributes are optional in case the attribute names correspond to a names used by the curve
- * input, in which case the curve input attributes take precedence.
- */
- Vector<std::optional<ResultAttributeData>> profile_point_attributes;
- Vector<std::optional<ResultAttributeData>> profile_spline_attributes;
-
- /**
- * Because some builtin attributes are not stored contiguously, and the curve inputs might have
- * attributes with those names, it's necessary to keep OutputAttributes around to give access to
- * the result data in a contiguous array.
- */
- Vector<OutputAttribute> attributes;
+/** Information at a specific combination of main and profile curves. */
+struct CombinationInfo {
+ int i_main;
+ int i_profile;
+
+ IndexRange main_points;
+ IndexRange profile_points;
+
+ bool main_cyclic;
+ bool profile_cyclic;
+
+ int main_segment_num;
+ int profile_segment_num;
+
+ IndexRange vert_range;
+ IndexRange edge_range;
+ IndexRange poly_range;
+ IndexRange loop_range;
};
-static ResultAttributes create_result_attributes(const CurveEval &curve,
- const CurveEval &profile,
- MeshComponent &mesh_component)
+template<typename Fn>
+static void foreach_curve_combination(const CurvesInfo &info,
+ const ResultOffsets &offsets,
+ const Fn &fn)
{
- Set<AttributeIDRef> curve_attributes;
-
- /* In order to prefer attributes on the main curve input when there are name collisions, first
- * check the attributes on the curve, then add attributes on the profile that are not also on the
- * main curve input. */
- ResultAttributes result;
- curve.splines().first()->attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- curve_attributes.add_new(id);
- result.curve_point_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- return true;
- },
- ATTR_DOMAIN_POINT);
- curve.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- curve_attributes.add_new(id);
- result.curve_spline_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- return true;
- },
- ATTR_DOMAIN_CURVE);
- profile.splines().first()->attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- if (curve_attributes.contains(id)) {
- result.profile_point_attributes.append({});
- }
- else {
- result.profile_point_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- }
- return true;
- },
- ATTR_DOMAIN_POINT);
- profile.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &meta_data) {
- if (curve_attributes.contains(id)) {
- result.profile_spline_attributes.append({});
- }
- else {
- result.profile_spline_attributes.append(
- create_attribute_and_get_span(mesh_component, id, meta_data, result.attributes));
- }
- return true;
- },
- ATTR_DOMAIN_CURVE);
-
- return result;
+ threading::parallel_for(IndexRange(offsets.total), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ const int i_main = offsets.main_indices[i];
+ const int i_profile = offsets.profile_indices[i];
+
+ const IndexRange main_points = info.main.evaluated_points_for_curve(i_main);
+ const IndexRange profile_points = info.profile.evaluated_points_for_curve(i_profile);
+
+ const bool main_cyclic = info.main_cyclic[i_main];
+ const bool profile_cyclic = info.profile_cyclic[i_profile];
+
+ /* Pass all information in a struct to avoid repeating arguments in many lambdas.
+ * The idea is that inlining `fn` will help avoid accessing unnecessary information,
+ * though that may or may not happen in practice. */
+ fn(CombinationInfo{i_main,
+ i_profile,
+ main_points,
+ profile_points,
+ main_cyclic,
+ profile_cyclic,
+ curves::curve_segment_size(main_points.size(), main_cyclic),
+ curves::curve_segment_size(profile_points.size(), profile_cyclic),
+ offsets_to_range(offsets.vert.as_span(), i),
+ offsets_to_range(offsets.edge.as_span(), i),
+ offsets_to_range(offsets.poly.as_span(), i),
+ offsets_to_range(offsets.loop.as_span(), i)});
+ }
+ });
}
template<typename T>
-static void copy_curve_point_data_to_mesh_verts(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
+static void copy_main_point_data_to_mesh_verts(const Span<T> src,
+ const int profile_point_num,
+ MutableSpan<T> dst)
{
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int ring_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
- dst.slice(ring_vert_start, info.profile_vert_len).fill(src[i_ring]);
+ for (const int i_ring : src.index_range()) {
+ const int ring_vert_start = i_ring * profile_point_num;
+ dst.slice(ring_vert_start, profile_point_num).fill(src[i_ring]);
}
}
template<typename T>
-static void copy_curve_point_data_to_mesh_edges(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
+static void copy_main_point_data_to_mesh_edges(const Span<T> src,
+ const int profile_point_num,
+ const int main_segment_num,
+ const int profile_segment_num,
+ MutableSpan<T> dst)
{
- const int edges_start = info.edge_offset + info.profile_vert_len * info.spline_edge_len;
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int ring_edge_start = edges_start + info.profile_edge_len * i_ring;
- dst.slice(ring_edge_start, info.profile_edge_len).fill(src[i_ring]);
+ const int edges_start = profile_point_num * main_segment_num;
+ for (const int i_ring : src.index_range()) {
+ const int ring_edge_start = edges_start + profile_segment_num * i_ring;
+ dst.slice(ring_edge_start, profile_segment_num).fill(src[i_ring]);
}
}
template<typename T>
-static void copy_curve_point_data_to_mesh_faces(const Span<T> src,
- const ResultInfo &info,
- MutableSpan<T> dst)
+static void copy_main_point_data_to_mesh_faces(const Span<T> src,
+ const int main_segment_num,
+ const int profile_segment_num,
+ MutableSpan<T> dst)
{
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int ring_face_start = info.poly_offset + info.profile_edge_len * i_ring;
- dst.slice(ring_face_start, info.profile_edge_len).fill(src[i_ring]);
+ for (const int i_ring : IndexRange(main_segment_num)) {
+ const int ring_face_start = profile_segment_num * i_ring;
+ dst.slice(ring_face_start, profile_segment_num).fill(src[i_ring]);
}
}
-static void copy_curve_point_attribute_to_mesh(const GSpan src,
- const ResultInfo &info,
- ResultAttributeData &dst)
+static void copy_main_point_domain_attribute_to_mesh(const CurvesInfo &curves_info,
+ const ResultOffsets &offsets,
+ const AttributeDomain dst_domain,
+ const GSpan src_all,
+ GMutableSpan dst_all)
{
- GVArray interpolated_gvarray = info.spline.interpolate_to_evaluated(src);
- GSpan interpolated = interpolated_gvarray.get_internal_span();
-
- attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) {
using T = decltype(dummy);
- switch (dst.domain) {
+ const Span<T> src = src_all.typed<T>();
+ MutableSpan<T> dst = dst_all.typed<T>();
+ switch (dst_domain) {
case ATTR_DOMAIN_POINT:
- copy_curve_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ copy_main_point_data_to_mesh_verts(
+ src.slice(info.main_points), info.profile_points.size(), dst.slice(info.vert_range));
+ });
break;
case ATTR_DOMAIN_EDGE:
- copy_curve_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ copy_main_point_data_to_mesh_edges(src.slice(info.main_points),
+ info.profile_points.size(),
+ info.main_segment_num,
+ info.profile_segment_num,
+ dst.slice(info.edge_range));
+ });
break;
case ATTR_DOMAIN_FACE:
- copy_curve_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ copy_main_point_data_to_mesh_faces(src.slice(info.main_points),
+ info.main_segment_num,
+ info.profile_segment_num,
+ dst.slice(info.poly_range));
+ });
break;
case ATTR_DOMAIN_CORNER:
/* Unsupported for now, since there are no builtin attributes to convert into. */
@@ -504,12 +509,12 @@ static void copy_curve_point_attribute_to_mesh(const GSpan src,
template<typename T>
static void copy_profile_point_data_to_mesh_verts(const Span<T> src,
- const ResultInfo &info,
+ const int main_point_num,
MutableSpan<T> dst)
{
- for (const int i_ring : IndexRange(info.spline_vert_len)) {
- const int profile_vert_start = info.vert_offset + i_ring * info.profile_vert_len;
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
+ for (const int i_ring : IndexRange(main_point_num)) {
+ const int profile_vert_start = i_ring * src.size();
+ for (const int i_profile : src.index_range()) {
dst[profile_vert_start + i_profile] = src[i_profile];
}
}
@@ -517,46 +522,59 @@ static void copy_profile_point_data_to_mesh_verts(const Span<T> src,
template<typename T>
static void copy_profile_point_data_to_mesh_edges(const Span<T> src,
- const ResultInfo &info,
+ const int main_segment_num,
MutableSpan<T> dst)
{
- for (const int i_profile : IndexRange(info.profile_vert_len)) {
- const int profile_edge_offset = info.edge_offset + i_profile * info.spline_edge_len;
- dst.slice(profile_edge_offset, info.spline_edge_len).fill(src[i_profile]);
+ for (const int i_profile : src.index_range()) {
+ const int profile_edge_offset = i_profile * main_segment_num;
+ dst.slice(profile_edge_offset, main_segment_num).fill(src[i_profile]);
}
}
template<typename T>
static void copy_profile_point_data_to_mesh_faces(const Span<T> src,
- const ResultInfo &info,
+ const int main_segment_num,
+ const int profile_segment_num,
MutableSpan<T> dst)
{
- for (const int i_ring : IndexRange(info.spline_edge_len)) {
- const int profile_face_start = info.poly_offset + i_ring * info.profile_edge_len;
- for (const int i_profile : IndexRange(info.profile_edge_len)) {
+ for (const int i_ring : IndexRange(main_segment_num)) {
+ const int profile_face_start = i_ring * profile_segment_num;
+ for (const int i_profile : IndexRange(profile_segment_num)) {
dst[profile_face_start + i_profile] = src[i_profile];
}
}
}
-static void copy_profile_point_attribute_to_mesh(const GSpan src,
- const ResultInfo &info,
- ResultAttributeData &dst)
+static void copy_profile_point_domain_attribute_to_mesh(const CurvesInfo &curves_info,
+ const ResultOffsets &offsets,
+ const AttributeDomain dst_domain,
+ const GSpan src_all,
+ GMutableSpan dst_all)
{
- GVArray interpolated_gvarray = info.profile.interpolate_to_evaluated(src);
- GSpan interpolated = interpolated_gvarray.get_internal_span();
-
- attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ attribute_math::convert_to_static_type(src_all.type(), [&](auto dummy) {
using T = decltype(dummy);
- switch (dst.domain) {
+ const Span<T> src = src_all.typed<T>();
+ MutableSpan<T> dst = dst_all.typed<T>();
+ switch (dst_domain) {
case ATTR_DOMAIN_POINT:
- copy_profile_point_data_to_mesh_verts(interpolated.typed<T>(), info, dst.data.typed<T>());
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ copy_profile_point_data_to_mesh_verts(
+ src.slice(info.profile_points), info.main_points.size(), dst.slice(info.vert_range));
+ });
break;
case ATTR_DOMAIN_EDGE:
- copy_profile_point_data_to_mesh_edges(interpolated.typed<T>(), info, dst.data.typed<T>());
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ copy_profile_point_data_to_mesh_edges(
+ src.slice(info.profile_points), info.main_segment_num, dst.slice(info.edge_range));
+ });
break;
case ATTR_DOMAIN_FACE:
- copy_profile_point_data_to_mesh_faces(interpolated.typed<T>(), info, dst.data.typed<T>());
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ copy_profile_point_data_to_mesh_faces(src.slice(info.profile_points),
+ info.main_segment_num,
+ info.profile_segment_num,
+ dst.slice(info.poly_range));
+ });
break;
case ATTR_DOMAIN_CORNER:
/* Unsupported for now, since there are no builtin attributes to convert into. */
@@ -568,198 +586,236 @@ static void copy_profile_point_attribute_to_mesh(const GSpan src,
});
}
-static void copy_point_domain_attributes_to_mesh(const ResultInfo &info,
- ResultAttributes &attributes)
-{
- if (!attributes.curve_point_attributes.is_empty()) {
- int i = 0;
- info.spline.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.curve_point_attributes[i]) {
- copy_curve_point_attribute_to_mesh(*info.spline.attributes.get_for_read(id),
- info,
- *attributes.curve_point_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_POINT);
- }
- if (!attributes.profile_point_attributes.is_empty()) {
- int i = 0;
- info.profile.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.profile_point_attributes[i]) {
- copy_profile_point_attribute_to_mesh(*info.profile.attributes.get_for_read(id),
- info,
- *attributes.profile_point_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_POINT);
- }
-}
-
template<typename T>
-static void copy_spline_data_to_mesh(Span<T> src, Span<int> offsets, MutableSpan<T> dst)
+static void copy_indices_to_offset_ranges(const VArray<T> &src,
+ const Span<int> curve_indices,
+ const Span<int> mesh_offsets,
+ MutableSpan<T> dst)
{
- for (const int i : IndexRange(src.size())) {
- dst.slice(offsets[i], offsets[i + 1] - offsets[i]).fill(src[i]);
- }
+ /* This unnecessarily instantiates the "is single" case (which should be handled elsewhere if
+ * it's ever used for attributes), but the alternative is duplicating the function for spans and
+ * other virtual arrays. */
+ devirtualize_varray(src, [&](const auto &src) {
+ threading::parallel_for(curve_indices.index_range(), 512, [&](IndexRange range) {
+ for (const int i : range) {
+ dst.slice(offsets_to_range(mesh_offsets, i)).fill(src[curve_indices[i]]);
+ }
+ });
+ });
}
-/**
- * Since the offsets for each combination of curve and profile spline are stored for every mesh
- * domain, and this just needs to fill the chunks corresponding to each combination, we can use
- * the same function for all mesh domains.
- */
-static void copy_spline_attribute_to_mesh(const GSpan src,
- const ResultOffsets &offsets,
- ResultAttributeData &dst_attribute)
+static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offsets,
+ const Span<int> curve_indices,
+ const AttributeDomain dst_domain,
+ const GVArray &src,
+ GMutableSpan dst)
{
+ Span<int> offsets;
+ switch (dst_domain) {
+ case ATTR_DOMAIN_POINT:
+ offsets = mesh_offsets.vert;
+ break;
+ case ATTR_DOMAIN_EDGE:
+ offsets = mesh_offsets.edge;
+ break;
+ case ATTR_DOMAIN_FACE:
+ offsets = mesh_offsets.poly;
+ break;
+ case ATTR_DOMAIN_CORNER:
+ offsets = mesh_offsets.loop;
+ break;
+ default:
+ BLI_assert_unreachable();
+ return;
+ }
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
using T = decltype(dummy);
- switch (dst_attribute.domain) {
- case ATTR_DOMAIN_POINT:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.vert, dst_attribute.data.typed<T>());
- break;
- case ATTR_DOMAIN_EDGE:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.edge, dst_attribute.data.typed<T>());
- break;
- case ATTR_DOMAIN_FACE:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.poly, dst_attribute.data.typed<T>());
- break;
- case ATTR_DOMAIN_CORNER:
- copy_spline_data_to_mesh(src.typed<T>(), offsets.loop, dst_attribute.data.typed<T>());
- break;
- default:
- BLI_assert_unreachable();
- break;
- }
+ copy_indices_to_offset_ranges(src.typed<T>(), curve_indices, offsets, dst.typed<T>());
});
}
-static void copy_spline_domain_attributes_to_mesh(const CurveEval &curve,
- const CurveEval &profile,
- const ResultOffsets &offsets,
- ResultAttributes &attributes)
-{
- if (!attributes.curve_spline_attributes.is_empty()) {
- int i = 0;
- curve.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.curve_spline_attributes[i]) {
- copy_spline_attribute_to_mesh(*curve.attributes.get_for_read(id),
- offsets,
- *attributes.curve_spline_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_CURVE);
- }
- if (!attributes.profile_spline_attributes.is_empty()) {
- int i = 0;
- profile.attributes.foreach_attribute(
- [&](const AttributeIDRef &id, const AttributeMetaData &UNUSED(meta_data)) {
- if (attributes.profile_spline_attributes[i]) {
- copy_spline_attribute_to_mesh(*profile.attributes.get_for_read(id),
- offsets,
- *attributes.profile_spline_attributes[i]);
- }
- i++;
- return true;
- },
- ATTR_DOMAIN_CURVE);
- }
-}
-
-Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile, const bool fill_caps)
+Mesh *curve_to_mesh_sweep(const CurvesGeometry &main,
+ const CurvesGeometry &profile,
+ const bool fill_caps)
{
- Span<SplinePtr> profiles = profile.splines();
- Span<SplinePtr> curves = curve.splines();
+ const CurvesInfo curves_info = get_curves_info(main, profile);
- const ResultOffsets offsets = calculate_result_offsets(profiles, curves, fill_caps);
+ const ResultOffsets offsets = calculate_result_offsets(curves_info, fill_caps);
if (offsets.vert.last() == 0) {
return nullptr;
}
Mesh *mesh = BKE_mesh_new_nomain(
offsets.vert.last(), offsets.edge.last(), 0, offsets.loop.last(), offsets.poly.last());
- BKE_id_material_eval_ensure_default_slot(&mesh->id);
mesh->flag |= ME_AUTOSMOOTH;
mesh->smoothresh = DEG2RADF(180.0f);
BKE_mesh_normals_tag_dirty(mesh);
+ MutableSpan<MVert> verts(mesh->mvert, mesh->totvert);
+ MutableSpan<MEdge> edges(mesh->medge, mesh->totedge);
+ MutableSpan<MLoop> loops(mesh->mloop, mesh->totloop);
+ MutableSpan<MPoly> polys(mesh->mpoly, mesh->totpoly);
+
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ fill_mesh_topology(info.vert_range.start(),
+ info.edge_range.start(),
+ info.poly_range.start(),
+ info.loop_range.start(),
+ info.main_points.size(),
+ info.profile_points.size(),
+ info.main_cyclic,
+ info.profile_cyclic,
+ fill_caps,
+ edges,
+ loops,
+ polys);
+ });
+
+ const Span<float3> main_positions = main.evaluated_positions();
+ const Span<float3> tangents = main.evaluated_tangents();
+ const Span<float3> normals = main.evaluated_normals();
+ const Span<float3> profile_positions = profile.evaluated_positions();
+
+ Vector<std::byte> eval_buffer;
+
+ Curves main_id = {nullptr};
+ main_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(main);
+ CurveComponent main_component;
+ main_component.replace(&main_id, GeometryOwnershipType::Editable);
+
+ Curves profile_id = {nullptr};
+ profile_id.geometry = reinterpret_cast<const ::CurvesGeometry &>(profile);
+ CurveComponent profile_component;
+ profile_component.replace(&profile_id, GeometryOwnershipType::Editable);
+
+ Span<float> radii = {};
+ if (main_component.attribute_exists("radius")) {
+ radii = evaluated_attribute_if_necessary(
+ main_component.attribute_get_for_read<float>("radius", ATTR_DOMAIN_POINT, 1.0f),
+ main,
+ curves_info.main_type_counts,
+ eval_buffer)
+ .typed<float>();
+ }
+
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ fill_mesh_positions(info.main_points.size(),
+ info.profile_points.size(),
+ main_positions.slice(info.main_points),
+ profile_positions.slice(info.profile_points),
+ tangents.slice(info.main_points),
+ normals.slice(info.main_points),
+ radii.is_empty() ? radii : radii.slice(info.main_points),
+ verts.slice(info.vert_range));
+ });
+
+ if (curves_info.profile_type_counts[CURVE_TYPE_BEZIER] > 0) {
+ const VArray<int8_t> curve_types = profile.curve_types();
+ const VArray_Span<int8_t> handle_types_left{profile.handle_types_left()};
+ const VArray_Span<int8_t> handle_types_right{profile.handle_types_right()};
+
+ foreach_curve_combination(curves_info, offsets, [&](const CombinationInfo &info) {
+ if (curve_types[info.i_profile] == CURVE_TYPE_BEZIER) {
+ const IndexRange points = profile.points_for_curve(info.i_profile);
+ mark_bezier_vector_edges_sharp(points.size(),
+ info.main_segment_num,
+ profile.bezier_evaluated_offsets_for_curve(info.i_profile),
+ handle_types_left.slice(points),
+ handle_types_right.slice(points),
+ edges.slice(info.edge_range));
+ }
+ });
+ }
+
+ Set<AttributeIDRef> main_attributes;
- /* Create the mesh component for retrieving attributes at this scope, since output attributes
- * can keep a reference to the component for updating after retrieving write access. */
MeshComponent mesh_component;
mesh_component.replace(mesh, GeometryOwnershipType::Editable);
- ResultAttributes attributes = create_result_attributes(curve, profile, mesh_component);
- threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) {
- for (const int i_spline : curves_range) {
- const Spline &spline = *curves[i_spline];
- if (spline.evaluated_points_size() == 0) {
- continue;
- }
- const int spline_start_index = i_spline * profiles.size();
- threading::parallel_for(profiles.index_range(), 128, [&](IndexRange profiles_range) {
- for (const int i_profile : profiles_range) {
- const Spline &profile = *profiles[i_profile];
- const int i_mesh = spline_start_index + i_profile;
- ResultInfo info{
- spline,
- profile,
- offsets.vert[i_mesh],
- offsets.edge[i_mesh],
- offsets.loop[i_mesh],
- offsets.poly[i_mesh],
- spline.evaluated_points_size(),
- spline.evaluated_edges_size(),
- profile.evaluated_points_size(),
- profile.evaluated_edges_size(),
- };
-
- spline_extrude_to_mesh_data(info,
- fill_caps,
- {mesh->mvert, mesh->totvert},
- {mesh->medge, mesh->totedge},
- {mesh->mloop, mesh->totloop},
- {mesh->mpoly, mesh->totpoly});
-
- copy_point_domain_attributes_to_mesh(info, attributes);
- }
- });
+ main_component.attribute_foreach([&](const AttributeIDRef &id,
+ const AttributeMetaData meta_data) {
+ if (!should_add_attribute_to_mesh(main_component, mesh_component, id)) {
+ return true;
}
+ main_attributes.add_new(id);
+
+ const AttributeDomain src_domain = meta_data.domain;
+ const CustomDataType type = meta_data.data_type;
+ GVArray src = main_component.attribute_try_get_for_read(id, src_domain, type);
+
+ const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id);
+ OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(id, dst_domain, type);
+ if (!dst) {
+ return true;
+ }
+
+ if (src_domain == ATTR_DOMAIN_POINT) {
+ copy_main_point_domain_attribute_to_mesh(
+ curves_info,
+ offsets,
+ dst_domain,
+ evaluated_attribute_if_necessary(src, main, curves_info.main_type_counts, eval_buffer),
+ dst.as_span());
+ }
+ else if (src_domain == ATTR_DOMAIN_CURVE) {
+ copy_curve_domain_attribute_to_mesh(
+ offsets, offsets.main_indices, dst_domain, src, dst.as_span());
+ }
+
+ dst.save();
+ return true;
});
- copy_spline_domain_attributes_to_mesh(curve, profile, offsets, attributes);
+ profile_component.attribute_foreach([&](const AttributeIDRef &id,
+ const AttributeMetaData meta_data) {
+ if (main_attributes.contains(id)) {
+ return true;
+ }
+ if (!should_add_attribute_to_mesh(profile_component, mesh_component, id)) {
+ return true;
+ }
+ const AttributeDomain src_domain = meta_data.domain;
+ const CustomDataType type = meta_data.data_type;
+ GVArray src = profile_component.attribute_try_get_for_read(id, src_domain, type);
+
+ const AttributeDomain dst_domain = get_attribute_domain_for_mesh(mesh_component, id);
+ OutputAttribute dst = mesh_component.attribute_try_get_for_output_only(id, dst_domain, type);
+ if (!dst) {
+ return true;
+ }
+
+ if (src_domain == ATTR_DOMAIN_POINT) {
+ copy_profile_point_domain_attribute_to_mesh(
+ curves_info,
+ offsets,
+ dst_domain,
+ evaluated_attribute_if_necessary(
+ src, profile, curves_info.profile_type_counts, eval_buffer),
+ dst.as_span());
+ }
+ else if (src_domain == ATTR_DOMAIN_CURVE) {
+ copy_curve_domain_attribute_to_mesh(
+ offsets, offsets.profile_indices, dst_domain, src, dst.as_span());
+ }
- for (OutputAttribute &output_attribute : attributes.attributes) {
- output_attribute.save();
- }
+ dst.save();
+ return true;
+ });
return mesh;
}
-static CurveEval get_curve_single_vert()
+static CurvesGeometry get_curve_single_vert()
{
- CurveEval curve;
- std::unique_ptr<PolySpline> spline = std::make_unique<PolySpline>();
- spline->resize(1.0f);
- spline->positions().fill(float3(0));
- spline->radii().fill(1.0f);
- spline->tilts().fill(0.0f);
- curve.add_spline(std::move(spline));
-
- return curve;
+ CurvesGeometry curves(1, 1);
+ curves.offsets_for_write().last() = 1;
+ curves.positions_for_write().fill(float3(0));
+
+ return curves;
}
-Mesh *curve_to_wire_mesh(const CurveEval &curve)
+Mesh *curve_to_wire_mesh(const CurvesGeometry &curve)
{
- static const CurveEval vert_curve = get_curve_single_vert();
+ static const CurvesGeometry vert_curve = get_curve_single_vert();
return curve_to_mesh_sweep(curve, vert_curve, false);
}
diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc
index bdd8b3fc3d0..8e97884516c 100644
--- a/source/blender/blenkernel/intern/curves_geometry.cc
+++ b/source/blender/blenkernel/intern/curves_geometry.cc
@@ -12,6 +12,7 @@
#include "BLI_bounds.hh"
#include "BLI_index_mask_ops.hh"
#include "BLI_length_parameterize.hh"
+#include "BLI_math_rotation.hh"
#include "DNA_curves_types.h"
@@ -22,6 +23,7 @@ namespace blender::bke {
static const std::string ATTR_POSITION = "position";
static const std::string ATTR_RADIUS = "radius";
+static const std::string ATTR_TILT = "tilt";
static const std::string ATTR_CURVE_TYPE = "curve_type";
static const std::string ATTR_CYCLIC = "cyclic";
static const std::string ATTR_RESOLUTION = "resolution";
@@ -330,6 +332,15 @@ MutableSpan<int8_t> CurvesGeometry::normal_mode_for_write()
return get_mutable_attribute<int8_t>(*this, ATTR_DOMAIN_CURVE, ATTR_NORMAL_MODE);
}
+VArray<float> CurvesGeometry::tilt() const
+{
+ return get_varray_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_TILT, 0.0f);
+}
+MutableSpan<float> CurvesGeometry::tilt_for_write()
+{
+ return get_mutable_attribute<float>(*this, ATTR_DOMAIN_POINT, ATTR_TILT);
+}
+
VArray<int8_t> CurvesGeometry::handle_types_left() const
{
return get_varray_attribute<int8_t>(*this, ATTR_DOMAIN_POINT, ATTR_HANDLE_TYPE_LEFT, 0);
@@ -717,6 +728,15 @@ Span<float3> CurvesGeometry::evaluated_tangents() const
return this->runtime->evaluated_tangent_cache;
}
+static void rotate_directions_around_axes(MutableSpan<float3> directions,
+ const Span<float3> axes,
+ const Span<float> angles)
+{
+ for (const int i : directions.index_range()) {
+ directions[i] = math::rotate_direction_around_axis(directions[i], axes[i], angles[i]);
+ }
+}
+
Span<float3> CurvesGeometry::evaluated_normals() const
{
if (!this->runtime->normal_cache_dirty) {
@@ -733,11 +753,16 @@ Span<float3> CurvesGeometry::evaluated_normals() const
const Span<float3> evaluated_tangents = this->evaluated_tangents();
const VArray<bool> cyclic = this->cyclic();
const VArray<int8_t> normal_mode = this->normal_mode();
+ const VArray<int8_t> types = this->curve_types();
+ const VArray<float> tilt = this->tilt();
this->runtime->evaluated_normal_cache.resize(this->evaluated_points_num());
MutableSpan<float3> evaluated_normals = this->runtime->evaluated_normal_cache;
threading::parallel_for(this->curves_range(), 128, [&](IndexRange curves_range) {
+ /* Reuse a buffer for the evaluated tilts. */
+ Vector<float> evaluated_tilts;
+
for (const int curve_index : curves_range) {
const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index);
if (UNLIKELY(evaluated_points.is_empty())) {
@@ -754,6 +779,27 @@ Span<float3> CurvesGeometry::evaluated_normals() const
evaluated_normals.slice(evaluated_points));
break;
}
+
+ /* If the "tilt" attribute exists, rotate the normals around the tangents by the
+ * evaluated angles. We can avoid copying the tilts to evaluate them for poly curves. */
+ if (!(tilt.is_single() && tilt.get_internal_single() == 0.0f)) {
+ const IndexRange points = this->points_for_curve(curve_index);
+ Span<float> curve_tilt = tilt.get_internal_span().slice(points);
+ if (types[curve_index] == CURVE_TYPE_POLY) {
+ rotate_directions_around_axes(evaluated_normals.slice(evaluated_points),
+ evaluated_tangents.slice(evaluated_points),
+ curve_tilt);
+ }
+ else {
+ evaluated_tilts.clear();
+ evaluated_tilts.resize(evaluated_points.size());
+ this->interpolate_to_evaluated(
+ curve_index, curve_tilt, evaluated_tilts.as_mutable_span());
+ rotate_directions_around_axes(evaluated_normals.slice(evaluated_points),
+ evaluated_tangents.slice(evaluated_points),
+ evaluated_tilts.as_span());
+ }
+ }
}
});
});
@@ -794,6 +840,48 @@ void CurvesGeometry::interpolate_to_evaluated(const int curve_index,
BLI_assert_unreachable();
}
+void CurvesGeometry::interpolate_to_evaluated(const GSpan src, GMutableSpan dst) const
+{
+ BLI_assert(!this->runtime->offsets_cache_dirty);
+ BLI_assert(!this->runtime->nurbs_basis_cache_dirty);
+ const VArray<int8_t> types = this->curve_types();
+ const VArray<int> resolution = this->resolution();
+ const VArray<bool> cyclic = this->cyclic();
+ const VArray<int8_t> nurbs_orders = this->nurbs_orders();
+ const Span<float> nurbs_weights = this->nurbs_weights();
+
+ threading::parallel_for(this->curves_range(), 512, [&](IndexRange curves_range) {
+ for (const int curve_index : curves_range) {
+ const IndexRange points = this->points_for_curve(curve_index);
+ const IndexRange evaluated_points = this->evaluated_points_for_curve(curve_index);
+ switch (types[curve_index]) {
+ case CURVE_TYPE_CATMULL_ROM:
+ curves::catmull_rom::interpolate_to_evaluated(src.slice(points),
+ cyclic[curve_index],
+ resolution[curve_index],
+ dst.slice(evaluated_points));
+ continue;
+ case CURVE_TYPE_POLY:
+ dst.slice(evaluated_points).copy_from(src.slice(points));
+ continue;
+ case CURVE_TYPE_BEZIER:
+ curves::bezier::interpolate_to_evaluated(
+ src.slice(points),
+ this->runtime->bezier_evaluated_offsets.as_span().slice(points),
+ dst.slice(evaluated_points));
+ continue;
+ case CURVE_TYPE_NURBS:
+ curves::nurbs::interpolate_to_evaluated(this->runtime->nurbs_basis_cache[curve_index],
+ nurbs_orders[curve_index],
+ nurbs_weights.slice(points),
+ src.slice(points),
+ dst.slice(evaluated_points));
+ continue;
+ }
+ }
+ });
+}
+
void CurvesGeometry::ensure_evaluated_lengths() const
{
if (!this->runtime->length_cache_dirty) {
diff --git a/source/blender/blenkernel/intern/fcurve_cache.c b/source/blender/blenkernel/intern/fcurve_cache.c
index d67a0ad0e1b..e3b9c4aa10b 100644
--- a/source/blender/blenkernel/intern/fcurve_cache.c
+++ b/source/blender/blenkernel/intern/fcurve_cache.c
@@ -71,7 +71,7 @@ struct FCurvePathCache *BKE_fcurve_pathcache_create(ListBase *list)
}
qsort(fcurve_array, fcurve_array_len, sizeof(FCurve *), fcurve_cmp_for_cache);
- /* Allow for the case no F-curves share an RNA-path, otherwise this is over-allocated.
+ /* Allow for the case no F-Curves share an RNA-path, otherwise this is over-allocated.
* Although in practice it's likely to only be 3-4x as large as is needed
* (with transform channels for e.g.). */
struct FCurvePathCache_Span *span_table = MEM_mallocN(sizeof(*span_table) * fcurve_array_len,
diff --git a/source/blender/blenkernel/intern/gpencil_geom.cc b/source/blender/blenkernel/intern/gpencil_geom.cc
index e1a79986719..041696fa8d3 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.cc
+++ b/source/blender/blenkernel/intern/gpencil_geom.cc
@@ -981,7 +981,7 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo
* \{ */
bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
- int i,
+ int point_index,
float influence,
int iterations,
const bool smooth_caps,
@@ -995,7 +995,7 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
/* Overview of the algorithm here and in the following smooth functions:
* The smooth functions return the new attribute in question for a single point.
- * The result is stored in r_gps->points[i], while the data is read from gps.
+ * The result is stored in r_gps->points[point_index], while the data is read from gps.
* To get a correct result, duplicate the stroke point data and read from the copy,
* while writing to the real stroke. Not doing that will result in acceptable, but
* asymmetric results.
@@ -1004,16 +1004,16 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
* the parameter "iterations" set to 1 or 2. (2 matches the old algorithm).
*/
- const bGPDspoint *pt = &gps->points[i];
+ const bGPDspoint *pt = &gps->points[point_index];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* If smooth_caps is false, the caps will not be translated by smoothing. */
- if (!smooth_caps && !is_cyclic && ELEM(i, 0, gps->totpoints - 1)) {
- copy_v3_v3(&r_gps->points[i].x, &pt->x);
+ if (!smooth_caps && !is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
+ copy_v3_v3(&r_gps->points[point_index].x, &pt->x);
return true;
}
/* This function uses a binomial kernel, which is the discrete version of gaussian blur.
- * The weight for a vertex at the relative index i is
+ * The weight for a vertex at the relative index point_index is
* w = nCr(n, j + n/2) / 2^n = (n/1 * (n-1)/2 * ... * (n-j-n/2)/(j+n/2)) / 2^n
* All weights together sum up to 1
* This is equivalent to doing multiple iterations of averaging neighbors,
@@ -1044,8 +1044,8 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
0.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
- int before = i - step;
- int after = i + step;
+ int before = point_index - step;
+ int after = point_index + step;
float w_before = (float)(w - w2);
float w_after = (float)(w - w2);
@@ -1056,13 +1056,13 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
else {
if (before < 0) {
if (!smooth_caps) {
- w_before *= -before / (float)i;
+ w_before *= -before / (float)point_index;
}
before = 0;
}
if (after > gps->totpoints - 1) {
if (!smooth_caps) {
- w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - i);
+ w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - point_index);
}
after = gps->totpoints - 1;
}
@@ -1089,7 +1089,7 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
add_v3_v3(sco, &pt->x);
/* Based on influence factor, blend between original and optimal smoothed coordinate. */
- interp_v3_v3v3(&r_gps->points[i].x, &pt->x, sco, influence);
+ interp_v3_v3v3(&r_gps->points[point_index].x, &pt->x, sco, influence);
return true;
}
@@ -1101,7 +1101,7 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps,
* \{ */
bool BKE_gpencil_stroke_smooth_strength(
- bGPDstroke *gps, int i, float influence, int iterations, bGPDstroke *r_gps)
+ bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
{
/* If nothing to do, return early */
if (gps->totpoints <= 2 || iterations <= 0) {
@@ -1110,15 +1110,15 @@ bool BKE_gpencil_stroke_smooth_strength(
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
- const bGPDspoint *pt = &gps->points[i];
+ const bGPDspoint *pt = &gps->points[point_index];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
float strength = 0.0f;
const int n_half = (iterations * iterations) / 4 + iterations;
double w = 1.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
- int before = i - step;
- int after = i + step;
+ int before = point_index - step;
+ int after = point_index + step;
float w_before = (float)w;
float w_after = (float)w;
@@ -1147,7 +1147,7 @@ bool BKE_gpencil_stroke_smooth_strength(
strength /= total_w;
/* Based on influence factor, blend between original and optimal smoothed value. */
- r_gps->points[i].strength = pt->strength + strength * influence;
+ r_gps->points[point_index].strength = pt->strength + strength * influence;
return true;
}
@@ -1159,7 +1159,7 @@ bool BKE_gpencil_stroke_smooth_strength(
* \{ */
bool BKE_gpencil_stroke_smooth_thickness(
- bGPDstroke *gps, int i, float influence, int iterations, bGPDstroke *r_gps)
+ bGPDstroke *gps, int point_index, float influence, int iterations, bGPDstroke *r_gps)
{
/* If nothing to do, return early */
if (gps->totpoints <= 2 || iterations <= 0) {
@@ -1168,15 +1168,15 @@ bool BKE_gpencil_stroke_smooth_thickness(
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
- const bGPDspoint *pt = &gps->points[i];
+ const bGPDspoint *pt = &gps->points[point_index];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
float pressure = 0.0f;
const int n_half = (iterations * iterations) / 4 + iterations;
double w = 1.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
- int before = i - step;
- int after = i + step;
+ int before = point_index - step;
+ int after = point_index + step;
float w_before = (float)w;
float w_after = (float)w;
@@ -1205,7 +1205,7 @@ bool BKE_gpencil_stroke_smooth_thickness(
pressure /= total_w;
/* Based on influence factor, blend between original and optimal smoothed value. */
- r_gps->points[i].pressure = pt->pressure + pressure * influence;
+ r_gps->points[point_index].pressure = pt->pressure + pressure * influence;
return true;
}
@@ -1216,8 +1216,11 @@ bool BKE_gpencil_stroke_smooth_thickness(
/** \name Stroke Smooth UV
* \{ */
-bool BKE_gpencil_stroke_smooth_uv(
- struct bGPDstroke *gps, int i, float influence, int iterations, struct bGPDstroke *r_gps)
+bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps,
+ int point_index,
+ float influence,
+ int iterations,
+ struct bGPDstroke *r_gps)
{
/* If nothing to do, return early */
if (gps->totpoints <= 2 || iterations <= 0) {
@@ -1226,13 +1229,13 @@ bool BKE_gpencil_stroke_smooth_uv(
/* See BKE_gpencil_stroke_smooth_point for details on the algorithm. */
- const bGPDspoint *pt = &gps->points[i];
+ const bGPDspoint *pt = &gps->points[point_index];
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* If don't change the caps. */
- if (!is_cyclic && ELEM(i, 0, gps->totpoints - 1)) {
- r_gps->points[i].uv_rot = pt->uv_rot;
- r_gps->points[i].uv_fac = pt->uv_fac;
+ if (!is_cyclic && ELEM(point_index, 0, gps->totpoints - 1)) {
+ r_gps->points[point_index].uv_rot = pt->uv_rot;
+ r_gps->points[point_index].uv_fac = pt->uv_fac;
return true;
}
@@ -1242,8 +1245,8 @@ bool BKE_gpencil_stroke_smooth_uv(
double w = 1.0;
double total_w = 0.0;
for (int step = iterations; step > 0; step--) {
- int before = i - step;
- int after = i + step;
+ int before = point_index - step;
+ int after = point_index + step;
float w_before = (float)w;
float w_after = (float)w;
@@ -1253,11 +1256,11 @@ bool BKE_gpencil_stroke_smooth_uv(
}
else {
if (before < 0) {
- w_before *= -before / (float)i;
+ w_before *= -before / (float)point_index;
before = 0;
}
if (after > gps->totpoints - 1) {
- w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - i);
+ w_after *= (after - (gps->totpoints - 1)) / (float)(gps->totpoints - 1 - point_index);
after = gps->totpoints - 1;
}
}
@@ -1281,8 +1284,8 @@ bool BKE_gpencil_stroke_smooth_uv(
uv_fac /= total_w;
/* Based on influence factor, blend between original and optimal smoothed value. */
- r_gps->points[i].uv_rot = pt->uv_rot + uv_rot * influence;
- r_gps->points[i].uv_fac = pt->uv_fac + uv_fac * influence;
+ r_gps->points[point_index].uv_rot = pt->uv_rot + uv_rot * influence;
+ r_gps->points[point_index].uv_fac = pt->uv_fac + uv_fac * influence;
return true;
}
diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc
index 482537d7fa9..53ec148fd2d 100644
--- a/source/blender/blenkernel/intern/image.cc
+++ b/source/blender/blenkernel/intern/image.cc
@@ -3106,7 +3106,7 @@ bool BKE_image_get_tile_info(char *filepath, ListBase *tiles, int *r_tile_start,
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern = BKE_image_get_tile_strformat(filename, &tile_format);
- bool is_udim = true;
+ bool all_valid_udim = true;
int min_udim = IMA_UDIM_MAX + 1;
int max_udim = 0;
int id;
@@ -3124,7 +3124,7 @@ bool BKE_image_get_tile_info(char *filepath, ListBase *tiles, int *r_tile_start,
}
if (id < 1001 || id > IMA_UDIM_MAX) {
- is_udim = false;
+ all_valid_udim = false;
break;
}
@@ -3135,7 +3135,10 @@ bool BKE_image_get_tile_info(char *filepath, ListBase *tiles, int *r_tile_start,
BLI_filelist_free(dirs, dirs_num);
MEM_SAFE_FREE(udim_pattern);
- if (is_udim && min_udim <= IMA_UDIM_MAX) {
+ /* Ensure that all discovered UDIMs are valid and that there's at least 2 files in total.
+ * Downstream code checks the range value to determine tiled-ness; it's important we match that
+ * expectation here too (T97366). */
+ if (all_valid_udim && min_udim <= IMA_UDIM_MAX && max_udim > min_udim) {
BLI_join_dirfile(filepath, FILE_MAX, dirname, filename);
*r_tile_start = min_udim;
diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c
index bc569956f66..4caaf314888 100644
--- a/source/blender/blenkernel/intern/material.c
+++ b/source/blender/blenkernel/intern/material.c
@@ -1423,13 +1423,15 @@ static bool fill_texpaint_slots_cb(bNode *node, void *userdata)
case SH_NODE_TEX_IMAGE: {
TexPaintSlot *slot = &ma->texpaintslot[index];
slot->ima = (Image *)node->id;
- slot->interp = ((NodeTexImage *)node->storage)->interpolation;
+ NodeTexImage *storage = (NodeTexImage *)node->storage;
+ slot->interp = storage->interpolation;
+ slot->image_user = &storage->iuser;
/* for new renderer, we need to traverse the treeback in search of a UV node */
bNode *uvnode = nodetree_uv_node_recursive(node);
if (uvnode) {
- NodeShaderUVMap *storage = (NodeShaderUVMap *)uvnode->storage;
- slot->uvname = storage->uv_map;
+ NodeShaderUVMap *uv_storage = (NodeShaderUVMap *)uvnode->storage;
+ slot->uvname = uv_storage->uv_map;
/* set a value to index so UI knows that we have a valid pointer for the mesh */
slot->valid = true;
}
diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc
index defca433968..ff953ef5b46 100644
--- a/source/blender/blenkernel/intern/mesh_convert.cc
+++ b/source/blender/blenkernel/intern/mesh_convert.cc
@@ -25,6 +25,7 @@
#include "BLI_utildefines.h"
#include "BKE_DerivedMesh.h"
+#include "BKE_curves.hh"
#include "BKE_deform.h"
#include "BKE_displist.h"
#include "BKE_editmesh.h"
@@ -970,8 +971,7 @@ static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_o
}
const Curves *curves = get_evaluated_curves_from_object(evaluated_object);
if (curves) {
- std::unique_ptr<CurveEval> curve = curves_to_curve_eval(*curves);
- return blender::bke::curve_to_wire_mesh(*curve);
+ return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry));
}
return nullptr;
}
diff --git a/source/blender/blenkernel/intern/mesh_iterators.c b/source/blender/blenkernel/intern/mesh_iterators.c
index e164ad9721b..77e62918441 100644
--- a/source/blender/blenkernel/intern/mesh_iterators.c
+++ b/source/blender/blenkernel/intern/mesh_iterators.c
@@ -310,6 +310,8 @@ void BKE_mesh_foreach_mapped_subdiv_face_center(
BKE_mesh_vertex_normals_ensure(mesh) :
NULL;
const int *index = CustomData_get_layer(&mesh->pdata, CD_ORIGINDEX);
+ const BLI_bitmap *facedot_tags = mesh->runtime.subsurf_face_dot_tags;
+ BLI_assert(facedot_tags != NULL);
if (index) {
for (int i = 0; i < mesh->totpoly; i++, mp++) {
@@ -320,8 +322,7 @@ void BKE_mesh_foreach_mapped_subdiv_face_center(
ml = &mesh->mloop[mp->loopstart];
for (int j = 0; j < mp->totloop; j++, ml++) {
mv = &mesh->mvert[ml->v];
- if (mv->flag & ME_VERT_FACEDOT) {
-
+ if (BLI_BITMAP_TEST(facedot_tags, ml->v)) {
func(userData,
orig,
mv->co,
@@ -335,7 +336,7 @@ void BKE_mesh_foreach_mapped_subdiv_face_center(
ml = &mesh->mloop[mp->loopstart];
for (int j = 0; j < mp->totloop; j++, ml++) {
mv = &mesh->mvert[ml->v];
- if (mv->flag & ME_VERT_FACEDOT) {
+ if (BLI_BITMAP_TEST(facedot_tags, ml->v)) {
func(userData, i, mv->co, (flag & MESH_FOREACH_USE_NORMAL) ? vert_normals[ml->v] : NULL);
}
}
diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc
index b06e867cf37..90e9a2a2ff6 100644
--- a/source/blender/blenkernel/intern/mesh_runtime.cc
+++ b/source/blender/blenkernel/intern/mesh_runtime.cc
@@ -86,6 +86,7 @@ void BKE_mesh_runtime_reset_on_copy(Mesh *mesh, const int UNUSED(flag))
runtime->looptris = blender::dna::shallow_zero_initialize();
runtime->bvh_cache = nullptr;
runtime->shrinkwrap_data = nullptr;
+ runtime->subsurf_face_dot_tags = nullptr;
runtime->vert_normals_dirty = true;
runtime->poly_normals_dirty = true;
@@ -254,6 +255,8 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
mesh->runtime.subdiv_ccg = nullptr;
}
BKE_shrinkwrap_discard_boundary_data(mesh);
+
+ MEM_SAFE_FREE(mesh->runtime.subsurf_face_dot_tags);
}
/** \} */
diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c
index 395deeda4ad..f4703b32582 100644
--- a/source/blender/blenkernel/intern/modifier.c
+++ b/source/blender/blenkernel/intern/modifier.c
@@ -845,41 +845,6 @@ bool BKE_modifiers_uses_armature(Object *ob, bArmature *arm)
return false;
}
-bool BKE_modifiers_uses_subsurf_facedots(const struct Scene *scene, Object *ob)
-{
- /* Search (backward) in the modifier stack to find if we have a subsurf modifier (enabled) before
- * the last modifier displayed on cage (or if the subsurf is the last). */
- VirtualModifierData virtualModifierData;
- ModifierData *md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData);
- int cage_index = BKE_modifiers_get_cage_index(scene, ob, NULL, 1);
- if (cage_index == -1) {
- return false;
- }
- /* Find first modifier enabled on cage. */
- for (int i = 0; md && i < cage_index; i++) {
- md = md->next;
- }
- /* Now from this point, search for subsurf modifier. */
- for (; md; md = md->prev) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
- if (md->type == eModifierType_Subsurf) {
- ModifierMode mode = eModifierMode_Realtime | eModifierMode_Editmode;
- if (BKE_modifier_is_enabled(scene, md, mode)) {
- return true;
- }
- }
- else if (mti->type == eModifierTypeType_OnlyDeform) {
- /* These modifiers do not reset the subdiv flag nor change the topology.
- * We can still search for a subsurf modifier. */
- }
- else {
- /* Other modifiers may reset the subdiv facedot flag or create. */
- return false;
- }
- }
- return false;
-}
-
bool BKE_modifier_is_correctable_deformed(ModifierData *md)
{
const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c
index eb3f47760fc..5fd7984ea90 100644
--- a/source/blender/blenkernel/intern/paint.c
+++ b/source/blender/blenkernel/intern/paint.c
@@ -1511,6 +1511,8 @@ void BKE_sculptsession_free(Object *ob)
BKE_sculptsession_free_vwpaint_data(ob->sculpt);
+ MEM_SAFE_FREE(ss->last_paint_canvas_key);
+
MEM_freeN(ss);
ob->sculpt = NULL;
@@ -1771,6 +1773,24 @@ static void sculpt_update_object(Depsgraph *depsgraph,
}
}
+ /*
+ * We should rebuild the PBVH_pixels when painting canvas changes.
+ *
+ * The relevant changes are stored/encoded in the paint canvas key.
+ * These include the active uv map, and resolutions.
+ */
+ if (U.experimental.use_sculpt_texture_paint && ss->pbvh) {
+ char *paint_canvas_key = BKE_paint_canvas_key_get(&scene->toolsettings->paint_mode, ob);
+ if (ss->last_paint_canvas_key == NULL || !STREQ(paint_canvas_key, ss->last_paint_canvas_key)) {
+ MEM_SAFE_FREE(ss->last_paint_canvas_key);
+ ss->last_paint_canvas_key = paint_canvas_key;
+ BKE_pbvh_mark_rebuild_pixels(ss->pbvh);
+ }
+ else {
+ MEM_freeN(paint_canvas_key);
+ }
+ }
+
/* We could be more precise when we have access to the active tool. */
const bool use_paint_slots = (ob->mode & OB_MODE_SCULPT) != 0;
if (use_paint_slots) {
@@ -1849,6 +1869,10 @@ void BKE_sculpt_color_layer_create_if_needed(struct Object *object)
BKE_id_attributes_active_color_set(&orig_me->id, layer);
DEG_id_tag_update(&orig_me->id, ID_RECALC_GEOMETRY_ALL_MODES);
+
+ if (object->sculpt && object->sculpt->pbvh) {
+ BKE_pbvh_update_active_vcol(object->sculpt->pbvh, orig_me);
+ }
}
void BKE_sculpt_update_object_for_edit(
diff --git a/source/blender/blenkernel/intern/paint_canvas.cc b/source/blender/blenkernel/intern/paint_canvas.cc
index c1145164642..b72418d88c0 100644
--- a/source/blender/blenkernel/intern/paint_canvas.cc
+++ b/source/blender/blenkernel/intern/paint_canvas.cc
@@ -6,9 +6,12 @@
#include "DNA_scene_types.h"
#include "BKE_customdata.h"
+#include "BKE_image.h"
#include "BKE_material.h"
#include "BKE_paint.h"
+#include "IMB_imbuf_types.h"
+
namespace blender::bke::paint::canvas {
static TexPaintSlot *get_active_slot(Object *ob)
{
@@ -33,22 +36,35 @@ extern "C" {
using namespace blender::bke::paint::canvas;
-Image *BKE_paint_canvas_image_get(const struct PaintModeSettings *settings, struct Object *ob)
+bool BKE_paint_canvas_image_get(PaintModeSettings *settings,
+ Object *ob,
+ Image **r_image,
+ ImageUser **r_image_user)
{
+ *r_image = nullptr;
+ *r_image_user = nullptr;
+
switch (settings->canvas_source) {
case PAINT_CANVAS_SOURCE_COLOR_ATTRIBUTE:
- return nullptr;
+ break;
+
case PAINT_CANVAS_SOURCE_IMAGE:
- return settings->canvas_image;
+ *r_image = settings->canvas_image;
+ *r_image_user = &settings->image_user;
+ break;
+
case PAINT_CANVAS_SOURCE_MATERIAL: {
TexPaintSlot *slot = get_active_slot(ob);
if (slot == nullptr) {
break;
}
- return slot->ima;
+
+ *r_image = slot->ima;
+ *r_image_user = slot->image_user;
+ break;
}
}
- return nullptr;
+ return *r_image != nullptr;
}
int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *settings,
@@ -87,4 +103,29 @@ int BKE_paint_canvas_uvmap_layer_index_get(const struct PaintModeSettings *setti
}
return -1;
}
+
+char *BKE_paint_canvas_key_get(struct PaintModeSettings *settings, struct Object *ob)
+{
+ std::stringstream ss;
+ int active_uv_map_layer_index = BKE_paint_canvas_uvmap_layer_index_get(settings, ob);
+ ss << "UV_MAP:" << active_uv_map_layer_index;
+
+ Image *image;
+ ImageUser *image_user;
+ if (BKE_paint_canvas_image_get(settings, ob, &image, &image_user)) {
+ ImageUser tile_user = *image_user;
+ LISTBASE_FOREACH (ImageTile *, image_tile, &image->tiles) {
+ tile_user.tile = image_tile->tile_number;
+ ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &tile_user, nullptr);
+ if (!image_buffer) {
+ continue;
+ }
+ ss << ",TILE_" << image_tile->tile_number;
+ ss << "(" << image_buffer->x << "," << image_buffer->y << ")";
+ BKE_image_release_ibuf(image, image_buffer, nullptr);
+ }
+ }
+
+ return BLI_strdup(ss.str().c_str());
+}
}
diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c
index 5d307697208..e91f360ef22 100644
--- a/source/blender/blenkernel/intern/pbvh.c
+++ b/source/blender/blenkernel/intern/pbvh.c
@@ -686,6 +686,8 @@ void BKE_pbvh_free(PBVH *pbvh)
if (node->bm_other_verts) {
BLI_gset_free(node->bm_other_verts, NULL);
}
+
+ pbvh_pixels_free(node);
}
}
@@ -1769,7 +1771,7 @@ BMesh *BKE_pbvh_get_bmesh(PBVH *pbvh)
void BKE_pbvh_node_mark_update(PBVHNode *node)
{
node->flag |= PBVH_UpdateNormals | PBVH_UpdateBB | PBVH_UpdateOriginalBB |
- PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
+ PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_RebuildPixels;
}
void BKE_pbvh_node_mark_update_mask(PBVHNode *node)
@@ -1782,6 +1784,16 @@ void BKE_pbvh_node_mark_update_color(PBVHNode *node)
node->flag |= PBVH_UpdateColor | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
}
+void BKE_pbvh_mark_rebuild_pixels(PBVH *pbvh)
+{
+ for (int n = 0; n < pbvh->totnode; n++) {
+ PBVHNode *node = &pbvh->nodes[n];
+ if (node->flag & PBVH_Leaf) {
+ node->flag |= PBVH_RebuildPixels;
+ }
+ }
+}
+
void BKE_pbvh_node_mark_update_visibility(PBVHNode *node)
{
node->flag |= PBVH_UpdateVisibility | PBVH_RebuildDrawBuffers | PBVH_UpdateDrawBuffers |
diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h
index ea1f0632f32..77bd00da50a 100644
--- a/source/blender/blenkernel/intern/pbvh_intern.h
+++ b/source/blender/blenkernel/intern/pbvh_intern.h
@@ -6,6 +6,10 @@
* \ingroup bke
*/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/* Axis-aligned bounding box */
typedef struct {
float bmin[3], bmax[3];
@@ -111,6 +115,7 @@ struct PBVHNode {
/* Used to store the brush color during a stroke and composite it over the original color */
PBVHColorBufferNode color_buffer;
+ PBVHPixelsNode pixels;
};
typedef enum {
@@ -260,3 +265,11 @@ bool pbvh_bmesh_node_nearest_to_ray(PBVHNode *node,
bool use_original);
void pbvh_bmesh_normals_update(PBVHNode **nodes, int totnode);
+
+/* pbvh_pixels.hh */
+void pbvh_pixels_free(PBVHNode *node);
+void pbvh_pixels_free_brush_test(PBVHNode *node);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/intern/pbvh_pixels.cc b/source/blender/blenkernel/intern/pbvh_pixels.cc
new file mode 100644
index 00000000000..d8dd2f4b382
--- /dev/null
+++ b/source/blender/blenkernel/intern/pbvh_pixels.cc
@@ -0,0 +1,393 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#include "BKE_customdata.h"
+#include "BKE_mesh_mapping.h"
+#include "BKE_pbvh.h"
+#include "BKE_pbvh_pixels.hh"
+
+#include "DNA_image_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_math.h"
+#include "BLI_task.h"
+
+#include "BKE_image_wrappers.hh"
+
+#include "bmesh.h"
+
+#include "pbvh_intern.h"
+
+namespace blender::bke::pbvh::pixels {
+
+/** Durind debugging this check could be enabled. It will write to each image pixel that is covered
+ * by the pbvh. */
+constexpr bool USE_WATERTIGHT_CHECK = false;
+
+/**
+ * Calculate the delta of two neighbour uv coordinates in the given image buffer.
+ */
+static float2 calc_barycentric_delta(const float2 uvs[3],
+ const float2 start_uv,
+ const float2 end_uv)
+{
+
+ float3 start_barycentric;
+ barycentric_weights_v2(uvs[0], uvs[1], uvs[2], start_uv, start_barycentric);
+ float3 end_barycentric;
+ barycentric_weights_v2(uvs[0], uvs[1], uvs[2], end_uv, end_barycentric);
+ float3 barycentric = end_barycentric - start_barycentric;
+ return float2(barycentric.x, barycentric.y);
+}
+
+static float2 calc_barycentric_delta_x(const ImBuf *image_buffer,
+ const float2 uvs[3],
+ const int x,
+ const int y)
+{
+ const float2 start_uv(float(x) / image_buffer->x, float(y) / image_buffer->y);
+ const float2 end_uv(float(x + 1) / image_buffer->x, float(y) / image_buffer->y);
+ return calc_barycentric_delta(uvs, start_uv, end_uv);
+}
+
+static void extract_barycentric_pixels(UDIMTilePixels &tile_data,
+ const ImBuf *image_buffer,
+ const int triangle_index,
+ const float2 uvs[3],
+ const int minx,
+ const int miny,
+ const int maxx,
+ const int maxy)
+{
+ for (int y = miny; y < maxy; y++) {
+ bool start_detected = false;
+ PackedPixelRow pixel_row;
+ pixel_row.triangle_index = triangle_index;
+ pixel_row.num_pixels = 0;
+ int x;
+
+ for (x = minx; x < maxx; x++) {
+ float2 uv(float(x) / image_buffer->x, float(y) / image_buffer->y);
+ float3 barycentric_weights;
+ barycentric_weights_v2(uvs[0], uvs[1], uvs[2], uv, barycentric_weights);
+
+ const bool is_inside = barycentric_inside_triangle_v2(barycentric_weights);
+ if (!start_detected && is_inside) {
+ start_detected = true;
+ pixel_row.start_image_coordinate = ushort2(x, y);
+ pixel_row.start_barycentric_coord = float2(barycentric_weights.x, barycentric_weights.y);
+ }
+ else if (start_detected && !is_inside) {
+ break;
+ }
+ }
+
+ if (!start_detected) {
+ continue;
+ }
+ pixel_row.num_pixels = x - pixel_row.start_image_coordinate.x;
+ tile_data.pixel_rows.append(pixel_row);
+ }
+}
+
+static void init_triangles(PBVH *pbvh, PBVHNode *node, NodeData *node_data, const MLoop *mloop)
+{
+ for (int i = 0; i < node->totprim; i++) {
+ const MLoopTri *lt = &pbvh->looptri[node->prim_indices[i]];
+ node_data->triangles.append(
+ int3(mloop[lt->tri[0]].v, mloop[lt->tri[1]].v, mloop[lt->tri[2]].v));
+ }
+}
+
+struct EncodePixelsUserData {
+ Image *image;
+ ImageUser *image_user;
+ PBVH *pbvh;
+ Vector<PBVHNode *> *nodes;
+ MLoopUV *ldata_uv;
+};
+
+static void do_encode_pixels(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ EncodePixelsUserData *data = static_cast<EncodePixelsUserData *>(userdata);
+ Image *image = data->image;
+ ImageUser image_user = *data->image_user;
+ PBVH *pbvh = data->pbvh;
+ PBVHNode *node = (*data->nodes)[n];
+ NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
+ LISTBASE_FOREACH (ImageTile *, tile, &data->image->tiles) {
+ image::ImageTileWrapper image_tile(tile);
+ image_user.tile = image_tile.get_tile_number();
+ ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user, nullptr);
+ if (image_buffer == nullptr) {
+ continue;
+ }
+
+ float2 tile_offset = float2(image_tile.get_tile_offset());
+ UDIMTilePixels tile_data;
+
+ Triangles &triangles = node_data->triangles;
+ for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) {
+ const MLoopTri *lt = &pbvh->looptri[node->prim_indices[triangle_index]];
+ float2 uvs[3] = {
+ float2(data->ldata_uv[lt->tri[0]].uv) - tile_offset,
+ float2(data->ldata_uv[lt->tri[1]].uv) - tile_offset,
+ float2(data->ldata_uv[lt->tri[2]].uv) - tile_offset,
+ };
+
+ const float minv = clamp_f(min_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
+ const int miny = floor(minv * image_buffer->y);
+ const float maxv = clamp_f(max_fff(uvs[0].y, uvs[1].y, uvs[2].y), 0.0f, 1.0f);
+ const int maxy = min_ii(ceil(maxv * image_buffer->y), image_buffer->y);
+ const float minu = clamp_f(min_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
+ const int minx = floor(minu * image_buffer->x);
+ const float maxu = clamp_f(max_fff(uvs[0].x, uvs[1].x, uvs[2].x), 0.0f, 1.0f);
+ const int maxx = min_ii(ceil(maxu * image_buffer->x), image_buffer->x);
+
+ TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index);
+ triangle.delta_barycentric_coord_u = calc_barycentric_delta_x(image_buffer, uvs, minx, miny);
+ extract_barycentric_pixels(
+ tile_data, image_buffer, triangle_index, uvs, minx, miny, maxx, maxy);
+ }
+
+ BKE_image_release_ibuf(image, image_buffer, nullptr);
+
+ if (tile_data.pixel_rows.is_empty()) {
+ continue;
+ }
+
+ tile_data.tile_number = image_tile.get_tile_number();
+ node_data->tiles.append(tile_data);
+ }
+}
+
+static bool should_pixels_be_updated(PBVHNode *node)
+{
+ if ((node->flag & PBVH_Leaf) == 0) {
+ return false;
+ }
+ if ((node->flag & PBVH_RebuildPixels) != 0) {
+ return true;
+ }
+ NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
+ if (node_data != nullptr) {
+ return false;
+ }
+ return true;
+}
+
+static int64_t count_nodes_to_update(PBVH *pbvh)
+{
+ int64_t result = 0;
+ for (int n = 0; n < pbvh->totnode; n++) {
+ PBVHNode *node = &pbvh->nodes[n];
+ if (should_pixels_be_updated(node)) {
+ result++;
+ }
+ }
+ return result;
+}
+
+/**
+ * Find the nodes that needs to be updated.
+ *
+ * The nodes that require updated are added to the r_nodes_to_update parameter.
+ * Will fill in r_visited_polygons with polygons that are owned by nodes that do not require
+ * updates.
+ *
+ * returns if there were any nodes found (true).
+ */
+static bool find_nodes_to_update(PBVH *pbvh, Vector<PBVHNode *> &r_nodes_to_update)
+{
+ int64_t nodes_to_update_len = count_nodes_to_update(pbvh);
+ if (nodes_to_update_len == 0) {
+ return false;
+ }
+
+ r_nodes_to_update.reserve(nodes_to_update_len);
+
+ for (int n = 0; n < pbvh->totnode; n++) {
+ PBVHNode *node = &pbvh->nodes[n];
+ if (!should_pixels_be_updated(node)) {
+ continue;
+ }
+ r_nodes_to_update.append(node);
+ node->flag = static_cast<PBVHNodeFlags>(node->flag | PBVH_RebuildPixels);
+
+ if (node->pixels.node_data == nullptr) {
+ NodeData *node_data = MEM_new<NodeData>(__func__);
+ node->pixels.node_data = node_data;
+ }
+ else {
+ NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
+ node_data->clear_data();
+ }
+ }
+
+ return true;
+}
+
+static void apply_watertight_check(PBVH *pbvh, Image *image, ImageUser *image_user)
+{
+ ImageUser watertight = *image_user;
+ LISTBASE_FOREACH (ImageTile *, tile_data, &image->tiles) {
+ image::ImageTileWrapper image_tile(tile_data);
+ watertight.tile = image_tile.get_tile_number();
+ ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &watertight, nullptr);
+ if (image_buffer == nullptr) {
+ continue;
+ }
+ for (int n = 0; n < pbvh->totnode; n++) {
+ PBVHNode *node = &pbvh->nodes[n];
+ if ((node->flag & PBVH_Leaf) == 0) {
+ continue;
+ }
+ NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
+ UDIMTilePixels *tile_node_data = node_data->find_tile_data(image_tile);
+ if (tile_node_data == nullptr) {
+ continue;
+ }
+
+ for (PackedPixelRow &pixel_row : tile_node_data->pixel_rows) {
+ int pixel_offset = pixel_row.start_image_coordinate.y * image_buffer->x +
+ pixel_row.start_image_coordinate.x;
+ for (int x = 0; x < pixel_row.num_pixels; x++) {
+ if (image_buffer->rect_float) {
+ copy_v4_fl(&image_buffer->rect_float[pixel_offset * 4], 1.0);
+ }
+ if (image_buffer->rect) {
+ uint8_t *dest = static_cast<uint8_t *>(
+ static_cast<void *>(&image_buffer->rect[pixel_offset]));
+ copy_v4_uchar(dest, 255);
+ }
+ pixel_offset += 1;
+ }
+ }
+ }
+ BKE_image_release_ibuf(image, image_buffer, nullptr);
+ }
+ BKE_image_partial_update_mark_full_update(image);
+}
+
+static void update_pixels(PBVH *pbvh,
+ const struct MLoop *mloop,
+ struct CustomData *ldata,
+ struct Image *image,
+ struct ImageUser *image_user)
+{
+ Vector<PBVHNode *> nodes_to_update;
+
+ if (!find_nodes_to_update(pbvh, nodes_to_update)) {
+ return;
+ }
+
+ MLoopUV *ldata_uv = static_cast<MLoopUV *>(CustomData_get_layer(ldata, CD_MLOOPUV));
+ if (ldata_uv == nullptr) {
+ return;
+ }
+
+ for (PBVHNode *node : nodes_to_update) {
+ NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
+ init_triangles(pbvh, node, node_data, mloop);
+ }
+
+ EncodePixelsUserData user_data;
+ user_data.pbvh = pbvh;
+ user_data.image = image;
+ user_data.image_user = image_user;
+ user_data.ldata_uv = ldata_uv;
+ user_data.nodes = &nodes_to_update;
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, nodes_to_update.size());
+ BLI_task_parallel_range(0, nodes_to_update.size(), &user_data, do_encode_pixels, &settings);
+ if (USE_WATERTIGHT_CHECK) {
+ apply_watertight_check(pbvh, image, image_user);
+ }
+
+ /* Clear the UpdatePixels flag. */
+ for (PBVHNode *node : nodes_to_update) {
+ node->flag = static_cast<PBVHNodeFlags>(node->flag & ~PBVH_RebuildPixels);
+ }
+
+//#define DO_PRINT_STATISTICS
+#ifdef DO_PRINT_STATISTICS
+ /* Print some statistics about compression ratio. */
+ {
+ int64_t compressed_data_len = 0;
+ int64_t num_pixels = 0;
+ for (int n = 0; n < pbvh->totnode; n++) {
+ PBVHNode *node = &pbvh->nodes[n];
+ if ((node->flag & PBVH_Leaf) == 0) {
+ continue;
+ }
+ NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
+ compressed_data_len += node_data->triangles.mem_size();
+ for (const UDIMTilePixels &tile_data : node_data->tiles) {
+ compressed_data_len += tile_data.encoded_pixels.size() * sizeof(PackedPixelRow);
+ for (const PackedPixelRow &encoded_pixels : tile_data.encoded_pixels) {
+ num_pixels += encoded_pixels.num_pixels;
+ }
+ }
+ }
+ printf("Encoded %lld pixels in %lld bytes (%f bytes per pixel)\n",
+ num_pixels,
+ compressed_data_len,
+ float(compressed_data_len) / num_pixels);
+ }
+#endif
+}
+
+NodeData &BKE_pbvh_pixels_node_data_get(PBVHNode &node)
+{
+ BLI_assert(node.pixels.node_data != nullptr);
+ NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data);
+ return *node_data;
+}
+
+void BKE_pbvh_pixels_mark_image_dirty(PBVHNode &node, Image &image, ImageUser &image_user)
+{
+ BLI_assert(node.pixels.node_data != nullptr);
+ NodeData *node_data = static_cast<NodeData *>(node.pixels.node_data);
+ if (node_data->flags.dirty) {
+ ImageUser local_image_user = image_user;
+ LISTBASE_FOREACH (ImageTile *, tile, &image.tiles) {
+ image::ImageTileWrapper image_tile(tile);
+ local_image_user.tile = image_tile.get_tile_number();
+ ImBuf *image_buffer = BKE_image_acquire_ibuf(&image, &local_image_user, nullptr);
+ if (image_buffer == nullptr) {
+ continue;
+ }
+
+ node_data->mark_region(image, image_tile, *image_buffer);
+ BKE_image_release_ibuf(&image, image_buffer, nullptr);
+ }
+ node_data->flags.dirty = false;
+ }
+}
+
+} // namespace blender::bke::pbvh::pixels
+
+extern "C" {
+using namespace blender::bke::pbvh::pixels;
+
+void BKE_pbvh_build_pixels(PBVH *pbvh,
+ const struct MLoop *mloop,
+ struct CustomData *ldata,
+ struct Image *image,
+ struct ImageUser *image_user)
+{
+ update_pixels(pbvh, mloop, ldata, image, image_user);
+}
+
+void pbvh_pixels_free(PBVHNode *node)
+{
+ NodeData *node_data = static_cast<NodeData *>(node->pixels.node_data);
+ MEM_delete(node_data);
+ node->pixels.node_data = nullptr;
+}
+}
diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c
index 38239e7ddd6..83e4336e3b1 100644
--- a/source/blender/blenkernel/intern/subdiv_mesh.c
+++ b/source/blender/blenkernel/intern/subdiv_mesh.c
@@ -15,6 +15,7 @@
#include "DNA_meshdata_types.h"
#include "BLI_alloca.h"
+#include "BLI_bitmap.h"
#include "BLI_math_vector.h"
#include "BKE_customdata.h"
@@ -464,6 +465,9 @@ static bool subdiv_mesh_topology_info(const SubdivForeachContext *foreach_contex
subdiv_context->coarse_mesh, num_vertices, num_edges, 0, num_loops, num_polygons, mask);
subdiv_mesh_ctx_cache_custom_data_layers(subdiv_context);
subdiv_mesh_prepare_accumulator(subdiv_context, num_vertices);
+ MEM_SAFE_FREE(subdiv_context->subdiv_mesh->runtime.subsurf_face_dot_tags);
+ subdiv_context->subdiv_mesh->runtime.subsurf_face_dot_tags = BLI_BITMAP_NEW(num_vertices,
+ __func__);
return true;
}
@@ -527,7 +531,7 @@ static void evaluate_vertex_and_apply_displacement_copy(const SubdivMeshContext
/* Apply displacement. */
add_v3_v3(subdiv_vert->co, D);
/* Remove facedot flag. This can happen if there is more than one subsurf modifier. */
- subdiv_vert->flag &= ~ME_VERT_FACEDOT;
+ BLI_BITMAP_DISABLE(ctx->subdiv_mesh->runtime.subsurf_face_dot_tags, subdiv_vertex_index);
}
static void evaluate_vertex_and_apply_displacement_interpolate(
@@ -687,12 +691,13 @@ static bool subdiv_mesh_is_center_vertex(const MPoly *coarse_poly, const float u
}
static void subdiv_mesh_tag_center_vertex(const MPoly *coarse_poly,
- MVert *subdiv_vert,
+ const int subdiv_vertex_index,
const float u,
- const float v)
+ const float v,
+ Mesh *subdiv_mesh)
{
if (subdiv_mesh_is_center_vertex(coarse_poly, u, v)) {
- subdiv_vert->flag |= ME_VERT_FACEDOT;
+ BLI_BITMAP_ENABLE(subdiv_mesh->runtime.subsurf_face_dot_tags, subdiv_vertex_index);
}
}
@@ -717,7 +722,7 @@ static void subdiv_mesh_vertex_inner(const SubdivForeachContext *foreach_context
subdiv_mesh_ensure_vertex_interpolation(ctx, tls, coarse_poly, coarse_corner);
subdiv_vertex_data_interpolate(ctx, subdiv_vert, &tls->vertex_interpolation, u, v);
BKE_subdiv_eval_final_point(subdiv, ptex_face_index, u, v, subdiv_vert->co);
- subdiv_mesh_tag_center_vertex(coarse_poly, subdiv_vert, u, v);
+ subdiv_mesh_tag_center_vertex(coarse_poly, subdiv_vertex_index, u, v, subdiv_mesh);
}
/** \} */
diff --git a/source/blender/blenkernel/intern/tracking_stabilize.c b/source/blender/blenkernel/intern/tracking_stabilize.c
index 882f7b87597..d86a0c10f01 100644
--- a/source/blender/blenkernel/intern/tracking_stabilize.c
+++ b/source/blender/blenkernel/intern/tracking_stabilize.c
@@ -198,7 +198,7 @@ static void use_values_from_fcurves(StabContext *ctx, bool toggle)
}
/* Prepare per call private working area.
- * Used for access to possibly animated values: retrieve available F-curves.
+ * Used for access to possibly animated values: retrieve available F-Curves.
*/
static StabContext *init_stabilization_working_context(MovieClip *clip)
{
diff --git a/source/blender/blenkernel/nla_private.h b/source/blender/blenkernel/nla_private.h
index cef9e543a59..41d1eef733c 100644
--- a/source/blender/blenkernel/nla_private.h
+++ b/source/blender/blenkernel/nla_private.h
@@ -258,7 +258,7 @@ void nlasnapshot_blend_strip(PointerRNA *ptr,
void nlasnapshot_blend_strip_get_inverted_lower_snapshot(
PointerRNA *ptr,
- NlaEvalData *eval_data,
+ NlaEvalData *channels,
ListBase *modifiers,
NlaEvalStrip *nes,
NlaEvalSnapshot *snapshot,
diff --git a/source/blender/blenlib/BLI_math_rotation.hh b/source/blender/blenlib/BLI_math_rotation.hh
new file mode 100644
index 00000000000..e8b746b34df
--- /dev/null
+++ b/source/blender/blenlib/BLI_math_rotation.hh
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+#include "BLI_math_vec_types.hh"
+
+namespace blender::math {
+
+/**
+ * Rotate the unit-length \a direction around the unit-length \a axis by the \a angle.
+ */
+float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, float angle);
+
+} // namespace blender::math
diff --git a/source/blender/blenlib/BLI_vector.hh b/source/blender/blenlib/BLI_vector.hh
index da9ab9c313e..acf47f67168 100644
--- a/source/blender/blenlib/BLI_vector.hh
+++ b/source/blender/blenlib/BLI_vector.hh
@@ -387,6 +387,16 @@ class Vector {
}
/**
+ * Reset the size of the vector so that it contains new_size elements.
+ * All existing elements are destructed, and not copied if the data must be reallocated.
+ */
+ void reinitialize(const int64_t new_size)
+ {
+ this->clear();
+ this->resize(new_size);
+ }
+
+ /**
* Afterwards the vector has 0 elements, but will still have
* memory to be refilled again.
*/
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index 99e07264276..e8a3851e082 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -94,6 +94,7 @@ set(SRC
intern/math_interp.c
intern/math_matrix.c
intern/math_rotation.c
+ intern/math_rotation.cc
intern/math_solvers.c
intern/math_statistics.c
intern/math_time.c
@@ -251,6 +252,7 @@ set(SRC
BLI_math_matrix.h
BLI_math_mpq.hh
BLI_math_rotation.h
+ BLI_math_rotation.hh
BLI_math_solvers.h
BLI_math_statistics.h
BLI_math_time.h
diff --git a/source/blender/blenlib/intern/math_rotation.cc b/source/blender/blenlib/intern/math_rotation.cc
new file mode 100644
index 00000000000..74300d55954
--- /dev/null
+++ b/source/blender/blenlib/intern/math_rotation.cc
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bli
+ */
+
+#include "BLI_math_base.h"
+#include "BLI_math_rotation.hh"
+#include "BLI_math_vector.h"
+#include "BLI_math_vector.hh"
+
+namespace blender::math {
+
+float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, const float angle)
+{
+ BLI_ASSERT_UNIT_V3(direction);
+ BLI_ASSERT_UNIT_V3(axis);
+
+ const float3 axis_scaled = axis * math::dot(direction, axis);
+ const float3 diff = direction - axis_scaled;
+ const float3 cross = math::cross(axis, diff);
+
+ return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle);
+}
+
+} // namespace blender::math
diff --git a/source/blender/blenlib/tests/BLI_math_rotation_test.cc b/source/blender/blenlib/tests/BLI_math_rotation_test.cc
index a10e441cfbe..a283118bea2 100644
--- a/source/blender/blenlib/tests/BLI_math_rotation_test.cc
+++ b/source/blender/blenlib/tests/BLI_math_rotation_test.cc
@@ -4,6 +4,8 @@
#include "BLI_math_base.h"
#include "BLI_math_rotation.h"
+#include "BLI_math_rotation.hh"
+#include "BLI_math_vector.hh"
#include <cmath>
@@ -147,3 +149,23 @@ TEST(math_rotation, quat_split_swing_and_twist_negative)
EXPECT_V4_NEAR(swing, expected_swing, FLT_EPSILON);
EXPECT_V4_NEAR(twist, expected_twist, FLT_EPSILON);
}
+
+namespace blender::math::tests {
+
+TEST(math_rotation, RotateDirectionAroundAxis)
+{
+ const float3 a = rotate_direction_around_axis({1, 0, 0}, {0, 0, 1}, M_PI_2);
+ EXPECT_NEAR(a.x, 0.0f, FLT_EPSILON);
+ EXPECT_NEAR(a.y, 1.0f, FLT_EPSILON);
+ EXPECT_NEAR(a.z, 0.0f, FLT_EPSILON);
+ const float3 b = rotate_direction_around_axis({1, 0, 0}, {0, 0, 1}, M_PI);
+ EXPECT_NEAR(b.x, -1.0f, FLT_EPSILON);
+ EXPECT_NEAR(b.y, 0.0f, FLT_EPSILON);
+ EXPECT_NEAR(b.z, 0.0f, FLT_EPSILON);
+ const float3 c = rotate_direction_around_axis({0, 0, 1}, {0, 0, 1}, 0.0f);
+ EXPECT_NEAR(c.x, 0.0f, FLT_EPSILON);
+ EXPECT_NEAR(c.y, 0.0f, FLT_EPSILON);
+ EXPECT_NEAR(c.z, 1.0f, FLT_EPSILON);
+}
+
+} // namespace blender::math::tests
diff --git a/source/blender/draw/engines/eevee/eevee_shaders_extra.cc b/source/blender/draw/engines/eevee/eevee_shaders_extra.cc
index 1d3e07217b5..bb1a0b0abe4 100644
--- a/source/blender/draw/engines/eevee/eevee_shaders_extra.cc
+++ b/source/blender/draw/engines/eevee/eevee_shaders_extra.cc
@@ -92,7 +92,7 @@ void eevee_shader_material_create_info_amend(GPUMaterial *gpumat,
const StageInterfaceInfo &iface = *info.vertex_out_interfaces_.first();
/* Globals the attrib_load() can write to when it is in the fragment shader. */
attr_load << "struct " << iface.name << " {\n";
- for (auto &inout : iface.inouts) {
+ for (const auto &inout : iface.inouts) {
attr_load << " " << inout.type << " " << inout.name << ";\n";
}
attr_load << "};\n";
diff --git a/source/blender/draw/intern/draw_cache_impl_mesh.c b/source/blender/draw/intern/draw_cache_impl_mesh.c
index e6f34d3dd0d..c4fa60ef51d 100644
--- a/source/blender/draw/intern/draw_cache_impl_mesh.c
+++ b/source/blender/draw/intern/draw_cache_impl_mesh.c
@@ -2121,8 +2121,7 @@ void DRW_mesh_batch_cache_create_requested(struct TaskGraph *task_graph,
MDEPS_ASSERT_MAP_INDEX(TRIS_PER_MAT_INDEX);
- /* Meh loose Scene const correctness here. */
- const bool use_subsurf_fdots = scene ? BKE_modifiers_uses_subsurf_facedots(scene, ob) : false;
+ const bool use_subsurf_fdots = me->runtime.subsurf_face_dot_tags != NULL;
if (do_uvcage) {
mesh_buffer_cache_create_requested(task_graph,
diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c
index ec3a5b3a7b1..88d05fcd928 100644
--- a/source/blender/draw/intern/draw_manager.c
+++ b/source/blender/draw/intern/draw_manager.c
@@ -1286,6 +1286,10 @@ void DRW_notify_view_update(const DRWUpdateContext *update_ctx)
const bool gpencil_engine_needed = drw_gpencil_engine_needed(depsgraph, v3d);
+ if (G.is_rendering) {
+ return;
+ }
+
/* XXX Really nasty locking. But else this could
* be executed by the material previews thread
* while rendering a viewport. */
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc
index 27149a80f9b..43aa52f08c8 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_edituv.cc
@@ -5,9 +5,7 @@
* \ingroup draw
*/
-#include "BLI_vector.hh"
-
-#include "MEM_guardedalloc.h"
+#include "BLI_bitmap.h"
#include "extract_mesh.h"
@@ -538,7 +536,8 @@ static void extract_edituv_fdots_iter_poly_mesh(const MeshRenderData *mr,
{
MeshExtract_EditUvElem_Data *data = static_cast<MeshExtract_EditUvElem_Data *>(_data);
if (mr->use_subsurf_fdots) {
- /* Check #ME_VERT_FACEDOT. */
+ const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags;
+
const MLoop *mloop = mr->mloop;
const int ml_index_end = mp->loopstart + mp->totloop;
for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) {
@@ -546,8 +545,7 @@ static void extract_edituv_fdots_iter_poly_mesh(const MeshRenderData *mr,
const bool real_fdot = (mr->extract_type == MR_EXTRACT_MAPPED && mr->p_origindex &&
mr->p_origindex[mp_index] != ORIGINDEX_NONE);
- const bool subd_fdot = (!mr->use_subsurf_fdots ||
- (mr->mvert[ml->v].flag & ME_VERT_FACEDOT) != 0);
+ const bool subd_fdot = BLI_BITMAP_TEST(facedot_tags, ml->v);
edituv_facedot_add(data,
((mp->flag & ME_HIDE) != 0) || !real_fdot || !subd_fdot,
(mp->flag & ME_FACE_SEL) != 0,
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc
index 8db1660b9d0..4bf732caf0a 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_ibo_fdots.cc
@@ -5,9 +5,7 @@
* \ingroup draw
*/
-#include "BLI_vector.hh"
-
-#include "MEM_guardedalloc.h"
+#include "BLI_bitmap.h"
#include "extract_mesh.h"
@@ -46,13 +44,13 @@ static void extract_fdots_iter_poly_mesh(const MeshRenderData *mr,
{
GPUIndexBufBuilder *elb = static_cast<GPUIndexBufBuilder *>(_userdata);
if (mr->use_subsurf_fdots) {
- /* Check #ME_VERT_FACEDOT. */
+ const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags;
+
const MLoop *mloop = mr->mloop;
const int ml_index_end = mp->loopstart + mp->totloop;
for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) {
const MLoop *ml = &mloop[ml_index];
- const MVert *mv = &mr->mvert[ml->v];
- if ((mv->flag & ME_VERT_FACEDOT) && !(mr->use_hide && (mp->flag & ME_HIDE))) {
+ if (BLI_BITMAP_TEST(facedot_tags, ml->v) && !(mr->use_hide && (mp->flag & ME_HIDE))) {
GPU_indexbuf_set_point_vert(elb, mp_index, mp_index);
return;
}
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc
index 0a34cfa012d..c2b4d389b7c 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_pos.cc
@@ -5,6 +5,8 @@
* \ingroup draw
*/
+#include "BLI_bitmap.h"
+
#include "extract_mesh.h"
#include "draw_subdivision.h"
@@ -75,14 +77,14 @@ static void extract_fdots_pos_iter_poly_mesh(const MeshRenderData *mr,
const MVert *mvert = mr->mvert;
const MLoop *mloop = mr->mloop;
+ const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags;
const int ml_index_end = mp->loopstart + mp->totloop;
for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) {
const MLoop *ml = &mloop[ml_index];
if (mr->use_subsurf_fdots) {
- const MVert *mv = &mr->mvert[ml->v];
- if (mv->flag & ME_VERT_FACEDOT) {
- copy_v3_v3(center[mp_index], mv->co);
+ if (BLI_BITMAP_TEST(facedot_tags, ml->v)) {
+ copy_v3_v3(center[mp_index], mvert[ml->v].co);
break;
}
}
diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc
index 7a3c108b75a..26f0b07f676 100644
--- a/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc
+++ b/source/blender/draw/intern/mesh_extractors/extract_mesh_vbo_fdots_uv.cc
@@ -5,6 +5,8 @@
* \ingroup draw
*/
+#include "BLI_bitmap.h"
+
#include "extract_mesh.h"
namespace blender::draw {
@@ -72,13 +74,14 @@ static void extract_fdots_uv_iter_poly_mesh(const MeshRenderData *mr,
void *_data)
{
MeshExtract_FdotUV_Data *data = static_cast<MeshExtract_FdotUV_Data *>(_data);
+ const BLI_bitmap *facedot_tags = mr->me->runtime.subsurf_face_dot_tags;
+
const MLoop *mloop = mr->mloop;
const int ml_index_end = mp->loopstart + mp->totloop;
for (int ml_index = mp->loopstart; ml_index < ml_index_end; ml_index += 1) {
const MLoop *ml = &mloop[ml_index];
if (mr->use_subsurf_fdots) {
- const MVert *mv = &mr->mvert[ml->v];
- if (mv->flag & ME_VERT_FACEDOT) {
+ if (BLI_BITMAP_TEST(facedot_tags, ml->v)) {
copy_v2_v2(data->vbo_data[mp_index], data->uv_data[ml_index].uv);
}
}
diff --git a/source/blender/editors/animation/anim_channels_defines.c b/source/blender/editors/animation/anim_channels_defines.c
index eb7f8e8ad83..9722de0ce3c 100644
--- a/source/blender/editors/animation/anim_channels_defines.c
+++ b/source/blender/editors/animation/anim_channels_defines.c
@@ -5187,7 +5187,7 @@ void ANIM_channel_draw_widgets(const bContext *C,
}
/* Visibility toggle. */
if (acf->has_setting(ac, ale, ACHANNEL_SETTING_VISIBLE)) {
- /* For F-curves, add the extra space for the color bands. */
+ /* For F-Curves, add the extra space for the color bands. */
if (ELEM(ale->type, ANIMTYPE_FCURVE, ANIMTYPE_NLACURVE)) {
offset += GRAPH_ICON_VISIBILITY_OFFSET;
}
diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c
index d42efcd81e5..5b742ddf272 100644
--- a/source/blender/editors/animation/keyframing.c
+++ b/source/blender/editors/animation/keyframing.c
@@ -1206,8 +1206,13 @@ static float *get_keyframe_values(ReportList *reports,
anim_eval_context,
r_force_all,
*r_successful_remaps);
- get_keyframe_values_create_reports(
- reports, ptr, prop, index, *r_count, *r_force_all, *r_successful_remaps);
+ get_keyframe_values_create_reports(reports,
+ ptr,
+ prop,
+ index,
+ *r_count,
+ r_force_all ? *r_force_all : false,
+ *r_successful_remaps);
return values;
}
diff --git a/source/blender/editors/include/ED_armature.h b/source/blender/editors/include/ED_armature.h
index 01885911ac4..d969277fef5 100644
--- a/source/blender/editors/include/ED_armature.h
+++ b/source/blender/editors/include/ED_armature.h
@@ -363,7 +363,7 @@ void ED_mesh_deform_bind_callback(struct Object *object,
struct MeshDeformModifierData *mmd,
struct Mesh *cagemesh,
float *vertexcos,
- int totvert,
+ int verts_num,
float cagemat[4][4]);
/* Pose backups, pose_backup.c */
diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h
index 3100c135db4..54d67c50d5c 100644
--- a/source/blender/editors/include/ED_sculpt.h
+++ b/source/blender/editors/include/ED_sculpt.h
@@ -17,6 +17,9 @@ struct UndoType;
struct ViewContext;
struct bContext;
struct rcti;
+struct wmMsgSubscribeKey;
+struct wmMsgSubscribeValue;
+struct wmRegionMessageSubscribeParams;
/* sculpt.c */
diff --git a/source/blender/editors/io/io_obj.c b/source/blender/editors/io/io_obj.c
index 97f1e08fdff..beed4abd52b 100644
--- a/source/blender/editors/io/io_obj.c
+++ b/source/blender/editors/io/io_obj.c
@@ -378,6 +378,7 @@ static int wm_obj_import_exec(bContext *C, wmOperator *op)
import_params.clamp_size = RNA_float_get(op->ptr, "clamp_size");
import_params.forward_axis = RNA_enum_get(op->ptr, "forward_axis");
import_params.up_axis = RNA_enum_get(op->ptr, "up_axis");
+ import_params.validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes");
OBJ_import(C, &import_params);
@@ -388,8 +389,8 @@ static void ui_obj_import_settings(uiLayout *layout, PointerRNA *imfptr)
{
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
- uiLayout *box = uiLayoutBox(layout);
+ uiLayout *box = uiLayoutBox(layout);
uiItemL(box, IFACE_("Transform"), ICON_OBJECT_DATA);
uiLayout *col = uiLayoutColumn(box, false);
uiLayout *sub = uiLayoutColumn(col, false);
@@ -397,6 +398,11 @@ static void ui_obj_import_settings(uiLayout *layout, PointerRNA *imfptr)
sub = uiLayoutColumn(col, false);
uiItemR(sub, imfptr, "forward_axis", 0, IFACE_("Axis Forward"), ICON_NONE);
uiItemR(sub, imfptr, "up_axis", 0, IFACE_("Up"), ICON_NONE);
+
+ box = uiLayoutBox(layout);
+ uiItemL(box, IFACE_("Options"), ICON_EXPORT);
+ col = uiLayoutColumn(box, false);
+ uiItemR(col, imfptr, "validate_meshes", 0, NULL, ICON_NONE);
}
static void wm_obj_import_draw(bContext *C, wmOperator *op)
@@ -442,4 +448,9 @@ void WM_OT_obj_import(struct wmOperatorType *ot)
"Forward Axis",
"");
RNA_def_enum(ot->srna, "up_axis", io_obj_transform_axis_up, OBJ_AXIS_Y_UP, "Up Axis", "");
+ RNA_def_boolean(ot->srna,
+ "validate_meshes",
+ false,
+ "Validate Meshes",
+ "Check imported mesh objects for invalid data (slow)");
}
diff --git a/source/blender/editors/io/io_usd.c b/source/blender/editors/io/io_usd.c
index bff1bdd0ed8..e0616a0cec3 100644
--- a/source/blender/editors/io/io_usd.c
+++ b/source/blender/editors/io/io_usd.c
@@ -215,47 +215,43 @@ void WM_OT_usd_export(struct wmOperatorType *ot)
"selected_objects_only",
false,
"Selection Only",
- "Only selected objects are exported. Unselected parents of selected objects are "
+ "Only export selected objects. Unselected parents of selected objects are "
"exported as empty transform");
RNA_def_boolean(ot->srna,
"visible_objects_only",
true,
"Visible Only",
- "Only visible objects are exported. Invisible parents of exported objects are "
- "exported as empty transform");
+ "Only export visible objects. Invisible parents of exported objects are "
+ "exported as empty transforms");
- RNA_def_boolean(ot->srna,
- "export_animation",
- false,
- "Animation",
- "When checked, the render frame range is exported. When false, only the current "
- "frame is exported");
RNA_def_boolean(
- ot->srna, "export_hair", false, "Hair", "When checked, hair is exported as USD curves");
- RNA_def_boolean(ot->srna,
- "export_uvmaps",
- true,
- "UV Maps",
- "When checked, all UV maps of exported meshes are included in the export");
+ ot->srna,
+ "export_animation",
+ false,
+ "Animation",
+ "Export all frames in the render frame range, rather than only the current frame");
+ RNA_def_boolean(
+ ot->srna, "export_hair", false, "Hair", "Export hair particle systems as USD curves");
+ RNA_def_boolean(
+ ot->srna, "export_uvmaps", true, "UV Maps", "Include all mesh UV maps in the export");
RNA_def_boolean(ot->srna,
"export_normals",
true,
"Normals",
- "When checked, normals of exported meshes are included in the export");
+ "Include normals of exported meshes in the export");
RNA_def_boolean(ot->srna,
"export_materials",
true,
"Materials",
- "When checked, the viewport settings of materials are exported as USD preview "
- "materials, and material assignments are exported as geometry subsets");
+ "Export viewport settings of materials as USD preview materials, and export "
+ "material assignments as geometry subsets");
RNA_def_boolean(ot->srna,
"use_instancing",
false,
"Instancing",
- "When checked, instanced objects are exported as references in USD. "
- "When unchecked, instanced objects are exported as real objects");
+ "Export instanced objects as references in USD rather than real objects");
RNA_def_enum(ot->srna,
"evaluation_mode",
diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt
index 02feccc211a..97376a495c1 100644
--- a/source/blender/editors/object/CMakeLists.txt
+++ b/source/blender/editors/object/CMakeLists.txt
@@ -43,7 +43,7 @@ set(SRC
object_gpencil_modifier.c
object_hook.c
object_modes.c
- object_modifier.c
+ object_modifier.cc
object_ops.c
object_random.c
object_relations.c
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index eae08e89104..fb61200be9d 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -201,6 +201,7 @@ void OBJECT_OT_skin_armature_create(struct wmOperatorType *ot);
void OBJECT_OT_laplaciandeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_surfacedeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_geometry_nodes_input_attribute_toggle(struct wmOperatorType *ot);
+void OBJECT_OT_geometry_node_tree_copy_assign(struct wmOperatorType *ot);
/* object_gpencil_modifiers.c */
diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.cc
index 6a230669056..f7543c97903 100644
--- a/source/blender/editors/object/object_modifier.c
+++ b/source/blender/editors/object/object_modifier.cc
@@ -5,9 +5,9 @@
* \ingroup edobj
*/
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
#include "MEM_guardedalloc.h"
@@ -142,19 +142,19 @@ static void object_force_modifier_bind_simple_options(Depsgraph *depsgraph,
ModifierData *ED_object_modifier_add(
ReportList *reports, Main *bmain, Scene *scene, Object *ob, const char *name, int type)
{
- ModifierData *md = NULL, *new_md = NULL;
- const ModifierTypeInfo *mti = BKE_modifier_get_info(type);
+ ModifierData *md = nullptr, *new_md = nullptr;
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)type);
/* Check compatibility of modifier [T25291, T50373]. */
if (!BKE_object_support_modifier_type_check(ob, type)) {
BKE_reportf(reports, RPT_WARNING, "Modifiers cannot be added to object '%s'", ob->id.name + 2);
- return NULL;
+ return nullptr;
}
if (mti->flags & eModifierTypeFlag_Single) {
- if (BKE_modifiers_findby_type(ob, type)) {
+ if (BKE_modifiers_findby_type(ob, (ModifierType)type)) {
BKE_report(reports, RPT_WARNING, "Only one modifier of this type is allowed");
- return NULL;
+ return nullptr;
}
}
@@ -169,9 +169,10 @@ ModifierData *ED_object_modifier_add(
new_md = BKE_modifier_new(type);
if (mti->flags & eModifierTypeFlag_RequiresOriginalData) {
- md = ob->modifiers.first;
+ md = static_cast<ModifierData *>(ob->modifiers.first);
- while (md && BKE_modifier_get_info(md->type)->type == eModifierTypeType_OnlyDeform) {
+ while (md &&
+ BKE_modifier_get_info((ModifierType)md->type)->type == eModifierTypeType_OnlyDeform) {
md = md->next;
}
@@ -217,7 +218,7 @@ ModifierData *ED_object_modifier_add(
}
else if (type == eModifierType_Skin) {
/* ensure skin-node customdata exists */
- BKE_mesh_ensure_skin_customdata(ob->data);
+ BKE_mesh_ensure_skin_customdata(static_cast<Mesh *>(ob->data));
}
}
@@ -248,7 +249,7 @@ bool ED_object_iter_other(Main *bmain,
bool (*callback)(Object *ob, void *callback_data),
void *callback_data)
{
- ID *ob_data_id = orig_ob->data;
+ ID *ob_data_id = static_cast<ID *>(orig_ob->data);
int users = ob_data_id->us;
if (ob_data_id->flag & LIB_FAKEUSER) {
@@ -260,7 +261,8 @@ bool ED_object_iter_other(Main *bmain,
Object *ob;
int totfound = include_orig ? 0 : 1;
- for (ob = bmain->objects.first; ob && totfound < users; ob = ob->id.next) {
+ for (ob = static_cast<Object *>(bmain->objects.first); ob && totfound < users;
+ ob = reinterpret_cast<Object *>(ob->id.next)) {
if (((ob != orig_ob) || include_orig) && (ob->data == orig_ob->data)) {
if (callback(ob, callback_data)) {
return true;
@@ -281,7 +283,7 @@ static bool object_has_modifier_cb(Object *ob, void *data)
{
ModifierType type = *((ModifierType *)data);
- return object_has_modifier(ob, NULL, type);
+ return object_has_modifier(ob, nullptr, type);
}
bool ED_object_multires_update_totlevels_cb(Object *ob, void *totlevel_v)
@@ -342,7 +344,7 @@ static bool object_modifier_remove(
else if (md->type == eModifierType_Multires) {
/* Delete MDisps layer if not used by another multires modifier */
if (object_modifier_safe_to_delete(bmain, ob, md, eModifierType_Multires)) {
- multires_customdata_delete(ob->data);
+ multires_customdata_delete(static_cast<Mesh *>(ob->data));
}
}
else if (md->type == eModifierType_Skin) {
@@ -384,7 +386,7 @@ bool ED_object_modifier_remove(
void ED_object_modifier_clear(Main *bmain, Scene *scene, Object *ob)
{
- ModifierData *md = ob->modifiers.first;
+ ModifierData *md = static_cast<ModifierData *>(ob->modifiers.first);
bool sort_depsgraph = false;
if (!md) {
@@ -406,10 +408,10 @@ void ED_object_modifier_clear(Main *bmain, Scene *scene, Object *ob)
bool ED_object_modifier_move_up(ReportList *reports, Object *ob, ModifierData *md)
{
if (md->prev) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (mti->type != eModifierTypeType_OnlyDeform) {
- const ModifierTypeInfo *nmti = BKE_modifier_get_info(md->prev->type);
+ const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md->prev->type);
if (nmti->flags & eModifierTypeFlag_RequiresOriginalData) {
BKE_report(reports, RPT_WARNING, "Cannot move above a modifier requiring original data");
@@ -430,10 +432,10 @@ bool ED_object_modifier_move_up(ReportList *reports, Object *ob, ModifierData *m
bool ED_object_modifier_move_down(ReportList *reports, Object *ob, ModifierData *md)
{
if (md->next) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
if (mti->flags & eModifierTypeFlag_RequiresOriginalData) {
- const ModifierTypeInfo *nmti = BKE_modifier_get_info(md->next->type);
+ const ModifierTypeInfo *nmti = BKE_modifier_get_info((ModifierType)md->next->type);
if (nmti->type != eModifierTypeType_OnlyDeform) {
BKE_report(reports, RPT_WARNING, "Cannot move beyond a non-deforming modifier");
@@ -456,7 +458,7 @@ bool ED_object_modifier_move_to_index(ReportList *reports,
ModifierData *md,
const int index)
{
- BLI_assert(md != NULL);
+ BLI_assert(md != nullptr);
BLI_assert(index >= 0);
if (index >= BLI_listbase_count(&ob->modifiers)) {
BKE_report(reports, RPT_WARNING, "Cannot move modifier beyond the end of the stack");
@@ -534,7 +536,7 @@ bool ED_object_modifier_convert(ReportList *UNUSED(reports),
return false;
}
ParticleSystem *psys_eval = psys_eval_get(depsgraph, ob, psys_orig);
- if (psys_eval->pathcache == NULL) {
+ if (psys_eval->pathcache == nullptr) {
return false;
}
@@ -572,15 +574,15 @@ bool ED_object_modifier_convert(ReportList *UNUSED(reports),
}
/* add new mesh */
- Object *obn = BKE_object_add(bmain, view_layer, OB_MESH, NULL);
- Mesh *me = obn->data;
+ Object *obn = BKE_object_add(bmain, view_layer, OB_MESH, nullptr);
+ Mesh *me = static_cast<Mesh *>(obn->data);
me->totvert = verts_num;
me->totedge = edges_num;
- me->mvert = CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, verts_num);
- me->medge = CustomData_add_layer(&me->edata, CD_MEDGE, CD_CALLOC, NULL, edges_num);
- me->mface = CustomData_add_layer(&me->fdata, CD_MFACE, CD_CALLOC, NULL, 0);
+ me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, nullptr, verts_num);
+ me->medge = (MEdge *)CustomData_add_layer(&me->edata, CD_MEDGE, CD_CALLOC, nullptr, edges_num);
+ me->mface = (MFace *)CustomData_add_layer(&me->fdata, CD_MFACE, CD_CALLOC, nullptr, 0);
MVert *mvert = me->mvert;
MEdge *medge = me->medge;
@@ -650,9 +652,9 @@ static bool modifier_apply_shape(Main *bmain,
Object *ob,
ModifierData *md_eval)
{
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md_eval->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_eval->type);
- if (mti->isDisabled && mti->isDisabled(scene, md_eval, 0)) {
+ if (mti->isDisabled && mti->isDisabled(scene, md_eval, false)) {
BKE_report(reports, RPT_ERROR, "Modifier is disabled, skipping apply");
return false;
}
@@ -668,7 +670,7 @@ static bool modifier_apply_shape(Main *bmain,
* we can look into supporting them. */
if (ob->type == OB_MESH) {
- Mesh *me = ob->data;
+ Mesh *me = static_cast<Mesh *>(ob->data);
Key *key = me->key;
if (!BKE_modifier_is_same_topology(md_eval) || mti->type == eModifierTypeType_NonGeometrical) {
@@ -683,19 +685,19 @@ static bool modifier_apply_shape(Main *bmain,
return false;
}
- if (key == NULL) {
+ if (key == nullptr) {
key = me->key = BKE_key_add(bmain, (ID *)me);
key->type = KEY_RELATIVE;
/* if that was the first key block added, then it was the basis.
* Initialize it with the mesh, and add another for the modifier */
- KeyBlock *kb = BKE_keyblock_add(key, NULL);
+ KeyBlock *kb = BKE_keyblock_add(key, nullptr);
BKE_keyblock_convert_from_mesh(me, key, kb);
}
KeyBlock *kb = BKE_keyblock_add(key, md_eval->name);
BKE_mesh_nomain_to_meshkey(mesh_applied, me, kb);
- BKE_id_free(NULL, mesh_applied);
+ BKE_id_free(nullptr, mesh_applied);
}
else {
/* TODO: implement for curves, point clouds and volumes. */
@@ -708,15 +710,15 @@ static bool modifier_apply_shape(Main *bmain,
static bool modifier_apply_obdata(
ReportList *reports, Depsgraph *depsgraph, Scene *scene, Object *ob, ModifierData *md_eval)
{
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md_eval->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_eval->type);
- if (mti->isDisabled && mti->isDisabled(scene, md_eval, 0)) {
+ if (mti->isDisabled && mti->isDisabled(scene, md_eval, false)) {
BKE_report(reports, RPT_ERROR, "Modifier is disabled, skipping apply");
return false;
}
if (ob->type == OB_MESH) {
- Mesh *me = ob->data;
+ Mesh *me = static_cast<Mesh *>(ob->data);
MultiresModifierData *mmd = find_multires_modifier_before(scene, md_eval);
if (me->key && mti->type != eModifierTypeType_NonGeometrical) {
@@ -757,9 +759,9 @@ static bool modifier_apply_obdata(
}
else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF)) {
Object *object_eval = DEG_get_evaluated_object(depsgraph, ob);
- Curve *curve = ob->data;
- Curve *curve_eval = (Curve *)object_eval->data;
- ModifierEvalContext mectx = {depsgraph, object_eval, 0};
+ Curve *curve = static_cast<Curve *>(ob->data);
+ Curve *curve_eval = static_cast<Curve *>(object_eval->data);
+ ModifierEvalContext mectx = {depsgraph, object_eval, (ModifierApplyFlag)0};
if (ELEM(mti->type, eModifierTypeType_Constructive, eModifierTypeType_Nonconstructive)) {
BKE_report(
@@ -773,7 +775,7 @@ static bool modifier_apply_obdata(
int verts_num;
float(*vertexCos)[3] = BKE_curve_nurbs_vert_coords_alloc(&curve_eval->nurb, &verts_num);
- mti->deformVerts(md_eval, &mectx, NULL, vertexCos, verts_num);
+ mti->deformVerts(md_eval, &mectx, nullptr, vertexCos, verts_num);
BKE_curve_nurbs_vert_coords_apply(&curve->nurb, vertexCos, false);
MEM_freeN(vertexCos);
@@ -782,8 +784,8 @@ static bool modifier_apply_obdata(
}
else if (ob->type == OB_LATTICE) {
Object *object_eval = DEG_get_evaluated_object(depsgraph, ob);
- Lattice *lattice = ob->data;
- ModifierEvalContext mectx = {depsgraph, object_eval, 0};
+ Lattice *lattice = static_cast<Lattice *>(ob->data);
+ ModifierEvalContext mectx = {depsgraph, object_eval, (ModifierApplyFlag)0};
if (ELEM(mti->type, eModifierTypeType_Constructive, eModifierTypeType_Nonconstructive)) {
BKE_report(reports, RPT_ERROR, "Constructive modifiers cannot be applied");
@@ -792,7 +794,7 @@ static bool modifier_apply_obdata(
int verts_num;
float(*vertexCos)[3] = BKE_lattice_vert_coords_alloc(lattice, &verts_num);
- mti->deformVerts(md_eval, &mectx, NULL, vertexCos, verts_num);
+ mti->deformVerts(md_eval, &mectx, nullptr, vertexCos, verts_num);
BKE_lattice_vert_coords_apply(lattice, vertexCos);
MEM_freeN(vertexCos);
@@ -918,7 +920,7 @@ static int modifier_add_exec(bContext *C, wmOperator *op)
Object *ob = ED_object_active_context(C);
int type = RNA_enum_get(op->ptr, "type");
- if (!ED_object_modifier_add(op->reports, bmain, scene, ob, NULL, type)) {
+ if (!ED_object_modifier_add(op->reports, bmain, scene, ob, nullptr, type)) {
return OPERATOR_CANCELLED;
}
@@ -938,15 +940,15 @@ static const EnumPropertyItem *modifier_add_itemf(bContext *C,
return rna_enum_object_modifier_type_items;
}
- EnumPropertyItem *items = NULL;
+ EnumPropertyItem *items = nullptr;
int totitem = 0;
- const EnumPropertyItem *group_item = NULL;
+ const EnumPropertyItem *group_item = nullptr;
for (int a = 0; rna_enum_object_modifier_type_items[a].identifier; a++) {
const EnumPropertyItem *md_item = &rna_enum_object_modifier_type_items[a];
if (md_item->identifier[0]) {
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md_item->value);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md_item->value);
if (mti->flags & eModifierTypeFlag_NoUserAdd) {
continue;
@@ -963,7 +965,7 @@ static const EnumPropertyItem *modifier_add_itemf(bContext *C,
if (group_item) {
RNA_enum_item_add(&items, &totitem, group_item);
- group_item = NULL;
+ group_item = nullptr;
}
RNA_enum_item_add(&items, &totitem, md_item);
@@ -1016,9 +1018,9 @@ bool edit_modifier_poll_generic(bContext *C,
Main *bmain = CTX_data_main(C);
PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", rna_type);
Object *ob = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C);
- ModifierData *mod = ptr.data; /* May be NULL. */
+ ModifierData *mod = static_cast<ModifierData *>(ptr.data); /* May be nullptr. */
- if (mod == NULL && ob != NULL) {
+ if (mod == nullptr && ob != nullptr) {
mod = BKE_object_active_modifier(ob);
}
@@ -1038,7 +1040,7 @@ bool edit_modifier_poll_generic(bContext *C,
return false;
}
- if (!is_editmode_allowed && CTX_data_edit_object(C) != NULL) {
+ if (!is_editmode_allowed && CTX_data_edit_object(C) != nullptr) {
CTX_wm_operator_poll_msg_set(C, "This modifier operation is not allowed from Edit mode");
return false;
}
@@ -1061,7 +1063,7 @@ static bool edit_modifier_liboverride_allowed_poll(bContext *C)
void edit_modifier_properties(wmOperatorType *ot)
{
PropertyRNA *prop = RNA_def_string(
- ot->srna, "modifier", NULL, MAX_NAME, "Modifier", "Name of the modifier to edit");
+ ot->srna, "modifier", nullptr, MAX_NAME, "Modifier", "Name of the modifier to edit");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
@@ -1087,8 +1089,8 @@ bool edit_modifier_invoke_properties(bContext *C, wmOperator *op)
}
PointerRNA ctx_ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier);
- if (ctx_ptr.data != NULL) {
- ModifierData *md = ctx_ptr.data;
+ if (ctx_ptr.data != nullptr) {
+ ModifierData *md = static_cast<ModifierData *>(ctx_ptr.data);
RNA_string_set(op->ptr, "modifier", md->name);
return true;
}
@@ -1112,14 +1114,14 @@ static bool edit_modifier_invoke_properties_with_hover(bContext *C,
/* Note that the context pointer is *not* the active modifier, it is set in UI layouts. */
PointerRNA ctx_ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier);
- if (ctx_ptr.data != NULL) {
- ModifierData *md = ctx_ptr.data;
+ if (ctx_ptr.data != nullptr) {
+ ModifierData *md = static_cast<ModifierData *>(ctx_ptr.data);
RNA_string_set(op->ptr, "modifier", md->name);
return true;
}
PointerRNA *panel_ptr = UI_region_panel_custom_data_under_cursor(C, event);
- if (panel_ptr == NULL || RNA_pointer_is_null(panel_ptr)) {
+ if (panel_ptr == nullptr || RNA_pointer_is_null(panel_ptr)) {
*r_retval = OPERATOR_CANCELLED;
return false;
}
@@ -1132,7 +1134,7 @@ static bool edit_modifier_invoke_properties_with_hover(bContext *C,
return false;
}
- const ModifierData *md = panel_ptr->data;
+ const ModifierData *md = static_cast<const ModifierData *>(panel_ptr->data);
RNA_string_set(op->ptr, "modifier", md->name);
return true;
}
@@ -1145,7 +1147,7 @@ ModifierData *edit_modifier_property_get(wmOperator *op, Object *ob, int type)
ModifierData *md = BKE_modifiers_findby_name(ob, modifier_name);
if (md && type != 0 && md->type != type) {
- md = NULL;
+ md = nullptr;
}
return md;
@@ -1166,7 +1168,7 @@ static int modifier_remove_exec(bContext *C, wmOperator *op)
ModifierData *md = edit_modifier_property_get(op, ob, 0);
int mode_orig = ob->mode;
- if (md == NULL) {
+ if (md == nullptr) {
return OPERATOR_CANCELLED;
}
@@ -1184,7 +1186,7 @@ static int modifier_remove_exec(bContext *C, wmOperator *op)
if (mode_orig & OB_MODE_PARTICLE_EDIT) {
if ((ob->mode & OB_MODE_PARTICLE_EDIT) == 0) {
if (ob == OBACT(view_layer)) {
- WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, NULL);
+ WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_MODE_OBJECT, nullptr);
}
}
}
@@ -1371,14 +1373,14 @@ static bool modifier_apply_poll(bContext *C)
Scene *scene = CTX_data_scene(C);
PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier);
- Object *ob = (ptr.owner_id != NULL) ? (Object *)ptr.owner_id : ED_object_active_context(C);
- ModifierData *md = ptr.data; /* May be NULL. */
+ Object *ob = (ptr.owner_id != nullptr) ? (Object *)ptr.owner_id : ED_object_active_context(C);
+ ModifierData *md = static_cast<ModifierData *>(ptr.data); /* May be nullptr. */
- if (ID_IS_OVERRIDE_LIBRARY(ob) || ((ob->data != NULL) && ID_IS_OVERRIDE_LIBRARY(ob->data))) {
+ if (ID_IS_OVERRIDE_LIBRARY(ob) || ((ob->data != nullptr) && ID_IS_OVERRIDE_LIBRARY(ob->data))) {
CTX_wm_operator_poll_msg_set(C, "Modifiers cannot be applied on override data");
return false;
}
- if (md != NULL) {
+ if (md != nullptr) {
if ((ob->mode & OB_MODE_SCULPT) && (find_multires_modifier_before(scene, md)) &&
(BKE_modifier_is_same_topology(md) == false)) {
CTX_wm_operator_poll_msg_set(
@@ -1399,14 +1401,14 @@ static int modifier_apply_exec_ex(bContext *C, wmOperator *op, int apply_as, boo
const bool do_report = RNA_boolean_get(op->ptr, "report");
const bool do_single_user = RNA_boolean_get(op->ptr, "single_user");
- if (md == NULL) {
+ if (md == nullptr) {
return OPERATOR_CANCELLED;
}
if (do_single_user && ID_REAL_USERS(ob->data) > 1) {
ED_object_single_obdata_user(bmain, scene, ob);
BKE_main_id_newptr_and_tag_clear(bmain);
- WM_event_add_notifier(C, NC_WINDOW, NULL);
+ WM_event_add_notifier(C, NC_WINDOW, nullptr);
DEG_relations_tag_update(bmain);
}
@@ -1447,9 +1449,9 @@ static int modifier_apply_invoke(bContext *C, wmOperator *op, const wmEvent *eve
int retval;
if (edit_modifier_invoke_properties_with_hover(C, op, event, &retval)) {
PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier);
- Object *ob = (ptr.owner_id != NULL) ? (Object *)ptr.owner_id : ED_object_active_context(C);
+ Object *ob = (ptr.owner_id != nullptr) ? (Object *)ptr.owner_id : ED_object_active_context(C);
- if ((ob->data != NULL) && ID_REAL_USERS(ob->data) > 1) {
+ if ((ob->data != nullptr) && ID_REAL_USERS(ob->data) > 1) {
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "single_user");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_property_boolean_set(op->ptr, prop, true);
@@ -1485,7 +1487,7 @@ void OBJECT_OT_modifier_apply(wmOperatorType *ot)
false,
"Make Data Single User",
"Make the object's data single user if needed");
- RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+ RNA_def_property_flag(prop, (PropertyFlag)(PROP_HIDDEN | PROP_SKIP_SAVE));
}
/** \} */
@@ -1525,7 +1527,7 @@ static char *modifier_apply_as_shapekey_get_description(struct bContext *UNUSED(
return BLI_strdup(TIP_("Apply modifier as a new shapekey and keep it in the stack"));
}
- return NULL;
+ return nullptr;
}
void OBJECT_OT_modifier_apply_as_shapekey(wmOperatorType *ot)
@@ -1703,7 +1705,7 @@ static int modifier_copy_to_selected_exec(bContext *C, wmOperator *op)
}
int num_copied = 0;
- const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info((ModifierType)md->type);
CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
if (ob == obact) {
@@ -1721,7 +1723,7 @@ static int modifier_copy_to_selected_exec(bContext *C, wmOperator *op)
}
if (mti->flags & eModifierTypeFlag_Single) {
- if (BKE_modifiers_findby_type(ob, md->type)) {
+ if (BKE_modifiers_findby_type(ob, (ModifierType)md->type)) {
BKE_reportf(op->reports,
RPT_WARNING,
"Modifier can only be added once to object '%s'",
@@ -1768,12 +1770,12 @@ static bool modifier_copy_to_selected_poll(bContext *C)
{
PointerRNA ptr = CTX_data_pointer_get_type(C, "modifier", &RNA_Modifier);
Object *obact = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C);
- ModifierData *md = ptr.data;
+ ModifierData *md = static_cast<ModifierData *>(ptr.data);
/* This just mirrors the check in #BKE_object_copy_modifier,
* but there is no reasoning for it there. */
if (md && ELEM(md->type, eModifierType_Hook, eModifierType_Collision)) {
- CTX_wm_operator_poll_msg_set(C, "Not supported for \"Collision\" or \"Hook\" modifiers");
+ CTX_wm_operator_poll_msg_set(C, R"(Not supported for "Collision" or "Hook" modifiers)");
return false;
}
@@ -1909,7 +1911,7 @@ static EnumPropertyItem prop_multires_subdivide_mode_type[] = {
0,
"Linear",
"Create a new level using linear interpolation of the sculpted displacement"},
- {0, NULL, 0, NULL, NULL},
+ {0, nullptr, 0, nullptr, nullptr},
};
static int multires_subdivide_exec(bContext *C, wmOperator *op)
@@ -1978,7 +1980,7 @@ void OBJECT_OT_multires_subdivide(wmOperatorType *ot)
static int multires_reshape_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
- Object *ob = ED_object_active_context(C), *secondob = NULL;
+ Object *ob = ED_object_active_context(C), *secondob = nullptr;
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
op, ob, eModifierType_Multires);
@@ -2048,7 +2050,7 @@ static int multires_external_save_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Object *ob = ED_object_active_context(C);
- Mesh *me = (ob) ? ob->data : op->customdata;
+ Mesh *me = (ob) ? static_cast<Mesh *>(ob->data) : static_cast<Mesh *>(op->customdata);
char path[FILE_MAX];
const bool relative = RNA_boolean_get(op->ptr, "relative_path");
@@ -2075,7 +2077,7 @@ static int multires_external_save_exec(bContext *C, wmOperator *op)
static int multires_external_save_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
Object *ob = ED_object_active_context(C);
- Mesh *me = ob->data;
+ Mesh *me = static_cast<Mesh *>(ob->data);
char path[FILE_MAX];
if (!edit_modifier_invoke_properties(C, op)) {
@@ -2140,7 +2142,7 @@ void OBJECT_OT_multires_external_save(wmOperatorType *ot)
static int multires_external_pack_exec(bContext *C, wmOperator *UNUSED(op))
{
Object *ob = ED_object_active_context(C);
- Mesh *me = ob->data;
+ Mesh *me = static_cast<Mesh *>(ob->data);
if (!CustomData_external_test(&me->ldata, CD_MDISPS)) {
return OPERATOR_CANCELLED;
@@ -2334,7 +2336,7 @@ void OBJECT_OT_multires_rebuild_subdiv(wmOperatorType *ot)
static void modifier_skin_customdata_delete(Object *ob)
{
- Mesh *me = ob->data;
+ Mesh *me = static_cast<Mesh *>(ob->data);
BMEditMesh *em = me->edit_mesh;
if (em) {
@@ -2353,7 +2355,7 @@ static bool skin_poll(bContext *C)
static bool skin_edit_poll(bContext *C)
{
Object *ob = CTX_data_edit_object(C);
- return (ob != NULL &&
+ return (ob != nullptr &&
edit_modifier_poll_generic(C, &RNA_SkinModifier, (1 << OB_MESH), true, false) &&
!ID_IS_OVERRIDE_LIBRARY(ob) && !ID_IS_OVERRIDE_LIBRARY(ob->data));
}
@@ -2367,7 +2369,7 @@ static void skin_root_clear(BMVert *bm_vert, GSet *visited, const int cd_vert_sk
BMVert *v2 = BM_edge_other_vert(bm_edge, bm_vert);
if (BLI_gset_add(visited, v2)) {
- MVertSkin *vs = BM_ELEM_CD_GET_VOID_P(v2, cd_vert_skin_offset);
+ MVertSkin *vs = static_cast<MVertSkin *>(BM_ELEM_CD_GET_VOID_P(v2, cd_vert_skin_offset));
/* clear vertex root flag and add to visited set */
vs->flag &= ~MVERT_SKIN_ROOT;
@@ -2385,7 +2387,7 @@ static int skin_root_mark_exec(bContext *C, wmOperator *UNUSED(op))
GSet *visited = BLI_gset_ptr_new(__func__);
- BKE_mesh_ensure_skin_customdata(ob->data);
+ BKE_mesh_ensure_skin_customdata(static_cast<Mesh *>(ob->data));
const int cd_vert_skin_offset = CustomData_get_offset(&bm->vdata, CD_MVERT_SKIN);
@@ -2393,7 +2395,8 @@ static int skin_root_mark_exec(bContext *C, wmOperator *UNUSED(op))
BMIter bm_iter;
BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT) && BLI_gset_add(visited, bm_vert)) {
- MVertSkin *vs = BM_ELEM_CD_GET_VOID_P(bm_vert, cd_vert_skin_offset);
+ MVertSkin *vs = static_cast<MVertSkin *>(
+ BM_ELEM_CD_GET_VOID_P(bm_vert, cd_vert_skin_offset));
/* mark vertex as root and add to visited set */
vs->flag |= MVERT_SKIN_ROOT;
@@ -2403,7 +2406,7 @@ static int skin_root_mark_exec(bContext *C, wmOperator *UNUSED(op))
}
}
- BLI_gset_free(visited, NULL);
+ BLI_gset_free(visited, nullptr);
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
@@ -2424,17 +2427,17 @@ void OBJECT_OT_skin_root_mark(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
-typedef enum {
+enum SkinLooseAction {
SKIN_LOOSE_MARK,
SKIN_LOOSE_CLEAR,
-} SkinLooseAction;
+};
static int skin_loose_mark_clear_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(ob);
BMesh *bm = em->bm;
- SkinLooseAction action = RNA_enum_get(op->ptr, "action");
+ SkinLooseAction action = static_cast<SkinLooseAction>(RNA_enum_get(op->ptr, "action"));
if (!CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) {
return OPERATOR_CANCELLED;
@@ -2444,7 +2447,8 @@ static int skin_loose_mark_clear_exec(bContext *C, wmOperator *op)
BMIter bm_iter;
BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT)) {
- MVertSkin *vs = CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN);
+ MVertSkin *vs = static_cast<MVertSkin *>(
+ CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN));
switch (action) {
case SKIN_LOOSE_MARK:
@@ -2468,7 +2472,7 @@ void OBJECT_OT_skin_loose_mark_clear(wmOperatorType *ot)
static const EnumPropertyItem action_items[] = {
{SKIN_LOOSE_MARK, "MARK", 0, "Mark", "Mark selected vertices as loose"},
{SKIN_LOOSE_CLEAR, "CLEAR", 0, "Clear", "Set selected vertices as not loose"},
- {0, NULL, 0, NULL, NULL},
+ {0, nullptr, 0, nullptr, nullptr},
};
ot->name = "Skin Mark/Clear Loose";
@@ -2481,7 +2485,7 @@ void OBJECT_OT_skin_loose_mark_clear(wmOperatorType *ot)
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
- RNA_def_enum(ot->srna, "action", action_items, SKIN_LOOSE_MARK, "Action", NULL);
+ RNA_def_enum(ot->srna, "action", action_items, SKIN_LOOSE_MARK, "Action", nullptr);
}
static int skin_radii_equalize_exec(bContext *C, wmOperator *UNUSED(op))
@@ -2498,7 +2502,8 @@ static int skin_radii_equalize_exec(bContext *C, wmOperator *UNUSED(op))
BMIter bm_iter;
BM_ITER_MESH (bm_vert, &bm_iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(bm_vert, BM_ELEM_SELECT)) {
- MVertSkin *vs = CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN);
+ MVertSkin *vs = static_cast<MVertSkin *>(
+ CustomData_bmesh_get(&bm->vdata, bm_vert->head.data, CD_MVERT_SKIN));
float avg = (vs->radius[0] + vs->radius[1]) * 0.5f;
vs->radius[0] = vs->radius[1] = avg;
@@ -2548,7 +2553,7 @@ static void skin_armature_bone_create(Object *skin_ob,
EditBone *bone = ED_armature_ebone_add(arm, "Bone");
bone->parent = parent_bone;
- if (parent_bone != NULL) {
+ if (parent_bone != nullptr) {
bone->flag |= BONE_CONNECTED;
}
@@ -2559,7 +2564,7 @@ static void skin_armature_bone_create(Object *skin_ob,
/* add bDeformGroup */
bDeformGroup *dg = BKE_object_defgroup_add_name(skin_ob, bone->name);
- if (dg != NULL) {
+ if (dg != nullptr) {
ED_vgroup_vert_add(skin_ob, dg, parent_v, 1, WEIGHT_REPLACE);
ED_vgroup_vert_add(skin_ob, dg, v, 1, WEIGHT_REPLACE);
}
@@ -2570,7 +2575,7 @@ static void skin_armature_bone_create(Object *skin_ob,
static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain, Object *skin_ob)
{
- Mesh *me = skin_ob->data;
+ Mesh *me = static_cast<Mesh *>(skin_ob->data);
Scene *scene_eval = DEG_get_evaluated_scene(depsgraph);
Object *ob_eval = DEG_get_evaluated_object(depsgraph, skin_ob);
@@ -2579,18 +2584,19 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain,
MVert *mvert = me_eval_deform->mvert;
/* add vertex weights to original mesh */
- CustomData_add_layer(&me->vdata, CD_MDEFORMVERT, CD_CALLOC, NULL, me->totvert);
+ CustomData_add_layer(&me->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, me->totvert);
ViewLayer *view_layer = DEG_get_input_view_layer(depsgraph);
- Object *arm_ob = BKE_object_add(bmain, view_layer, OB_ARMATURE, NULL);
+ Object *arm_ob = BKE_object_add(bmain, view_layer, OB_ARMATURE, nullptr);
BKE_object_transform_copy(arm_ob, skin_ob);
- bArmature *arm = arm_ob->data;
+ bArmature *arm = static_cast<bArmature *>(arm_ob->data);
arm->layer = 1;
arm_ob->dtx |= OB_DRAW_IN_FRONT;
arm->drawtype = ARM_LINE;
- arm->edbo = MEM_callocN(sizeof(ListBase), "edbo armature");
+ arm->edbo = MEM_cnew<ListBase>("edbo armature");
- MVertSkin *mvert_skin = CustomData_get_layer(&me->vdata, CD_MVERT_SKIN);
+ MVertSkin *mvert_skin = static_cast<MVertSkin *>(
+ CustomData_get_layer(&me->vdata, CD_MVERT_SKIN));
int *emap_mem;
MeshElemMap *emap;
BKE_mesh_vert_edge_map_create(&emap, &emap_mem, me->medge, me->totvert, me->totedge);
@@ -2601,7 +2607,7 @@ static Object *modifier_skin_armature_create(Depsgraph *depsgraph, Main *bmain,
* edit-armature functions to convert back to regular bones */
for (int v = 0; v < me->totvert; v++) {
if (mvert_skin[v].flag & MVERT_SKIN_ROOT) {
- EditBone *bone = NULL;
+ EditBone *bone = nullptr;
/* Unless the skin root has just one adjacent edge, create
* a fake root bone (have it going off in the Y direction
@@ -2637,7 +2643,7 @@ static int skin_armature_create_exec(bContext *C, wmOperator *op)
Main *bmain = CTX_data_main(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *ob = CTX_data_active_object(C);
- Mesh *me = ob->data;
+ Mesh *me = static_cast<Mesh *>(ob->data);
ModifierData *skin_md;
if (!CustomData_has_layer(&me->vdata, CD_MVERT_SKIN)) {
@@ -2716,7 +2722,7 @@ static int correctivesmooth_bind_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
- const bool is_bind = (csmd->bind_coords != NULL);
+ const bool is_bind = (csmd->bind_coords != nullptr);
MEM_SAFE_FREE(csmd->bind_coords);
MEM_SAFE_FREE(csmd->delta_cache.deltas);
@@ -2785,11 +2791,11 @@ static int meshdeform_bind_exec(bContext *C, wmOperator *op)
MeshDeformModifierData *mmd = (MeshDeformModifierData *)edit_modifier_property_get(
op, ob, eModifierType_MeshDeform);
- if (mmd == NULL) {
+ if (mmd == nullptr) {
return OPERATOR_CANCELLED;
}
- if (mmd->bindcagecos != NULL) {
+ if (mmd->bindcagecos != nullptr) {
MEM_SAFE_FREE(mmd->bindcagecos);
MEM_SAFE_FREE(mmd->dyngrid);
MEM_SAFE_FREE(mmd->dyninfluences);
@@ -2809,7 +2815,7 @@ static int meshdeform_bind_exec(bContext *C, wmOperator *op)
depsgraph, ob, &mmd->modifier);
mmd_eval->bindfunc = ED_mesh_deform_bind_callback;
object_force_modifier_bind_simple_options(depsgraph, ob, &mmd->modifier);
- mmd_eval->bindfunc = NULL;
+ mmd_eval->bindfunc = nullptr;
}
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
@@ -2905,7 +2911,7 @@ static bool ocean_bake_poll(bContext *C)
return edit_modifier_poll_generic(C, &RNA_OceanModifier, 0, true, false);
}
-typedef struct OceanBakeJob {
+struct OceanBakeJob {
/* from wmJob */
struct Object *owner;
short *stop, *do_update;
@@ -2914,12 +2920,12 @@ typedef struct OceanBakeJob {
struct OceanCache *och;
struct Ocean *ocean;
struct OceanModifierData *omd;
-} OceanBakeJob;
+};
static void oceanbake_free(void *customdata)
{
- OceanBakeJob *oj = customdata;
- MEM_freeN(oj);
+ OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata);
+ MEM_delete(oj);
}
/* called by oceanbake, only to check job 'stop' value */
@@ -2937,7 +2943,7 @@ static int oceanbake_breakjob(void *UNUSED(customdata))
/* called by oceanbake, wmJob sends notifier */
static void oceanbake_update(void *customdata, float progress, int *cancel)
{
- OceanBakeJob *oj = customdata;
+ OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata);
if (oceanbake_breakjob(oj)) {
*cancel = 1;
@@ -2949,7 +2955,7 @@ static void oceanbake_update(void *customdata, float progress, int *cancel)
static void oceanbake_startjob(void *customdata, short *stop, short *do_update, float *progress)
{
- OceanBakeJob *oj = customdata;
+ OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata);
oj->stop = stop;
oj->do_update = do_update;
@@ -2965,11 +2971,11 @@ static void oceanbake_startjob(void *customdata, short *stop, short *do_update,
static void oceanbake_endjob(void *customdata)
{
- OceanBakeJob *oj = customdata;
+ OceanBakeJob *oj = static_cast<OceanBakeJob *>(customdata);
if (oj->ocean) {
BKE_ocean_free(oj->ocean);
- oj->ocean = NULL;
+ oj->ocean = nullptr;
}
oj->omd->oceancache = oj->och;
@@ -3009,7 +3015,7 @@ static int ocean_bake_exec(bContext *C, wmOperator *op)
omd->foam_fade,
omd->resolution);
- och->time = MEM_mallocN(och->duration * sizeof(float), "foam bake time");
+ och->time = static_cast<float *>(MEM_mallocN(och->duration * sizeof(float), "foam bake time"));
int cfra = scene->r.cfra;
@@ -3057,7 +3063,7 @@ static int ocean_bake_exec(bContext *C, wmOperator *op)
"Ocean Simulation",
WM_JOB_PROGRESS,
WM_JOB_TYPE_OBJECT_SIM_OCEAN);
- OceanBakeJob *oj = MEM_callocN(sizeof(OceanBakeJob), "ocean bake job");
+ OceanBakeJob *oj = MEM_cnew<OceanBakeJob>("ocean bake job");
oj->owner = ob;
oj->ocean = ocean;
oj->och = och;
@@ -3065,7 +3071,7 @@ static int ocean_bake_exec(bContext *C, wmOperator *op)
WM_jobs_customdata_set(wm_job, oj, oceanbake_free);
WM_jobs_timer(wm_job, 0.1, NC_OBJECT | ND_MODIFIER, NC_OBJECT | ND_MODIFIER);
- WM_jobs_callbacks(wm_job, oceanbake_startjob, NULL, NULL, oceanbake_endjob);
+ WM_jobs_callbacks(wm_job, oceanbake_startjob, nullptr, nullptr, oceanbake_endjob);
WM_jobs_start(CTX_wm_manager(C), wm_job);
@@ -3115,7 +3121,7 @@ static int laplaciandeform_bind_exec(bContext *C, wmOperator *op)
LaplacianDeformModifierData *lmd = (LaplacianDeformModifierData *)edit_modifier_property_get(
op, ob, eModifierType_LaplacianDeform);
- if (lmd == NULL) {
+ if (lmd == nullptr) {
return OPERATOR_CANCELLED;
}
@@ -3137,11 +3143,11 @@ static int laplaciandeform_bind_exec(bContext *C, wmOperator *op)
/* This is hard to know from the modifier itself whether the evaluation is
* happening for binding or not. So we copy all the required data here. */
lmd->verts_num = lmd_eval->verts_num;
- if (lmd_eval->vertexco == NULL) {
+ if (lmd_eval->vertexco == nullptr) {
MEM_SAFE_FREE(lmd->vertexco);
}
else {
- lmd->vertexco = MEM_dupallocN(lmd_eval->vertexco);
+ lmd->vertexco = static_cast<float *>(MEM_dupallocN(lmd_eval->vertexco));
}
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
@@ -3192,7 +3198,7 @@ static int surfacedeform_bind_exec(bContext *C, wmOperator *op)
SurfaceDeformModifierData *smd = (SurfaceDeformModifierData *)edit_modifier_property_get(
op, ob, eModifierType_SurfaceDeform);
- if (smd == NULL) {
+ if (smd == nullptr) {
return OPERATOR_CANCELLED;
}
@@ -3258,7 +3264,7 @@ static int geometry_nodes_input_attribute_toggle_exec(bContext *C, wmOperator *o
char modifier_name[MAX_NAME];
RNA_string_get(op->ptr, "modifier_name", modifier_name);
NodesModifierData *nmd = (NodesModifierData *)BKE_modifiers_findby_name(ob, modifier_name);
- if (nmd == NULL) {
+ if (nmd == nullptr) {
return OPERATOR_CANCELLED;
}
@@ -3288,8 +3294,55 @@ void OBJECT_OT_geometry_nodes_input_attribute_toggle(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
- RNA_def_string(ot->srna, "prop_path", NULL, 0, "Prop Path", "");
- RNA_def_string(ot->srna, "modifier_name", NULL, MAX_NAME, "Modifier Name", "");
+ RNA_def_string(ot->srna, "prop_path", nullptr, 0, "Prop Path", "");
+ RNA_def_string(ot->srna, "modifier_name", nullptr, MAX_NAME, "Modifier Name", "");
+}
+
+/** \} */
+
+/* ------------------------------------------------------------------- */
+/** \name Copy and Assign Geometry Node Group operator
+ * \{ */
+
+static int geometry_node_tree_copy_assign_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain = CTX_data_main(C);
+ Object *ob = ED_object_active_context(C);
+
+ ModifierData *md = BKE_object_active_modifier(ob);
+ if (md->type != eModifierType_Nodes) {
+ return OPERATOR_CANCELLED;
+ }
+
+ NodesModifierData *nmd = (NodesModifierData *)md;
+ bNodeTree *tree = nmd->node_group;
+ if (tree == nullptr) {
+ return OPERATOR_CANCELLED;
+ }
+
+ bNodeTree *new_tree = (bNodeTree *)BKE_id_copy_ex(
+ bmain, &tree->id, nullptr, LIB_ID_COPY_ACTIONS | LIB_ID_COPY_DEFAULT);
+
+ if (new_tree == nullptr) {
+ return OPERATOR_CANCELLED;
+ }
+
+ nmd->node_group = new_tree;
+ id_us_min(&tree->id);
+
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+ return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_geometry_node_tree_copy_assign(wmOperatorType *ot)
+{
+ ot->name = "Copy Geometry Node Group";
+ ot->description = "Copy the active geometry node group and assign it to the active modifier";
+ ot->idname = "OBJECT_OT_geometry_node_tree_copy_assign";
+
+ ot->exec = geometry_node_tree_copy_assign_exec;
+
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index ad0d6b88123..9b21dabb4bb 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -133,6 +133,7 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_skin_radii_equalize);
WM_operatortype_append(OBJECT_OT_skin_armature_create);
WM_operatortype_append(OBJECT_OT_geometry_nodes_input_attribute_toggle);
+ WM_operatortype_append(OBJECT_OT_geometry_node_tree_copy_assign);
/* grease pencil modifiers */
WM_operatortype_append(OBJECT_OT_gpencil_modifier_add);
diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc
index c0ec6c6678e..976fe683f95 100644
--- a/source/blender/editors/object/object_transform.cc
+++ b/source/blender/editors/object/object_transform.cc
@@ -597,7 +597,7 @@ static bool apply_objects_internal_can_multiuser(bContext *C)
{
Object *obact = CTX_data_active_object(C);
- if (ELEM(NULL, obact, obact->data)) {
+ if (ELEM(nullptr, obact, obact->data)) {
return false;
}
@@ -1176,7 +1176,7 @@ void OBJECT_OT_transform_apply(wmOperatorType *ot)
static int object_parent_inverse_apply_exec(bContext *C, wmOperator *UNUSED(op))
{
CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) {
- if (ob->parent == NULL) {
+ if (ob->parent == nullptr) {
continue;
}
@@ -1185,7 +1185,7 @@ static int object_parent_inverse_apply_exec(bContext *C, wmOperator *UNUSED(op))
}
CTX_DATA_END;
- WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
+ WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, nullptr);
return OPERATOR_FINISHED;
}
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index b89cbcf87fa..abdae5c44f9 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -72,6 +72,7 @@ set(SRC
sculpt_multiplane_scrape.c
sculpt_ops.c
sculpt_paint_color.c
+ sculpt_paint_image.cc
sculpt_pose.c
sculpt_smooth.c
sculpt_transform.c
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 24c5a9fce52..bbbed32e316 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -2188,7 +2188,8 @@ void SCULPT_calc_area_normal_and_center(
static float brush_strength(const Sculpt *sd,
const StrokeCache *cache,
const float feather,
- const UnifiedPaintSettings *ups)
+ const UnifiedPaintSettings *ups,
+ const PaintModeSettings *UNUSED(paint_mode_settings))
{
const Scene *scene = cache->vc->scene;
const Brush *brush = BKE_paint_brush((Paint *)&sd->paint);
@@ -2750,6 +2751,41 @@ static void update_brush_local_mat(Sculpt *sd, Object *ob)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Texture painting
+ * \{ */
+
+static bool sculpt_needs_pbvh_pixels(PaintModeSettings *paint_mode_settings,
+ const Brush *brush,
+ Object *ob)
+{
+ if (brush->sculpt_tool == SCULPT_TOOL_PAINT && U.experimental.use_sculpt_texture_paint) {
+ Image *image;
+ ImageUser *image_user;
+ return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user);
+ }
+
+ return false;
+}
+
+static void sculpt_pbvh_update_pixels(PaintModeSettings *paint_mode_settings,
+ SculptSession *ss,
+ Object *ob)
+{
+ BLI_assert(ob->type == OB_MESH);
+ Mesh *mesh = (Mesh *)ob->data;
+
+ Image *image;
+ ImageUser *image_user;
+ if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) {
+ return;
+ }
+
+ BKE_pbvh_build_pixels(ss->pbvh, mesh->mloop, &mesh->ldata, image, image_user);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Generic Brush Plane & Symmetry Utilities
* \{ */
@@ -3075,7 +3111,8 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3])
static void sculpt_topology_update(Sculpt *sd,
Object *ob,
Brush *brush,
- UnifiedPaintSettings *UNUSED(ups))
+ UnifiedPaintSettings *UNUSED(ups),
+ PaintModeSettings *UNUSED(paint_mode_settings))
{
SculptSession *ss = ob->sculpt;
@@ -3170,7 +3207,11 @@ static void do_brush_action_task_cb(void *__restrict userdata,
}
}
-static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups)
+static void do_brush_action(Sculpt *sd,
+ Object *ob,
+ Brush *brush,
+ UnifiedPaintSettings *ups,
+ PaintModeSettings *paint_mode_settings)
{
SculptSession *ss = ob->sculpt;
int totnode;
@@ -3209,6 +3250,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode);
}
+ if (sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob)) {
+ sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob);
+ }
+
/* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the
* vertices and uses regular coords undo. */
/* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type
@@ -3399,7 +3444,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode);
break;
case SCULPT_TOOL_PAINT:
- SCULPT_do_paint_brush(sd, ob, nodes, totnode);
+ SCULPT_do_paint_brush(paint_mode_settings, sd, ob, nodes, totnode);
break;
case SCULPT_TOOL_SMEAR:
SCULPT_do_smear_brush(sd, ob, nodes, totnode);
@@ -3704,10 +3749,18 @@ void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache,
}
}
-typedef void (*BrushActionFunc)(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups);
+typedef void (*BrushActionFunc)(Sculpt *sd,
+ Object *ob,
+ Brush *brush,
+ UnifiedPaintSettings *ups,
+ PaintModeSettings *paint_mode_settings);
-static void do_tiled(
- Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushActionFunc action)
+static void do_tiled(Sculpt *sd,
+ Object *ob,
+ Brush *brush,
+ UnifiedPaintSettings *ups,
+ PaintModeSettings *paint_mode_settings,
+ BrushActionFunc action)
{
SculptSession *ss = ob->sculpt;
StrokeCache *cache = ss->cache;
@@ -3741,7 +3794,7 @@ static void do_tiled(
/* First do the "un-tiled" position to initialize the stroke for this location. */
cache->tile_pass = 0;
- action(sd, ob, brush, ups);
+ action(sd, ob, brush, ups, paint_mode_settings);
/* Now do it for all the tiles. */
copy_v3_v3_int(cur, start);
@@ -3760,7 +3813,7 @@ static void do_tiled(
cache->plane_offset[dim] = cur[dim] * step[dim];
cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim];
}
- action(sd, ob, brush, ups);
+ action(sd, ob, brush, ups, paint_mode_settings);
}
}
}
@@ -3770,6 +3823,7 @@ static void do_radial_symmetry(Sculpt *sd,
Object *ob,
Brush *brush,
UnifiedPaintSettings *ups,
+ PaintModeSettings *paint_mode_settings,
BrushActionFunc action,
const char symm,
const int axis,
@@ -3781,7 +3835,7 @@ static void do_radial_symmetry(Sculpt *sd,
const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X'];
ss->cache->radial_symmetry_pass = i;
SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle);
- do_tiled(sd, ob, brush, ups, action);
+ do_tiled(sd, ob, brush, ups, paint_mode_settings, action);
}
}
@@ -3803,7 +3857,8 @@ static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob)
static void do_symmetrical_brush_actions(Sculpt *sd,
Object *ob,
BrushActionFunc action,
- UnifiedPaintSettings *ups)
+ UnifiedPaintSettings *ups,
+ PaintModeSettings *paint_mode_settings)
{
Brush *brush = BKE_paint_brush(&sd->paint);
SculptSession *ss = ob->sculpt;
@@ -3812,7 +3867,7 @@ static void do_symmetrical_brush_actions(Sculpt *sd,
float feather = calc_symmetry_feather(sd, ss->cache);
- cache->bstrength = brush_strength(sd, cache, feather, ups);
+ cache->bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings);
cache->symmetry = symm;
/* `symm` is a bit combination of XYZ -
@@ -3825,11 +3880,11 @@ static void do_symmetrical_brush_actions(Sculpt *sd,
cache->radial_symmetry_pass = 0;
SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0);
- do_tiled(sd, ob, brush, ups, action);
+ do_tiled(sd, ob, brush, ups, paint_mode_settings, action);
- do_radial_symmetry(sd, ob, brush, ups, action, i, 'X', feather);
- do_radial_symmetry(sd, ob, brush, ups, action, i, 'Y', feather);
- do_radial_symmetry(sd, ob, brush, ups, action, i, 'Z', feather);
+ do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'X', feather);
+ do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Y', feather);
+ do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Z', feather);
}
}
@@ -4609,7 +4664,8 @@ static bool sculpt_needs_connectivity_info(const Sculpt *sd,
SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool) ||
(brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) ||
(brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) ||
- (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR));
+ (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR) ||
+ (brush->sculpt_tool == SCULPT_TOOL_PAINT));
}
void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush)
@@ -5057,6 +5113,15 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags)
multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED);
}
+ if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) {
+ ED_region_tag_redraw(region);
+ if (update_flags == SCULPT_UPDATE_IMAGE) {
+ /* Early exit when only need to update the images. We don't want to tag any geometry updates
+ * that would rebuilt the PBVH. */
+ return;
+ }
+ }
+
DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
/* Only current viewport matters, slower update for all viewports will
@@ -5136,6 +5201,16 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up
}
}
}
+
+ if (update_flags & SCULPT_UPDATE_IMAGE) {
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ SpaceLink *sl = area->spacedata.first;
+ if (sl->spacetype != SPACE_IMAGE) {
+ continue;
+ }
+ ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW);
+ }
+ }
}
if (update_flags & SCULPT_UPDATE_COORDS) {
@@ -5227,6 +5302,7 @@ static void sculpt_stroke_update_step(bContext *C,
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
const Brush *brush = BKE_paint_brush(&sd->paint);
+ ToolSettings *tool_settings = CTX_data_tool_settings(C);
SCULPT_stroke_modifiers_check(C, ob, brush);
sculpt_update_cache_variants(C, sd, ob, itemptr);
@@ -5246,10 +5322,10 @@ static void sculpt_stroke_update_step(bContext *C,
}
if (SCULPT_stroke_is_dynamic_topology(ss, brush)) {
- do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups);
+ do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode);
}
- do_symmetrical_brush_actions(sd, ob, do_brush_action, ups);
+ do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode);
sculpt_combine_proxies(sd, ob);
/* Hack to fix noise texture tearing mesh. */
@@ -5280,7 +5356,12 @@ static void sculpt_stroke_update_step(bContext *C,
SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK);
}
else if (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) {
- SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR);
+ if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) {
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE);
+ }
+ else {
+ SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR);
+ }
}
else {
SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS);
@@ -5302,6 +5383,7 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ ToolSettings *tool_settings = CTX_data_tool_settings(C);
/* Finished. */
if (!ss->cache) {
@@ -5335,6 +5417,11 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str
if (brush->sculpt_tool == SCULPT_TOOL_MASK) {
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
}
+ else if (brush->sculpt_tool == SCULPT_TOOL_PAINT) {
+ if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) {
+ SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_IMAGE);
+ }
+ }
else {
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 466432a35ec..3839c0e71e4 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -11,11 +11,13 @@
#include "DNA_key_types.h"
#include "DNA_listBase.h"
#include "DNA_meshdata_types.h"
+#include "DNA_scene_types.h"
#include "DNA_vec_types.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
#include "BLI_bitmap.h"
+#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "BLI_gsqueue.h"
#include "BLI_threads.h"
@@ -25,12 +27,13 @@ extern "C" {
#endif
struct AutomaskingCache;
+struct Image;
+struct ImageUser;
struct KeyBlock;
struct Object;
struct SculptUndoNode;
struct bContext;
-
-enum ePaintSymmetryFlags;
+struct PaintModeSettings;
/* Updates */
@@ -43,6 +46,7 @@ typedef enum SculptUpdateType {
SCULPT_UPDATE_MASK = 1 << 1,
SCULPT_UPDATE_VISIBILITY = 1 << 2,
SCULPT_UPDATE_COLOR = 1 << 3,
+ SCULPT_UPDATE_IMAGE = 1 << 4,
} SculptUpdateType;
typedef struct SculptCursorGeometryInfo {
@@ -1626,7 +1630,29 @@ void SCULPT_multiplane_scrape_preview_draw(uint gpuattr,
void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
/* Paint Brush. */
-void SCULPT_do_paint_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
+void SCULPT_do_paint_brush(struct PaintModeSettings *paint_mode_settings,
+ Sculpt *sd,
+ Object *ob,
+ PBVHNode **nodes,
+ int totnode) ATTR_NONNULL();
+
+/**
+ * @brief Get the image canvas for painting on the given object.
+ *
+ * @return #true if an image is found. The #r_image and #r_image_user fields are filled with the
+ * image and image user. Returns false when the image isn't found. In the later case the r_image
+ * and r_image_user are set to nullptr/NULL.
+ */
+bool SCULPT_paint_image_canvas_get(struct PaintModeSettings *paint_mode_settings,
+ struct Object *ob,
+ struct Image **r_image,
+ struct ImageUser **r_image_user) ATTR_NONNULL();
+void SCULPT_do_paint_brush_image(struct PaintModeSettings *paint_mode_settings,
+ Sculpt *sd,
+ Object *ob,
+ PBVHNode **nodes,
+ int totnode) ATTR_NONNULL();
+bool SCULPT_use_image_paint_brush(struct PaintModeSettings *settings, Object *ob) ATTR_NONNULL();
/* Smear Brush. */
void SCULPT_do_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode);
diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.c b/source/blender/editors/sculpt_paint/sculpt_ops.c
index f84852d1d0e..9581bf2071c 100644
--- a/source/blender/editors/sculpt_paint/sculpt_ops.c
+++ b/source/blender/editors/sculpt_paint/sculpt_ops.c
@@ -1030,14 +1030,6 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven
return OPERATOR_CANCELLED;
}
- if (!SCULPT_has_colors(ss)) {
- return OPERATOR_CANCELLED;
- }
-
- if (SCULPT_has_loop_colors(ob)) {
- BKE_pbvh_ensure_node_loops(ss->pbvh);
- }
-
SCULPT_vertex_random_access_ensure(ss);
/* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move,
@@ -1049,12 +1041,17 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven
SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
SCULPT_undo_push_begin(ob, "Mask by color");
+ BKE_sculpt_color_layer_create_if_needed(ob);
const int active_vertex = SCULPT_active_vertex_get(ss);
const float threshold = RNA_float_get(op->ptr, "threshold");
const bool invert = RNA_boolean_get(op->ptr, "invert");
const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask");
+ if (SCULPT_has_loop_colors(ob)) {
+ BKE_pbvh_ensure_node_loops(ss->pbvh);
+ }
+
if (RNA_boolean_get(op->ptr, "contiguous")) {
sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask);
}
@@ -1080,7 +1077,7 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
/* api callbacks */
ot->invoke = sculpt_mask_by_color_invoke;
- ot->poll = SCULPT_vertex_colors_poll;
+ ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER;
diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_color.c b/source/blender/editors/sculpt_paint/sculpt_paint_color.c
index e7a713efa14..7a8a6e8e484 100644
--- a/source/blender/editors/sculpt_paint/sculpt_paint_color.c
+++ b/source/blender/editors/sculpt_paint/sculpt_paint_color.c
@@ -240,8 +240,14 @@ static void sample_wet_paint_reduce(const void *__restrict UNUSED(userdata),
add_v4_v4(join->color, swptd->color);
}
-void SCULPT_do_paint_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+void SCULPT_do_paint_brush(
+ PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
+ if (SCULPT_use_image_paint_brush(paint_mode_settings, ob)) {
+ SCULPT_do_paint_brush_image(paint_mode_settings, sd, ob, nodes, totnode);
+ return;
+ }
+
Brush *brush = BKE_paint_brush(&sd->paint);
SculptSession *ss = ob->sculpt;
diff --git a/source/blender/editors/sculpt_paint/sculpt_paint_image.cc b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc
new file mode 100644
index 00000000000..1fc7551f545
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/sculpt_paint_image.cc
@@ -0,0 +1,430 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#include "DNA_image_types.h"
+#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_node_types.h"
+#include "DNA_object_types.h"
+
+#include "ED_paint.h"
+#include "ED_uvedit.h"
+
+#include "BLI_math.h"
+#include "BLI_math_color_blend.h"
+#include "BLI_task.h"
+
+#include "IMB_colormanagement.h"
+#include "IMB_imbuf.h"
+
+#include "BKE_brush.h"
+#include "BKE_image_wrappers.hh"
+#include "BKE_material.h"
+#include "BKE_pbvh.h"
+#include "BKE_pbvh_pixels.hh"
+
+#include "bmesh.h"
+
+#include "NOD_shader.h"
+
+#include "sculpt_intern.h"
+
+namespace blender::ed::sculpt_paint::paint::image {
+
+using namespace blender::bke::pbvh::pixels;
+using namespace blender::bke::image;
+
+struct ImageData {
+ Image *image = nullptr;
+ ImageUser *image_user = nullptr;
+
+ ~ImageData() = default;
+
+ static bool init_active_image(Object *ob,
+ ImageData *r_image_data,
+ PaintModeSettings *paint_mode_settings)
+ {
+ return BKE_paint_canvas_image_get(
+ paint_mode_settings, ob, &r_image_data->image, &r_image_data->image_user);
+ }
+};
+
+struct TexturePaintingUserData {
+ Object *ob;
+ Brush *brush;
+ PBVHNode **nodes;
+ ImageData image_data;
+};
+
+/** Reading and writing to image buffer with 4 float channels. */
+class ImageBufferFloat4 {
+ private:
+ int pixel_offset;
+
+ public:
+ void set_image_position(ImBuf *image_buffer, ushort2 image_pixel_position)
+ {
+ pixel_offset = int(image_pixel_position.y) * image_buffer->x + int(image_pixel_position.x);
+ }
+
+ void next_pixel()
+ {
+ pixel_offset += 1;
+ }
+
+ float4 read_pixel(ImBuf *image_buffer) const
+ {
+ return &image_buffer->rect_float[pixel_offset * 4];
+ }
+
+ void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const
+ {
+ copy_v4_v4(&image_buffer->rect_float[pixel_offset * 4], pixel_data);
+ }
+
+ const char *get_colorspace_name(ImBuf *image_buffer)
+ {
+ return IMB_colormanagement_get_float_colorspace(image_buffer);
+ }
+};
+
+/** Reading and writing to image buffer with 4 byte channels. */
+class ImageBufferByte4 {
+ private:
+ int pixel_offset;
+
+ public:
+ void set_image_position(ImBuf *image_buffer, ushort2 image_pixel_position)
+ {
+ pixel_offset = int(image_pixel_position.y) * image_buffer->x + int(image_pixel_position.x);
+ }
+
+ void next_pixel()
+ {
+ pixel_offset += 1;
+ }
+
+ float4 read_pixel(ImBuf *image_buffer) const
+ {
+ float4 result;
+ rgba_uchar_to_float(result,
+ static_cast<const uchar *>(
+ static_cast<const void *>(&(image_buffer->rect[pixel_offset]))));
+ return result;
+ }
+
+ void write_pixel(ImBuf *image_buffer, const float4 pixel_data) const
+ {
+ rgba_float_to_uchar(
+ static_cast<uchar *>(static_cast<void *>(&image_buffer->rect[pixel_offset])), pixel_data);
+ }
+
+ const char *get_colorspace_name(ImBuf *image_buffer)
+ {
+ return IMB_colormanagement_get_rect_colorspace(image_buffer);
+ }
+};
+
+template<typename ImageBuffer> class PaintingKernel {
+ ImageBuffer image_accessor;
+
+ SculptSession *ss;
+ const Brush *brush;
+ const int thread_id;
+ const MVert *mvert;
+
+ float4 brush_color;
+ float brush_strength;
+
+ SculptBrushTestFn brush_test_fn;
+ SculptBrushTest test;
+ /* Pointer to the last used image buffer to detect when buffers are switched. */
+ void *last_used_image_buffer_ptr = nullptr;
+ const char *last_used_color_space = nullptr;
+
+ public:
+ explicit PaintingKernel(SculptSession *ss,
+ const Brush *brush,
+ const int thread_id,
+ const MVert *mvert)
+ : ss(ss), brush(brush), thread_id(thread_id), mvert(mvert)
+ {
+ init_brush_strength();
+ init_brush_test();
+ }
+
+ bool paint(const Triangles &triangles, const PackedPixelRow &pixel_row, ImBuf *image_buffer)
+ {
+ image_accessor.set_image_position(image_buffer, pixel_row.start_image_coordinate);
+ const TrianglePaintInput triangle = triangles.get_paint_input(pixel_row.triangle_index);
+ float3 pixel_pos = get_start_pixel_pos(triangle, pixel_row);
+ const float3 delta_pixel_pos = get_delta_pixel_pos(triangle, pixel_row, pixel_pos);
+ bool pixels_painted = false;
+ for (int x = 0; x < pixel_row.num_pixels; x++) {
+ if (!brush_test_fn(&test, pixel_pos)) {
+ pixel_pos += delta_pixel_pos;
+ image_accessor.next_pixel();
+ continue;
+ }
+
+ float4 color = image_accessor.read_pixel(image_buffer);
+ const float3 normal(0.0f, 0.0f, 0.0f);
+ const float3 face_normal(0.0f, 0.0f, 0.0f);
+ const float mask = 0.0f;
+ const float falloff_strength = SCULPT_brush_strength_factor(
+ ss, brush, pixel_pos, sqrtf(test.dist), normal, face_normal, mask, 0, thread_id);
+ float4 paint_color = brush_color * falloff_strength * brush_strength;
+ float4 buffer_color;
+ blend_color_mix_float(buffer_color, color, paint_color);
+ buffer_color *= brush->alpha;
+ IMB_blend_color_float(color, color, buffer_color, static_cast<IMB_BlendMode>(brush->blend));
+ image_accessor.write_pixel(image_buffer, color);
+ pixels_painted = true;
+
+ image_accessor.next_pixel();
+ pixel_pos += delta_pixel_pos;
+ }
+ return pixels_painted;
+ }
+
+ void init_brush_color(ImBuf *image_buffer)
+ {
+ const char *to_colorspace = image_accessor.get_colorspace_name(image_buffer);
+ if (last_used_color_space == to_colorspace) {
+ return;
+ }
+ copy_v3_v3(brush_color,
+ ss->cache->invert ? BKE_brush_secondary_color_get(ss->scene, brush) :
+ BKE_brush_color_get(ss->scene, brush));
+ /* NOTE: Brush colors are stored in sRGB. We use math color to follow other areas that
+ * use brush colors. From there on we use IMB_colormanagement to convert the brush color to the
+ * colorspace of the texture. This isn't ideal, but would need more refactoring to make sure
+ * that brush colors are stored in scene linear by default. */
+ srgb_to_linearrgb_v3_v3(brush_color, brush_color);
+ brush_color[3] = 1.0f;
+
+ const char *from_colorspace = IMB_colormanagement_role_colorspace_name_get(
+ COLOR_ROLE_SCENE_LINEAR);
+ ColormanageProcessor *cm_processor = IMB_colormanagement_colorspace_processor_new(
+ from_colorspace, to_colorspace);
+ IMB_colormanagement_processor_apply_v4(cm_processor, brush_color);
+ IMB_colormanagement_processor_free(cm_processor);
+ last_used_color_space = to_colorspace;
+ }
+
+ private:
+ void init_brush_strength()
+ {
+ brush_strength = ss->cache->bstrength;
+ }
+ void init_brush_test()
+ {
+ brush_test_fn = SCULPT_brush_test_init_with_falloff_shape(ss, &test, brush->falloff_shape);
+ }
+
+ /**
+ * Extract the starting pixel position from the given encoded_pixels belonging to the triangle.
+ */
+ float3 get_start_pixel_pos(const TrianglePaintInput &triangle,
+ const PackedPixelRow &encoded_pixels) const
+ {
+ return init_pixel_pos(triangle, encoded_pixels.start_barycentric_coord);
+ }
+
+ /**
+ * Extract the delta pixel position that will be used to advance a Pixel instance to the next
+ * pixel.
+ */
+ float3 get_delta_pixel_pos(const TrianglePaintInput &triangle,
+ const PackedPixelRow &encoded_pixels,
+ const float3 &start_pixel) const
+ {
+ float3 result = init_pixel_pos(
+ triangle, encoded_pixels.start_barycentric_coord + triangle.delta_barycentric_coord_u);
+ return result - start_pixel;
+ }
+
+ float3 init_pixel_pos(const TrianglePaintInput &triangle,
+ const float2 &barycentric_weights) const
+ {
+ const int3 &vert_indices = triangle.vert_indices;
+ float3 result;
+ const float3 barycentric(barycentric_weights.x,
+ barycentric_weights.y,
+ 1.0f - barycentric_weights.x - barycentric_weights.y);
+ interp_v3_v3v3v3(result,
+ mvert[vert_indices[0]].co,
+ mvert[vert_indices[1]].co,
+ mvert[vert_indices[2]].co,
+ barycentric);
+ return result;
+ }
+};
+
+static std::vector<bool> init_triangle_brush_test(SculptSession *ss,
+ Triangles &triangles,
+ const MVert *mvert)
+{
+ std::vector<bool> brush_test(triangles.size());
+ SculptBrushTest test;
+ SCULPT_brush_test_init(ss, &test);
+ float3 brush_min_bounds(test.location[0] - test.radius,
+ test.location[1] - test.radius,
+ test.location[2] - test.radius);
+ float3 brush_max_bounds(test.location[0] + test.radius,
+ test.location[1] + test.radius,
+ test.location[2] + test.radius);
+ for (int triangle_index = 0; triangle_index < triangles.size(); triangle_index++) {
+ TrianglePaintInput &triangle = triangles.get_paint_input(triangle_index);
+
+ float3 triangle_min_bounds(mvert[triangle.vert_indices[0]].co);
+ float3 triangle_max_bounds(triangle_min_bounds);
+ for (int i = 1; i < 3; i++) {
+ const float3 &pos = mvert[triangle.vert_indices[i]].co;
+ triangle_min_bounds.x = min_ff(triangle_min_bounds.x, pos.x);
+ triangle_min_bounds.y = min_ff(triangle_min_bounds.y, pos.y);
+ triangle_min_bounds.z = min_ff(triangle_min_bounds.z, pos.z);
+ triangle_max_bounds.x = max_ff(triangle_max_bounds.x, pos.x);
+ triangle_max_bounds.y = max_ff(triangle_max_bounds.y, pos.y);
+ triangle_max_bounds.z = max_ff(triangle_max_bounds.z, pos.z);
+ }
+ brush_test[triangle_index] = isect_aabb_aabb_v3(
+ brush_min_bounds, brush_max_bounds, triangle_min_bounds, triangle_max_bounds);
+ }
+ return brush_test;
+}
+
+static void do_paint_pixels(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata);
+ Object *ob = data->ob;
+ SculptSession *ss = ob->sculpt;
+ const Brush *brush = data->brush;
+ PBVHNode *node = data->nodes[n];
+
+ NodeData &node_data = BKE_pbvh_pixels_node_data_get(*node);
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+ MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss);
+
+ std::vector<bool> brush_test = init_triangle_brush_test(ss, node_data.triangles, mvert);
+
+ PaintingKernel<ImageBufferFloat4> kernel_float4(ss, brush, thread_id, mvert);
+ PaintingKernel<ImageBufferByte4> kernel_byte4(ss, brush, thread_id, mvert);
+
+ ImageUser image_user = *data->image_data.image_user;
+ bool pixels_updated = false;
+ for (UDIMTilePixels &tile_data : node_data.tiles) {
+ LISTBASE_FOREACH (ImageTile *, tile, &data->image_data.image->tiles) {
+ ImageTileWrapper image_tile(tile);
+ if (image_tile.get_tile_number() == tile_data.tile_number) {
+ image_user.tile = image_tile.get_tile_number();
+
+ ImBuf *image_buffer = BKE_image_acquire_ibuf(data->image_data.image, &image_user, nullptr);
+ if (image_buffer == nullptr) {
+ continue;
+ }
+
+ if (image_buffer->rect_float != nullptr) {
+ kernel_float4.init_brush_color(image_buffer);
+ }
+ else {
+ kernel_float4.init_brush_color(image_buffer);
+ }
+
+ for (const PackedPixelRow &pixel_row : tile_data.pixel_rows) {
+ if (!brush_test[pixel_row.triangle_index]) {
+ continue;
+ }
+ bool pixels_painted = false;
+ if (image_buffer->rect_float != nullptr) {
+ pixels_painted = kernel_float4.paint(node_data.triangles, pixel_row, image_buffer);
+ }
+ else {
+ pixels_painted = kernel_byte4.paint(node_data.triangles, pixel_row, image_buffer);
+ }
+
+ if (pixels_painted) {
+ tile_data.mark_dirty(pixel_row);
+ }
+ }
+
+ BKE_image_release_ibuf(data->image_data.image, image_buffer, nullptr);
+ pixels_updated |= tile_data.flags.dirty;
+ break;
+ }
+ }
+ }
+
+ node_data.flags.dirty |= pixels_updated;
+}
+
+static void do_mark_dirty_regions(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ TexturePaintingUserData *data = static_cast<TexturePaintingUserData *>(userdata);
+ PBVHNode *node = data->nodes[n];
+ BKE_pbvh_pixels_mark_image_dirty(*node, *data->image_data.image, *data->image_data.image_user);
+}
+
+} // namespace blender::ed::sculpt_paint::paint::image
+
+extern "C" {
+
+using namespace blender::ed::sculpt_paint::paint::image;
+
+bool SCULPT_paint_image_canvas_get(PaintModeSettings *paint_mode_settings,
+ Object *ob,
+ Image **r_image,
+ ImageUser **r_image_user)
+{
+ BLI_assert(r_image);
+ BLI_assert(r_image_user);
+ ImageData image_data;
+ if (!ImageData::init_active_image(ob, &image_data, paint_mode_settings)) {
+ return false;
+ }
+
+ *r_image = image_data.image;
+ *r_image_user = image_data.image_user;
+ return true;
+}
+
+bool SCULPT_use_image_paint_brush(PaintModeSettings *settings, Object *ob)
+{
+ if (!U.experimental.use_sculpt_texture_paint) {
+ return false;
+ }
+ if (ob->type != OB_MESH) {
+ return false;
+ }
+ Image *image;
+ ImageUser *image_user;
+ return BKE_paint_canvas_image_get(settings, ob, &image, &image_user);
+}
+
+void SCULPT_do_paint_brush_image(
+ PaintModeSettings *paint_mode_settings, Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ TexturePaintingUserData data = {nullptr};
+ data.ob = ob;
+ data.brush = brush;
+ data.nodes = nodes;
+
+ if (!ImageData::init_active_image(ob, &data.image_data, paint_mode_settings)) {
+ return;
+ }
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_paint_pixels, &settings);
+
+ TaskParallelSettings settings_flush;
+ BKE_pbvh_parallel_range_settings(&settings_flush, false, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_mark_dirty_regions, &settings_flush);
+}
+}
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index 692940d405c..47d35ced371 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -1882,7 +1882,7 @@ static void node_draw_extra_info_row(const bNode &node,
0,
0,
extra_info_row.tooltip);
- if (extra_info_row.tooltip_fn != NULL) {
+ if (extra_info_row.tooltip_fn != nullptr) {
UI_but_func_tooltip_set(but_icon,
extra_info_row.tooltip_fn,
extra_info_row.tooltip_fn_arg,
diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc
index 9d83f977fe0..2d7972e2291 100644
--- a/source/blender/editors/space_node/node_edit.cc
+++ b/source/blender/editors/space_node/node_edit.cc
@@ -1306,6 +1306,11 @@ static int node_duplicate_exec(bContext *C, wmOperator *op)
newlink->flag = link->flag;
newlink->tonode = node_map.lookup(link->tonode);
newlink->tosock = socket_map.lookup(link->tosock);
+
+ if (link->tosock->flag & SOCK_MULTI_INPUT) {
+ newlink->multi_input_socket_index = link->multi_input_socket_index;
+ }
+
if (link->fromnode && (link->fromnode->flag & NODE_SELECT)) {
newlink->fromnode = node_map.lookup(link->fromnode);
newlink->fromsock = socket_map.lookup(link->fromsock);
diff --git a/source/blender/editors/space_outliner/outliner_tools.cc b/source/blender/editors/space_outliner/outliner_tools.cc
index 65b4ba15369..5da64177e51 100644
--- a/source/blender/editors/space_outliner/outliner_tools.cc
+++ b/source/blender/editors/space_outliner/outliner_tools.cc
@@ -2017,7 +2017,7 @@ static const EnumPropertyItem prop_id_op_types[] = {
{OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET,
"OVERRIDE_LIBRARY_RESET",
0,
- "Reset Library Override",
+ "Reset Library Override Single",
"Reset this local override to its linked values"},
{OUTLINER_IDOP_OVERRIDE_LIBRARY_RESET_HIERARCHY,
"OVERRIDE_LIBRARY_RESET_HIERARCHY",
@@ -2037,18 +2037,18 @@ static const EnumPropertyItem prop_id_op_types[] = {
"Rebuild this local override from its linked reference, as well as its hierarchy of "
"dependencies, enforcing that hierarchy to match the linked data (i.e. ignoring exiting "
"overrides on data-blocks pointer properties)"},
+ {OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE,
+ "OVERRIDE_LIBRARY_CLEAR_SINGLE",
+ 0,
+ "Clear Library Override Single",
+ "Delete this local override and relink its usages to the linked data-blocks if possible, "
+ "else reset it and mark it as non editable"},
{OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_HIERARCHY,
"OVERRIDE_LIBRARY_CLEAR_HIERARCHY",
0,
"Clear Library Override Hierarchy",
"Delete this local override (including its hierarchy of override dependencies) and relink "
"its usages to the linked data-blocks"},
- {OUTLINER_IDOP_OVERRIDE_LIBRARY_CLEAR_SINGLE,
- "OVERRIDE_LIBRARY_CLEAR_SINGLE",
- 0,
- "Clear Single Library Override",
- "Delete this local override if possible, else reset it and mark it as non editable, and "
- "relink its usages to the linked data-blocks"},
{0, "", 0, nullptr, nullptr},
{OUTLINER_IDOP_COPY, "COPY", ICON_COPYDOWN, "Copy", ""},
{OUTLINER_IDOP_PASTE, "PASTE", ICON_PASTEDOWN, "Paste", ""},
diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c
index 31e885d16f2..1c1694479d3 100644
--- a/source/blender/editors/space_sequencer/sequencer_draw.c
+++ b/source/blender/editors/space_sequencer/sequencer_draw.c
@@ -383,7 +383,7 @@ static void draw_seq_waveform_overlay(View2D *v2d,
return;
}
- /* F-curve lookup is quite expensive, so do this after precondition. */
+ /* F-Curve lookup is quite expensive, so do this after precondition. */
FCurve *fcu = id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "volume", 0, NULL);
WaveVizData *tri_strip_arr = MEM_callocN(sizeof(*tri_strip_arr) * pix_strip_len * 2,
diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c
index 055aac041f1..4606908b91f 100644
--- a/source/blender/editors/space_view3d/view3d_iterators.c
+++ b/source/blender/editors/space_view3d/view3d_iterators.c
@@ -568,7 +568,7 @@ void mesh_foreachScreenFace(
BM_mesh_elem_table_ensure(vc->em->bm, BM_FACE);
- if (BKE_modifiers_uses_subsurf_facedots(vc->scene, vc->obedit)) {
+ if (me->runtime.subsurf_face_dot_tags != NULL) {
BKE_mesh_foreach_mapped_subdiv_face_center(
me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP);
}
diff --git a/source/blender/editors/transform/transform_convert_action.c b/source/blender/editors/transform/transform_convert_action.c
index ac1e961e361..71c245cd512 100644
--- a/source/blender/editors/transform/transform_convert_action.c
+++ b/source/blender/editors/transform/transform_convert_action.c
@@ -337,7 +337,7 @@ void createTransActionData(bContext *C, TransInfo *t)
t->frame_side = 'B';
}
- /* loop 1: fully select F-curve keys and count how many BezTriples are selected */
+ /* loop 1: fully select F-Curve keys and count how many BezTriples are selected */
for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ANIM_nla_mapping_get(&ac, ale);
int adt_count = 0;
diff --git a/source/blender/editors/transform/transform_snap_object.cc b/source/blender/editors/transform/transform_snap_object.cc
index c3168b58c47..ab78ef6a5aa 100644
--- a/source/blender/editors/transform/transform_snap_object.cc
+++ b/source/blender/editors/transform/transform_snap_object.cc
@@ -178,13 +178,20 @@ static const Mesh *mesh_for_snap(Object *ob_eval, eSnapEditType edit_mode_type,
/**
* Calculate the minimum and maximum coordinates of the box that encompasses this mesh.
*/
-static void bm_mesh_minmax(BMesh *bm, float r_min[3], float r_max[3])
+static void snap_editmesh_minmax(SnapObjectContext *sctx,
+ BMesh *bm,
+ float r_min[3],
+ float r_max[3])
{
INIT_MINMAX(r_min, r_max);
BMIter iter;
BMVert *v;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (sctx->callbacks.edit_mesh.test_vert_fn &&
+ !sctx->callbacks.edit_mesh.test_vert_fn(v, sctx->callbacks.edit_mesh.user_data)) {
+ continue;
+ }
minmax_v3v3_v3(r_min, r_max, v->co);
}
}
@@ -312,10 +319,10 @@ static SnapObjectData *snap_object_data_mesh_get(SnapObjectContext *sctx,
use_hide ? BVHTREE_FROM_LOOPTRI_NO_HIDDEN : BVHTREE_FROM_LOOPTRI,
4);
- BLI_assert(sod->treedata_mesh.vert != nullptr);
- BLI_assert(sod->treedata_mesh.vert_normals != nullptr);
- BLI_assert(sod->treedata_mesh.loop != nullptr);
- BLI_assert(sod->treedata_mesh.looptri != nullptr);
+ BLI_assert(sod->treedata_mesh.vert == me_eval->mvert);
+ BLI_assert(!me_eval->mvert || sod->treedata_mesh.vert_normals);
+ BLI_assert(sod->treedata_mesh.loop == me_eval->mloop);
+ BLI_assert(!me_eval->mpoly || sod->treedata_mesh.looptri);
BLI_assert(sod->has_looptris == false);
sod->has_looptris = sod->treedata_mesh.tree != nullptr;
@@ -423,7 +430,7 @@ static SnapObjectData *snap_object_data_editmesh_get(SnapObjectContext *sctx,
sod->type = SnapObjectData::Type::EditMesh;
sod->treedata_editmesh.em = em;
sod->mesh_runtime = snap_object_data_editmesh_runtime_get(ob_eval);
- bm_mesh_minmax(em->bm, sod->min, sod->max);
+ snap_editmesh_minmax(sctx, em->bm, sod->min, sod->max);
}
return sod;
@@ -877,9 +884,6 @@ static bool raycastEditMesh(SnapObjectContext *sctx,
BVHTreeFromEditMesh *treedata = &sod->treedata_editmesh;
if (treedata->tree == nullptr) {
- /* Operators only update the editmesh looptris of the original mesh. */
- BLI_assert(sod->treedata_editmesh.em ==
- BKE_editmesh_from_object(DEG_get_original_object(ob_eval)));
em = sod->treedata_editmesh.em;
if (sctx->callbacks.edit_mesh.test_face_fn) {
@@ -1031,14 +1035,15 @@ static void raycast_obj_fn(SnapObjectContext *sctx,
bool use_hide = false;
const Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide);
if (me_eval == nullptr) {
- /* Operators only update the editmesh looptris of the original mesh. */
- BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval));
+ BMEditMesh *em = BKE_editmesh_from_object(ob_eval);
+ BLI_assert_msg(em == BKE_editmesh_from_object(DEG_get_original_object(ob_eval)),
+ "Make sure there is only one pointer for looptris");
retval = raycastEditMesh(sctx,
params,
dt->ray_start,
dt->ray_dir,
ob_eval,
- em_orig,
+ em,
obmat,
ob_index,
ray_depth,
@@ -2701,10 +2706,11 @@ static void snap_obj_fn(SnapObjectContext *sctx,
bool use_hide;
const Mesh *me_eval = mesh_for_snap(ob_eval, edit_mode_type, &use_hide);
if (me_eval == nullptr) {
- /* Operators only update the editmesh looptris of the original mesh. */
- BMEditMesh *em_orig = BKE_editmesh_from_object(DEG_get_original_object(ob_eval));
+ BMEditMesh *em = BKE_editmesh_from_object(ob_eval);
+ BLI_assert_msg(em == BKE_editmesh_from_object(DEG_get_original_object(ob_eval)),
+ "Make sure there is only one pointer for looptris");
retval = snapEditMesh(
- sctx, params, ob_eval, em_orig, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index);
+ sctx, params, ob_eval, em, obmat, dt->dist_px, dt->r_loc, dt->r_no, dt->r_index);
break;
}
if (ob_eval->dt == OB_BOUNDBOX) {
diff --git a/source/blender/geometry/intern/mesh_merge_by_distance.cc b/source/blender/geometry/intern/mesh_merge_by_distance.cc
index 6dc6271194b..9bb1cbb324e 100644
--- a/source/blender/geometry/intern/mesh_merge_by_distance.cc
+++ b/source/blender/geometry/intern/mesh_merge_by_distance.cc
@@ -104,7 +104,7 @@ struct WeldMesh {
/* References all polygons and loops that will be affected. */
Vector<WeldLoop> wloop;
Vector<WeldPoly> wpoly;
- WeldPoly *wpoly_new;
+ MutableSpan<WeldPoly> wpoly_new;
int wloop_len;
int wpoly_len;
int wpoly_new_len;
@@ -806,11 +806,9 @@ static void weld_poly_loop_ctx_alloc(Span<MPoly> mpoly,
wpoly.resize(wpoly_len + maybe_new_poly);
}
- WeldPoly *poly_new = wpoly.data() + wpoly_len;
-
r_weld_mesh->wloop = std::move(wloop);
r_weld_mesh->wpoly = std::move(wpoly);
- r_weld_mesh->wpoly_new = poly_new;
+ r_weld_mesh->wpoly_new = r_weld_mesh->wpoly.as_mutable_span().drop_front(wpoly_len);
r_weld_mesh->wloop_len = wloop_len;
r_weld_mesh->wpoly_len = wpoly_len;
r_weld_mesh->wpoly_new_len = 0;
@@ -830,133 +828,134 @@ static void weld_poly_split_recursive(Span<int> vert_dest_map,
int *r_loop_kill)
{
int poly_len = r_wp->len;
- if (poly_len > 3 && ctx_verts_len > 1) {
- const int ctx_loops_len = r_wp->loops.len;
- const int ctx_loops_ofs = r_wp->loops.ofs;
- MutableSpan<WeldLoop> wloop = r_weld_mesh->wloop;
- WeldPoly *wpoly_new = r_weld_mesh->wpoly_new;
-
- int loop_kill = 0;
-
- WeldLoop *poly_loops = &wloop[ctx_loops_ofs];
- WeldLoop *wla = &poly_loops[0];
- WeldLoop *wla_prev = &poly_loops[ctx_loops_len - 1];
- while (wla_prev->flag == ELEM_COLLAPSED) {
- wla_prev--;
- }
- const int la_len = ctx_loops_len - 1;
- for (int la = 0; la < la_len; la++, wla++) {
- wa_continue:
- if (wla->flag == ELEM_COLLAPSED) {
- continue;
- }
- int vert_a = wla->vert;
- /* Only test vertices that will be merged. */
- if (vert_dest_map[vert_a] != OUT_OF_CONTEXT) {
- int lb = la + 1;
- WeldLoop *wlb = wla + 1;
- WeldLoop *wlb_prev = wla;
- int killed_ab = 0;
- ctx_verts_len = 1;
- for (; lb < ctx_loops_len; lb++, wlb++) {
- BLI_assert(wlb->loop_skip_to == OUT_OF_CONTEXT);
- if (wlb->flag == ELEM_COLLAPSED) {
- killed_ab++;
- continue;
- }
- int vert_b = wlb->vert;
- if (vert_dest_map[vert_b] != OUT_OF_CONTEXT) {
- ctx_verts_len++;
+ if (poly_len < 3 || ctx_verts_len < 1) {
+ return;
+ }
+
+ const int ctx_loops_len = r_wp->loops.len;
+ const int ctx_loops_ofs = r_wp->loops.ofs;
+ MutableSpan<WeldLoop> wloop = r_weld_mesh->wloop;
+
+ int loop_kill = 0;
+
+ WeldLoop *poly_loops = &wloop[ctx_loops_ofs];
+ WeldLoop *wla = &poly_loops[0];
+ WeldLoop *wla_prev = &poly_loops[ctx_loops_len - 1];
+ while (wla_prev->flag == ELEM_COLLAPSED) {
+ wla_prev--;
+ }
+ const int la_len = ctx_loops_len - 1;
+ for (int la = 0; la < la_len; la++, wla++) {
+ wa_continue:
+ if (wla->flag == ELEM_COLLAPSED) {
+ continue;
+ }
+ int vert_a = wla->vert;
+ /* Only test vertices that will be merged. */
+ if (vert_dest_map[vert_a] != OUT_OF_CONTEXT) {
+ int lb = la + 1;
+ WeldLoop *wlb = wla + 1;
+ WeldLoop *wlb_prev = wla;
+ int killed_ab = 0;
+ ctx_verts_len = 1;
+ for (; lb < ctx_loops_len; lb++, wlb++) {
+ BLI_assert(wlb->loop_skip_to == OUT_OF_CONTEXT);
+ if (wlb->flag == ELEM_COLLAPSED) {
+ killed_ab++;
+ continue;
+ }
+ int vert_b = wlb->vert;
+ if (vert_dest_map[vert_b] != OUT_OF_CONTEXT) {
+ ctx_verts_len++;
+ }
+ if (vert_a == vert_b) {
+ const int dist_a = wlb->loop_orig - wla->loop_orig - killed_ab;
+ const int dist_b = poly_len - dist_a;
+
+ BLI_assert(dist_a != 0 && dist_b != 0);
+ if (dist_a == 1 || dist_b == 1) {
+ BLI_assert(dist_a != dist_b);
+ BLI_assert((wla->flag == ELEM_COLLAPSED) || (wlb->flag == ELEM_COLLAPSED));
}
- if (vert_a == vert_b) {
- const int dist_a = wlb->loop_orig - wla->loop_orig - killed_ab;
- const int dist_b = poly_len - dist_a;
-
- BLI_assert(dist_a != 0 && dist_b != 0);
- if (dist_a == 1 || dist_b == 1) {
- BLI_assert(dist_a != dist_b);
- BLI_assert((wla->flag == ELEM_COLLAPSED) || (wlb->flag == ELEM_COLLAPSED));
+ else {
+ WeldLoop *wl_tmp = nullptr;
+ if (dist_a == 2) {
+ wl_tmp = wlb_prev;
+ BLI_assert(wla->flag != ELEM_COLLAPSED);
+ BLI_assert(wl_tmp->flag != ELEM_COLLAPSED);
+ wla->flag = ELEM_COLLAPSED;
+ wl_tmp->flag = ELEM_COLLAPSED;
+ loop_kill += 2;
+ poly_len -= 2;
}
- else {
- WeldLoop *wl_tmp = nullptr;
- if (dist_a == 2) {
- wl_tmp = wlb_prev;
- BLI_assert(wla->flag != ELEM_COLLAPSED);
+ if (dist_b == 2) {
+ if (wl_tmp != nullptr) {
+ r_wp->flag = ELEM_COLLAPSED;
+ *r_poly_kill += 1;
+ }
+ else {
+ wl_tmp = wla_prev;
+ BLI_assert(wlb->flag != ELEM_COLLAPSED);
BLI_assert(wl_tmp->flag != ELEM_COLLAPSED);
- wla->flag = ELEM_COLLAPSED;
+ wlb->flag = ELEM_COLLAPSED;
wl_tmp->flag = ELEM_COLLAPSED;
- loop_kill += 2;
- poly_len -= 2;
- }
- if (dist_b == 2) {
- if (wl_tmp != nullptr) {
- r_wp->flag = ELEM_COLLAPSED;
- *r_poly_kill += 1;
- }
- else {
- wl_tmp = wla_prev;
- BLI_assert(wlb->flag != ELEM_COLLAPSED);
- BLI_assert(wl_tmp->flag != ELEM_COLLAPSED);
- wlb->flag = ELEM_COLLAPSED;
- wl_tmp->flag = ELEM_COLLAPSED;
- }
- loop_kill += 2;
- poly_len -= 2;
}
- if (wl_tmp == nullptr) {
- const int new_loops_len = lb - la;
- const int new_loops_ofs = ctx_loops_ofs + la;
-
- WeldPoly *new_wp = &wpoly_new[r_weld_mesh->wpoly_new_len++];
- new_wp->poly_dst = OUT_OF_CONTEXT;
- new_wp->poly_orig = r_wp->poly_orig;
- new_wp->loops.len = new_loops_len;
- new_wp->loops.ofs = new_loops_ofs;
- new_wp->loop_start = wla->loop_orig;
- new_wp->loop_end = wlb_prev->loop_orig;
- new_wp->len = dist_a;
- weld_poly_split_recursive(vert_dest_map,
+ loop_kill += 2;
+ poly_len -= 2;
+ }
+ if (wl_tmp == nullptr) {
+ const int new_loops_len = lb - la;
+ const int new_loops_ofs = ctx_loops_ofs + la;
+
+ WeldPoly *new_wp = &r_weld_mesh->wpoly_new[r_weld_mesh->wpoly_new_len++];
+ new_wp->poly_dst = OUT_OF_CONTEXT;
+ new_wp->poly_orig = r_wp->poly_orig;
+ new_wp->loops.len = new_loops_len;
+ new_wp->loops.ofs = new_loops_ofs;
+ new_wp->loop_start = wla->loop_orig;
+ new_wp->loop_end = wlb_prev->loop_orig;
+ new_wp->len = dist_a;
+ weld_poly_split_recursive(vert_dest_map,
#ifdef USE_WELD_DEBUG
- mloop,
+ mloop,
#endif
- ctx_verts_len,
- new_wp,
- r_weld_mesh,
- r_poly_kill,
- r_loop_kill);
- BLI_assert(dist_b == poly_len - dist_a);
- poly_len = dist_b;
- if (wla_prev->loop_orig > wla->loop_orig) {
- /* New start. */
- r_wp->loop_start = wlb->loop_orig;
- }
- else {
- /* The `loop_start` doesn't change but some loops must be skipped. */
- wla_prev->loop_skip_to = wlb->loop_orig;
- }
- wla = wlb;
- la = lb;
- goto wa_continue;
+ ctx_verts_len,
+ new_wp,
+ r_weld_mesh,
+ r_poly_kill,
+ r_loop_kill);
+ BLI_assert(dist_b == poly_len - dist_a);
+ poly_len = dist_b;
+ if (wla_prev->loop_orig > wla->loop_orig) {
+ /* New start. */
+ r_wp->loop_start = wlb->loop_orig;
}
- break;
+ else {
+ /* The `loop_start` doesn't change but some loops must be skipped. */
+ wla_prev->loop_skip_to = wlb->loop_orig;
+ }
+ wla = wlb;
+ la = lb;
+ goto wa_continue;
}
- }
- if (wlb->flag != ELEM_COLLAPSED) {
- wlb_prev = wlb;
+ break;
}
}
+ if (wlb->flag != ELEM_COLLAPSED) {
+ wlb_prev = wlb;
+ }
}
- if (wla->flag != ELEM_COLLAPSED) {
- wla_prev = wla;
- }
}
- r_wp->len = poly_len;
- *r_loop_kill += loop_kill;
+ if (wla->flag != ELEM_COLLAPSED) {
+ wla_prev = wla;
+ }
+ }
+ r_wp->len = poly_len;
+ *r_loop_kill += loop_kill;
#ifdef USE_WELD_DEBUG
- weld_assert_poly_no_vert_repetition(*r_wp, wloop, mloop, r_weld_mesh->loop_map);
+ weld_assert_poly_no_vert_repetition(*r_wp, wloop, mloop, r_weld_mesh->loop_map);
#endif
- }
}
static void weld_poly_loop_ctx_setup(Span<MLoop> mloop,
@@ -971,7 +970,6 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop,
{
MutableSpan<WeldPoly> wpoly = r_weld_mesh->wpoly;
MutableSpan<WeldLoop> wloop = r_weld_mesh->wloop;
- WeldPoly *wpoly_new = r_weld_mesh->wpoly_new;
int wpoly_len = r_weld_mesh->wpoly_len;
int wpoly_new_len = 0;
int poly_kill_len = 0;
@@ -1034,7 +1032,7 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop,
#ifdef USE_WELD_DEBUG
weld_assert_poly_and_loop_kill_len(wpoly,
- {wpoly_new, wpoly_new_len},
+ r_weld_mesh->wpoly_new,
wloop,
mloop,
loop_map,
@@ -1170,7 +1168,7 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop,
#ifdef USE_WELD_DEBUG
weld_assert_poly_and_loop_kill_len(wpoly,
- {wpoly_new, wpoly_new_len},
+ r_weld_mesh->wpoly_new,
wloop,
mloop,
loop_map,
@@ -1180,7 +1178,6 @@ static void weld_poly_loop_ctx_setup(Span<MLoop> mloop,
loop_kill_len);
#endif
- r_weld_mesh->wpoly_new = wpoly_new;
r_weld_mesh->poly_kill_len = poly_kill_len;
r_weld_mesh->loop_kill_len = loop_kill_len;
}
diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc
index d1242e540fc..460b6d32967 100644
--- a/source/blender/gpu/intern/gpu_shader_dependency.cc
+++ b/source/blender/gpu/intern/gpu_shader_dependency.cc
@@ -292,7 +292,7 @@ struct GPUSource {
const char whitespace_chars[] = " \r\n\t";
- auto function_parse = [&](const StringRef &input,
+ auto function_parse = [&](const StringRef input,
int64_t &cursor,
StringRef &out_return_type,
StringRef &out_name,
@@ -330,7 +330,7 @@ struct GPUSource {
return true;
};
- auto keyword_parse = [&](const StringRef &str, int64_t &cursor) -> const StringRef {
+ auto keyword_parse = [&](const StringRef str, int64_t &cursor) -> StringRef {
int64_t keyword_start = str.find_first_not_of(whitespace_chars, cursor);
if (keyword_start == -1) {
/* No keyword found. */
@@ -345,7 +345,7 @@ struct GPUSource {
return str.substr(keyword_start, keyword_end - keyword_start);
};
- auto arg_parse = [&](const StringRef &str,
+ auto arg_parse = [&](const StringRef str,
int64_t &cursor,
StringRef &out_qualifier,
StringRef &out_type,
@@ -416,55 +416,51 @@ struct GPUSource {
break;
}
- auto parse_qualifier = [](StringRef &qualifier) -> GPUFunctionQual {
+ auto parse_qualifier = [](StringRef qualifier) -> GPUFunctionQual {
if (qualifier == "out") {
return FUNCTION_QUAL_OUT;
}
- else if (qualifier == "inout") {
+ if (qualifier == "inout") {
return FUNCTION_QUAL_INOUT;
}
- else {
- return FUNCTION_QUAL_IN;
- }
+ return FUNCTION_QUAL_IN;
};
- auto parse_type = [](StringRef &type) -> eGPUType {
+ auto parse_type = [](StringRef type) -> eGPUType {
if (type == "float") {
return GPU_FLOAT;
}
- else if (type == "vec2") {
+ if (type == "vec2") {
return GPU_VEC2;
}
- else if (type == "vec3") {
+ if (type == "vec3") {
return GPU_VEC3;
}
- else if (type == "vec4") {
+ if (type == "vec4") {
return GPU_VEC4;
}
- else if (type == "mat3") {
+ if (type == "mat3") {
return GPU_MAT3;
}
- else if (type == "mat4") {
+ if (type == "mat4") {
return GPU_MAT4;
}
- else if (type == "sampler1DArray") {
+ if (type == "sampler1DArray") {
return GPU_TEX1D_ARRAY;
}
- else if (type == "sampler2DArray") {
+ if (type == "sampler2DArray") {
return GPU_TEX2D_ARRAY;
}
- else if (type == "sampler2D") {
+ if (type == "sampler2D") {
return GPU_TEX2D;
}
- else if (type == "sampler3D") {
+ if (type == "sampler3D") {
return GPU_TEX3D;
}
- else if (type == "Closure") {
+ if (type == "Closure") {
return GPU_CLOSURE;
}
- else {
- return GPU_NONE;
- }
+ return GPU_NONE;
};
func->paramqual[func->totparam] = parse_qualifier(arg_qualifier);
diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp
index 8f5d2742b0e..923a392dbde 100644
--- a/source/blender/io/collada/AnimationImporter.cpp
+++ b/source/blender/io/collada/AnimationImporter.cpp
@@ -1059,7 +1059,7 @@ void AnimationImporter::translate_Animations(
apply_matrix_curves(ob, animcurves, root, node, transform);
}
else {
- /* Calculate RNA-paths and array index of F-curves according to transformation and
+ /* Calculate RNA-paths and array index of F-Curves according to transformation and
* animation class */
Assign_transform_animations(transform, &bindings[j], &animcurves, is_joint, joint_path);
diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt
index 02bd5b2b81f..b1add38bf01 100644
--- a/source/blender/io/common/CMakeLists.txt
+++ b/source/blender/io/common/CMakeLists.txt
@@ -7,6 +7,8 @@ set(INC
../../blenlib
../../depsgraph
../../makesdna
+ ../../../../intern/guardedalloc
+ ../../../../extern/fast_float
)
set(INC_SYS
@@ -17,9 +19,11 @@ set(SRC
intern/dupli_parent_finder.cc
intern/dupli_persistent_id.cc
intern/object_identifier.cc
+ intern/string_utils.cc
IO_abstract_hierarchy_iterator.h
IO_dupli_persistent_id.hh
+ IO_string_utils.hh
IO_types.h
intern/dupli_parent_finder.hh
)
@@ -38,6 +42,7 @@ if(WITH_GTESTS)
intern/abstract_hierarchy_iterator_test.cc
intern/hierarchy_context_order_test.cc
intern/object_identifier_test.cc
+ intern/string_utils_test.cc
)
set(TEST_INC
../../blenloader
diff --git a/source/blender/io/common/IO_string_utils.hh b/source/blender/io/common/IO_string_utils.hh
new file mode 100644
index 00000000000..25f1f01c6ed
--- /dev/null
+++ b/source/blender/io/common/IO_string_utils.hh
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+#include "BLI_string_ref.hh"
+
+/*
+ * Various text parsing utilities commonly used by text-based input formats.
+ */
+
+namespace blender::io {
+
+/**
+ * Fetches next line from an input string buffer.
+ *
+ * The returned line will not have '\n' characters at the end;
+ * the `buffer` is modified to contain remaining text without
+ * the input line.
+ *
+ * Note that backslash (\) character is treated as a line
+ * continuation, similar to OBJ file format or a C preprocessor.
+ */
+StringRef read_next_line(StringRef &buffer);
+
+/**
+ * Drop leading white-space from a StringRef.
+ * Note that backslash character is considered white-space.
+ */
+StringRef drop_whitespace(StringRef str);
+
+/**
+ * Drop leading non-white-space from a StringRef.
+ * Note that backslash character is considered white-space.
+ */
+StringRef drop_non_whitespace(StringRef str);
+
+/**
+ * Parse an integer from an input string.
+ * The parsed result is stored in `dst`. The function skips
+ * leading white-space unless `skip_space=false`. If the
+ * number can't be parsed (invalid syntax, out of range),
+ * `fallback` value is stored instead.
+ *
+ * Returns the remainder of the input string after parsing.
+ */
+StringRef parse_int(StringRef str, int fallback, int &dst, bool skip_space = true);
+
+/**
+ * Parse a float from an input string.
+ * The parsed result is stored in `dst`. The function skips
+ * leading white-space unless `skip_space=false`. If the
+ * number can't be parsed (invalid syntax, out of range),
+ * `fallback` value is stored instead.
+ *
+ * Returns the remainder of the input string after parsing.
+ */
+StringRef parse_float(StringRef str, float fallback, float &dst, bool skip_space = true);
+
+/**
+ * Parse a number of white-space separated floats from an input string.
+ * The parsed `count` numbers are stored in `dst`. If a
+ * number can't be parsed (invalid syntax, out of range),
+ * `fallback` value is stored instead.
+ *
+ * Returns the remainder of the input string after parsing.
+ */
+StringRef parse_floats(StringRef str, float fallback, float *dst, int count);
+
+} // namespace blender::io
diff --git a/source/blender/io/common/intern/string_utils.cc b/source/blender/io/common/intern/string_utils.cc
new file mode 100644
index 00000000000..01107b0866e
--- /dev/null
+++ b/source/blender/io/common/intern/string_utils.cc
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "IO_string_utils.hh"
+
+/* Note: we could use C++17 <charconv> from_chars to parse
+ * floats, but even if some compilers claim full support,
+ * their standard libraries are not quite there yet.
+ * LLVM/libc++ only has a float parser since LLVM 14,
+ * and gcc/libstdc++ since 11.1. So until at least these are
+ * the mininum spec, use an external library. */
+#include "fast_float.h"
+#include <charconv>
+
+namespace blender::io {
+
+StringRef read_next_line(StringRef &buffer)
+{
+ const char *start = buffer.begin();
+ const char *end = buffer.end();
+ size_t len = 0;
+ char prev = 0;
+ const char *ptr = start;
+ while (ptr < end) {
+ char c = *ptr++;
+ if (c == '\n' && prev != '\\') {
+ break;
+ }
+ prev = c;
+ ++len;
+ }
+
+ buffer = StringRef(ptr, end);
+ return StringRef(start, len);
+}
+
+static bool is_whitespace(char c)
+{
+ return c <= ' ' || c == '\\';
+}
+
+StringRef drop_whitespace(StringRef str)
+{
+ while (!str.is_empty() && is_whitespace(str[0])) {
+ str = str.drop_prefix(1);
+ }
+ return str;
+}
+
+StringRef drop_non_whitespace(StringRef str)
+{
+ while (!str.is_empty() && !is_whitespace(str[0])) {
+ str = str.drop_prefix(1);
+ }
+ return str;
+}
+
+static StringRef drop_plus(StringRef str)
+{
+ if (!str.is_empty() && str[0] == '+') {
+ str = str.drop_prefix(1);
+ }
+ return str;
+}
+
+StringRef parse_float(StringRef str, float fallback, float &dst, bool skip_space)
+{
+ if (skip_space) {
+ str = drop_whitespace(str);
+ }
+ str = drop_plus(str);
+ fast_float::from_chars_result res = fast_float::from_chars(str.begin(), str.end(), dst);
+ if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) {
+ dst = fallback;
+ }
+ return StringRef(res.ptr, str.end());
+}
+
+StringRef parse_floats(StringRef str, float fallback, float *dst, int count)
+{
+ for (int i = 0; i < count; ++i) {
+ str = parse_float(str, fallback, dst[i]);
+ }
+ return str;
+}
+
+StringRef parse_int(StringRef str, int fallback, int &dst, bool skip_space)
+{
+ if (skip_space) {
+ str = drop_whitespace(str);
+ }
+ str = drop_plus(str);
+ std::from_chars_result res = std::from_chars(str.begin(), str.end(), dst);
+ if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) {
+ dst = fallback;
+ }
+ return StringRef(res.ptr, str.end());
+}
+
+} // namespace blender::io
diff --git a/source/blender/io/common/intern/string_utils_test.cc b/source/blender/io/common/intern/string_utils_test.cc
new file mode 100644
index 00000000000..a78bd7ab8a3
--- /dev/null
+++ b/source/blender/io/common/intern/string_utils_test.cc
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include "IO_string_utils.hh"
+
+#include "testing/testing.h"
+
+namespace blender::io {
+
+#define EXPECT_STRREF_EQ(str1, str2) EXPECT_STREQ(str1, std::string(str2).c_str())
+
+TEST(string_utils, read_next_line)
+{
+ std::string str = "abc\n \n\nline with \\\ncontinuation\nCRLF ending:\r\na";
+ StringRef s = str;
+ EXPECT_STRREF_EQ("abc", read_next_line(s));
+ EXPECT_STRREF_EQ(" ", read_next_line(s));
+ EXPECT_STRREF_EQ("", read_next_line(s));
+ EXPECT_STRREF_EQ("line with \\\ncontinuation", read_next_line(s));
+ EXPECT_STRREF_EQ("CRLF ending:\r", read_next_line(s));
+ EXPECT_STRREF_EQ("a", read_next_line(s));
+ EXPECT_TRUE(s.is_empty());
+}
+
+TEST(string_utils, drop_whitespace)
+{
+ /* Empty */
+ EXPECT_STRREF_EQ("", drop_whitespace(""));
+ /* Only whitespace */
+ EXPECT_STRREF_EQ("", drop_whitespace(" "));
+ EXPECT_STRREF_EQ("", drop_whitespace(" "));
+ EXPECT_STRREF_EQ("", drop_whitespace(" \t\n\r "));
+ /* Drops leading whitespace */
+ EXPECT_STRREF_EQ("a", drop_whitespace(" a"));
+ EXPECT_STRREF_EQ("a b", drop_whitespace(" a b"));
+ EXPECT_STRREF_EQ("a b ", drop_whitespace(" a b "));
+ /* No leading whitespace */
+ EXPECT_STRREF_EQ("c", drop_whitespace("c"));
+ /* Case with backslash, should be treated as whitespace */
+ EXPECT_STRREF_EQ("d", drop_whitespace(" \\ d"));
+}
+
+TEST(string_utils, parse_int_valid)
+{
+ std::string str = "1 -10 \t 1234 1234567890 +7 123a";
+ StringRef s = str;
+ int val;
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(1, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(-10, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(1234, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(1234567890, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(7, val);
+ s = parse_int(s, 0, val);
+ EXPECT_EQ(123, val);
+ EXPECT_STRREF_EQ("a", s);
+}
+
+TEST(string_utils, parse_int_invalid)
+{
+ int val;
+ /* Invalid syntax */
+ EXPECT_STRREF_EQ("--123", parse_int("--123", -1, val));
+ EXPECT_EQ(val, -1);
+ EXPECT_STRREF_EQ("foobar", parse_int("foobar", -2, val));
+ EXPECT_EQ(val, -2);
+ /* Out of integer range */
+ EXPECT_STRREF_EQ(" a", parse_int("1234567890123 a", -3, val));
+ EXPECT_EQ(val, -3);
+ /* Has leading white-space when we don't expect it */
+ EXPECT_STRREF_EQ(" 1", parse_int(" 1", -4, val, false));
+ EXPECT_EQ(val, -4);
+}
+
+TEST(string_utils, parse_float_valid)
+{
+ std::string str = "1 -10 123.5 -17.125 0.1 1e6 50.0e-1";
+ StringRef s = str;
+ float val;
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(1.0f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(-10.0f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(123.5f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(-17.125f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(0.1f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(1.0e6f, val);
+ s = parse_float(s, 0, val);
+ EXPECT_EQ(5.0f, val);
+ EXPECT_TRUE(s.is_empty());
+}
+
+TEST(string_utils, parse_float_invalid)
+{
+ float val;
+ /* Invalid syntax */
+ EXPECT_STRREF_EQ("_0", parse_float("_0", -1.0f, val));
+ EXPECT_EQ(val, -1.0f);
+ EXPECT_STRREF_EQ("..5", parse_float("..5", -2.0f, val));
+ EXPECT_EQ(val, -2.0f);
+ /* Out of float range. Current float parser (fast_float)
+ * clamps out of range numbers to +/- infinity, so this
+ * one gets a +inf instead of fallback -3.0. */
+ EXPECT_STRREF_EQ(" a", parse_float("9.0e500 a", -3.0f, val));
+ EXPECT_EQ(val, std::numeric_limits<float>::infinity());
+ /* Has leading white-space when we don't expect it */
+ EXPECT_STRREF_EQ(" 1", parse_float(" 1", -4.0f, val, false));
+ EXPECT_EQ(val, -4.0f);
+}
+
+} // namespace blender::io
diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt
index 2b5ea39617e..e2e959814fa 100644
--- a/source/blender/io/usd/CMakeLists.txt
+++ b/source/blender/io/usd/CMakeLists.txt
@@ -16,6 +16,24 @@ add_definitions(-DPXR_STATIC)
# USD headers use deprecated TBB headers, silence warning.
add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)
+# Check if USD has the imaging headers available, if they are
+# add a USD_HAS_IMAGING define so code can dynamically detect this.
+# Cleanup of this variable is done at the end of the file since
+# test code further down uses it to add imaging tests.
+FIND_FILE(USD_IMAGING_HEADERS
+ NAMES
+ capsuleAdapter.h
+ PATHS
+ ${USD_INCLUDE_DIRS}
+ PATH_SUFFIXES
+ pxr/usdImaging/usdImaging/
+ NO_DEFAULT_PATH
+)
+
+if(USD_IMAGING_HEADERS)
+ add_definitions(-DUSD_HAS_IMAGING)
+endif()
+
set(INC
.
../common
@@ -129,7 +147,13 @@ target_link_libraries(bf_usd INTERFACE ${TBB_LIBRARIES})
if(WITH_GTESTS)
set(TEST_SRC
tests/usd_stage_creation_test.cc
+ tests/usd_tests_common.cc
+ tests/usd_tests_common.h
)
+ if(USD_IMAGING_HEADERS)
+ LIST(APPEND TEST_SRC tests/usd_imaging_test.cc)
+ endif()
+
set(TEST_INC
)
set(TEST_LIB
@@ -137,3 +161,7 @@ if(WITH_GTESTS)
include(GTestTesting)
blender_add_test_lib(bf_io_usd_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
endif()
+
+# In cmake version 3.21 and up, we can instead use the NO_CACHE option for
+# find_file so we don't need to clear it from the cache here.
+unset(USD_IMAGING_HEADERS CACHE)
diff --git a/source/blender/io/usd/tests/usd_imaging_test.cc b/source/blender/io/usd/tests/usd_imaging_test.cc
new file mode 100644
index 00000000000..497319c59bd
--- /dev/null
+++ b/source/blender/io/usd/tests/usd_imaging_test.cc
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+#include "testing/testing.h"
+
+#include "usd_tests_common.h"
+
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdGeom/capsule.h>
+#include <pxr/usdImaging/usdImaging/capsuleAdapter.h>
+
+namespace blender::io::usd {
+
+class USDImagingTest : public testing::Test {
+};
+
+TEST_F(USDImagingTest, CapsuleAdapterTest)
+{
+ /* A simple test to exercise the UsdImagingGprimAdapter API to
+ * ensure the code compiles, links and returns reasonable results.
+ * We create a capsule shape on an in-memory stage and attempt
+ * to access the shape's points and topology. */
+
+ /* We must register USD plugin paths before creating the stage
+ * to avoid a crash in the USD asset resolver initialization code. */
+ if (register_usd_plugins_for_tests().empty()) {
+ FAIL();
+ return;
+ }
+
+ pxr::UsdStageRefPtr stage = pxr::UsdStage::CreateInMemory();
+
+ if (!stage) {
+ FAIL() << "Couldn't create in-memory stage.";
+ return;
+ }
+
+ pxr::UsdGeomCapsule capsule = pxr::UsdGeomCapsule::Define(stage, pxr::SdfPath("/Capsule"));
+
+ if (!capsule) {
+ FAIL() << "Couldn't create UsdGeomCapsule.";
+ return;
+ }
+
+ pxr::UsdImagingCapsuleAdapter capsule_adapter;
+ pxr::VtValue points_value = capsule_adapter.GetMeshPoints(capsule.GetPrim(),
+ pxr::UsdTimeCode::Default());
+ if (!points_value.IsHolding<pxr::VtArray<pxr::GfVec3f>>()) {
+ FAIL() << "Mesh points value holding unexpected type.";
+ return;
+ }
+
+ pxr::VtArray<pxr::GfVec3f> points = points_value.Get<pxr::VtArray<pxr::GfVec3f>>();
+ EXPECT_FALSE(points.empty());
+
+ pxr::VtValue topology_value = capsule_adapter.GetMeshTopology();
+
+ if (!topology_value.IsHolding<pxr::HdMeshTopology>()) {
+ FAIL() << "Mesh topology value holding unexpected type.";
+ return;
+ }
+
+ pxr::HdMeshTopology topology = topology_value.Get<pxr::HdMeshTopology>();
+
+ pxr::VtArray<int> vertex_counts = topology.GetFaceVertexCounts();
+ EXPECT_FALSE(vertex_counts.empty());
+
+ pxr::VtArray<int> vertex_indices = topology.GetFaceVertexIndices();
+ EXPECT_FALSE(vertex_indices.empty());
+}
+
+} // namespace blender::io::usd
diff --git a/source/blender/io/usd/tests/usd_stage_creation_test.cc b/source/blender/io/usd/tests/usd_stage_creation_test.cc
index dbe5f8cd9ce..1aa6f9691ff 100644
--- a/source/blender/io/usd/tests/usd_stage_creation_test.cc
+++ b/source/blender/io/usd/tests/usd_stage_creation_test.cc
@@ -2,6 +2,8 @@
* Copyright 2019 Blender Foundation. All rights reserved. */
#include "testing/testing.h"
+#include "usd_tests_common.h"
+
#include <pxr/base/plug/registry.h>
#include <pxr/usd/usd/stage.h>
@@ -19,23 +21,12 @@ class USDStageCreationTest : public testing::Test {
TEST_F(USDStageCreationTest, JSONFileLoadingTest)
{
- const std::string &release_dir = blender::tests::flags_test_release_dir();
- if (release_dir.empty()) {
+ std::string usd_datafiles_dir = register_usd_plugins_for_tests();
+ if (usd_datafiles_dir.empty()) {
FAIL();
+ return;
}
- char usd_datafiles_dir[FILE_MAX];
- const size_t path_len = BLI_path_join(
- usd_datafiles_dir, FILE_MAX, release_dir.c_str(), "datafiles", "usd", nullptr);
-
- /* #BLI_path_join removes trailing slashes, but the USD library requires one in order to
- * recognize the path as directory. */
- BLI_assert(path_len + 1 < FILE_MAX);
- usd_datafiles_dir[path_len] = '/';
- usd_datafiles_dir[path_len + 1] = '\0';
-
- pxr::PlugRegistry::GetInstance().RegisterPlugins(usd_datafiles_dir);
-
/* Simply the ability to create a USD Stage for a specific filename means that the extension
* has been recognized by the USD library, and that a USD plugin has been loaded to write such
* files. Practically, this is a test to see whether the USD JSON files can be found and
diff --git a/source/blender/io/usd/tests/usd_tests_common.cc b/source/blender/io/usd/tests/usd_tests_common.cc
new file mode 100644
index 00000000000..9f18a289433
--- /dev/null
+++ b/source/blender/io/usd/tests/usd_tests_common.cc
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+
+#include "usd_tests_common.h"
+
+#include "testing/testing.h"
+
+#include <pxr/base/plug/registry.h>
+
+#include "BLI_path_util.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_appdir.h"
+
+namespace blender::io::usd {
+
+std::string register_usd_plugins_for_tests()
+{
+ static char usd_datafiles_dir[FILE_MAX] = {'\0'};
+ static bool plugin_path_registered = false;
+ if (plugin_path_registered) {
+ return usd_datafiles_dir;
+ }
+ plugin_path_registered = true;
+
+ const std::string &release_dir = blender::tests::flags_test_release_dir();
+ if (release_dir.empty()) {
+ return "";
+ }
+
+ const size_t path_len = BLI_path_join(
+ usd_datafiles_dir, FILE_MAX, release_dir.c_str(), "datafiles", "usd", nullptr);
+
+ /* #BLI_path_join removes trailing slashes, but the USD library requires one in order to
+ * recognize the path as directory. */
+ BLI_assert(path_len + 1 < FILE_MAX);
+ usd_datafiles_dir[path_len] = '/';
+ usd_datafiles_dir[path_len + 1] = '\0';
+
+ pxr::PlugRegistry::GetInstance().RegisterPlugins(usd_datafiles_dir);
+
+ return usd_datafiles_dir;
+}
+
+} // namespace blender::io::usd
diff --git a/source/blender/io/usd/tests/usd_tests_common.h b/source/blender/io/usd/tests/usd_tests_common.h
new file mode 100644
index 00000000000..b298a253ddc
--- /dev/null
+++ b/source/blender/io/usd/tests/usd_tests_common.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2022 Blender Foundation. All rights reserved. */
+#pragma once
+
+#include <string>
+
+namespace blender::io::usd {
+
+/* Calls the function to load the USD plugins from the
+ * USD data directory under the Blender bin directory
+ * that was supplied as the --test-release-dir flag to ctest.
+ * Thus function must be called before instantiating a USD
+ * stage to avoid errors. The returned string is the path to
+ * the USD data files directory from which the plugins were
+ * loaded. If the USD data files directory can't be determined,
+ * plugin registration is skipped and the empty string is
+ * returned. */
+std::string register_usd_plugins_for_tests();
+
+} // namespace blender::io::usd
diff --git a/source/blender/io/wavefront_obj/CMakeLists.txt b/source/blender/io/wavefront_obj/CMakeLists.txt
index 9cdd96ee7be..67cec000778 100644
--- a/source/blender/io/wavefront_obj/CMakeLists.txt
+++ b/source/blender/io/wavefront_obj/CMakeLists.txt
@@ -4,6 +4,7 @@ set(INC
.
./exporter
./importer
+ ../common
../../blenkernel
../../blenlib
../../bmesh
@@ -35,7 +36,6 @@ set(SRC
importer/obj_import_mtl.cc
importer/obj_import_nurbs.cc
importer/obj_importer.cc
- importer/parser_string_utils.cc
IO_wavefront_obj.h
exporter/obj_export_file_writer.hh
@@ -51,11 +51,11 @@ set(SRC
importer/obj_import_nurbs.hh
importer/obj_import_objects.hh
importer/obj_importer.hh
- importer/parser_string_utils.hh
)
set(LIB
bf_blenkernel
+ bf_io_common
)
if(WITH_TBB)
@@ -70,6 +70,7 @@ if(WITH_GTESTS)
set(TEST_SRC
tests/obj_exporter_tests.cc
tests/obj_importer_tests.cc
+ tests/obj_mtl_parser_tests.cc
tests/obj_exporter_tests.hh
)
diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h
index b06ccbf8702..8b71ec750c0 100644
--- a/source/blender/io/wavefront_obj/IO_wavefront_obj.h
+++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h
@@ -84,6 +84,7 @@ struct OBJImportParams {
float clamp_size;
eTransformAxisForward forward_axis;
eTransformAxisUp up_axis;
+ bool validate_meshes;
};
/**
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
index f78ef334d4d..96b342252c4 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
@@ -9,6 +9,7 @@
#include "BKE_blender_version.h"
+#include "BLI_enumerable_thread_specific.hh"
#include "BLI_path_util.h"
#include "BLI_task.hh"
@@ -308,6 +309,9 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
obj_mesh_data.tot_uv_vertices());
const int tot_polygons = obj_mesh_data.tot_polygons();
+ const int tot_deform_groups = obj_mesh_data.tot_deform_groups();
+ threading::EnumerableThreadSpecific<Vector<float>> group_weights;
+
obj_parallel_chunked_output(fh, tot_polygons, [&](FormatHandler<eFileType::OBJ> &buf, int idx) {
/* Polygon order for writing into the file is not necessarily the same
* as order in the mesh; it will be sorted by material indices. Remap current
@@ -330,9 +334,12 @@ void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
/* Write vertex group if different from previous. */
if (export_params_.export_vertex_groups) {
+ Vector<float> &local_weights = group_weights.local();
+ local_weights.resize(tot_deform_groups);
const int16_t prev_group = idx == 0 ? NEGATIVE_INIT :
- obj_mesh_data.get_poly_deform_group_index(prev_i);
- const int16_t group = obj_mesh_data.get_poly_deform_group_index(i);
+ obj_mesh_data.get_poly_deform_group_index(
+ prev_i, local_weights);
+ const int16_t group = obj_mesh_data.get_poly_deform_group_index(i, local_weights);
if (group != prev_group) {
buf.write<eOBJSyntaxElement::object_group>(
group == NOT_FOUND ? DEFORM_GROUP_DISABLED :
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
index 8c9b04a5ac3..c2a9e0574eb 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
@@ -426,6 +426,9 @@ void OBJMesh::store_normal_coords_and_indices()
Vector<int> OBJMesh::calc_poly_normal_indices(const int poly_index) const
{
+ if (loop_to_normal_index_.is_empty()) {
+ return {};
+ }
const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
const int totloop = mpoly.totloop;
Vector<int> r_poly_normal_indices(totloop);
@@ -436,46 +439,47 @@ Vector<int> OBJMesh::calc_poly_normal_indices(const int poly_index) const
return r_poly_normal_indices;
}
-int16_t OBJMesh::get_poly_deform_group_index(const int poly_index) const
+int OBJMesh::tot_deform_groups() const
+{
+ if (!BKE_object_supports_vertex_groups(&export_object_eval_)) {
+ return 0;
+ }
+ return BKE_object_defgroup_count(&export_object_eval_);
+}
+
+int16_t OBJMesh::get_poly_deform_group_index(const int poly_index,
+ MutableSpan<float> group_weights) const
{
BLI_assert(poly_index < export_mesh_eval_->totpoly);
- const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
- const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart];
- const Object *obj = &export_object_eval_;
- const int tot_deform_groups = BKE_object_defgroup_count(obj);
- /* Indices of the vector index into deform groups of an object; values are the]
- * number of vertex members in one deform group. */
- Vector<int16_t> deform_group_members(tot_deform_groups, 0);
- /* Whether at least one vertex in the polygon belongs to any group. */
- bool found_group = false;
-
- const MDeformVert *dvert_orig = static_cast<MDeformVert *>(
+ BLI_assert(group_weights.size() == BKE_object_defgroup_count(&export_object_eval_));
+
+ const MDeformVert *dvert_layer = static_cast<MDeformVert *>(
CustomData_get_layer(&export_mesh_eval_->vdata, CD_MDEFORMVERT));
- if (!dvert_orig) {
+ if (!dvert_layer) {
return NOT_FOUND;
}
- const MDeformWeight *curr_weight = nullptr;
- const MDeformVert *dvert = nullptr;
- for (int loop_index = 0; loop_index < mpoly.totloop; loop_index++) {
- dvert = &dvert_orig[(mloop + loop_index)->v];
- curr_weight = dvert->dw;
- if (curr_weight) {
- bDeformGroup *vertex_group = static_cast<bDeformGroup *>(
- BLI_findlink(BKE_object_defgroup_list(obj), curr_weight->def_nr));
- if (vertex_group) {
- deform_group_members[curr_weight->def_nr] += 1;
- found_group = true;
+ group_weights.fill(0);
+ bool found_any_group = false;
+ const MPoly &mpoly = export_mesh_eval_->mpoly[poly_index];
+ const MLoop *mloop = &export_mesh_eval_->mloop[mpoly.loopstart];
+ for (int loop_i = 0; loop_i < mpoly.totloop; ++loop_i, ++mloop) {
+ const MDeformVert &dvert = dvert_layer[mloop->v];
+ for (int weight_i = 0; weight_i < dvert.totweight; ++weight_i) {
+ const auto group = dvert.dw[weight_i].def_nr;
+ if (group < group_weights.size()) {
+ group_weights[group] += dvert.dw[weight_i].weight;
+ found_any_group = true;
}
}
}
- if (!found_group) {
+ if (!found_any_group) {
return NOT_FOUND;
}
/* Index of the group with maximum vertices. */
- int16_t max_idx = std::max_element(deform_group_members.begin(), deform_group_members.end()) -
- deform_group_members.begin();
+ int16_t max_idx = std::max_element(group_weights.begin(), group_weights.end()) -
+ group_weights.begin();
return max_idx;
}
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
index 7650a220810..f47ca423dbc 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
@@ -116,6 +116,7 @@ class OBJMesh : NonCopyable {
int tot_uv_vertices() const;
int tot_normal_indices() const;
int tot_edges() const;
+ int tot_deform_groups() const;
bool is_mirrored_transform() const
{
return mirrored_transform_;
@@ -204,13 +205,15 @@ class OBJMesh : NonCopyable {
*/
Vector<int> calc_poly_normal_indices(int poly_index) const;
/**
- * Find the index of the vertex group with the maximum number of vertices in a polygon.
- * The index indices into the #Object.defbase.
+ * Find the most representative vertex group of a polygon.
+ *
+ * This adds up vertex group weights, and the group with the largest
+ * weight sum across the polygon is the one returned.
*
- * If two or more groups have the same number of vertices (maximum), group name depends on the
- * implementation of #std::max_element.
+ * group_weights is temporary storage to avoid reallocations, it must
+ * be the size of amount of vertex groups in the object.
*/
- int16_t get_poly_deform_group_index(int poly_index) const;
+ int16_t get_poly_deform_group_index(int poly_index, MutableSpan<float> group_weights) const;
/**
* Find the name of the vertex deform group at the given index.
* The index indices into the #Object.defbase.
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
index 17069c18295..c247048ce13 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_nurbs.cc
@@ -72,12 +72,12 @@ float3 OBJCurve::vertex_coordinates(const int spline_index,
int OBJCurve::total_spline_control_points(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
- const int r_nurbs_degree = nurb->orderu - 1;
+ int degree = nurb->type == CU_POLY ? 1 : nurb->orderu - 1;
/* Total control points = Number of points in the curve (+ degree of the
* curve if it is cyclic). */
int r_tot_control_points = nurb->pntsv * nurb->pntsu;
if (nurb->flagu & CU_NURB_CYCLIC) {
- r_tot_control_points += r_nurbs_degree;
+ r_tot_control_points += degree;
}
return r_tot_control_points;
}
@@ -85,7 +85,7 @@ int OBJCurve::total_spline_control_points(const int spline_index) const
int OBJCurve::get_nurbs_degree(const int spline_index) const
{
const Nurb *const nurb = static_cast<Nurb *>(BLI_findlink(&export_curve_->nurb, spline_index));
- return nurb->orderu - 1;
+ return nurb->type == CU_POLY ? 1 : nurb->orderu - 1;
}
short OBJCurve::get_nurbs_flagu(const int spline_index) const
diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
index 584d8ae4ec0..78b709c884a 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
@@ -68,6 +68,17 @@ static void print_exception_error(const std::system_error &ex)
<< std::endl;
}
+static bool is_curve_nurbs_compatible(const Nurb *nurb)
+{
+ while (nurb) {
+ if (nurb->type == CU_BEZIER || nurb->pntsv != 1) {
+ return false;
+ }
+ nurb = nurb->next;
+ }
+ return true;
+}
+
/**
* Filter supported objects from the Scene.
*
@@ -104,27 +115,13 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par
}
break;
}
- switch (nurb->type) {
- case CU_NURBS:
- if (export_params.export_curves_as_nurbs) {
- /* Export in parameter form: control points. */
- r_exportable_nurbs.append(
- std::make_unique<OBJCurve>(depsgraph, export_params, object));
- }
- else {
- /* Export in mesh form: edges and vertices. */
- r_exportable_meshes.append(
- std::make_unique<OBJMesh>(depsgraph, export_params, object));
- }
- break;
- case CU_BEZIER:
- /* Always export in mesh form: edges and vertices. */
- r_exportable_meshes.append(
- std::make_unique<OBJMesh>(depsgraph, export_params, object));
- break;
- default:
- /* Other curve types are not supported. */
- break;
+ if (export_params.export_curves_as_nurbs && is_curve_nurbs_compatible(nurb)) {
+ /* Export in parameter form: control points. */
+ r_exportable_nurbs.append(std::make_unique<OBJCurve>(depsgraph, export_params, object));
+ }
+ else {
+ /* Export in mesh form: edges and vertices. */
+ r_exportable_meshes.append(std::make_unique<OBJMesh>(depsgraph, export_params, object));
}
break;
}
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
index d3d4e1ba860..3e722f8a0dd 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc
@@ -8,7 +8,7 @@
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
-#include "parser_string_utils.hh"
+#include "IO_string_utils.hh"
#include "obj_import_file_reader.hh"
@@ -34,7 +34,8 @@ static Geometry *create_geometry(Geometry *const prev_geometry,
Geometry *g = r_all_geometries.last().get();
g->geom_type_ = new_type;
g->geometry_name_ = name.is_empty() ? "New object" : name;
- r_offset.set_index_offset(global_vertices.vertices.size());
+ g->vertex_start_ = global_vertices.vertices.size();
+ r_offset.set_index_offset(g->vertex_start_);
return g;
};
@@ -66,48 +67,40 @@ static Geometry *create_geometry(Geometry *const prev_geometry,
}
static void geom_add_vertex(Geometry *geom,
- const StringRef rest_line,
+ const StringRef line,
GlobalVertices &r_global_vertices)
{
- float3 curr_vert;
- Vector<StringRef> str_vert_split;
- split_by_char(rest_line, ' ', str_vert_split);
- copy_string_to_float(str_vert_split, FLT_MAX, {curr_vert, 3});
- r_global_vertices.vertices.append(curr_vert);
- geom->vertex_indices_.append(r_global_vertices.vertices.size() - 1);
+ float3 vert;
+ parse_floats(line, FLT_MAX, vert, 3);
+ r_global_vertices.vertices.append(vert);
+ geom->vertex_count_++;
}
static void geom_add_vertex_normal(Geometry *geom,
- const StringRef rest_line,
+ const StringRef line,
GlobalVertices &r_global_vertices)
{
- float3 curr_vert_normal;
- Vector<StringRef> str_vert_normal_split;
- split_by_char(rest_line, ' ', str_vert_normal_split);
- copy_string_to_float(str_vert_normal_split, FLT_MAX, {curr_vert_normal, 3});
- r_global_vertices.vertex_normals.append(curr_vert_normal);
+ float3 normal;
+ parse_floats(line, FLT_MAX, normal, 3);
+ r_global_vertices.vertex_normals.append(normal);
geom->has_vertex_normals_ = true;
}
-static void geom_add_uv_vertex(const StringRef rest_line, GlobalVertices &r_global_vertices)
+static void geom_add_uv_vertex(const StringRef line, GlobalVertices &r_global_vertices)
{
- float2 curr_uv_vert;
- Vector<StringRef> str_uv_vert_split;
- split_by_char(rest_line, ' ', str_uv_vert_split);
- copy_string_to_float(str_uv_vert_split, FLT_MAX, {curr_uv_vert, 2});
- r_global_vertices.uv_vertices.append(curr_uv_vert);
+ float2 uv;
+ parse_floats(line, FLT_MAX, uv, 2);
+ r_global_vertices.uv_vertices.append(uv);
}
static void geom_add_edge(Geometry *geom,
- const StringRef rest_line,
+ StringRef line,
const VertexIndexOffset &offsets,
GlobalVertices &r_global_vertices)
{
- int edge_v1 = -1, edge_v2 = -1;
- Vector<StringRef> str_edge_split;
- split_by_char(rest_line, ' ', str_edge_split);
- copy_string_to_int(str_edge_split[0], -1, edge_v1);
- copy_string_to_int(str_edge_split[1], -1, edge_v2);
+ int edge_v1, edge_v2;
+ line = parse_int(line, -1, edge_v1);
+ line = parse_int(line, -1, edge_v2);
/* Always keep stored indices non-negative and zero-based. */
edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1;
@@ -116,78 +109,45 @@ static void geom_add_edge(Geometry *geom,
}
static void geom_add_polygon(Geometry *geom,
- const StringRef rest_line,
+ StringRef line,
const GlobalVertices &global_vertices,
const VertexIndexOffset &offsets,
- const StringRef state_material_name,
- const StringRef state_object_group,
- const bool state_shaded_smooth)
+ const int material_index,
+ const int group_index,
+ const bool shaded_smooth)
{
PolyElem curr_face;
- curr_face.shaded_smooth = state_shaded_smooth;
- if (!state_material_name.is_empty()) {
- curr_face.material_name = state_material_name;
- }
- if (!state_object_group.is_empty()) {
- curr_face.vertex_group = state_object_group;
- /* Yes it repeats several times, but another if-check will not reduce steps either. */
+ curr_face.shaded_smooth = shaded_smooth;
+ curr_face.material_index = material_index;
+ if (group_index >= 0) {
+ curr_face.vertex_group_index = group_index;
geom->use_vertex_groups_ = true;
}
+ const int orig_corners_size = geom->face_corners_.size();
+ curr_face.start_index_ = orig_corners_size;
+
bool face_valid = true;
- Vector<StringRef> str_corners_split;
- split_by_char(rest_line, ' ', str_corners_split);
- for (StringRef str_corner : str_corners_split) {
+ while (!line.is_empty() && face_valid) {
PolyCorner corner;
- const size_t n_slash = std::count(str_corner.begin(), str_corner.end(), '/');
bool got_uv = false, got_normal = false;
- if (n_slash == 0) {
- /* Case: "f v1 v2 v3". */
- copy_string_to_int(str_corner, INT32_MAX, corner.vert_index);
- }
- else if (n_slash == 1) {
- /* Case: "f v1/vt1 v2/vt2 v3/vt3". */
- Vector<StringRef> vert_uv_split;
- split_by_char(str_corner, '/', vert_uv_split);
- if (vert_uv_split.size() != 1 && vert_uv_split.size() != 2) {
- fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str());
- face_valid = false;
+ /* Parse vertex index. */
+ line = parse_int(line, INT32_MAX, corner.vert_index, false);
+ face_valid &= corner.vert_index != INT32_MAX;
+ if (!line.is_empty() && line[0] == '/') {
+ /* Parse UV index. */
+ line = line.drop_prefix(1);
+ if (!line.is_empty() && line[0] != '/') {
+ line = parse_int(line, INT32_MAX, corner.uv_vert_index, false);
+ got_uv = corner.uv_vert_index != INT32_MAX;
}
- else {
- copy_string_to_int(vert_uv_split[0], INT32_MAX, corner.vert_index);
- if (vert_uv_split.size() == 2) {
- copy_string_to_int(vert_uv_split[1], INT32_MAX, corner.uv_vert_index);
- got_uv = corner.uv_vert_index != INT32_MAX;
- }
+ /* Parse normal index. */
+ if (!line.is_empty() && line[0] == '/') {
+ line = line.drop_prefix(1);
+ line = parse_int(line, INT32_MAX, corner.vertex_normal_index, false);
+ got_normal = corner.uv_vert_index != INT32_MAX;
}
}
- else if (n_slash == 2) {
- /* Case: "f v1//vn1 v2//vn2 v3//vn3". */
- /* Case: "f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3". */
- Vector<StringRef> vert_uv_normal_split;
- split_by_char(str_corner, '/', vert_uv_normal_split);
- if (vert_uv_normal_split.size() != 2 && vert_uv_normal_split.size() != 3) {
- fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str());
- face_valid = false;
- }
- else {
- copy_string_to_int(vert_uv_normal_split[0], INT32_MAX, corner.vert_index);
- if (vert_uv_normal_split.size() == 3) {
- copy_string_to_int(vert_uv_normal_split[1], INT32_MAX, corner.uv_vert_index);
- got_uv = corner.uv_vert_index != INT32_MAX;
- copy_string_to_int(vert_uv_normal_split[2], INT32_MAX, corner.vertex_normal_index);
- got_normal = corner.vertex_normal_index != INT32_MAX;
- }
- else {
- copy_string_to_int(vert_uv_normal_split[1], INT32_MAX, corner.vertex_normal_index);
- got_normal = corner.vertex_normal_index != INT32_MAX;
- }
- }
- }
- else {
- fprintf(stderr, "Invalid face syntax '%s', ignoring\n", std::string(str_corner).c_str());
- face_valid = false;
- }
/* Always keep stored indices non-negative and zero-based. */
corner.vert_index += corner.vert_index < 0 ? global_vertices.vertices.size() :
-offsets.get_index_offset() - 1;
@@ -221,19 +181,28 @@ static void geom_add_polygon(Geometry *geom,
face_valid = false;
}
}
- curr_face.face_corners.append(corner);
+ geom->face_corners_.append(corner);
+ curr_face.corner_count_++;
+
+ /* Skip whitespace to get to the next face corner. */
+ line = drop_whitespace(line);
}
if (face_valid) {
geom->face_elements_.append(curr_face);
- geom->total_loops_ += curr_face.face_corners.size();
+ geom->total_loops_ += curr_face.corner_count_;
+ }
+ else {
+ /* Remove just-added corners for the invalid face. */
+ geom->face_corners_.resize(orig_corners_size);
+ geom->has_invalid_polys_ = true;
}
}
static Geometry *geom_set_curve_type(Geometry *geom,
const StringRef rest_line,
const GlobalVertices &global_vertices,
- const StringRef state_object_group,
+ const StringRef group_name,
VertexIndexOffset &r_offsets,
Vector<std::unique_ptr<Geometry>> &r_all_geometries)
{
@@ -242,254 +211,411 @@ static Geometry *geom_set_curve_type(Geometry *geom,
return geom;
}
geom = create_geometry(
- geom, GEOM_CURVE, state_object_group, global_vertices, r_all_geometries, r_offsets);
- geom->nurbs_element_.group_ = state_object_group;
+ geom, GEOM_CURVE, group_name, global_vertices, r_all_geometries, r_offsets);
+ geom->nurbs_element_.group_ = group_name;
return geom;
}
-static void geom_set_curve_degree(Geometry *geom, const StringRef rest_line)
+static void geom_set_curve_degree(Geometry *geom, const StringRef line)
{
- copy_string_to_int(rest_line, 3, geom->nurbs_element_.degree);
+ parse_int(line, 3, geom->nurbs_element_.degree);
}
static void geom_add_curve_vertex_indices(Geometry *geom,
- const StringRef rest_line,
+ StringRef line,
const GlobalVertices &global_vertices)
{
- Vector<StringRef> str_curv_split;
- split_by_char(rest_line, ' ', str_curv_split);
- /* Remove "0.0" and "1.0" from the strings. They are hardcoded. */
- str_curv_split.remove(0);
- str_curv_split.remove(0);
- geom->nurbs_element_.curv_indices.resize(str_curv_split.size());
- copy_string_to_int(str_curv_split, INT32_MAX, geom->nurbs_element_.curv_indices);
- for (int &curv_index : geom->nurbs_element_.curv_indices) {
+ /* Curve lines always have "0.0" and "1.0", skip over them. */
+ float dummy[2];
+ line = parse_floats(line, 0, dummy, 2);
+ /* Parse indices. */
+ while (!line.is_empty()) {
+ int index;
+ line = parse_int(line, INT32_MAX, index);
+ if (index == INT32_MAX) {
+ return;
+ }
/* Always keep stored indices non-negative and zero-based. */
- curv_index += curv_index < 0 ? global_vertices.vertices.size() : -1;
+ index += index < 0 ? global_vertices.vertices.size() : -1;
+ geom->nurbs_element_.curv_indices.append(index);
}
}
-static void geom_add_curve_parameters(Geometry *geom, const StringRef rest_line)
+static void geom_add_curve_parameters(Geometry *geom, StringRef line)
{
- Vector<StringRef> str_parm_split;
- split_by_char(rest_line, ' ', str_parm_split);
- if (str_parm_split[0] != "u" && str_parm_split[0] != "v") {
- std::cerr << "Surfaces are not supported:'" << str_parm_split[0] << "'" << std::endl;
+ line = drop_whitespace(line);
+ if (line.is_empty()) {
+ std::cerr << "Invalid OBJ curve parm line: '" << line << "'" << std::endl;
+ return;
+ }
+ if (line[0] != 'u') {
+ std::cerr << "OBJ curve surfaces are not supported: '" << line[0] << "'" << std::endl;
return;
}
- str_parm_split.remove(0);
- geom->nurbs_element_.parm.resize(str_parm_split.size());
- copy_string_to_float(str_parm_split, FLT_MAX, geom->nurbs_element_.parm);
+ line = line.drop_prefix(1);
+
+ while (!line.is_empty()) {
+ float val;
+ line = parse_float(line, FLT_MAX, val);
+ if (val != FLT_MAX) {
+ geom->nurbs_element_.parm.append(val);
+ }
+ else {
+ std::cerr << "OBJ curve parm line has invalid number" << std::endl;
+ return;
+ }
+ }
}
-static void geom_update_object_group(const StringRef rest_line, std::string &r_state_object_group)
+static void geom_update_group(const StringRef rest_line, std::string &r_group_name)
{
if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos ||
rest_line.find("default") != string::npos) {
/* Set group for future elements like faces or curves to empty. */
- r_state_object_group = "";
+ r_group_name = "";
return;
}
- r_state_object_group = rest_line;
-}
-
-static void geom_update_polygon_material(Geometry *geom,
- const StringRef rest_line,
- std::string &r_state_material_name)
-{
- /* Materials may repeat if faces are written without sorting. */
- geom->material_names_.add(string(rest_line));
- r_state_material_name = rest_line;
+ r_group_name = rest_line;
}
-static void geom_update_smooth_group(const StringRef rest_line, bool &r_state_shaded_smooth)
+static void geom_update_smooth_group(StringRef line, bool &r_state_shaded_smooth)
{
+ line = drop_whitespace(line);
/* Some implementations use "0" and "null" too, in addition to "off". */
- if (rest_line != "0" && rest_line.find("off") == StringRef::not_found &&
- rest_line.find("null") == StringRef::not_found) {
- int smooth = 0;
- copy_string_to_int(rest_line, 0, smooth);
- r_state_shaded_smooth = smooth != 0;
- }
- else {
- /* The OBJ file explicitly set shading to off. */
+ if (line == "0" || line.startswith("off") || line.startswith("null")) {
r_state_shaded_smooth = false;
+ return;
}
+
+ int smooth = 0;
+ parse_int(line, 0, smooth);
+ r_state_shaded_smooth = smooth != 0;
}
-OBJParser::OBJParser(const OBJImportParams &import_params) : import_params_(import_params)
+OBJParser::OBJParser(const OBJImportParams &import_params, size_t read_buffer_size = 64 * 1024)
+ : import_params_(import_params), read_buffer_size_(read_buffer_size)
{
- obj_file_.open(import_params_.filepath);
- if (!obj_file_.good()) {
+ obj_file_ = BLI_fopen(import_params_.filepath, "rb");
+ if (!obj_file_) {
fprintf(stderr, "Cannot read from OBJ file:'%s'.\n", import_params_.filepath);
return;
}
}
+OBJParser::~OBJParser()
+{
+ if (obj_file_) {
+ fclose(obj_file_);
+ }
+}
+
void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries,
GlobalVertices &r_global_vertices)
{
- if (!obj_file_.good()) {
+ if (!obj_file_) {
return;
}
- string line;
- /* Store vertex coordinates that belong to other Geometry instances. */
VertexIndexOffset offsets;
- /* Non owning raw pointer to a Geometry. To be updated while creating a new Geometry. */
Geometry *curr_geom = create_geometry(
nullptr, GEOM_MESH, "", r_global_vertices, r_all_geometries, offsets);
- /* State-setting variables: if set, they remain the same for the remaining
+ /* State variables: once set, they remain the same for the remaining
* elements in the object. */
bool state_shaded_smooth = false;
- string state_object_group;
+ string state_group_name;
+ int state_group_index = -1;
string state_material_name;
+ int state_material_index = -1;
+
+ /* Read the input file in chunks. We need up to twice the possible chunk size,
+ * to possibly store remainder of the previous input line that got broken mid-chunk. */
+ Array<char> buffer(read_buffer_size_ * 2);
+
+ size_t buffer_offset = 0;
+ size_t line_number = 0;
+ while (true) {
+ /* Read a chunk of input from the file. */
+ size_t bytes_read = fread(buffer.data() + buffer_offset, 1, read_buffer_size_, obj_file_);
+ if (bytes_read == 0 && buffer_offset == 0) {
+ break; /* No more data to read. */
+ }
- while (std::getline(obj_file_, line)) {
- /* Keep reading new lines if the last character is `\`. */
- /* Another way is to make a getline wrapper and use it in the while condition. */
- read_next_line(obj_file_, line);
+ /* Ensure buffer ends in a newline. */
+ if (bytes_read < read_buffer_size_) {
+ if (bytes_read == 0 || buffer[buffer_offset + bytes_read - 1] != '\n') {
+ buffer[buffer_offset + bytes_read] = '\n';
+ bytes_read++;
+ }
+ }
- StringRef line_key, rest_line;
- split_line_key_rest(line, line_key, rest_line);
- if (line.empty() || rest_line.is_empty()) {
- continue;
+ size_t buffer_end = buffer_offset + bytes_read;
+ if (buffer_end == 0) {
+ break;
}
- switch (line_key_str_to_enum(line_key)) {
- case eOBJLineKey::V: {
- geom_add_vertex(curr_geom, rest_line, r_global_vertices);
- break;
+
+ /* Find last newline. */
+ size_t last_nl = buffer_end;
+ while (last_nl > 0) {
+ --last_nl;
+ if (buffer[last_nl] == '\n') {
+ if (last_nl < 1 || buffer[last_nl - 1] != '\\') {
+ break;
+ }
}
- case eOBJLineKey::VN: {
- geom_add_vertex_normal(curr_geom, rest_line, r_global_vertices);
- break;
+ }
+ if (buffer[last_nl] != '\n') {
+ /* Whole line did not fit into our read buffer. Warn and exit. */
+ fprintf(stderr,
+ "OBJ file contains a line #%zu that is too long (max. length %zu)\n",
+ line_number,
+ read_buffer_size_);
+ break;
+ }
+ ++last_nl;
+
+ /* Parse the buffer (until last newline) that we have so far,
+ * line by line. */
+ StringRef buffer_str{buffer.data(), (int64_t)last_nl};
+ while (!buffer_str.is_empty()) {
+ StringRef line = read_next_line(buffer_str);
+ ++line_number;
+ if (line.is_empty()) {
+ continue;
}
- case eOBJLineKey::VT: {
- geom_add_uv_vertex(rest_line, r_global_vertices);
- break;
+ /* Most common things that start with 'v': vertices, normals, UVs. */
+ if (line[0] == 'v') {
+ if (line.startswith("v ")) {
+ line = line.drop_prefix(2);
+ geom_add_vertex(curr_geom, line, r_global_vertices);
+ }
+ else if (line.startswith("vn ")) {
+ line = line.drop_prefix(3);
+ geom_add_vertex_normal(curr_geom, line, r_global_vertices);
+ }
+ else if (line.startswith("vt ")) {
+ line = line.drop_prefix(3);
+ geom_add_uv_vertex(line, r_global_vertices);
+ }
}
- case eOBJLineKey::F: {
+ /* Faces. */
+ else if (line.startswith("f ")) {
+ line = line.drop_prefix(2);
geom_add_polygon(curr_geom,
- rest_line,
+ line,
r_global_vertices,
offsets,
- state_material_name,
- state_material_name,
+ state_material_index,
+ state_group_index, /* TODO was wrongly material name! */
state_shaded_smooth);
- break;
- }
- case eOBJLineKey::L: {
- geom_add_edge(curr_geom, rest_line, offsets, r_global_vertices);
- break;
- }
- case eOBJLineKey::CSTYPE: {
- curr_geom = geom_set_curve_type(curr_geom,
- rest_line,
- r_global_vertices,
- state_object_group,
- offsets,
- r_all_geometries);
- break;
- }
- case eOBJLineKey::DEG: {
- geom_set_curve_degree(curr_geom, rest_line);
- break;
- }
- case eOBJLineKey::CURV: {
- geom_add_curve_vertex_indices(curr_geom, rest_line, r_global_vertices);
- break;
- }
- case eOBJLineKey::PARM: {
- geom_add_curve_parameters(curr_geom, rest_line);
- break;
- }
- case eOBJLineKey::O: {
+ }
+ /* Faces. */
+ else if (line.startswith("l ")) {
+ line = line.drop_prefix(2);
+ geom_add_edge(curr_geom, line, offsets, r_global_vertices);
+ }
+ /* Objects. */
+ else if (line.startswith("o ")) {
+ line = line.drop_prefix(2);
state_shaded_smooth = false;
- state_object_group = "";
+ state_group_name = "";
state_material_name = "";
curr_geom = create_geometry(
- curr_geom, GEOM_MESH, rest_line, r_global_vertices, r_all_geometries, offsets);
- break;
- }
- case eOBJLineKey::G: {
- geom_update_object_group(rest_line, state_object_group);
- break;
- }
- case eOBJLineKey::S: {
- geom_update_smooth_group(rest_line, state_shaded_smooth);
- break;
- }
- case eOBJLineKey::USEMTL: {
- geom_update_polygon_material(curr_geom, rest_line, state_material_name);
- break;
- }
- case eOBJLineKey::MTLLIB: {
- mtl_libraries_.append(string(rest_line));
- break;
- }
- case eOBJLineKey::COMMENT:
- break;
- default:
- std::cout << "Element not recognised: '" << line_key << "'" << std::endl;
- break;
+ curr_geom, GEOM_MESH, line, r_global_vertices, r_all_geometries, offsets);
+ }
+ /* Groups. */
+ else if (line.startswith("g ")) {
+ line = line.drop_prefix(2);
+ geom_update_group(line, state_group_name);
+ int new_index = curr_geom->group_indices_.size();
+ state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index);
+ if (new_index == state_group_index) {
+ curr_geom->group_order_.append(state_group_name);
+ }
+ }
+ /* Smoothing groups. */
+ else if (line.startswith("s ")) {
+ line = line.drop_prefix(2);
+ geom_update_smooth_group(line, state_shaded_smooth);
+ }
+ /* Materials and their libraries. */
+ else if (line.startswith("usemtl ")) {
+ line = line.drop_prefix(7);
+ state_material_name = line;
+ int new_mat_index = curr_geom->material_indices_.size();
+ state_material_index = curr_geom->material_indices_.lookup_or_add(state_material_name,
+ new_mat_index);
+ if (new_mat_index == state_material_index) {
+ curr_geom->material_order_.append(state_material_name);
+ }
+ }
+ else if (line.startswith("mtllib ")) {
+ line = line.drop_prefix(7);
+ mtl_libraries_.append(string(line));
+ }
+ /* Comments. */
+ else if (line.startswith("#")) {
+ /* Nothing to do. */
+ }
+ /* Curve related things. */
+ else if (line.startswith("cstype ")) {
+ line = line.drop_prefix(7);
+ curr_geom = geom_set_curve_type(
+ curr_geom, line, r_global_vertices, state_group_name, offsets, r_all_geometries);
+ }
+ else if (line.startswith("deg ")) {
+ line = line.drop_prefix(4);
+ geom_set_curve_degree(curr_geom, line);
+ }
+ else if (line.startswith("curv ")) {
+ line = line.drop_prefix(5);
+ geom_add_curve_vertex_indices(curr_geom, line, r_global_vertices);
+ }
+ else if (line.startswith("parm ")) {
+ line = line.drop_prefix(5);
+ geom_add_curve_parameters(curr_geom, line);
+ }
+ else if (line.startswith("end")) {
+ /* End of curve definition, nothing else to do. */
+ }
+ else {
+ std::cout << "OBJ element not recognised: '" << line << "'" << std::endl;
+ }
}
+
+ /* We might have a line that was cut in the middle by the previous buffer;
+ * copy it over for next chunk reading. */
+ size_t left_size = buffer_end - last_nl;
+ memmove(buffer.data(), buffer.data() + last_nl, left_size);
+ buffer_offset = left_size;
}
}
-/**
- * Skip all texture map options and get the filepath from a "map_" line.
- */
-static StringRef skip_unsupported_options(StringRef line)
+static eMTLSyntaxElement mtl_line_start_to_enum(StringRef &line)
{
- TextureMapOptions map_options;
- StringRef last_option;
- int64_t last_option_pos = 0;
-
- /* Find the last texture map option. */
- for (StringRef option : map_options.all_options()) {
- const int64_t pos{line.find(option)};
- /* Equality (>=) takes care of finding an option in the beginning of the line. Avoid messing
- * with signed-unsigned int comparison. */
- if (pos != StringRef::not_found && pos >= last_option_pos) {
- last_option = option;
- last_option_pos = pos;
- }
+ if (line.startswith("map_Kd")) {
+ line = line.drop_prefix(6);
+ return eMTLSyntaxElement::map_Kd;
}
-
- if (last_option.is_empty()) {
- /* No option found, line is the filepath */
- return line;
+ if (line.startswith("map_Ks")) {
+ line = line.drop_prefix(6);
+ return eMTLSyntaxElement::map_Ks;
+ }
+ if (line.startswith("map_Ns")) {
+ line = line.drop_prefix(6);
+ return eMTLSyntaxElement::map_Ns;
+ }
+ if (line.startswith("map_d")) {
+ line = line.drop_prefix(5);
+ return eMTLSyntaxElement::map_d;
}
+ if (line.startswith("refl")) {
+ line = line.drop_prefix(4);
+ return eMTLSyntaxElement::map_refl;
+ }
+ if (line.startswith("map_refl")) {
+ line = line.drop_prefix(8);
+ return eMTLSyntaxElement::map_refl;
+ }
+ if (line.startswith("map_Ke")) {
+ line = line.drop_prefix(6);
+ return eMTLSyntaxElement::map_Ke;
+ }
+ if (line.startswith("bump")) {
+ line = line.drop_prefix(4);
+ return eMTLSyntaxElement::map_Bump;
+ }
+ if (line.startswith("map_Bump") || line.startswith("map_bump")) {
+ line = line.drop_prefix(8);
+ return eMTLSyntaxElement::map_Bump;
+ }
+ return eMTLSyntaxElement::string;
+}
- /* Remove up to start of the last option + size of the last option + space after it. */
- line = line.drop_prefix(last_option_pos + last_option.size() + 1);
- for (int i = 0; i < map_options.number_of_args(last_option); i++) {
- const int64_t pos_space{line.find_first_of(' ')};
- if (pos_space != StringRef::not_found) {
- BLI_assert(pos_space + 1 < line.size());
- line = line.drop_prefix(pos_space + 1);
+static const std::pair<const char *, int> unsupported_texture_options[] = {
+ {"-blendu ", 1},
+ {"-blendv ", 1},
+ {"-boost ", 1},
+ {"-cc ", 1},
+ {"-clamp ", 1},
+ {"-imfchan ", 1},
+ {"-mm ", 2},
+ {"-t ", 3},
+ {"-texres ", 1},
+};
+
+static bool parse_texture_option(StringRef &line, MTLMaterial *material, tex_map_XX &tex_map)
+{
+ line = drop_whitespace(line);
+ if (line.startswith("-o ")) {
+ line = line.drop_prefix(3);
+ line = parse_floats(line, 0.0f, tex_map.translation, 3);
+ return true;
+ }
+ if (line.startswith("-s ")) {
+ line = line.drop_prefix(3);
+ line = parse_floats(line, 1.0f, tex_map.scale, 3);
+ return true;
+ }
+ if (line.startswith("-bm ")) {
+ line = line.drop_prefix(4);
+ line = parse_float(line, 0.0f, material->map_Bump_strength);
+ return true;
+ }
+ if (line.startswith("-type ")) {
+ line = line.drop_prefix(6);
+ line = drop_whitespace(line);
+ /* Only sphere is supported. */
+ tex_map.projection_type = SHD_PROJ_SPHERE;
+ if (!line.startswith("sphere")) {
+ std::cerr << "OBJ import: only sphere MTL projection type is supported: '" << line << "'"
+ << std::endl;
+ }
+ line = drop_non_whitespace(line);
+ return true;
+ }
+ /* Check for unsupported options and skip them. */
+ for (const auto &opt : unsupported_texture_options) {
+ if (line.startswith(opt.first)) {
+ /* Drop the option name. */
+ line = line.drop_known_prefix(opt.first);
+ /* Drop the arguments. */
+ for (int i = 0; i < opt.second; ++i) {
+ line = drop_whitespace(line);
+ line = drop_non_whitespace(line);
+ }
+ return true;
}
}
- return line;
+ return false;
}
-/**
- * Fix incoming texture map line keys for variations due to other exporters.
- */
-static string fix_bad_map_keys(StringRef map_key)
+static void parse_texture_map(StringRef line, MTLMaterial *material, const char *mtl_dir_path)
{
- string new_map_key(map_key);
- if (map_key == "refl") {
- new_map_key = "map_refl";
+ bool is_map = line.startswith("map_");
+ bool is_refl = line.startswith("refl");
+ bool is_bump = line.startswith("bump");
+ if (!is_map && !is_refl && !is_bump) {
+ return;
+ }
+ eMTLSyntaxElement key = mtl_line_start_to_enum(line);
+ if (key == eMTLSyntaxElement::string || !material->texture_maps.contains(key)) {
+ /* No supported texture map found. */
+ std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl;
+ return;
}
- if (map_key.find("bump") != StringRef::not_found) {
- /* Handles both "bump" and "map_Bump" */
- new_map_key = "map_Bump";
+ tex_map_XX &tex_map = material->texture_maps.lookup(key);
+ tex_map.mtl_dir_path = mtl_dir_path;
+
+ /* Parse texture map options. */
+ while (parse_texture_option(line, material, tex_map)) {
}
- return new_map_key;
+
+ /* What remains is the image path. */
+ line = line.trim();
+ tex_map.image_path = line;
}
Span<std::string> OBJParser::mtl_libraries() const
@@ -503,125 +629,73 @@ MTLParser::MTLParser(StringRef mtl_library, StringRefNull obj_filepath)
BLI_split_dir_part(obj_filepath.data(), obj_file_dir, FILE_MAXDIR);
BLI_path_join(mtl_file_path_, FILE_MAX, obj_file_dir, mtl_library.data(), NULL);
BLI_split_dir_part(mtl_file_path_, mtl_dir_path_, FILE_MAXDIR);
- mtl_file_.open(mtl_file_path_);
- if (!mtl_file_.good()) {
- fprintf(stderr, "Cannot read from MTL file:'%s'\n", mtl_file_path_);
- return;
- }
}
-void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_mtl_materials)
+void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_materials)
{
- if (!mtl_file_.good()) {
+ size_t buffer_len;
+ void *buffer = BLI_file_read_text_as_mem(mtl_file_path_, 0, &buffer_len);
+ if (buffer == nullptr) {
+ fprintf(stderr, "OBJ import: cannot read from MTL file: '%s'\n", mtl_file_path_);
return;
}
- string line;
- MTLMaterial *current_mtlmaterial = nullptr;
+ MTLMaterial *material = nullptr;
- while (std::getline(mtl_file_, line)) {
- StringRef line_key, rest_line;
- split_line_key_rest(line, line_key, rest_line);
- if (line.empty() || rest_line.is_empty()) {
+ StringRef buffer_str{(const char *)buffer, (int64_t)buffer_len};
+ while (!buffer_str.is_empty()) {
+ StringRef line = read_next_line(buffer_str);
+ if (line.is_empty()) {
continue;
}
- /* Fix lower case/ incomplete texture map identifiers. */
- const string fixed_key = fix_bad_map_keys(line_key);
- line_key = fixed_key;
-
- if (line_key == "newmtl") {
- if (r_mtl_materials.remove_as(rest_line)) {
- std::cerr << "Duplicate material found:'" << rest_line
+ if (line.startswith("newmtl ")) {
+ line = line.drop_prefix(7);
+ if (r_materials.remove_as(line)) {
+ std::cerr << "Duplicate material found:'" << line
<< "', using the last encountered Material definition." << std::endl;
}
- current_mtlmaterial =
- r_mtl_materials.lookup_or_add(string(rest_line), std::make_unique<MTLMaterial>()).get();
- }
- else if (line_key == "Ns") {
- copy_string_to_float(rest_line, 324.0f, current_mtlmaterial->Ns);
- }
- else if (line_key == "Ka") {
- Vector<StringRef> str_ka_split;
- split_by_char(rest_line, ' ', str_ka_split);
- copy_string_to_float(str_ka_split, 0.0f, {current_mtlmaterial->Ka, 3});
- }
- else if (line_key == "Kd") {
- Vector<StringRef> str_kd_split;
- split_by_char(rest_line, ' ', str_kd_split);
- copy_string_to_float(str_kd_split, 0.8f, {current_mtlmaterial->Kd, 3});
- }
- else if (line_key == "Ks") {
- Vector<StringRef> str_ks_split;
- split_by_char(rest_line, ' ', str_ks_split);
- copy_string_to_float(str_ks_split, 0.5f, {current_mtlmaterial->Ks, 3});
- }
- else if (line_key == "Ke") {
- Vector<StringRef> str_ke_split;
- split_by_char(rest_line, ' ', str_ke_split);
- copy_string_to_float(str_ke_split, 0.0f, {current_mtlmaterial->Ke, 3});
- }
- else if (line_key == "Ni") {
- copy_string_to_float(rest_line, 1.45f, current_mtlmaterial->Ni);
- }
- else if (line_key == "d") {
- copy_string_to_float(rest_line, 1.0f, current_mtlmaterial->d);
+ material = r_materials.lookup_or_add(string(line), std::make_unique<MTLMaterial>()).get();
}
- else if (line_key == "illum") {
- copy_string_to_int(rest_line, 2, current_mtlmaterial->illum);
- }
-
- /* Parse image textures. */
- else if (line_key.find("map_") != StringRef::not_found) {
- /* TODO(@howardt): fix this. */
- eMTLSyntaxElement line_key_enum = mtl_line_key_str_to_enum(line_key);
- if (line_key_enum == eMTLSyntaxElement::string ||
- !current_mtlmaterial->texture_maps.contains_as(line_key_enum)) {
- /* No supported texture map found. */
- std::cerr << "Texture map type not supported:'" << line_key << "'" << std::endl;
- continue;
+ else if (material != nullptr) {
+ if (line.startswith("Ns ")) {
+ line = line.drop_prefix(3);
+ parse_float(line, 324.0f, material->Ns);
}
- tex_map_XX &tex_map = current_mtlmaterial->texture_maps.lookup(line_key_enum);
- Vector<StringRef> str_map_xx_split;
- split_by_char(rest_line, ' ', str_map_xx_split);
-
- /* TODO(@ankitm): use `skip_unsupported_options` for parsing these options too? */
- const int64_t pos_o{str_map_xx_split.first_index_of_try("-o")};
- if (pos_o != -1 && pos_o + 3 < str_map_xx_split.size()) {
- copy_string_to_float({str_map_xx_split[pos_o + 1],
- str_map_xx_split[pos_o + 2],
- str_map_xx_split[pos_o + 3]},
- 0.0f,
- {tex_map.translation, 3});
- }
- const int64_t pos_s{str_map_xx_split.first_index_of_try("-s")};
- if (pos_s != -1 && pos_s + 3 < str_map_xx_split.size()) {
- copy_string_to_float({str_map_xx_split[pos_s + 1],
- str_map_xx_split[pos_s + 2],
- str_map_xx_split[pos_s + 3]},
- 1.0f,
- {tex_map.scale, 3});
- }
- /* Only specific to Normal Map node. */
- const int64_t pos_bm{str_map_xx_split.first_index_of_try("-bm")};
- if (pos_bm != -1 && pos_bm + 1 < str_map_xx_split.size()) {
- copy_string_to_float(
- str_map_xx_split[pos_bm + 1], 0.0f, current_mtlmaterial->map_Bump_strength);
- }
- const int64_t pos_projection{str_map_xx_split.first_index_of_try("-type")};
- if (pos_projection != -1 && pos_projection + 1 < str_map_xx_split.size()) {
- /* Only Sphere is supported, so whatever the type is, set it to Sphere. */
- tex_map.projection_type = SHD_PROJ_SPHERE;
- if (str_map_xx_split[pos_projection + 1] != "sphere") {
- std::cerr << "Using projection type 'sphere', not:'"
- << str_map_xx_split[pos_projection + 1] << "'." << std::endl;
- }
+ else if (line.startswith("Ka ")) {
+ line = line.drop_prefix(3);
+ parse_floats(line, 0.0f, material->Ka, 3);
+ }
+ else if (line.startswith("Kd ")) {
+ line = line.drop_prefix(3);
+ parse_floats(line, 0.8f, material->Kd, 3);
+ }
+ else if (line.startswith("Ks ")) {
+ line = line.drop_prefix(3);
+ parse_floats(line, 0.5f, material->Ks, 3);
+ }
+ else if (line.startswith("Ke ")) {
+ line = line.drop_prefix(3);
+ parse_floats(line, 0.0f, material->Ke, 3);
+ }
+ else if (line.startswith("Ni ")) {
+ line = line.drop_prefix(3);
+ parse_float(line, 1.45f, material->Ni);
+ }
+ else if (line.startswith("d ")) {
+ line = line.drop_prefix(2);
+ parse_float(line, 1.0f, material->d);
+ }
+ else if (line.startswith("illum ")) {
+ line = line.drop_prefix(6);
+ parse_int(line, 2, material->illum);
+ }
+ else {
+ parse_texture_map(line, material, mtl_dir_path_);
}
-
- /* Skip all unsupported options and arguments. */
- tex_map.image_path = string(skip_unsupported_options(rest_line));
- tex_map.mtl_dir_path = mtl_dir_path_;
}
}
+
+ MEM_freeN(buffer);
}
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh
index 24d026d75e5..8093417fcda 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.hh
@@ -18,14 +18,16 @@ namespace blender::io::obj {
class OBJParser {
private:
const OBJImportParams &import_params_;
- blender::fstream obj_file_;
+ FILE *obj_file_;
Vector<std::string> mtl_libraries_;
+ size_t read_buffer_size_;
public:
/**
* Open OBJ file at the path given in import parameters.
*/
- OBJParser(const OBJImportParams &import_params);
+ OBJParser(const OBJImportParams &import_params, size_t read_buffer_size);
+ ~OBJParser();
/**
* Read the OBJ file line by line and create OBJ Geometry instances. Also store all the vertex
@@ -39,111 +41,6 @@ class OBJParser {
Span<std::string> mtl_libraries() const;
};
-enum class eOBJLineKey {
- V,
- VN,
- VT,
- F,
- L,
- CSTYPE,
- DEG,
- CURV,
- PARM,
- O,
- G,
- S,
- USEMTL,
- MTLLIB,
- COMMENT
-};
-
-constexpr eOBJLineKey line_key_str_to_enum(const std::string_view key_str)
-{
- if (key_str == "v" || key_str == "V") {
- return eOBJLineKey::V;
- }
- if (key_str == "vn" || key_str == "VN") {
- return eOBJLineKey::VN;
- }
- if (key_str == "vt" || key_str == "VT") {
- return eOBJLineKey::VT;
- }
- if (key_str == "f" || key_str == "F") {
- return eOBJLineKey::F;
- }
- if (key_str == "l" || key_str == "L") {
- return eOBJLineKey::L;
- }
- if (key_str == "cstype" || key_str == "CSTYPE") {
- return eOBJLineKey::CSTYPE;
- }
- if (key_str == "deg" || key_str == "DEG") {
- return eOBJLineKey::DEG;
- }
- if (key_str == "curv" || key_str == "CURV") {
- return eOBJLineKey::CURV;
- }
- if (key_str == "parm" || key_str == "PARM") {
- return eOBJLineKey::PARM;
- }
- if (key_str == "o" || key_str == "O") {
- return eOBJLineKey::O;
- }
- if (key_str == "g" || key_str == "G") {
- return eOBJLineKey::G;
- }
- if (key_str == "s" || key_str == "S") {
- return eOBJLineKey::S;
- }
- if (key_str == "usemtl" || key_str == "USEMTL") {
- return eOBJLineKey::USEMTL;
- }
- if (key_str == "mtllib" || key_str == "MTLLIB") {
- return eOBJLineKey::MTLLIB;
- }
- if (key_str == "#") {
- return eOBJLineKey::COMMENT;
- }
- return eOBJLineKey::COMMENT;
-}
-
-/**
- * All texture map options with number of arguments they accept.
- */
-class TextureMapOptions {
- private:
- Map<const std::string, int> tex_map_options;
-
- public:
- TextureMapOptions()
- {
- tex_map_options.add_new("-blendu", 1);
- tex_map_options.add_new("-blendv", 1);
- tex_map_options.add_new("-boost", 1);
- tex_map_options.add_new("-mm", 2);
- tex_map_options.add_new("-o", 3);
- tex_map_options.add_new("-s", 3);
- tex_map_options.add_new("-t", 3);
- tex_map_options.add_new("-texres", 1);
- tex_map_options.add_new("-clamp", 1);
- tex_map_options.add_new("-bm", 1);
- tex_map_options.add_new("-imfchan", 1);
- }
-
- /**
- * All valid option strings.
- */
- Map<const std::string, int>::KeyIterator all_options() const
- {
- return tex_map_options.keys();
- }
-
- int number_of_args(StringRef option) const
- {
- return tex_map_options.lookup_as(std::string(option));
- }
-};
-
class MTLParser {
private:
char mtl_file_path_[FILE_MAX];
@@ -151,7 +48,6 @@ class MTLParser {
* Directory in which the MTL file is found.
*/
char mtl_dir_path_[FILE_MAX];
- blender::fstream mtl_file_;
public:
/**
@@ -162,6 +58,6 @@ class MTLParser {
/**
* Read MTL file(s) and add MTLMaterial instances to the given Map reference.
*/
- void parse_and_store(Map<std::string, std::unique_ptr<MTLMaterial>> &r_mtl_materials);
+ void parse_and_store(Map<std::string, std::unique_ptr<MTLMaterial>> &r_materials);
};
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
index 55b2873a3de..01a2d63927e 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc
@@ -18,6 +18,7 @@
#include "BLI_math_vector.h"
#include "BLI_set.hh"
+#include "IO_wavefront_obj.h"
#include "importer_mesh_utils.hh"
#include "obj_import_mesh.hh"
@@ -35,7 +36,7 @@ Object *MeshFromGeometry::create_mesh(
}
fixup_invalid_faces();
- const int64_t tot_verts_object{mesh_geometry_.vertex_indices_.size()};
+ const int64_t tot_verts_object{mesh_geometry_.vertex_count_};
/* Total explicitly imported edges, not the ones belonging the polygons to be created. */
const int64_t tot_edges{mesh_geometry_.edges_.size()};
const int64_t tot_face_elems{mesh_geometry_.face_elements_.size()};
@@ -52,11 +53,13 @@ Object *MeshFromGeometry::create_mesh(
create_normals(mesh);
create_materials(bmain, materials, created_materials, obj);
- bool verbose_validate = false;
+ if (import_params.validate_meshes || mesh_geometry_.has_invalid_polys_) {
+ bool verbose_validate = false;
#ifdef DEBUG
- verbose_validate = true;
+ verbose_validate = true;
#endif
- BKE_mesh_validate(mesh, verbose_validate, false);
+ BKE_mesh_validate(mesh, verbose_validate, false);
+ }
transform_object(obj, import_params);
/* FIXME: after 2.80; `mesh->flag` isn't copied by #BKE_mesh_nomain_to_mesh() */
@@ -73,9 +76,9 @@ void MeshFromGeometry::fixup_invalid_faces()
for (int64_t face_idx = 0; face_idx < mesh_geometry_.face_elements_.size(); ++face_idx) {
const PolyElem &curr_face = mesh_geometry_.face_elements_[face_idx];
- if (curr_face.face_corners.size() < 3) {
+ if (curr_face.corner_count_ < 3) {
/* Skip and remove faces that have fewer than 3 corners. */
- mesh_geometry_.total_loops_ -= curr_face.face_corners.size();
+ mesh_geometry_.total_loops_ -= curr_face.corner_count_;
mesh_geometry_.face_elements_.remove_and_reorder(face_idx);
continue;
}
@@ -84,12 +87,14 @@ void MeshFromGeometry::fixup_invalid_faces()
* basically whether it has duplicate vertex indices. */
bool valid = true;
Set<int, 8> used_verts;
- for (const PolyCorner &corner : curr_face.face_corners) {
- if (used_verts.contains(corner.vert_index)) {
+ for (int i = 0; i < curr_face.corner_count_; ++i) {
+ int corner_idx = curr_face.start_index_ + i;
+ int vertex_idx = mesh_geometry_.face_corners_[corner_idx].vert_index;
+ if (used_verts.contains(vertex_idx)) {
valid = false;
break;
}
- used_verts.add(corner.vert_index);
+ used_verts.add(vertex_idx);
}
if (valid) {
continue;
@@ -100,20 +105,22 @@ void MeshFromGeometry::fixup_invalid_faces()
Vector<int, 8> face_verts;
Vector<int, 8> face_uvs;
Vector<int, 8> face_normals;
- face_verts.reserve(curr_face.face_corners.size());
- face_uvs.reserve(curr_face.face_corners.size());
- face_normals.reserve(curr_face.face_corners.size());
- for (const PolyCorner &corner : curr_face.face_corners) {
+ face_verts.reserve(curr_face.corner_count_);
+ face_uvs.reserve(curr_face.corner_count_);
+ face_normals.reserve(curr_face.corner_count_);
+ for (int i = 0; i < curr_face.corner_count_; ++i) {
+ int corner_idx = curr_face.start_index_ + i;
+ const PolyCorner &corner = mesh_geometry_.face_corners_[corner_idx];
face_verts.append(corner.vert_index);
face_normals.append(corner.vertex_normal_index);
face_uvs.append(corner.uv_vert_index);
}
- std::string face_vertex_group = curr_face.vertex_group;
- std::string face_material_name = curr_face.material_name;
+ int face_vertex_group = curr_face.vertex_group_index;
+ int face_material = curr_face.material_index;
bool face_shaded_smooth = curr_face.shaded_smooth;
/* Remove the invalid face. */
- mesh_geometry_.total_loops_ -= curr_face.face_corners.size();
+ mesh_geometry_.total_loops_ -= curr_face.corner_count_;
mesh_geometry_.face_elements_.remove_and_reorder(face_idx);
Vector<Vector<int>> new_faces = fixup_invalid_polygon(global_vertices_.vertices, face_verts);
@@ -124,13 +131,14 @@ void MeshFromGeometry::fixup_invalid_faces()
continue;
}
PolyElem new_face{};
- new_face.vertex_group = face_vertex_group;
- new_face.material_name = face_material_name;
+ new_face.vertex_group_index = face_vertex_group;
+ new_face.material_index = face_material;
new_face.shaded_smooth = face_shaded_smooth;
- new_face.face_corners.reserve(face.size());
+ new_face.start_index_ = mesh_geometry_.face_corners_.size();
+ new_face.corner_count_ = face.size();
for (int idx : face) {
BLI_assert(idx >= 0 && idx < face_verts.size());
- new_face.face_corners.append({face_verts[idx], face_uvs[idx], face_normals[idx]});
+ mesh_geometry_.face_corners_.append({face_verts[idx], face_uvs[idx], face_normals[idx]});
}
mesh_geometry_.face_elements_.append(new_face);
mesh_geometry_.total_loops_ += face.size();
@@ -140,13 +148,14 @@ void MeshFromGeometry::fixup_invalid_faces()
void MeshFromGeometry::create_vertices(Mesh *mesh)
{
- const int64_t tot_verts_object{mesh_geometry_.vertex_indices_.size()};
+ const int tot_verts_object{mesh_geometry_.vertex_count_};
for (int i = 0; i < tot_verts_object; ++i) {
- if (mesh_geometry_.vertex_indices_[i] < global_vertices_.vertices.size()) {
- copy_v3_v3(mesh->mvert[i].co, global_vertices_.vertices[mesh_geometry_.vertex_indices_[i]]);
+ int vi = mesh_geometry_.vertex_start_ + i;
+ if (vi < global_vertices_.vertices.size()) {
+ copy_v3_v3(mesh->mvert[i].co, global_vertices_.vertices[vi]);
}
else {
- std::cerr << "Vertex index:" << mesh_geometry_.vertex_indices_[i]
+ std::cerr << "Vertex index:" << vi
<< " larger than total vertices:" << global_vertices_.vertices.size() << " ."
<< std::endl;
}
@@ -158,7 +167,7 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh)
/* Will not be used if vertex groups are not imported. */
mesh->dvert = nullptr;
float weight = 0.0f;
- const int64_t total_verts = mesh_geometry_.vertex_indices_.size();
+ const int64_t total_verts = mesh_geometry_.vertex_count_;
if (total_verts && mesh_geometry_.use_vertex_groups_) {
mesh->dvert = static_cast<MDeformVert *>(
CustomData_add_layer(&mesh->vdata, CD_MDEFORMVERT, CD_CALLOC, nullptr, total_verts));
@@ -168,34 +177,32 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh)
UNUSED_VARS(weight);
}
- /* Do not remove elements from the VectorSet since order of insertion is required.
- * StringRef is fine since per-face deform group name outlives the VectorSet. */
- VectorSet<StringRef> group_names;
const int64_t tot_face_elems{mesh->totpoly};
int tot_loop_idx = 0;
for (int poly_idx = 0; poly_idx < tot_face_elems; ++poly_idx) {
const PolyElem &curr_face = mesh_geometry_.face_elements_[poly_idx];
- if (curr_face.face_corners.size() < 3) {
+ if (curr_face.corner_count_ < 3) {
/* Don't add single vertex face, or edges. */
std::cerr << "Face with less than 3 vertices found, skipping." << std::endl;
continue;
}
MPoly &mpoly = mesh->mpoly[poly_idx];
- mpoly.totloop = curr_face.face_corners.size();
+ mpoly.totloop = curr_face.corner_count_;
mpoly.loopstart = tot_loop_idx;
if (curr_face.shaded_smooth) {
mpoly.flag |= ME_SMOOTH;
}
- mpoly.mat_nr = mesh_geometry_.material_names_.index_of_try(curr_face.material_name);
+ mpoly.mat_nr = curr_face.material_index;
/* Importing obj files without any materials would result in negative indices, which is not
* supported. */
if (mpoly.mat_nr < 0) {
mpoly.mat_nr = 0;
}
- for (const PolyCorner &curr_corner : curr_face.face_corners) {
+ for (int idx = 0; idx < curr_face.corner_count_; ++idx) {
+ const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx];
MLoop &mloop = mesh->mloop[tot_loop_idx];
tot_loop_idx++;
mloop.v = curr_corner.vert_index;
@@ -212,23 +219,17 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh)
MEM_callocN(sizeof(MDeformWeight), "OBJ Import Deform Weight"));
}
/* Every vertex in a face is assigned the same deform group. */
- int64_t pos_name{group_names.index_of_try(curr_face.vertex_group)};
- if (pos_name == -1) {
- group_names.add_new(curr_face.vertex_group);
- pos_name = group_names.size() - 1;
- }
- BLI_assert(pos_name >= 0);
+ int group_idx = curr_face.vertex_group_index;
/* Deform group number (def_nr) must behave like an index into the names' list. */
- *(def_vert.dw) = {static_cast<unsigned int>(pos_name), weight};
+ *(def_vert.dw) = {static_cast<unsigned int>(group_idx), weight};
}
}
if (!mesh->dvert) {
return;
}
- /* Add deform group(s) to the object's defbase. */
- for (StringRef name : group_names) {
- /* Adding groups in this order assumes that def_nr is an index into the names' list. */
+ /* Add deform group names. */
+ for (const std::string &name : mesh_geometry_.group_order_) {
BKE_object_defgroup_add_name(obj, name.data());
}
}
@@ -236,7 +237,7 @@ void MeshFromGeometry::create_polys_loops(Object *obj, Mesh *mesh)
void MeshFromGeometry::create_edges(Mesh *mesh)
{
const int64_t tot_edges{mesh_geometry_.edges_.size()};
- const int64_t total_verts{mesh_geometry_.vertex_indices_.size()};
+ const int64_t total_verts{mesh_geometry_.vertex_count_};
UNUSED_VARS_NDEBUG(total_verts);
for (int i = 0; i < tot_edges; ++i) {
const MEdge &src_edge = mesh_geometry_.edges_[i];
@@ -263,7 +264,8 @@ void MeshFromGeometry::create_uv_verts(Mesh *mesh)
int tot_loop_idx = 0;
for (const PolyElem &curr_face : mesh_geometry_.face_elements_) {
- for (const PolyCorner &curr_corner : curr_face.face_corners) {
+ for (int idx = 0; idx < curr_face.corner_count_; ++idx) {
+ const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx];
if (curr_corner.uv_vert_index >= 0 &&
curr_corner.uv_vert_index < global_vertices_.uv_vertices.size()) {
const float2 &mluv_src = global_vertices_.uv_vertices[curr_corner.uv_vert_index];
@@ -317,7 +319,7 @@ void MeshFromGeometry::create_materials(
Map<std::string, Material *> &created_materials,
Object *obj)
{
- for (const std::string &name : mesh_geometry_.material_names_) {
+ for (const std::string &name : mesh_geometry_.material_order_) {
Material *mat = get_or_create_material(bmain, name, materials, created_materials);
if (mat == nullptr) {
continue;
@@ -340,7 +342,8 @@ void MeshFromGeometry::create_normals(Mesh *mesh)
MEM_malloc_arrayN(mesh_geometry_.total_loops_, sizeof(float[3]), __func__));
int tot_loop_idx = 0;
for (const PolyElem &curr_face : mesh_geometry_.face_elements_) {
- for (const PolyCorner &curr_corner : curr_face.face_corners) {
+ for (int idx = 0; idx < curr_face.corner_count_; ++idx) {
+ const PolyCorner &curr_corner = mesh_geometry_.face_corners_[curr_face.start_index_ + idx];
int n_index = curr_corner.vertex_normal_index;
float3 normal(0, 0, 0);
if (n_index >= 0) {
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
index 2a838215421..7cc7ed25ad1 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh
@@ -45,11 +45,8 @@ class MeshFromGeometry : NonMovable, NonCopyable {
void fixup_invalid_faces();
void create_vertices(Mesh *mesh);
/**
- * Create polygons for the Mesh, set smooth shading flag, deform group name,
- * assigned material also.
- *
- * It must receive all polygons to be added to the mesh.
- * Remove holes from polygons before * calling this.
+ * Create polygons for the Mesh, set smooth shading flags, deform group names,
+ * Materials.
*/
void create_polys_loops(Object *obj, Mesh *mesh);
/**
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
index 88a8c07e325..f2a8941e8a7 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.cc
@@ -13,12 +13,13 @@
#include "DNA_material_types.h"
#include "DNA_node_types.h"
+#include "IO_string_utils.hh"
+
#include "NOD_shader.h"
/* TODO: move eMTLSyntaxElement out of following file into a more neutral place */
#include "obj_export_io.hh"
#include "obj_import_mtl.hh"
-#include "parser_string_utils.hh"
namespace blender::io::obj {
@@ -96,13 +97,16 @@ static bool load_texture_image(Main *bmain, const tex_map_XX &tex_map, bNode *r_
return true;
}
/* Try removing quotes. */
- std::string no_quote_path{replace_all_occurences(tex_path, "\"", "")};
+ std::string no_quote_path{tex_path};
+ auto end_pos = std::remove(no_quote_path.begin(), no_quote_path.end(), '"');
+ no_quote_path.erase(end_pos, no_quote_path.end());
if (no_quote_path != tex_path &&
load_texture_image_at_path(bmain, tex_map, r_node, no_quote_path)) {
return true;
}
/* Try replacing underscores with spaces. */
- std::string no_underscore_path{replace_all_occurences(no_quote_path, "_", " ")};
+ std::string no_underscore_path{no_quote_path};
+ std::replace(no_underscore_path.begin(), no_underscore_path.end(), '_', ' ');
if (no_underscore_path != no_quote_path && no_underscore_path != tex_path &&
load_texture_image_at_path(bmain, tex_map, r_node, no_underscore_path)) {
return true;
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh b/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh
index 4b7827b2035..74bc9f21bc4 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_import_mtl.hh
@@ -90,29 +90,4 @@ class ShaderNodetreeWrap {
void add_image_textures(Main *bmain, Material *mat);
};
-constexpr eMTLSyntaxElement mtl_line_key_str_to_enum(const std::string_view key_str)
-{
- if (key_str == "map_Kd") {
- return eMTLSyntaxElement::map_Kd;
- }
- if (key_str == "map_Ks") {
- return eMTLSyntaxElement::map_Ks;
- }
- if (key_str == "map_Ns") {
- return eMTLSyntaxElement::map_Ns;
- }
- if (key_str == "map_d") {
- return eMTLSyntaxElement::map_d;
- }
- if (key_str == "refl" || key_str == "map_refl") {
- return eMTLSyntaxElement::map_refl;
- }
- if (key_str == "map_Ke") {
- return eMTLSyntaxElement::map_Ke;
- }
- if (key_str == "map_Bump" || key_str == "bump") {
- return eMTLSyntaxElement::map_Bump;
- }
- return eMTLSyntaxElement::string;
-}
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh
index c6ce7d3c434..b67ba46af03 100644
--- a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh
@@ -8,6 +8,7 @@
#include "BKE_lib_id.h"
+#include "BLI_map.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
@@ -61,10 +62,11 @@ struct PolyCorner {
};
struct PolyElem {
- std::string vertex_group;
- std::string material_name;
+ int vertex_group_index = -1;
+ int material_index = -1;
bool shaded_smooth = false;
- Vector<PolyCorner> face_corners;
+ int start_index_ = 0;
+ int corner_count_ = 0;
};
/**
@@ -93,15 +95,20 @@ enum eGeometryType {
struct Geometry {
eGeometryType geom_type_ = GEOM_MESH;
std::string geometry_name_;
- VectorSet<std::string> material_names_;
- /**
- * Indices in the vector range from zero to total vertices in a geometry.
- * Values range from zero to total coordinates in the global list.
- */
- Vector<int> vertex_indices_;
+ Map<std::string, int> group_indices_;
+ Vector<std::string> group_order_;
+ Map<std::string, int> material_indices_;
+ Vector<std::string> material_order_;
+
+ int vertex_start_ = 0;
+ int vertex_count_ = 0;
/** Edges written in the file in addition to (or even without polygon) elements. */
Vector<MEdge> edges_;
+
+ Vector<PolyCorner> face_corners_;
Vector<PolyElem> face_elements_;
+
+ bool has_invalid_polys_ = false;
bool has_vertex_normals_ = false;
bool use_vertex_groups_ = false;
NurbsElement nurbs_element_;
diff --git a/source/blender/io/wavefront_obj/importer/obj_importer.cc b/source/blender/io/wavefront_obj/importer/obj_importer.cc
index 631ddcc5cf4..c21d2d9583c 100644
--- a/source/blender/io/wavefront_obj/importer/obj_importer.cc
+++ b/source/blender/io/wavefront_obj/importer/obj_importer.cc
@@ -42,6 +42,12 @@ static void geometry_to_blender_objects(
BKE_view_layer_base_deselect_all(view_layer);
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
+ /* Don't do collection syncs for each object, will do once after the loop. */
+ BKE_layer_collection_resync_forbid();
+
+ /* Create all the objects. */
+ Vector<Object *> objects;
+ objects.reserve(all_geometries.size());
for (const std::unique_ptr<Geometry> &geometry : all_geometries) {
Object *obj = nullptr;
if (geometry->geom_type_ == GEOM_MESH) {
@@ -54,17 +60,25 @@ static void geometry_to_blender_objects(
}
if (obj != nullptr) {
BKE_collection_object_add(bmain, lc->collection, obj);
- Base *base = BKE_view_layer_base_find(view_layer, obj);
- /* TODO: is setting active needed? */
- BKE_view_layer_base_select_and_set_active(view_layer, base);
-
- DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE);
- DEG_id_tag_update_ex(bmain,
- &obj->id,
- ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
- ID_RECALC_BASE_FLAGS);
+ objects.append(obj);
}
}
+
+ /* Sync the collection after all objects are created. */
+ BKE_layer_collection_resync_allow();
+ BKE_main_collection_sync(bmain);
+
+ /* After collection sync, select objects in the view layer and do DEG updates. */
+ for (Object *obj : objects) {
+ Base *base = BKE_view_layer_base_find(view_layer, obj);
+ BKE_view_layer_base_select_and_set_active(view_layer, base);
+
+ DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE);
+ int flags = ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
+ ID_RECALC_BASE_FLAGS;
+ DEG_id_tag_update_ex(bmain, &obj->id, flags);
+ }
+
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS);
DEG_relations_tag_update(bmain);
}
@@ -81,7 +95,8 @@ void importer_main(bContext *C, const OBJImportParams &import_params)
void importer_main(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
- const OBJImportParams &import_params)
+ const OBJImportParams &import_params,
+ size_t read_buffer_size)
{
/* List of Geometry instances to be parsed from OBJ file. */
Vector<std::unique_ptr<Geometry>> all_geometries;
@@ -91,7 +106,7 @@ void importer_main(Main *bmain,
Map<std::string, std::unique_ptr<MTLMaterial>> materials;
Map<std::string, Material *> created_materials;
- OBJParser obj_parser{import_params};
+ OBJParser obj_parser{import_params, read_buffer_size};
obj_parser.parse(all_geometries, global_vertices);
for (StringRef mtl_library : obj_parser.mtl_libraries()) {
diff --git a/source/blender/io/wavefront_obj/importer/obj_importer.hh b/source/blender/io/wavefront_obj/importer/obj_importer.hh
index fd83117ebc6..35f401d7cb0 100644
--- a/source/blender/io/wavefront_obj/importer/obj_importer.hh
+++ b/source/blender/io/wavefront_obj/importer/obj_importer.hh
@@ -17,6 +17,7 @@ void importer_main(bContext *C, const OBJImportParams &import_params);
void importer_main(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
- const OBJImportParams &import_params);
+ const OBJImportParams &import_params,
+ size_t read_buffer_size = 64 * 1024);
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/parser_string_utils.cc b/source/blender/io/wavefront_obj/importer/parser_string_utils.cc
deleted file mode 100644
index 6671a86f5ee..00000000000
--- a/source/blender/io/wavefront_obj/importer/parser_string_utils.cc
+++ /dev/null
@@ -1,174 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-#include <fstream>
-#include <iostream>
-#include <sstream>
-
-#include "BLI_math_vec_types.hh"
-#include "BLI_span.hh"
-#include "BLI_string_ref.hh"
-#include "BLI_vector.hh"
-
-#include "parser_string_utils.hh"
-
-/* Note: these OBJ parser helper functions are planned to get fairly large
- * changes "soon", so don't read too much into current implementation... */
-
-namespace blender::io::obj {
-using std::string;
-
-void read_next_line(std::fstream &file, string &r_line)
-{
- std::string new_line;
- while (file.good() && !r_line.empty() && r_line.back() == '\\') {
- new_line.clear();
- const bool ok = static_cast<bool>(std::getline(file, new_line));
- /* Remove the last backslash character. */
- r_line.pop_back();
- r_line.append(new_line);
- if (!ok || new_line.empty()) {
- return;
- }
- }
-}
-
-void split_line_key_rest(const StringRef line, StringRef &r_line_key, StringRef &r_rest_line)
-{
- if (line.is_empty()) {
- return;
- }
-
- const int64_t pos_split{line.find_first_of(' ')};
- if (pos_split == StringRef::not_found) {
- /* Use the first character if no space is found in the line. It's usually a comment like:
- * #This is a comment. */
- r_line_key = line.substr(0, 1);
- }
- else {
- r_line_key = line.substr(0, pos_split);
- }
-
- /* Eat the delimiter also using "+ 1". */
- r_rest_line = line.drop_prefix(r_line_key.size() + 1);
- if (r_rest_line.is_empty()) {
- return;
- }
-
- /* Remove any leading spaces, trailing spaces & \r character, if any. */
- const int64_t leading_space{r_rest_line.find_first_not_of(' ')};
- if (leading_space != StringRef::not_found) {
- r_rest_line = r_rest_line.drop_prefix(leading_space);
- }
-
- /* Another way is to do a test run before the actual parsing to find the newline
- * character and use it in the getline. */
- const int64_t carriage_return{r_rest_line.find_first_of('\r')};
- if (carriage_return != StringRef::not_found) {
- r_rest_line = r_rest_line.substr(0, carriage_return + 1);
- }
-
- const int64_t trailing_space{r_rest_line.find_last_not_of(' ')};
- if (trailing_space != StringRef::not_found) {
- /* The position is of a character that is not ' ', so count of characters is position + 1. */
- r_rest_line = r_rest_line.substr(0, trailing_space + 1);
- }
-}
-
-void split_by_char(StringRef in_string, const char delimiter, Vector<StringRef> &r_out_list)
-{
- r_out_list.clear();
-
- while (!in_string.is_empty()) {
- const int64_t pos_delim{in_string.find_first_of(delimiter)};
- const int64_t word_len = pos_delim == StringRef::not_found ? in_string.size() : pos_delim;
-
- StringRef word{in_string.data(), word_len};
- if (!word.is_empty() && !(word == " " && !(word[0] == '\0'))) {
- r_out_list.append(word);
- }
- if (pos_delim == StringRef::not_found) {
- return;
- }
- /* Skip the word already stored. */
- in_string = in_string.drop_prefix(word_len);
- /* Skip all delimiters. */
- const int64_t pos_non_delim = in_string.find_first_not_of(delimiter);
- if (pos_non_delim == StringRef::not_found) {
- return;
- }
- in_string = in_string.drop_prefix(std::min(pos_non_delim, in_string.size()));
- }
-}
-
-void copy_string_to_float(StringRef src, const float fallback_value, float &r_dst)
-{
- try {
- r_dst = std::stof(string(src));
- }
- catch (const std::invalid_argument &inv_arg) {
- std::cerr << "Bad conversion to float:'" << inv_arg.what() << "':'" << src << "'" << std::endl;
- r_dst = fallback_value;
- }
- catch (const std::out_of_range &out_of_range) {
- std::cerr << "Out of range for float:'" << out_of_range.what() << ":'" << src << "'"
- << std::endl;
- r_dst = fallback_value;
- }
-}
-
-void copy_string_to_float(Span<StringRef> src,
- const float fallback_value,
- MutableSpan<float> r_dst)
-{
- for (int i = 0; i < r_dst.size(); ++i) {
- if (i < src.size()) {
- copy_string_to_float(src[i], fallback_value, r_dst[i]);
- }
- else {
- r_dst[i] = fallback_value;
- }
- }
-}
-
-void copy_string_to_int(StringRef src, const int fallback_value, int &r_dst)
-{
- try {
- r_dst = std::stoi(string(src));
- }
- catch (const std::invalid_argument &inv_arg) {
- std::cerr << "Bad conversion to int:'" << inv_arg.what() << "':'" << src << "'" << std::endl;
- r_dst = fallback_value;
- }
- catch (const std::out_of_range &out_of_range) {
- std::cerr << "Out of range for int:'" << out_of_range.what() << ":'" << src << "'"
- << std::endl;
- r_dst = fallback_value;
- }
-}
-
-void copy_string_to_int(Span<StringRef> src, const int fallback_value, MutableSpan<int> r_dst)
-{
- for (int i = 0; i < r_dst.size(); ++i) {
- if (i < src.size()) {
- copy_string_to_int(src[i], fallback_value, r_dst[i]);
- }
- else {
- r_dst[i] = fallback_value;
- }
- }
-}
-
-std::string replace_all_occurences(StringRef original, StringRef to_remove, StringRef to_add)
-{
- std::string clean{original};
- while (true) {
- const std::string::size_type pos = clean.find(to_remove);
- if (pos == std::string::npos) {
- break;
- }
- clean.replace(pos, to_add.size(), to_add);
- }
- return clean;
-}
-
-} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/importer/parser_string_utils.hh b/source/blender/io/wavefront_obj/importer/parser_string_utils.hh
deleted file mode 100644
index 62cfbebccf3..00000000000
--- a/source/blender/io/wavefront_obj/importer/parser_string_utils.hh
+++ /dev/null
@@ -1,54 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-namespace blender::io::obj {
-
-/* Note: these OBJ parser helper functions are planned to get fairly large
- * changes "soon", so don't read too much into current implementation... */
-
-/**
- * Store multiple lines separated by an escaped newline character: `\\n`.
- * Use this before doing any parse operations on the read string.
- */
-void read_next_line(std::fstream &file, std::string &r_line);
-/**
- * Split a line string into the first word (key) and the rest of the line.
- * Also remove leading & trailing spaces as well as `\r` carriage return
- * character if present.
- */
-void split_line_key_rest(StringRef line, StringRef &r_line_key, StringRef &r_rest_line);
-/**
- * Split the given string by the delimiter and fill the given vector.
- * If an intermediate string is empty, or space or null character, it is not appended to the
- * vector.
- */
-void split_by_char(StringRef in_string, const char delimiter, Vector<StringRef> &r_out_list);
-/**
- * Convert the given string to float and assign it to the destination value.
- *
- * If the string cannot be converted to a float, the fallback value is used.
- */
-void copy_string_to_float(StringRef src, const float fallback_value, float &r_dst);
-/**
- * Convert all members of the Span of strings to floats and assign them to the float
- * array members. Usually used for values like coordinates.
- *
- * If a string cannot be converted to a float, the fallback value is used.
- */
-void copy_string_to_float(Span<StringRef> src,
- const float fallback_value,
- MutableSpan<float> r_dst);
-/**
- * Convert the given string to int and assign it to the destination value.
- *
- * If the string cannot be converted to an integer, the fallback value is used.
- */
-void copy_string_to_int(StringRef src, const int fallback_value, int &r_dst);
-/**
- * Convert the given strings to ints and fill the destination int buffer.
- *
- * If a string cannot be converted to an integer, the fallback value is used.
- */
-void copy_string_to_int(Span<StringRef> src, const int fallback_value, MutableSpan<int> r_dst);
-std::string replace_all_occurences(StringRef original, StringRef to_remove, StringRef to_add);
-
-} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
index 8599b38d55b..f74bfc155dd 100644
--- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
+++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
@@ -48,7 +48,6 @@ class obj_exporter_test : public BlendfileLoadingBaseTest {
};
const std::string all_objects_file = "io_tests/blend_scene/all_objects.blend";
-const std::string all_curve_objects_file = "io_tests/blend_scene/all_curves.blend";
TEST_F(obj_exporter_test, filter_objects_curves_as_mesh)
{
@@ -58,7 +57,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_mesh)
return;
}
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
- EXPECT_EQ(objmeshes.size(), 19);
+ EXPECT_EQ(objmeshes.size(), 20);
EXPECT_EQ(objcurves.size(), 0);
}
@@ -72,7 +71,7 @@ TEST_F(obj_exporter_test, filter_objects_curves_as_nurbs)
_export.params.export_curves_as_nurbs = true;
auto [objmeshes, objcurves]{filter_supported_objects(depsgraph, _export.params)};
EXPECT_EQ(objmeshes.size(), 18);
- EXPECT_EQ(objcurves.size(), 2);
+ EXPECT_EQ(objcurves.size(), 3);
}
TEST_F(obj_exporter_test, filter_objects_selected)
@@ -111,64 +110,6 @@ TEST(obj_exporter_utils, append_positive_frame_to_filename)
EXPECT_EQ_ARRAY(path_with_frame, path_truth, BLI_strlen_utf8(path_truth));
}
-TEST_F(obj_exporter_test, curve_nurbs_points)
-{
- if (!load_file_and_depsgraph(all_curve_objects_file)) {
- ADD_FAILURE();
- return;
- }
-
- OBJExportParamsDefault _export;
- _export.params.export_curves_as_nurbs = true;
- auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)};
-
- for (auto &objcurve : objcurves) {
- if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) {
- ADD_FAILURE();
- return;
- }
- const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get();
- EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines());
- for (int spline_index : IndexRange(objcurve->total_splines())) {
- EXPECT_EQ(objcurve->total_spline_vertices(spline_index),
- nurbs_truth->total_spline_vertices(spline_index));
- EXPECT_EQ(objcurve->get_nurbs_degree(spline_index),
- nurbs_truth->get_nurbs_degree(spline_index));
- EXPECT_EQ(objcurve->total_spline_control_points(spline_index),
- nurbs_truth->total_spline_control_points(spline_index));
- }
- }
-}
-
-TEST_F(obj_exporter_test, curve_coordinates)
-{
- if (!load_file_and_depsgraph(all_curve_objects_file)) {
- ADD_FAILURE();
- return;
- }
-
- OBJExportParamsDefault _export;
- _export.params.export_curves_as_nurbs = true;
- auto [objmeshes_unused, objcurves]{filter_supported_objects(depsgraph, _export.params)};
-
- for (auto &objcurve : objcurves) {
- if (all_nurbs_truth.count(objcurve->get_curve_name()) != 1) {
- ADD_FAILURE();
- return;
- }
- const NurbsObject *const nurbs_truth = all_nurbs_truth.at(objcurve->get_curve_name()).get();
- EXPECT_EQ(objcurve->total_splines(), nurbs_truth->total_splines());
- for (int spline_index : IndexRange(objcurve->total_splines())) {
- for (int vertex_index : IndexRange(objcurve->total_spline_vertices(spline_index))) {
- EXPECT_V3_NEAR(objcurve->vertex_coordinates(
- spline_index, vertex_index, _export.params.scaling_factor),
- nurbs_truth->vertex_coordinates(spline_index, vertex_index),
- 0.000001f);
- }
- }
- }
-}
-
static std::unique_ptr<OBJWriter> init_writer(const OBJExportParams &params,
const std::string out_filepath)
{
@@ -467,6 +408,19 @@ TEST_F(obj_exporter_regression_test, cube_normal_edit)
_export.params);
}
+TEST_F(obj_exporter_regression_test, cube_vertex_groups)
+{
+ OBJExportParamsDefault _export;
+ _export.params.export_materials = false;
+ _export.params.export_normals = false;
+ _export.params.export_uv = false;
+ _export.params.export_vertex_groups = true;
+ compare_obj_export_to_golden("io_tests/blend_geometry/cube_vertex_groups.blend",
+ "io_tests/obj/cube_vertex_groups.obj",
+ "",
+ _export.params);
+}
+
TEST_F(obj_exporter_regression_test, cubes_positioned)
{
OBJExportParamsDefault _export;
@@ -504,6 +458,25 @@ TEST_F(obj_exporter_regression_test, suzanne_all_data)
_export.params);
}
+TEST_F(obj_exporter_regression_test, all_curves)
+{
+ OBJExportParamsDefault _export;
+ _export.params.export_materials = false;
+ compare_obj_export_to_golden(
+ "io_tests/blend_scene/all_curves.blend", "io_tests/obj/all_curves.obj", "", _export.params);
+}
+
+TEST_F(obj_exporter_regression_test, all_curves_as_nurbs)
+{
+ OBJExportParamsDefault _export;
+ _export.params.export_materials = false;
+ _export.params.export_curves_as_nurbs = true;
+ compare_obj_export_to_golden("io_tests/blend_scene/all_curves.blend",
+ "io_tests/obj/all_curves_as_nurbs.obj",
+ "",
+ _export.params);
+}
+
TEST_F(obj_exporter_regression_test, all_objects)
{
OBJExportParamsDefault _export;
diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
index 42660bbbe56..6a821e0b1bf 100644
--- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
+++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh
@@ -1,77 +1,11 @@
/* SPDX-License-Identifier: Apache-2.0 */
-/**
- * This file contains default values for several items like
- * vertex coordinates, export parameters, MTL values etc.
- */
-
#pragma once
-#include <array>
-#include <gtest/gtest.h>
-#include <string>
-#include <vector>
-
#include "IO_wavefront_obj.h"
namespace blender::io::obj {
-using array_float_3 = std::array<float, 3>;
-
-/**
- * This matches #OBJCurve's member functions, except that all the numbers and names are known
- * constants. Used to store expected values of NURBS curves objects.
- */
-class NurbsObject {
- private:
- std::string nurbs_name_;
- /* The indices in these vectors are spline indices. */
- std::vector<std::vector<array_float_3>> coordinates_;
- std::vector<int> degrees_;
- std::vector<int> control_points_;
-
- public:
- NurbsObject(const std::string nurbs_name,
- const std::vector<std::vector<array_float_3>> coordinates,
- const std::vector<int> degrees,
- const std::vector<int> control_points)
- : nurbs_name_(nurbs_name),
- coordinates_(coordinates),
- degrees_(degrees),
- control_points_(control_points)
- {
- }
-
- int total_splines() const
- {
- return coordinates_.size();
- }
-
- int total_spline_vertices(const int spline_index) const
- {
- if (spline_index >= coordinates_.size()) {
- ADD_FAILURE();
- return 0;
- }
- return coordinates_[spline_index].size();
- }
-
- const float *vertex_coordinates(const int spline_index, const int vertex_index) const
- {
- return coordinates_[spline_index][vertex_index].data();
- }
-
- int get_nurbs_degree(const int spline_index) const
- {
- return degrees_[spline_index];
- }
-
- int total_spline_control_points(const int spline_index) const
- {
- return control_points_[spline_index];
- }
-};
-
struct OBJExportParamsDefault {
OBJExportParams params;
OBJExportParamsDefault()
@@ -103,48 +37,4 @@ struct OBJExportParamsDefault {
}
};
-const std::vector<std::vector<array_float_3>> coordinates_NurbsCurve{
- {{6.94742, 0.000000, 0.000000},
- {7.44742, 0.000000, -1.000000},
- {9.44742, 0.000000, -1.000000},
- {9.94742, 0.000000, 0.000000}}};
-const std::vector<std::vector<array_float_3>> coordinates_NurbsCircle{
- {{11.463165, 0.000000, 1.000000},
- {10.463165, 0.000000, 1.000000},
- {10.463165, 0.000000, 0.000000},
- {10.463165, 0.000000, -1.000000},
- {11.463165, 0.000000, -1.000000},
- {12.463165, 0.000000, -1.000000},
- {12.463165, 0.000000, 0.000000},
- {12.463165, 0.000000, 1.000000}}};
-const std::vector<std::vector<array_float_3>> coordinates_NurbsPathCurve{
- {{13.690557, 0.000000, 0.000000},
- {14.690557, 0.000000, 0.000000},
- {15.690557, 0.000000, 0.000000},
- {16.690557, 0.000000, 0.000000},
- {17.690557, 0.000000, 0.000000}},
- {{14.192808, 0.000000, 0.000000},
- {14.692808, 0.000000, -1.000000},
- {16.692808, 0.000000, -1.000000},
- {17.192808, 0.000000, 0.000000}}};
-
-const std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs_truth = []() {
- std::map<std::string, std::unique_ptr<NurbsObject>> all_nurbs;
- all_nurbs.emplace(
- "NurbsCurve",
- /* Name, coordinates, degrees of splines, control points of splines. */
- std::make_unique<NurbsObject>(
- "NurbsCurve", coordinates_NurbsCurve, std::vector<int>{3}, std::vector<int>{4}));
- all_nurbs.emplace(
- "NurbsCircle",
- std::make_unique<NurbsObject>(
- "NurbsCircle", coordinates_NurbsCircle, std::vector<int>{3}, std::vector<int>{11}));
- /* This is actually an Object containing a NurbsPath and a NurbsCurve spline. */
- all_nurbs.emplace("NurbsPathCurve",
- std::make_unique<NurbsObject>("NurbsPathCurve",
- coordinates_NurbsPathCurve,
- std::vector<int>{3, 3},
- std::vector<int>{5, 4}));
- return all_nurbs;
-}();
} // namespace blender::io::obj
diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc
index 9dd9e7c1a37..3d34fb6f9c6 100644
--- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc
+++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc
@@ -60,7 +60,8 @@ class obj_importer_test : public BlendfileLoadingBaseTest {
std::string obj_path = blender::tests::flags_test_asset_dir() + "/io_tests/obj/" + path;
strncpy(params.filepath, obj_path.c_str(), FILE_MAX - 1);
- importer_main(bfile->main, bfile->curscene, bfile->cur_view_layer, params);
+ const size_t read_buffer_size = 650;
+ importer_main(bfile->main, bfile->curscene, bfile->cur_view_layer, params, read_buffer_size);
depsgraph_create(DAG_EVAL_VIEWPORT);
@@ -444,6 +445,7 @@ TEST_F(obj_importer_test, import_all_objects)
float3(5, 1, 1),
float3(0, 0, 1),
float2(0.654526f, 0.579873f)},
+ {"OBNurbsCircle.001", OB_MESH, 4, 4, 0, 0, float3(2, -3, 0), float3(3, -2, 0)},
{"OBSurface",
OB_MESH,
256,
diff --git a/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc
new file mode 100644
index 00000000000..176d32a0be4
--- /dev/null
+++ b/source/blender/io/wavefront_obj/tests/obj_mtl_parser_tests.cc
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include <gtest/gtest.h>
+
+#include "testing/testing.h"
+
+#include "obj_import_file_reader.hh"
+
+namespace blender::io::obj {
+
+class obj_mtl_parser_test : public testing::Test {
+ public:
+ void check(const char *file, const MTLMaterial *expect, size_t expect_count)
+ {
+ std::string obj_dir = blender::tests::flags_test_asset_dir() + "/io_tests/obj/";
+ MTLParser parser(file, obj_dir + "dummy.obj");
+ Map<std::string, std::unique_ptr<MTLMaterial>> materials;
+ parser.parse_and_store(materials);
+
+ for (int i = 0; i < expect_count; ++i) {
+ const MTLMaterial &exp = expect[i];
+ if (!materials.contains(exp.name)) {
+ fprintf(stderr, "Material '%s' was expected in parsed result\n", exp.name.c_str());
+ ADD_FAILURE();
+ continue;
+ }
+ const MTLMaterial &got = *materials.lookup(exp.name);
+ const float tol = 0.0001f;
+ EXPECT_V3_NEAR(exp.Ka, got.Ka, tol);
+ EXPECT_V3_NEAR(exp.Kd, got.Kd, tol);
+ EXPECT_V3_NEAR(exp.Ks, got.Ks, tol);
+ EXPECT_V3_NEAR(exp.Ke, got.Ke, tol);
+ EXPECT_NEAR(exp.Ns, got.Ns, tol);
+ EXPECT_NEAR(exp.Ni, got.Ni, tol);
+ EXPECT_NEAR(exp.d, got.d, tol);
+ EXPECT_NEAR(exp.map_Bump_strength, got.map_Bump_strength, tol);
+ EXPECT_EQ(exp.illum, got.illum);
+ for (const auto &it : exp.texture_maps.items()) {
+ const tex_map_XX &exp_tex = it.value;
+ const tex_map_XX &got_tex = got.texture_maps.lookup(it.key);
+ EXPECT_STREQ(exp_tex.image_path.c_str(), got_tex.image_path.c_str());
+ EXPECT_V3_NEAR(exp_tex.translation, got_tex.translation, tol);
+ EXPECT_V3_NEAR(exp_tex.scale, got_tex.scale, tol);
+ EXPECT_EQ(exp_tex.projection_type, got_tex.projection_type);
+ }
+ }
+ EXPECT_EQ(materials.size(), expect_count);
+ }
+};
+
+TEST_F(obj_mtl_parser_test, cube)
+{
+ MTLMaterial mat;
+ mat.name = "red";
+ mat.Ka = {0.2f, 0.2f, 0.2f};
+ mat.Kd = {1, 0, 0};
+ check("cube.mtl", &mat, 1);
+}
+
+TEST_F(obj_mtl_parser_test, all_objects)
+{
+ MTLMaterial mat[7];
+ for (auto &m : mat) {
+ m.Ka = {1, 1, 1};
+ m.Ks = {0.5f, 0.5f, 0.5f};
+ m.Ke = {0, 0, 0};
+ m.Ns = 250;
+ m.Ni = 1;
+ m.d = 1;
+ m.illum = 2;
+ }
+ mat[0].name = "Blue";
+ mat[0].Kd = {0, 0, 1};
+ mat[1].name = "BlueDark";
+ mat[1].Kd = {0, 0, 0.5f};
+ mat[2].name = "Green";
+ mat[2].Kd = {0, 1, 0};
+ mat[3].name = "GreenDark";
+ mat[3].Kd = {0, 0.5f, 0};
+ mat[4].name = "Material";
+ mat[4].Kd = {0.8f, 0.8f, 0.8f};
+ mat[5].name = "Red";
+ mat[5].Kd = {1, 0, 0};
+ mat[6].name = "RedDark";
+ mat[6].Kd = {0.5f, 0, 0};
+ check("all_objects.mtl", mat, ARRAY_SIZE(mat));
+}
+
+TEST_F(obj_mtl_parser_test, materials)
+{
+ MTLMaterial mat[5];
+ mat[0].name = "no_textures_red";
+ mat[0].Ka = {0.3f, 0.3f, 0.3f};
+ mat[0].Kd = {0.8f, 0.3f, 0.1f};
+ mat[0].Ns = 5.624998f;
+
+ mat[1].name = "four_maps";
+ mat[1].Ka = {1, 1, 1};
+ mat[1].Kd = {0.8f, 0.8f, 0.8f};
+ mat[1].Ks = {0.5f, 0.5f, 0.5f};
+ mat[1].Ke = {0, 0, 0};
+ mat[1].Ns = 1000;
+ mat[1].Ni = 1.45f;
+ mat[1].d = 1;
+ mat[1].illum = 2;
+ mat[1].map_Bump_strength = 1;
+ {
+ tex_map_XX &kd = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Kd);
+ kd.image_path = "texture.png";
+ tex_map_XX &ns = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Ns);
+ ns.image_path = "sometexture_Roughness.png";
+ tex_map_XX &refl = mat[1].tex_map_of_type(eMTLSyntaxElement::map_refl);
+ refl.image_path = "sometexture_Metallic.png";
+ tex_map_XX &bump = mat[1].tex_map_of_type(eMTLSyntaxElement::map_Bump);
+ bump.image_path = "sometexture_Normal.png";
+ }
+
+ mat[2].name = "Clay";
+ mat[2].Ka = {1, 1, 1};
+ mat[2].Kd = {0.8f, 0.682657f, 0.536371f};
+ mat[2].Ks = {0.5f, 0.5f, 0.5f};
+ mat[2].Ke = {0, 0, 0};
+ mat[2].Ns = 440.924042f;
+ mat[2].Ni = 1.45f;
+ mat[2].d = 1;
+ mat[2].illum = 2;
+
+ mat[3].name = "Hat";
+ mat[3].Ka = {1, 1, 1};
+ mat[3].Kd = {0.8f, 0.8f, 0.8f};
+ mat[3].Ks = {0.5f, 0.5f, 0.5f};
+ mat[3].Ns = 800;
+ mat[3].map_Bump_strength = 0.5f;
+ {
+ tex_map_XX &kd = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Kd);
+ kd.image_path = "someHatTexture_BaseColor.jpg";
+ tex_map_XX &ns = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Ns);
+ ns.image_path = "someHatTexture_Roughness.jpg";
+ tex_map_XX &refl = mat[3].tex_map_of_type(eMTLSyntaxElement::map_refl);
+ refl.image_path = "someHatTexture_Metalness.jpg";
+ tex_map_XX &bump = mat[3].tex_map_of_type(eMTLSyntaxElement::map_Bump);
+ bump.image_path = "someHatTexture_Normal.jpg";
+ }
+
+ mat[4].name = "Parser_Test";
+ mat[4].Ka = {0.1f, 0.2f, 0.3f};
+ mat[4].Kd = {0.4f, 0.5f, 0.6f};
+ mat[4].Ks = {0.7f, 0.8f, 0.9f};
+ mat[4].illum = 6;
+ mat[4].Ns = 15.5;
+ mat[4].Ni = 1.5;
+ mat[4].d = 0.5;
+ mat[4].map_Bump_strength = 0.1f;
+ {
+ tex_map_XX &kd = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Kd);
+ kd.image_path = "sometex_d.png";
+ tex_map_XX &ns = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Ns);
+ ns.image_path = "sometex_ns.psd";
+ tex_map_XX &refl = mat[4].tex_map_of_type(eMTLSyntaxElement::map_refl);
+ refl.image_path = "clouds.tiff";
+ refl.scale = {1.5f, 2.5f, 3.5f};
+ refl.translation = {4.5f, 5.5f, 6.5f};
+ refl.projection_type = SHD_PROJ_SPHERE;
+ tex_map_XX &bump = mat[4].tex_map_of_type(eMTLSyntaxElement::map_Bump);
+ bump.image_path = "somebump.tga";
+ bump.scale = {3, 4, 5};
+ }
+
+ check("materials.mtl", mat, ARRAY_SIZE(mat));
+}
+
+} // namespace blender::io::obj
diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h
index 38e99896ab1..332317142c7 100644
--- a/source/blender/makesdna/DNA_material_types.h
+++ b/source/blender/makesdna/DNA_material_types.h
@@ -31,6 +31,8 @@ typedef struct TexPaintSlot {
/** Image to be painted on. Mutual exclusive with attribute_name. */
struct Image *ima;
+ struct ImageUser *image_user;
+
/** Custom-data index for uv layer, #MAX_NAME. */
char *uvname;
/**
diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h
index 0ff9ebb2337..2b4858d4106 100644
--- a/source/blender/makesdna/DNA_mesh_types.h
+++ b/source/blender/makesdna/DNA_mesh_types.h
@@ -138,7 +138,11 @@ typedef struct Mesh_Runtime {
float (*vert_normals)[3];
float (*poly_normals)[3];
- void *_pad2;
+ /**
+ * A #BLI_bitmap containing tags for the center vertices of subdivided polygons, set by the
+ * subdivision surface modifier and used by drawing code instead of polygon center face dots.
+ */
+ uint32_t *subsurf_face_dot_tags;
} Mesh_Runtime;
typedef struct Mesh {
diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h
index 3c734419bfe..2a4234bde6a 100644
--- a/source/blender/makesdna/DNA_meshdata_types.h
+++ b/source/blender/makesdna/DNA_meshdata_types.h
@@ -33,7 +33,6 @@ typedef struct MVert {
enum {
/* SELECT = (1 << 0), */
ME_HIDE = (1 << 4),
- ME_VERT_FACEDOT = (1 << 5),
};
/**
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index 9cc4d5ed55b..d6c1040110f 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -939,6 +939,7 @@ typedef struct PaintModeSettings {
/** Selected image when canvas_source=PAINT_CANVAS_SOURCE_IMAGE. */
Image *canvas_image;
+ ImageUser image_user;
} PaintModeSettings;
diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h
index 838213dd2f3..8d527caa361 100644
--- a/source/blender/makesdna/DNA_space_types.h
+++ b/source/blender/makesdna/DNA_space_types.h
@@ -517,7 +517,7 @@ typedef enum eGraphEdit_Mode {
typedef enum eGraphEdit_Runtime_Flag {
/** Temporary flag to force channel selections to be synced with main. */
SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC = (1 << 0),
- /** Temporary flag to force F-curves to recalculate colors. */
+ /** Temporary flag to force F-Curves to recalculate colors. */
SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC_COLOR = (1 << 1),
/**
diff --git a/source/blender/makesrna/intern/rna_action.c b/source/blender/makesrna/intern/rna_action.c
index 11d5298dfd7..76d2087d904 100644
--- a/source/blender/makesrna/intern/rna_action.c
+++ b/source/blender/makesrna/intern/rna_action.c
@@ -783,7 +783,7 @@ static void rna_def_action_fcurves(BlenderRNA *brna, PropertyRNA *cprop)
/* Action.fcurves.remove(...) */
func = RNA_def_function(srna, "remove", "rna_Action_fcurve_remove");
- RNA_def_function_ui_description(func, "Remove action group");
+ RNA_def_function_ui_description(func, "Remove F-Curve");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "F-Curve to remove");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index 8c86e44aebf..5ddc8e97e45 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -5256,7 +5256,7 @@ static void rna_def_userdef_edit(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_duplicate_fcurve", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "dupflag", USER_DUP_FCURVE);
RNA_def_property_ui_text(
- prop, "Duplicate F-Curve", "Causes F-curve data to be duplicated with the object");
+ prop, "Duplicate F-Curve", "Causes F-Curve data to be duplicated with the object");
# endif
prop = RNA_def_property(srna, "use_duplicate_action", PROP_BOOLEAN, PROP_NONE);
diff --git a/source/blender/nodes/NOD_node_tree_ref.hh b/source/blender/nodes/NOD_node_tree_ref.hh
index 3ed09de8fab..61d1d11d859 100644
--- a/source/blender/nodes/NOD_node_tree_ref.hh
+++ b/source/blender/nodes/NOD_node_tree_ref.hh
@@ -65,7 +65,6 @@ class SocketRef : NonCopyable, NonMovable {
bool is_input_;
int id_;
int index_;
- PointerRNA rna_;
Vector<LinkRef *> directly_linked_links_;
/* These sockets are linked directly, i.e. with a single link in between. */
@@ -101,7 +100,7 @@ class SocketRef : NonCopyable, NonMovable {
const InputSocketRef &as_input() const;
const OutputSocketRef &as_output() const;
- PointerRNA *rna() const;
+ PointerRNA rna() const;
StringRefNull idname() const;
StringRefNull name() const;
@@ -152,7 +151,6 @@ class NodeRef : NonCopyable, NonMovable {
private:
NodeTreeRef *tree_;
bNode *bnode_;
- PointerRNA rna_;
int id_;
Vector<InputSocketRef *> inputs_;
Vector<OutputSocketRef *> outputs_;
@@ -183,7 +181,7 @@ class NodeRef : NonCopyable, NonMovable {
bNode *bnode() const;
bNodeTree *btree() const;
- PointerRNA *rna() const;
+ PointerRNA rna() const;
StringRefNull idname() const;
StringRefNull name() const;
StringRefNull label() const;
@@ -410,11 +408,6 @@ inline const OutputSocketRef &SocketRef::as_output() const
return static_cast<const OutputSocketRef &>(*this);
}
-inline PointerRNA *SocketRef::rna() const
-{
- return const_cast<PointerRNA *>(&rna_);
-}
-
inline StringRefNull SocketRef::idname() const
{
return bsocket_->idname;
@@ -571,11 +564,6 @@ inline bNodeTree *NodeRef::btree() const
return tree_->btree();
}
-inline PointerRNA *NodeRef::rna() const
-{
- return const_cast<PointerRNA *>(&rna_);
-}
-
inline StringRefNull NodeRef::idname() const
{
return bnode_->idname;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
index e7a8c61290b..903a5e7c1d7 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
-#include "BKE_spline.hh"
+#include "BKE_curves.hh"
#include "BKE_curve_to_mesh.hh"
@@ -27,17 +27,18 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set,
const GeometrySet &profile_set,
const bool fill_caps)
{
- const std::unique_ptr<CurveEval> curve = curves_to_curve_eval(
- *geometry_set.get_curves_for_read());
+ const Curves &curves = *geometry_set.get_curves_for_read();
+
const Curves *profile_curves = profile_set.get_curves_for_read();
if (profile_curves == nullptr) {
- Mesh *mesh = bke::curve_to_wire_mesh(*curve);
+ Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry));
geometry_set.replace_mesh(mesh);
}
else {
- const std::unique_ptr<CurveEval> profile_curve = curves_to_curve_eval(*profile_curves);
- Mesh *mesh = bke::curve_to_mesh_sweep(*curve, *profile_curve, fill_caps);
+ Mesh *mesh = bke::curve_to_mesh_sweep(bke::CurvesGeometry::wrap(curves.geometry),
+ bke::CurvesGeometry::wrap(profile_curves->geometry),
+ fill_caps);
geometry_set.replace_mesh(mesh);
}
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
index 1e170dd5350..1b26cfe31fe 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc
@@ -759,6 +759,7 @@ static void duplicate_edges(GeometrySet &geometry_set,
MEdge &new_edge = new_edges[edge_range[i_duplicate]];
new_edge.v1 = vert_range[i_duplicate * 2];
new_edge.v2 = vert_range[i_duplicate * 2] + 1;
+ new_edge.flag = ME_LOOSEEDGE;
}
}
});
diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc
index c4befd5828c..abbfe4b823d 100644
--- a/source/blender/nodes/intern/node_common.cc
+++ b/source/blender/nodes/intern/node_common.cc
@@ -476,7 +476,10 @@ void node_group_input_update(bNodeTree *ntree, bNode *node)
/* redirect links from the extension socket */
for (link = (bNodeLink *)tmplinks.first; link; link = link->next) {
- nodeAddLink(ntree, node, newsock, link->tonode, link->tosock);
+ bNodeLink *newlink = nodeAddLink(ntree, node, newsock, link->tonode, link->tosock);
+ if (newlink->tosock->flag & SOCK_MULTI_INPUT) {
+ newlink->multi_input_socket_index = link->multi_input_socket_index;
+ }
}
}
diff --git a/source/blender/nodes/intern/node_tree_ref.cc b/source/blender/nodes/intern/node_tree_ref.cc
index 01a31ab852d..64a8690a869 100644
--- a/source/blender/nodes/intern/node_tree_ref.cc
+++ b/source/blender/nodes/intern/node_tree_ref.cc
@@ -21,7 +21,6 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree)
node.tree_ = this;
node.bnode_ = bnode;
node.id_ = nodes_by_id_.append_and_get_index(&node);
- RNA_pointer_create(&btree->id, &RNA_Node, bnode, &node.rna_);
LISTBASE_FOREACH (bNodeSocket *, bsocket, &bnode->inputs) {
InputSocketRef &socket = *allocator_.construct<InputSocketRef>().release();
@@ -30,7 +29,6 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree)
socket.is_input_ = true;
socket.bsocket_ = bsocket;
socket.id_ = sockets_by_id_.append_and_get_index(&socket);
- RNA_pointer_create(&btree->id, &RNA_NodeSocket, bsocket, &socket.rna_);
}
LISTBASE_FOREACH (bNodeSocket *, bsocket, &bnode->outputs) {
@@ -40,7 +38,6 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree)
socket.is_input_ = false;
socket.bsocket_ = bsocket;
socket.id_ = sockets_by_id_.append_and_get_index(&socket);
- RNA_pointer_create(&btree->id, &RNA_NodeSocket, bsocket, &socket.rna_);
}
LISTBASE_FOREACH (bNodeLink *, blink, &bnode->internal_links) {
@@ -664,4 +661,18 @@ const NodeTreeRef &get_tree_ref_from_map(NodeTreeRefMap &node_tree_refs, bNodeTr
[&]() { return std::make_unique<NodeTreeRef>(&btree); });
}
+PointerRNA NodeRef::rna() const
+{
+ PointerRNA rna;
+ RNA_pointer_create(&tree_->btree()->id, &RNA_Node, bnode_, &rna);
+ return rna;
+}
+
+PointerRNA SocketRef::rna() const
+{
+ PointerRNA rna;
+ RNA_pointer_create(&this->tree().btree()->id, &RNA_NodeSocket, bsocket_, &rna);
+ return rna;
+}
+
} // namespace blender::nodes
diff --git a/source/blender/nodes/shader/node_shader_tree.cc b/source/blender/nodes/shader/node_shader_tree.cc
index 42a31f39041..d917106807c 100644
--- a/source/blender/nodes/shader/node_shader_tree.cc
+++ b/source/blender/nodes/shader/node_shader_tree.cc
@@ -16,7 +16,7 @@
#include "DNA_workspace_types.h"
#include "DNA_world_types.h"
-#include "BLI_alloca.h"
+#include "BLI_array.hh"
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_threads.h"
@@ -48,6 +48,7 @@
#include "node_shader_util.hh"
#include "node_util.h"
+using blender::Array;
using blender::Vector;
static bool shader_tree_poll(const bContext *C, bNodeTreeType *UNUSED(treetype))
@@ -570,8 +571,7 @@ static bNode *ntree_shader_copy_branch(bNodeTree *ntree,
iter_data.node_count = 1;
nodeChainIterBackwards(ntree, start_node, ntree_branch_count_and_tag_nodes, &iter_data, 1);
/* Make a full copy of the branch */
- bNode **nodes_copy = static_cast<bNode **>(
- MEM_mallocN(sizeof(bNode *) * iter_data.node_count, __func__));
+ Array<bNode *> nodes_copy(iter_data.node_count);
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->tmp_flag >= 0) {
int id = node->tmp_flag;
@@ -604,7 +604,6 @@ static bNode *ntree_shader_copy_branch(bNodeTree *ntree,
}
}
bNode *start_node_copy = nodes_copy[start_node->tmp_flag];
- MEM_freeN(nodes_copy);
return start_node_copy;
}
@@ -705,7 +704,7 @@ static void ntree_shader_weight_tree_invert(bNodeTree *ntree, bNode *output_node
int node_count = 1;
nodeChainIterBackwards(ntree, output_node, ntree_weight_tree_tag_nodes, &node_count, 0);
/* Make a mirror copy of the weight tree. */
- bNode **nodes_copy = static_cast<bNode **>(MEM_mallocN(sizeof(bNode *) * node_count, __func__));
+ Array<bNode *> nodes_copy(node_count);
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->tmp_flag >= 0) {
int id = node->tmp_flag;
@@ -909,8 +908,6 @@ static void ntree_shader_weight_tree_invert(bNodeTree *ntree, bNode *output_node
ntree, thickness_link->fromnode, thickness_link->fromsock, output_node, thickness_output);
}
BKE_ntree_update_main_tree(G.main, ntree, nullptr);
-
- MEM_freeN(nodes_copy);
}
static bool closure_node_filter(const bNode *node)
diff --git a/source/blender/sequencer/SEQ_animation.h b/source/blender/sequencer/SEQ_animation.h
index f2c66393b65..b8f74e5a510 100644
--- a/source/blender/sequencer/SEQ_animation.h
+++ b/source/blender/sequencer/SEQ_animation.h
@@ -20,15 +20,15 @@ void SEQ_free_animdata(struct Scene *scene, struct Sequence *seq);
void SEQ_offset_animdata(struct Scene *scene, struct Sequence *seq, int ofs);
struct GSet *SEQ_fcurves_by_strip_get(const struct Sequence *seq, struct ListBase *fcurve_base);
/**
- * Move all `F-curves` from `scene` to `list`.
+ * Move all `F-Curves` from `scene` to `list`.
*/
void SEQ_animation_backup_original(struct Scene *scene, struct ListBase *list);
/**
- * Move all `F-curves` from `list` to `scene`.
+ * Move all `F-Curves` from `list` to `scene`.
*/
void SEQ_animation_restore_original(struct Scene *scene, struct ListBase *list);
/**
- * Duplicate `F-curves` used by `seq` from `list` to `scene`.
+ * Duplicate `F-Curves` used by `seq` from `list` to `scene`.
*/
void SEQ_animation_duplicate(struct Scene *scene, struct Sequence *seq, struct ListBase *list);
diff --git a/source/blender/sequencer/intern/strip_edit.c b/source/blender/sequencer/intern/strip_edit.c
index d678518e3b0..7aa81f5ae8a 100644
--- a/source/blender/sequencer/intern/strip_edit.c
+++ b/source/blender/sequencer/intern/strip_edit.c
@@ -461,7 +461,7 @@ Sequence *SEQ_edit_strip_split(Main *bmain,
return NULL;
}
- /* Store `F-curves`, so original ones aren't renamed. */
+ /* Store `F-Curves`, so original ones aren't renamed. */
ListBase fcurves_original_backup = {NULL, NULL};
SEQ_animation_backup_original(scene, &fcurves_original_backup);
diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c
index 3c8474b1b6c..4b506564260 100644
--- a/source/blender/windowmanager/intern/wm_draw.c
+++ b/source/blender/windowmanager/intern/wm_draw.c
@@ -24,6 +24,7 @@
#include "BLI_utildefines.h"
#include "BKE_context.h"
+#include "BKE_global.h"
#include "BKE_image.h"
#include "BKE_main.h"
#include "BKE_scene.h"
@@ -459,13 +460,24 @@ static void wm_draw_region_buffer_create(ARegion *region, bool stereo, bool use_
}
}
-static void wm_draw_region_bind(ARegion *region, int view)
+static bool wm_draw_region_bind(bContext *C, ARegion *region, int view)
{
if (!region->draw_buffer) {
- return;
+ return true;
}
if (region->draw_buffer->viewport) {
+ if (G.is_rendering && C != NULL) {
+ Scene *scene = CTX_data_scene(C);
+ RenderEngineType *render_engine_type = RE_engines_find(scene->r.engine);
+ if (RE_engine_is_opengl(render_engine_type)) {
+ /* Do not try to acquire the viewport as this would be locking at the moment.
+ * But tag the viewport to update after the rendering finishes. */
+ GPU_viewport_tag_update(region->draw_buffer->viewport);
+ return false;
+ }
+ }
+
GPU_viewport_bind(region->draw_buffer->viewport, view, &region->winrct);
}
else {
@@ -478,6 +490,7 @@ static void wm_draw_region_bind(ARegion *region, int view)
}
region->draw_buffer->bound_view = view;
+ return true;
}
static void wm_draw_region_unbind(ARegion *region)
@@ -700,9 +713,10 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo)
wm_draw_region_stereo_set(bmain, area, region, sview);
}
- wm_draw_region_bind(region, view);
- ED_region_do_draw(C, region);
- wm_draw_region_unbind(region);
+ if (wm_draw_region_bind(C, region, view)) {
+ ED_region_do_draw(C, region);
+ wm_draw_region_unbind(region);
+ }
}
if (use_viewport) {
GPUViewport *viewport = region->draw_buffer->viewport;
@@ -711,9 +725,10 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo)
}
else {
wm_draw_region_buffer_create(region, false, use_viewport);
- wm_draw_region_bind(region, 0);
- ED_region_do_draw(C, region);
- wm_draw_region_unbind(region);
+ if (wm_draw_region_bind(C, region, 0)) {
+ ED_region_do_draw(C, region);
+ wm_draw_region_unbind(region);
+ }
}
GPU_debug_group_end();
@@ -744,10 +759,11 @@ static void wm_draw_window_offscreen(bContext *C, wmWindow *win, bool stereo)
}
wm_draw_region_buffer_create(region, false, false);
- wm_draw_region_bind(region, 0);
- GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);
- ED_region_do_draw(C, region);
- wm_draw_region_unbind(region);
+ if (wm_draw_region_bind(C, region, 0)) {
+ GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f);
+ ED_region_do_draw(C, region);
+ wm_draw_region_unbind(region);
+ }
GPU_debug_group_end();
@@ -1102,10 +1118,11 @@ void wm_draw_region_test(bContext *C, ScrArea *area, ARegion *region)
/* Function for redraw timer benchmark. */
bool use_viewport = WM_region_use_viewport(area, region);
wm_draw_region_buffer_create(region, false, use_viewport);
- wm_draw_region_bind(region, 0);
- ED_region_do_draw(C, region);
- wm_draw_region_unbind(region);
- region->do_draw = false;
+ if (wm_draw_region_bind(C, region, 0)) {
+ ED_region_do_draw(C, region);
+ wm_draw_region_unbind(region);
+ region->do_draw = false;
+ }
}
void WM_redraw_windows(bContext *C)
@@ -1141,7 +1158,7 @@ void WM_draw_region_viewport_ensure(ARegion *region, short space_type)
void WM_draw_region_viewport_bind(ARegion *region)
{
- wm_draw_region_bind(region, 0);
+ wm_draw_region_bind(NULL, region, 0);
}
void WM_draw_region_viewport_unbind(ARegion *region)
diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c
index 89bb6906a22..382a37e09e5 100644
--- a/source/blender/windowmanager/intern/wm_window.c
+++ b/source/blender/windowmanager/intern/wm_window.c
@@ -1541,7 +1541,15 @@ void wm_ghost_init(bContext *C)
}
g_system = GHOST_CreateSystem();
- GHOST_SystemInitDebug(g_system, G.debug & G_DEBUG_GHOST);
+
+ GHOST_Debug debug = {0};
+ if (G.debug & G_DEBUG_GHOST) {
+ debug.flags |= GHOST_kDebugDefault;
+ }
+ if (G.debug & G_DEBUG_WINTAB) {
+ debug.flags |= GHOST_kDebugWintab;
+ }
+ GHOST_SystemInitDebug(g_system, debug);
if (C != NULL) {
GHOST_AddEventConsumer(g_system, consumer);
diff --git a/source/creator/creator_args.c b/source/creator/creator_args.c
index 05b7f1bcb85..b3f5d24ee8c 100644
--- a/source/creator/creator_args.c
+++ b/source/creator/creator_args.c
@@ -576,6 +576,7 @@ static int arg_handle_print_help(int UNUSED(argc), const char **UNUSED(argv), vo
BLI_args_print_arg_doc(ba, "--debug-depsgraph-pretty");
BLI_args_print_arg_doc(ba, "--debug-depsgraph-uuid");
BLI_args_print_arg_doc(ba, "--debug-ghost");
+ BLI_args_print_arg_doc(ba, "--debug-wintab");
BLI_args_print_arg_doc(ba, "--debug-gpu");
BLI_args_print_arg_doc(ba, "--debug-gpu-force-workarounds");
BLI_args_print_arg_doc(ba, "--debug-wm");
@@ -943,6 +944,12 @@ static const char arg_handle_debug_mode_generic_set_doc_wm[] =
"\n\t"
"Enable debug messages for the window manager, shows all operators in search, shows "
"keymap errors.";
+static const char arg_handle_debug_mode_generic_set_doc_ghost[] =
+ "\n\t"
+ "Enable debug messages for Ghost (Linux only).";
+static const char arg_handle_debug_mode_generic_set_doc_wintab[] =
+ "\n\t"
+ "Enable debug messages for Wintab.";
# ifdef WITH_XR_OPENXR
static const char arg_handle_debug_mode_generic_set_doc_xr[] =
"\n\t"
@@ -2130,8 +2137,13 @@ void main_args_setup(bContext *C, bArgs *ba)
BLI_args_add(ba,
NULL,
"--debug-ghost",
- CB_EX(arg_handle_debug_mode_generic_set, handlers),
+ CB_EX(arg_handle_debug_mode_generic_set, ghost),
(void *)G_DEBUG_GHOST);
+ BLI_args_add(ba,
+ NULL,
+ "--debug-wintab",
+ CB_EX(arg_handle_debug_mode_generic_set, wintab),
+ (void *)G_DEBUG_WINTAB);
BLI_args_add(ba, NULL, "--debug-all", CB(arg_handle_debug_mode_all), NULL);
BLI_args_add(ba, NULL, "--debug-io", CB(arg_handle_debug_mode_io), NULL);
diff --git a/source/tools b/source/tools
-Subproject 1e658ca996f11e5ff3398d89bd81f5b719304a5
+Subproject 4c1e01e3e309282beb1af3b1eddb2c7f9a666b5
diff --git a/tests/performance/api/config.py b/tests/performance/api/config.py
index 03d699cfdfb..6d095065123 100644
--- a/tests/performance/api/config.py
+++ b/tests/performance/api/config.py
@@ -68,11 +68,14 @@ class TestQueue:
def rows(self, use_revision_columns: bool) -> List:
# Generate rows of entries for printing and running.
- entries = sorted(self.entries, key=lambda entry:
- (entry.revision,
- entry.device_id,
- entry.category,
- entry.test))
+ entries = sorted(
+ self.entries,
+ key=lambda entry: (
+ entry.revision,
+ entry.device_id,
+ entry.category,
+ entry.test,
+ ))
if not use_revision_columns:
# One entry per row.
diff --git a/tests/performance/api/environment.py b/tests/performance/api/environment.py
index 61a7c5dff7b..1e2e4a84e81 100644
--- a/tests/performance/api/environment.py
+++ b/tests/performance/api/environment.py
@@ -32,7 +32,7 @@ class TestEnvironment:
self._init_default_blender_executable()
self.set_default_blender_executable()
- def get_machine(self, need_gpus: bool=True) -> None:
+ def get_machine(self, need_gpus: bool = True) -> None:
if not self.machine or (need_gpus and not self.machine.has_gpus):
self.machine = TestMachine(self, need_gpus)
@@ -61,7 +61,8 @@ class TestEnvironment:
if not self.blender_dir.exists():
print(f'Init git worktree in {self.blender_dir}')
- self.call([self.git_executable, 'worktree', 'add', '--detach', self.blender_dir, 'HEAD'], self.blender_git_dir)
+ self.call([self.git_executable, 'worktree', 'add', '--detach',
+ self.blender_dir, 'HEAD'], self.blender_git_dir)
else:
print(f'Exists {self.blender_dir}')
@@ -165,7 +166,7 @@ class TestEnvironment:
def unset_log_file(self) -> None:
self.log_file = None
- def call(self, args: List[str], cwd: pathlib.Path, silent: bool=False, environment: Dict={}) -> List[str]:
+ def call(self, args: List[str], cwd: pathlib.Path, silent: bool = False, environment: Dict = {}) -> List[str]:
# Execute command with arguments in specified directory,
# and return combined stdout and stderr output.
@@ -220,7 +221,7 @@ class TestEnvironment:
def run_in_blender(self,
function: Callable[[Dict], Dict],
args: Dict,
- blender_args: List=[],
+ blender_args: List = [],
foreground=False) -> Dict:
# Run function in a Blender instance. Arguments and return values are
# passed as a Python object that must be serializable with pickle.
@@ -274,7 +275,7 @@ class TestEnvironment:
return names
- def get_configs(self, name: str=None, names_only: bool=False) -> List:
+ def get_configs(self, name: str = None, names_only: bool = False) -> List:
# Get list of configurations in the benchmarks directory.
configs = []
diff --git a/tests/performance/api/test.py b/tests/performance/api/test.py
index 72e72463cd1..d3ec092f78d 100644
--- a/tests/performance/api/test.py
+++ b/tests/performance/api/test.py
@@ -32,7 +32,7 @@ class Test:
class TestCollection:
- def __init__(self, env, names_filter: List=['*'], categories_filter: List=['*']):
+ def __init__(self, env, names_filter: List = ['*'], categories_filter: List = ['*']):
import importlib
import pkgutil
import tests
diff --git a/tests/python/bevel_operator.py b/tests/python/bevel_operator.py
index 64cf034bb85..d6d7ade944f 100644
--- a/tests/python/bevel_operator.py
+++ b/tests/python/bevel_operator.py
@@ -19,265 +19,265 @@ def main():
# 0
SpecMeshTest('Cube_test_1', 'Cube_test', 'Cube_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2}, 'EDGE', {10})]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2}, 'EDGE', {10})]),
SpecMeshTest('Cube_test_2', 'Cube_test', 'Cube_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'offset_type': 'WIDTH'}, 'EDGE', {10, 7}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'offset_type': 'WIDTH'}, 'EDGE', {10, 7}, )]),
SpecMeshTest('Cube_test_3', 'Cube_test', 'Cube_result_3',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'offset_type': 'DEPTH'}, 'EDGE', {8, 10, 7}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'offset_type': 'DEPTH'}, 'EDGE', {8, 10, 7}, )]),
SpecMeshTest('Cube_test_4', 'Cube_test', 'Cube_result_4',
- [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 2}, 'EDGE', {10}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 2}, 'EDGE', {10}, )]),
SpecMeshTest('Cube_test_5', 'Cube_test', 'Cube_result_5',
- [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 3}, 'EDGE', {10, 7}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 3}, 'EDGE', {10, 7}, )]),
# 5
SpecMeshTest('Cube_test_6', 'Cube_test', 'Cube_result_6',
- [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 4}, 'EDGE', {8, 10, 7}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.4, 'segments': 4}, 'EDGE', {8, 10, 7}, )]),
SpecMeshTest('Cube_test_7', 'Cube_test', 'Cube_result_7',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 5, 'profile': 0.2}, 'EDGE', {0, 10, 4, 7}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 5, 'profile': 0.2}, 'EDGE', {0, 10, 4, 7}, )]),
SpecMeshTest('Cube_test_8', 'Cube_test', 'Cube_result_8',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 5, 'profile': 0.25}, 'EDGE', {8, 10, 7}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 5, 'profile': 0.25}, 'EDGE', {8, 10, 7}, )]),
SpecMeshTest('Cube_test_9', 'Cube_test', 'Cube_result_9',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 6, 'profile': 0.9}, 'EDGE', {8, 10, 7}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 6, 'profile': 0.9}, 'EDGE', {8, 10, 7}, )]),
SpecMeshTest('Cube_test_10', 'Cube_test', 'Cube_result_10',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 4, 'profile': 1.0}, 'EDGE', {10, 7}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 4, 'profile': 1.0}, 'EDGE', {10, 7}, )]),
# 10
SpecMeshTest('Cube_test_11', 'Cube_test', 'Cube_result_11',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 5, 'profile': 1.0}, 'EDGE', {8, 10, 7}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 5, 'profile': 1.0}, 'EDGE', {8, 10, 7}, )]),
SpecMeshTest("test 12", 'Cube_test', 'Cube_result_12',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 8}, 'EDGE',
- {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 8}, 'EDGE',
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]),
SpecMeshTest('Pyramid4_test_1', 'Pyr4_test', 'Pyr4_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {5}, )]),
SpecMeshTest('Pyramid4_test_2', 'Pyr4_test', 'Pyr4_result_2',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 5}, )]),
SpecMeshTest('Pyramid4_test_3', 'Pyr4_test', 'Pyr4_result_3',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3, 5}, )]),
# 15
SpecMeshTest('Pyramid4_test_4', 'Pyr4_test', 'Pyr4_result_4',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {1, 2, 3, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {1, 2, 3, 5}, )]),
SpecMeshTest('Pyramid4_test_5', 'Pyr4_test', 'Pyr4_result_5',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 2, 3, 5}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 2, 3, 5}, )]),
SpecMeshTest('Pyramid4_test_6', 'Pyr4_test', 'Pyr4_result_6',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {2, 3}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {2, 3}, )]),
SpecMeshTest('Pyramid4_test_7', 'Pyr4_test', 'Pyr4_result_7',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 4, 'profile': 0.15}, 'EDGE', {1, 2, 3, 5}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 4, 'profile': 0.15}, 'EDGE', {1, 2, 3, 5}, )]),
SpecMeshTest('Pyramid4_test_8', 'Pyr4_test', 'Pyr4_result_8',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.75, 'segments': 4, 'affect': 'VERTICES'}, 'VERT', {1}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.75, 'segments': 4, 'affect': 'VERTICES'}, 'VERT', {1}, )]),
# 20
SpecMeshTest('Pyramid4_test_9', 'Pyr4_test', 'Pyr4_result_9',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.75, 'segments': 3, 'affect': 'VERTICES', 'profile': 0.25}, 'VERT',
- {1}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.75, 'segments': 3, 'affect': 'VERTICES', 'profile': 0.25}, 'VERT',
+ {1}, )]),
SpecMeshTest('Pyramid6_test_1', 'Pyr6_test', 'Pyr6_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {2, 3}, )]),
SpecMeshTest('Pyramid6_test_2', 'Pyr6_test', 'Pyr6_result_2',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {8, 2, 3}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {8, 2, 3}, )]),
SpecMeshTest('Pyramid6_test_3', 'Pyr6_test', 'Pyr6_result_3',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 4, 'profile': 0.8}, 'EDGE',
- {0, 2, 3, 4, 6, 7, 9, 10, 11}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 4, 'profile': 0.8}, 'EDGE',
+ {0, 2, 3, 4, 6, 7, 9, 10, 11}, )]),
SpecMeshTest('Sept_test_1', 'Sept_test', 'Sept_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.1}, 'EDGE', {8, 9, 3, 11}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.1}, 'EDGE', {8, 9, 3, 11}, )]),
# 25
SpecMeshTest('Sept_test_2', 'Sept_test', 'Sept_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.1, 'offset_type': 'WIDTH'}, 'EDGE', {8, 9, 11}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.1, 'offset_type': 'WIDTH'}, 'EDGE', {8, 9, 11}, )]),
SpecMeshTest('Saddle_test_1', 'Saddle_test', 'Saddle_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.3, 'segments': 5}, 'EDGE', {2, 8, 9, 12, 13, 14}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.3, 'segments': 5}, 'EDGE', {2, 8, 9, 12, 13, 14}, )]),
SpecMeshTest('Saddle_test_2', 'Saddle_test', 'Saddle_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.6, 'segments': 6, 'affect': 'VERTICES'}, 'VERT', {4}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.6, 'segments': 6, 'affect': 'VERTICES'}, 'VERT', {4}, )]),
SpecMeshTest('Bent_test', 'Bent_test', 'Bent_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3},
- 'EDGE',
- {2, 5, 8, 11, 14, 18, 21, 24, 27, 30, 34, 37, 40, 43, 46, 50, 53, 56, 59, 62,
- 112, 113, 114, 115}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3},
+ 'EDGE',
+ {2, 5, 8, 11, 14, 18, 21, 24, 27, 30, 34, 37, 40, 43, 46, 50, 53, 56, 59, 62,
+ 112, 113, 114, 115}, )]),
SpecMeshTest('Bentlines_test_1', 'Bentlines_test', 'Bentlines_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 8, 9, 10, 11}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 8, 9, 10, 11}, )]),
# 30
SpecMeshTest('Flaretop_test_1', 'Flaretop_test', 'Flaretop_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 2}, 'EDGE', {26, 12, 20}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 2}, 'EDGE', {26, 12, 20}, )]),
SpecMeshTest('Flaretop_test_2', 'Flaretop_test', 'Flaretop_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 2, 'profile': 1.0}, 'EDGE', {26, 12, 20}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 2, 'profile': 1.0}, 'EDGE', {26, 12, 20}, )]),
SpecMeshTest('Flaretop_test_3', 'Flaretop_test', 'Flaretop_result_3',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.4, 'segments': 4}, 'FACE', {1, 6, 7, 8, 9, 10, 11, 12}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.4, 'segments': 4}, 'FACE', {1, 6, 7, 8, 9, 10, 11, 12}, )]),
SpecMeshTest('BentL_test', 'BentL_test', 'BentL_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {4, 8, 10, 18, 24}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {4, 8, 10, 18, 24}, )]),
SpecMeshTest('Wires_test_1', 'Wires_test', 'Wires_test_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.3}, 'EDGE', {0, 1, 2, 10}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.3}, 'EDGE', {0, 1, 2, 10}, )]),
# 35
SpecMeshTest('Wires_test_2', 'Wires_test', 'Wires_test_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.3, 'affect': 'VERTICES'}, 'VERT',
- {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.3, 'affect': 'VERTICES'}, 'VERT',
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, )]),
SpecMeshTest('tri_test_1', 'tri', 'tri_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri_test_2', 'tri', 'tri_result_2',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri_test_3', 'tri', 'tri_result_3',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri_test_4', 'tri', 'tri_result_4',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]),
# 40
SpecMeshTest('tri_test_5', 'tri', 'tri_result_5',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]),
SpecMeshTest('tri_test_6', 'tri', 'tri_result_6',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
SpecMeshTest('tri_test_7', 'tri', 'tri_result_7',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
SpecMeshTest('tri_test_8', 'tri', 'tri_result_8',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
SpecMeshTest('tri_test_9', 'tri', 'tri_result_9',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {1}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {1}, )]),
# 45
SpecMeshTest('tri1gap_test_2', 'tri1gap', 'tri1gap_result_2',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri1gap_test_3', 'tri1gap', 'tri1gap_result_3',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri1gap_test_1', 'tri1gap', 'tri1gap_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri1gap_test_4', 'tri1gap', 'tri1gap_result_4',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]),
SpecMeshTest('tri1gap_test_5', 'tri1gap', 'tri1gap_result_5',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]),
# 50
SpecMeshTest('tri1gap_test_6', 'tri1gap', 'tri1gap_result_6',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]),
SpecMeshTest('tri1gap_test_7', 'tri1gap', 'tri1gap_result_7',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 5}, )]),
SpecMeshTest('tri1gap_test_8', 'tri1gap', 'tri1gap_result_8',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 5}, )]),
SpecMeshTest('tri1gap_test_9', 'tri1gap', 'tri1gap_result_9',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 5}, )]),
SpecMeshTest('tri1gap_test_10', 'tri1gap', 'tri1gap_result_10',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'affect': 'VERTICES'}, 'VERT', {3}, )]),
# 55
SpecMeshTest('tri2gaps_test_1', 'tri2gaps', 'tri2gaps_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri2gaps_test_2', 'tri2gaps', 'tri2gaps_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri2gaps_test_3', 'tri2gaps', 'tri2gaps_result_3',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri2gaps_test_4', 'tri2gaps', 'tri2gaps_result_4',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4}, )]),
SpecMeshTest('tri2gaps_test_5', 'tri2gaps', 'tri2gaps_result_5',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4}, )]),
# 60
SpecMeshTest('tri2gaps_test_6', 'tri2gaps', 'tri2gaps_result_6',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4}, )]),
SpecMeshTest('tri3gaps_test_1', 'tri3gaps', 'tri3gaps_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri3gaps_test_2', 'tri3gaps', 'tri3gaps_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('tri3gaps_test_3', 'tri3gaps', 'tri3gaps_result_3',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3}, 'EDGE', {3, 4, 5}, )]),
SpecMeshTest('cube3_test_1', 'cube3', 'cube3_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2}, 'EDGE', {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2}, 'EDGE', {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]),
# 65
SpecMeshTest('cube3_test_2', 'cube3', 'cube3_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2}, 'EDGE',
- {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2}, 'EDGE',
+ {32, 33, 34, 35, 24, 25, 26, 27, 28, 29, 30, 31}, )]),
SpecMeshTest('cube3_test_3', 'cube3', 'cube3_result_3',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {32, 35}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {32, 35}, )]),
SpecMeshTest('cube3_test_4', 'cube3', 'cube3_result_4',
- [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {24, 35}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2}, 'EDGE', {24, 35}, )]),
SpecMeshTest('cube3_test_5', 'cube3', 'cube3_result_5',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {24, 32, 35}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 2}, 'EDGE', {24, 32, 35}, )]),
SpecMeshTest('cube3_test_6', 'cube3', 'cube3_result_6',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {24, 32, 35}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {24, 32, 35}, )]),
# 70
SpecMeshTest('Tray', 'Tray', 'Tray_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.01, 'segments': 2}, 'EDGE', {0, 1, 6, 7, 12, 14, 16, 17}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.01, 'segments': 2}, 'EDGE', {0, 1, 6, 7, 12, 14, 16, 17}, )]),
SpecMeshTest("test 73", 'Bumptop', 'Bumptop_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.1, 'segments': 4}, 'EDGE',
- {33, 4, 38, 8, 41, 10, 42, 12, 14, 17, 24, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.1, 'segments': 4}, 'EDGE',
+ {33, 4, 38, 8, 41, 10, 42, 12, 14, 17, 24, 31}, )]),
SpecMeshTest('Multisegment_test_1', 'Multisegment_test', 'Multisegment_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2}, 'EDGE', {16, 14, 15}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2}, 'EDGE', {16, 14, 15}, )]),
SpecMeshTest('Window_test', 'Window_test', 'Window_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.05, 'segments': 2}, 'EDGE', {19, 20, 23, 15}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.05, 'segments': 2}, 'EDGE', {19, 20, 23, 15}, )]),
# 75
SpecMeshTest("test 77", 'Cube_hn_test', 'Cube_hn_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'harden_normals': True}, 'EDGE', {8}, )]),
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'harden_normals': True}, 'EDGE', {8}, )]),
SpecMeshTest('Blocksteps_test_1', 'Blocksteps_test', 'Blocksteps_result_1',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'miter_outer': 'PATCH'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'miter_outer': 'PATCH'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps_test_2', 'Blocksteps_test', 'Blocksteps_result_2',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps_test_3', 'Blocksteps_test', 'Blocksteps_result_3',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3, 'miter_outer': 'PATCH'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3, 'miter_outer': 'PATCH'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps_test_4', 'Blocksteps_test', 'Blocksteps_result_4',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'miter_outer': 'ARC'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'miter_outer': 'ARC'}, 'EDGE', {4, 7, 39, 27, 30, 31}, )]),
# 80
SpecMeshTest('Blocksteps_test_5', 'Blocksteps_test', 'Blocksteps_result_5',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps_test_6', 'Blocksteps_test', 'Blocksteps_result_6',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps_test_7', 'Blocksteps_test', 'Blocksteps_result_7',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest("Blocksteps_test_8", 'Blocksteps_test', 'Blocksteps_result_8',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'},
- 'EDGE', {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2, 'miter_outer': 'PATCH', 'miter_inner': 'ARC'},
+ 'EDGE', {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps2_test', 'Blocksteps2_test', 'Blocksteps2_result_9',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
# 85
SpecMeshTest('Blocksteps3_test', 'Blocksteps3_test', 'Blocksteps3_result_10',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps4_test_1', 'Blocksteps4_test', 'Blocksteps4_result_11',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 2, 'miter_outer': 'ARC'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Blocksteps4_test_2', 'Blocksteps4_test', 'Blocksteps4_result_12',
- [OperatorSpecEditMode('bevel',
- {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE',
- {4, 7, 39, 27, 30, 31}, )]),
+ [OperatorSpecEditMode('bevel',
+ {'offset': 0.2, 'segments': 3, 'miter_outer': 'ARC'}, 'EDGE',
+ {4, 7, 39, 27, 30, 31}, )]),
SpecMeshTest('Spike_test', 'Spike_test', 'Spike_result_1',
- [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 7})])
+ [OperatorSpecEditMode('bevel', {'offset': 0.2, 'segments': 3}, 'EDGE', {1, 7})])
]
operator_test = RunTest(tests)
diff --git a/tests/python/bl_animation_fcurves.py b/tests/python/bl_animation_fcurves.py
index b93c201dc08..449f17ebfec 100644
--- a/tests/python/bl_animation_fcurves.py
+++ b/tests/python/bl_animation_fcurves.py
@@ -72,7 +72,7 @@ class EulerFilterTest(AbstractAnimationTest, unittest.TestCase):
self.activate_object('Three-Channel-Jump')
fcu_rot = self.active_object_rotation_channels()
- ## Check some pre-filter values to make sure the file is as we expect.
+ # # Check some pre-filter values to make sure the file is as we expect.
# Keyframes before the "jump". These shouldn't be touched by the filter.
self.assertEqualAngle(-87.5742, fcu_rot[0], 22)
self.assertEqualAngle(69.1701, fcu_rot[1], 22)
@@ -99,7 +99,7 @@ class EulerFilterTest(AbstractAnimationTest, unittest.TestCase):
self.activate_object('One-Channel-Jumps')
fcu_rot = self.active_object_rotation_channels()
- ## Check some pre-filter values to make sure the file is as we expect.
+ # # Check some pre-filter values to make sure the file is as we expect.
# Keyframes before the "jump". These shouldn't be touched by the filter.
self.assertEqualAngle(360, fcu_rot[0], 15)
self.assertEqualAngle(396, fcu_rot[1], 21) # X and Y are keyed on different frames.
diff --git a/tests/python/bl_blendfile_liblink.py b/tests/python/bl_blendfile_liblink.py
index 9a6aa9c9e10..120afba4911 100644
--- a/tests/python/bl_blendfile_liblink.py
+++ b/tests/python/bl_blendfile_liblink.py
@@ -204,7 +204,12 @@ class TestBlendLibAppendBasic(TestBlendLibLinkHelper):
bpy.ops.wm.append(directory=link_dir, filename="LibMesh",
instance_object_data=False, set_fake=False, use_recursive=False, do_reuse_local_id=False)
- print(bpy.data.materials[:], bpy.data.materials[0].library, bpy.data.materials[0].users, bpy.data.materials[0].use_fake_user)
+ print(
+ bpy.data.materials[:],
+ bpy.data.materials[0].library,
+ bpy.data.materials[0].users,
+ bpy.data.materials[0].use_fake_user,
+ )
assert(len(bpy.data.materials) == 1)
assert(bpy.data.materials[0].library is not None)
@@ -400,7 +405,6 @@ class TestBlendLibLibraryReload(TestBlendLibLinkHelper):
assert(orig_data == reload_data)
-
class TestBlendLibLibraryRelocate(TestBlendLibLinkHelper):
def __init__(self, args):
diff --git a/tests/python/bl_blendfile_library_overrides.py b/tests/python/bl_blendfile_library_overrides.py
index 6d9530e771e..1acc1e4d862 100644
--- a/tests/python/bl_blendfile_library_overrides.py
+++ b/tests/python/bl_blendfile_library_overrides.py
@@ -59,7 +59,7 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
self.assertIsNone(local_id.data.override_library)
assert(len(local_id.override_library.properties) == 0)
- ##### Generate an override property & operation automatically by editing the local override data.
+ # #### Generate an override property & operation automatically by editing the local override data.
local_id.location.y = 1.0
local_id.override_library.operations_update()
assert(len(local_id.override_library.properties) == 1)
@@ -71,12 +71,12 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
# Setting location.y overrode all elements in the location array. -1 is a wildcard.
assert(override_operation.subitem_local_index == -1)
- ##### Reset the override to its linked reference data.
+ # #### Reset the override to its linked reference data.
local_id.override_library.reset()
assert(len(local_id.override_library.properties) == 0)
assert(local_id.location == local_id.override_library.reference.location)
- ##### Generate an override property & operation manually using the API.
+ # #### Generate an override property & operation manually using the API.
override_property = local_id.override_library.properties.add(rna_path="location")
override_property.operations.add(operation='REPLACE')
@@ -95,11 +95,11 @@ class TestLibraryOverrides(TestHelper, unittest.TestCase):
assert(len(local_id.override_library.properties) == 0)
- ##### Delete the override.
+ # #### Delete the override.
local_id_name = local_id.name
assert(bpy.data.objects.get((local_id_name, None), None) == local_id)
local_id.override_library.destroy()
- assert(bpy.data.objects.get((local_id_name, None), None) == None)
+ assert(bpy.data.objects.get((local_id_name, None), None) is None)
def test_link_permissive(self):
"""
diff --git a/tests/python/bl_keymap_validate.py b/tests/python/bl_keymap_validate.py
index 1743893dc8a..b87eed0c0df 100644
--- a/tests/python/bl_keymap_validate.py
+++ b/tests/python/bl_keymap_validate.py
@@ -72,6 +72,7 @@ ALLOW_DUPLICATES = {
# -----------------------------------------------------------------------------
# Generic Utilities
+
@contextlib.contextmanager
def temp_fn_argument_extractor(
mod: types.ModuleType,
@@ -200,7 +201,7 @@ def keyconfig_config_as_filename_component(values: Sequence[Tuple[str, Any]]) ->
return "(" + quote(
".".join([
"-".join((str(key), str(val)))
- for key, val in values
+ for key, val in values
]),
# Needed so forward slashes aren't included in the resulting name.
safe="",
diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py
index ac1bd618570..ddb5be03594 100644
--- a/tests/python/bl_pyapi_idprop.py
+++ b/tests/python/bl_pyapi_idprop.py
@@ -12,6 +12,7 @@ try:
except ImportError:
np = None
+
class TestHelper:
@property
@@ -179,7 +180,6 @@ class TestIdPropertyGroupView(TestHelper, unittest.TestCase):
self.assertEqual(len(group), len(text))
self.assertEqual(list(iter(group)), text)
-
def test_contains(self):
# Check `idprop.types.IDPropertyGroupView{Keys/Values/Items}.__contains__`
text = ["A", "B", "C"]
diff --git a/tests/python/bl_rigging_symmetrize.py b/tests/python/bl_rigging_symmetrize.py
index dcd0ab65ced..3a5a0777d6c 100644
--- a/tests/python/bl_rigging_symmetrize.py
+++ b/tests/python/bl_rigging_symmetrize.py
@@ -98,7 +98,7 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone):
# Make sure that the constraint exists
self.assertTrue(const_name in bone.constraints,
"Bone %s is expected to contain constraint %s, but it does not." % (
- bone.name, const_name))
+ bone.name, const_name))
constraint = bone.constraints[const_name]
const_variables = constraint.bl_rna.properties.keys()
@@ -119,7 +119,7 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone):
if isinstance(value, str):
self.assertEqual(value, exp_value,
"Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
- bone.name, const_name, var))
+ bone.name, const_name, var))
elif hasattr(value, "name"):
# Some constraints targets the armature itself, so the armature name should missmatch.
if value.name == input_arm.name and exp_value.name == expected_arm.name:
@@ -127,16 +127,16 @@ def check_constraints(self, input_arm, expected_arm, bone, exp_bone):
self.assertEqual(value.name, exp_value.name,
"Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
- bone.name, const_name, var))
+ bone.name, const_name, var))
elif isinstance(value, bool):
self.assertEqual(value, exp_value,
- "Missmatching constraint boolean in pose.bones[%s].constraints[%s].%s" % (
- bone.name, const_name, var))
+ "Missmatching constraint boolean in pose.bones[%s].constraints[%s].%s" % (
+ bone.name, const_name, var))
else:
self.assertAlmostEqual(value, exp_value,
"Missmatching constraint value in pose.bones[%s].constraints[%s].%s" % (
- bone.name, const_name, var))
+ bone.name, const_name, var))
class AbstractAnimationTest:
diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py
index fc2fc819c8d..1ba9b4f1edf 100644
--- a/tests/python/bl_usd_import_test.py
+++ b/tests/python/bl_usd_import_test.py
@@ -10,6 +10,7 @@ import bpy
args = None
+
class AbstractUSDTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -22,6 +23,7 @@ class AbstractUSDTest(unittest.TestCase):
# Make sure we always start with a known-empty file.
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
+
class USDImportTest(AbstractUSDTest):
def test_import_prim_hierarchy(self):
@@ -42,6 +44,7 @@ class USDImportTest(AbstractUSDTest):
self.assertEqual(objects['World'], objects['Empty'].parent)
self.assertEqual(objects['Empty'], objects['Plane_002'].parent)
+
def main():
global args
import argparse
diff --git a/tests/python/boolean_operator.py b/tests/python/boolean_operator.py
index afad48fad36..fed0b2bddfd 100644
--- a/tests/python/boolean_operator.py
+++ b/tests/python/boolean_operator.py
@@ -20,31 +20,31 @@ def main():
tests = [
SpecMeshTest('Cubecube_intersect_union', 'Cubecube', 'Cubecube_result_1',
- [OperatorSpecEditMode('intersect_boolean',
- {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
+ [OperatorSpecEditMode('intersect_boolean',
+ {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
SpecMeshTest('Cubecube_intersect_intersect', 'Cubecube', 'Cubecube_result_2',
- [OperatorSpecEditMode('intersect_boolean', {'operation': 'INTERSECT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
+ [OperatorSpecEditMode('intersect_boolean', {'operation': 'INTERSECT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
SpecMeshTest('Cubecube_intersect_difference', 'Cubecube', 'Cubecube_result_3',
- [OperatorSpecEditMode('intersect_boolean', {'operation': 'DIFFERENCE', 'solver': 'FAST'}, 'FACE',
- {0, 1, 2, 3, 4, 5}, )]),
+ [OperatorSpecEditMode('intersect_boolean', {'operation': 'DIFFERENCE', 'solver': 'FAST'}, 'FACE',
+ {0, 1, 2, 3, 4, 5}, )]),
SpecMeshTest('Cubecube_intersect_cut', 'Cubecube', 'Cubecube_result_4', [OperatorSpecEditMode('intersect',
- {'separate_mode': 'CUT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
+ {'separate_mode': 'CUT', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
SpecMeshTest('Cubecube_intersect_all', 'Cubecube', 'Cubecube_result_5',
- [OperatorSpecEditMode('intersect',
- {'separate_mode': 'ALL', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
+ [OperatorSpecEditMode('intersect',
+ {'separate_mode': 'ALL', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
SpecMeshTest('Cubecube_intersect_none', 'Cubecube', 'Cubecube_result_6',
- [OperatorSpecEditMode('intersect',
- {'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
+ [OperatorSpecEditMode('intersect',
+ {'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
SpecMeshTest('Cubecube_intersect_select_none', 'Cubecube',
- 'Cubecube_result_7',
- [OperatorSpecEditMode('intersect',
- {'mode': 'SELECT', 'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE',
- {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]),
+ 'Cubecube_result_7',
+ [OperatorSpecEditMode('intersect',
+ {'mode': 'SELECT', 'separate_mode': 'NONE', 'solver': 'FAST'}, 'FACE',
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, )]),
SpecMeshTest('Cubecone_intersect_union', 'Cubecone', 'Cubecone_result_1',
- [OperatorSpecEditMode('intersect_boolean',
- {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {6, 7, 8, 9, 10}, )]),
+ [OperatorSpecEditMode('intersect_boolean',
+ {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {6, 7, 8, 9, 10}, )]),
SpecMeshTest('Cubecones_intersect_union', 'Cubecones', 'Cubecones_result_1',
- [OperatorSpecEditMode('intersect_boolean', {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
+ [OperatorSpecEditMode('intersect_boolean', {'operation': 'UNION', 'solver': 'FAST'}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
]
diff --git a/tests/python/curve_to_mesh.py b/tests/python/curve_to_mesh.py
index d37e7ef23a8..1d0c87b273a 100644
--- a/tests/python/curve_to_mesh.py
+++ b/tests/python/curve_to_mesh.py
@@ -14,69 +14,69 @@ from modules.mesh_test import SpecMeshTest, OperatorSpecObjectMode, RunTest
def main():
tests = [
SpecMeshTest('2D Non Cyclic', 'test2DNonCyclic', 'expected2DNonCyclic',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('2D NURBS With Tail', 'test2DNURBSWithTail', 'expected2DNURBSWithTail',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('2D Shape With Hole', 'test2DShapeWithHole', 'expected2DShapeWithHole',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('2D Simple Lower Res', 'test2DSimpleLowerRes', 'expected2DSimpleLowerRes',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('2D Simple Low Res', 'test2DSimpleLowRes', 'expected2DSimpleLowRes',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('2D Square', 'test2DSquare', 'expected2DSquare',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('2D Extrude', 'test2DExtrude', 'expected2DExtrude',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Back', 'testBevelBack', 'expectedBevelBack',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Back Low Res', 'testBevelBackLowRes', 'expectedBevelBackLowRes',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Extrude Back', 'testBevelExtrudeBack', 'expectedBevelExtrudeBack',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Extrude Front', 'testBevelExtrudeFront', 'expectedBevelExtrudeFront',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Extrude Full', 'testBevelExtrudeFull', 'expectedBevelExtrudeFull',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Extrude Half', 'testBevelExtrudeHalf', 'expectedBevelExtrudeHalf',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Front', 'testBevelFront', 'expectedBevelFront',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Front Low Res', 'testBevelFrontLowRes', 'expectedBevelFrontLowRes',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Full', 'testBevelFull', 'expectedBevelFull',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Full Low Res', 'testBevelFullLowRes', 'expectedBevelFullLowRes',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Half', 'testBevelHalf', 'expectedBevelHalf',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Bevel Half Low Res', 'testBevelHalfLowRes', 'expectedBevelHalfLowRes',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps None', 'testCapsNone', 'expectedCapsNone',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Object Bevel', 'testCapsObjectBevel', 'expectedCapsObjectBevel',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Profile Bevel', 'testCapsProfileBevel', 'expectedCapsProfileBevel',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Profile Bevel Half', 'testCapsProfileBevelHalf', 'expectedCapsProfileBevelHalf',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Profile Bevel Quarter', 'testCapsProfileBevelQuarter', 'expectedCapsProfileBevelQuarter',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Round Bevel', 'testCapsRoundBevel', 'expectedCapsRoundBevel',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Round Bevel Extrude', 'testCapsRoundBevelExtrude', 'expectedCapsRoundBevelExtrude',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Round Bevel Half', 'testCapsRoundBevelHalf', 'expectedCapsRoundBevelHalf',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Caps Round Bevel Quarter', 'testCapsRoundBevelQuarter', 'expectedCapsRoundBevelQuarter',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Extrude Back', 'testExtrudeBack', 'expectedExtrudeBack',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Extrude Front', 'testExtrudeFront', 'expectedExtrudeFront',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Extrude Full', 'testExtrudeFull', 'expectedExtrudeFull',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
SpecMeshTest('Extrude Half', 'testExtrudeHalf', 'expectedExtrudeHalf',
- [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
+ [OperatorSpecObjectMode('convert', {'target': 'MESH'})]),
]
operator_test = RunTest(tests)
diff --git a/tests/python/deform_modifiers.py b/tests/python/deform_modifiers.py
index cf0d1924e8f..40cd9d4839c 100644
--- a/tests/python/deform_modifiers.py
+++ b/tests/python/deform_modifiers.py
@@ -20,70 +20,70 @@ tests = [
# Actual deformation occurs by animating imitating user input.
SpecMeshTest("SurfaceDeform", "testObjMonkeySurfaceDeform", "expObjMonkeySurfaceDeform",
- [DeformModifierSpec(10, [
- ModifierSpec('surface_deform', 'SURFACE_DEFORM', {'target': bpy.data.objects["Cube"]})],
- OperatorSpecObjectMode('surfacedeform_bind', {'modifier': 'surface_deform'}))]),
+ [DeformModifierSpec(10, [
+ ModifierSpec('surface_deform', 'SURFACE_DEFORM', {'target': bpy.data.objects["Cube"]})],
+ OperatorSpecObjectMode('surfacedeform_bind', {'modifier': 'surface_deform'}))]),
# Mesh Deform Test, finally can bind to the Target object.
# Actual deformation occurs by animating imitating user input.
SpecMeshTest("MeshDeform", "testObjMonkeyMeshDeform", "expObjMonkeyMeshDeform",
- [DeformModifierSpec(10, [ModifierSpec('mesh_deform', 'MESH_DEFORM',
- {'object': bpy.data.objects["MeshCube"], 'precision': 2})],
- OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform'}))]),
+ [DeformModifierSpec(10, [ModifierSpec('mesh_deform', 'MESH_DEFORM',
+ {'object': bpy.data.objects["MeshCube"], 'precision': 2})],
+ OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform'}))]),
# Surface Deform Test, finally can bind to the Target object.
# Actual deformation occurs by animating imitating user input.
SpecMeshTest("Hook", "testObjHookPlane", "expObjHookPlane",
- [DeformModifierSpec(10, [ModifierSpec('hook', 'HOOK',
- {'object': bpy.data.objects["Empty"], 'falloff_radius': 1,
- 'vertex_group': 'Group'})])]),
+ [DeformModifierSpec(10, [ModifierSpec('hook', 'HOOK',
+ {'object': bpy.data.objects["Empty"], 'falloff_radius': 1,
+ 'vertex_group': 'Group'})])]),
# Laplacian Deform Test, first a hook is attached.
SpecMeshTest("Laplace", "testObjCubeLaplacian", "expObjCubeLaplacian",
- [DeformModifierSpec(10,
- [ModifierSpec('hook2', 'HOOK', {'object': bpy.data.objects["Empty.001"],
- 'vertex_group': 'hook_vg'}),
- ModifierSpec('laplace', 'LAPLACIANDEFORM', {'vertex_group': 'laplace_vg'})],
- OperatorSpecObjectMode('laplaciandeform_bind', {'modifier': 'laplace'}))]),
+ [DeformModifierSpec(10,
+ [ModifierSpec('hook2', 'HOOK', {'object': bpy.data.objects["Empty.001"],
+ 'vertex_group': 'hook_vg'}),
+ ModifierSpec('laplace', 'LAPLACIANDEFORM', {'vertex_group': 'laplace_vg'})],
+ OperatorSpecObjectMode('laplaciandeform_bind', {'modifier': 'laplace'}))]),
SpecMeshTest("WarpPlane", "testObjPlaneWarp", "expObjPlaneWarp",
- [DeformModifierSpec(10, [ModifierSpec('warp', 'WARP',
- {'object_from': bpy.data.objects["From"],
- 'object_to': bpy.data.objects["To"],
- })])]),
+ [DeformModifierSpec(10, [ModifierSpec('warp', 'WARP',
+ {'object_from': bpy.data.objects["From"],
+ 'object_to': bpy.data.objects["To"],
+ })])]),
#############################################
# Curves Deform Modifiers
#############################################
SpecMeshTest("CurveArmature", "testObjBezierCurveArmature", "expObjBezierCurveArmature",
- [DeformModifierSpec(10, [ModifierSpec('curve_armature', 'ARMATURE',
- {'object': bpy.data.objects['testArmatureHelper'],
- 'use_vertex_groups': False, 'use_bone_envelopes': True})])]),
+ [DeformModifierSpec(10, [ModifierSpec('curve_armature', 'ARMATURE',
+ {'object': bpy.data.objects['testArmatureHelper'],
+ 'use_vertex_groups': False, 'use_bone_envelopes': True})])]),
SpecMeshTest("CurveLattice", "testObjBezierCurveLattice", "expObjBezierCurveLattice",
- [DeformModifierSpec(10, [ModifierSpec('curve_lattice', 'LATTICE',
- {'object': bpy.data.objects['testLatticeCurve']})])]),
+ [DeformModifierSpec(10, [ModifierSpec('curve_lattice', 'LATTICE',
+ {'object': bpy.data.objects['testLatticeCurve']})])]),
# HOOK for Curves can't be tested with current framework, as it requires going to Edit Mode to select vertices,
# here is no equivalent of a vertex group in Curves.
# Dummy test for Hook, can also be called corner case
SpecMeshTest("CurveHook", "testObjBezierCurveHook", "expObjBezierCurveHook",
- [DeformModifierSpec(10,
- [ModifierSpec('curve_Hook', 'HOOK', {'object': bpy.data.objects['EmptyCurve']})])]),
+ [DeformModifierSpec(10,
+ [ModifierSpec('curve_Hook', 'HOOK', {'object': bpy.data.objects['EmptyCurve']})])]),
SpecMeshTest("MeshDeformCurve", "testObjCurveMeshDeform", "expObjCurveMeshDeform",
- [DeformModifierSpec(10, [
- ModifierSpec('mesh_deform_curve', 'MESH_DEFORM', {'object': bpy.data.objects["Cylinder"],
- 'precision': 2})],
- OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform_curve'}))]),
+ [DeformModifierSpec(10, [
+ ModifierSpec('mesh_deform_curve', 'MESH_DEFORM', {'object': bpy.data.objects["Cylinder"],
+ 'precision': 2})],
+ OperatorSpecObjectMode('meshdeform_bind', {'modifier': 'mesh_deform_curve'}))]),
SpecMeshTest("WarpCurve", "testObjBezierCurveWarp", "expObjBezierCurveWarp",
- [DeformModifierSpec(10, [ModifierSpec('warp_curve', 'WARP',
- {'object_from': bpy.data.objects["From_curve"],
- 'object_to': bpy.data.objects["To_curve"]})])]),
+ [DeformModifierSpec(10, [ModifierSpec('warp_curve', 'WARP',
+ {'object_from': bpy.data.objects["From_curve"],
+ 'object_to': bpy.data.objects["To_curve"]})])]),
]
diff --git a/tests/python/modifiers.py b/tests/python/modifiers.py
index 2433ecd29f9..8f8b5c6498c 100644
--- a/tests/python/modifiers.py
+++ b/tests/python/modifiers.py
@@ -73,147 +73,147 @@ def main():
# 0
# SpecMeshTest("testCube", "expectedCube", get_generate_modifiers_list("testCube")),
SpecMeshTest("CubeRandom", "testCubeRandom", "expectedCubeRandom",
- get_generate_modifiers_list("testCubeRandom", randomize=True)),
+ get_generate_modifiers_list("testCubeRandom", randomize=True)),
SpecMeshTest("CubeMaskFirst", "testCubeMaskFirst", "expectedCubeMaskFirst", mask_first_list),
SpecMeshTest("CollapseDecimate", "testCollapseDecimate", "expectedCollapseDecimate",
- [ModifierSpec('decimate', 'DECIMATE',
- {'decimate_type': 'COLLAPSE', 'ratio': 0.25, 'use_collapse_triangulate': True})]),
+ [ModifierSpec('decimate', 'DECIMATE',
+ {'decimate_type': 'COLLAPSE', 'ratio': 0.25, 'use_collapse_triangulate': True})]),
SpecMeshTest("PlanarDecimate", "testPlanarDecimate", "expectedPlanarDecimate",
- [ModifierSpec('decimate', 'DECIMATE',
- {'decimate_type': 'DISSOLVE', 'angle_limit': math.radians(30)})]),
+ [ModifierSpec('decimate', 'DECIMATE',
+ {'decimate_type': 'DISSOLVE', 'angle_limit': math.radians(30)})]),
SpecMeshTest("UnsubdivideDecimate", "testUnsubdivideDecimate", "expectedUnsubdivideDecimate",
- [ModifierSpec('decimate', 'DECIMATE', {'decimate_type': 'UNSUBDIV', 'iterations': 2})]),
+ [ModifierSpec('decimate', 'DECIMATE', {'decimate_type': 'UNSUBDIV', 'iterations': 2})]),
# 5
SpecMeshTest("RadialBisectMirror", "testRadialBisectMirror", "expectedRadialBisectMirror",
- [ModifierSpec('mirror1', 'MIRROR', {'use_bisect_axis': (True, False, False)}),
- ModifierSpec('mirror2', 'MIRROR', {'use_bisect_axis': (True, False, False),
- 'mirror_object': bpy.data.objects[
+ [ModifierSpec('mirror1', 'MIRROR', {'use_bisect_axis': (True, False, False)}),
+ ModifierSpec('mirror2', 'MIRROR', {'use_bisect_axis': (True, False, False),
+ 'mirror_object': bpy.data.objects[
"testRadialBisectMirrorHelper"]}),
- ModifierSpec('mirror3', 'MIRROR',
- {'use_axis': (False, True, False), 'use_bisect_axis': (False, True, False),
- 'use_bisect_flip_axis': (False, True, False),
- 'mirror_object': bpy.data.objects["testRadialBisectMirrorHelper"]})]),
+ ModifierSpec('mirror3', 'MIRROR',
+ {'use_axis': (False, True, False), 'use_bisect_axis': (False, True, False),
+ 'use_bisect_flip_axis': (False, True, False),
+ 'mirror_object': bpy.data.objects["testRadialBisectMirrorHelper"]})]),
SpecMeshTest("T58411Mirror", "regressT58411Mirror", "expectedT58411Mirror",
- [ModifierSpec('mirror', 'MIRROR', {}),
- ModifierSpec('bevel', 'BEVEL', {'segments': 2, 'limit_method': 'WEIGHT'}),
- ModifierSpec('subd', 'SUBSURF', {'levels': 1})]),
+ [ModifierSpec('mirror', 'MIRROR', {}),
+ ModifierSpec('bevel', 'BEVEL', {'segments': 2, 'limit_method': 'WEIGHT'}),
+ ModifierSpec('subd', 'SUBSURF', {'levels': 1})]),
SpecMeshTest("BasicScrew", "testBasicScrew", "expectedBasicScrew",
- [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testBasicScrewHelper"]}),
- ModifierSpec("screw", 'SCREW',
- {'angle': math.radians(400), 'steps': 20, 'iterations': 2, 'screw_offset': 2,
- 'use_normal_calculate': True})]),
+ [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testBasicScrewHelper"]}),
+ ModifierSpec("screw", 'SCREW',
+ {'angle': math.radians(400), 'steps': 20, 'iterations': 2, 'screw_offset': 2,
+ 'use_normal_calculate': True})]),
SpecMeshTest("ObjectScrew", "testObjectScrew", "expectedObjectScrew",
- [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testObjectScrewHelper2"]}),
- ModifierSpec("screw", 'SCREW',
- {"angle": math.radians(600), 'steps': 32, 'iterations': 1,
- 'use_object_screw_offset': True,
- 'use_normal_calculate': True, 'object': bpy.data.objects["testObjectScrewHelper1"]})]),
+ [ModifierSpec('mirror', 'MIRROR', {'mirror_object': bpy.data.objects["testObjectScrewHelper2"]}),
+ ModifierSpec("screw", 'SCREW',
+ {"angle": math.radians(600), 'steps': 32, 'iterations': 1,
+ 'use_object_screw_offset': True,
+ 'use_normal_calculate': True, 'object': bpy.data.objects["testObjectScrewHelper1"]})]),
# 9
SpecMeshTest("MergedScrewWeld", "testMergedScrewWeld", "expectedMergedScrewWeld",
- [ModifierSpec("screw", 'SCREW',
- {'angle': math.radians(360), 'steps': 12, 'iterations': 1, 'screw_offset': 1,
- 'use_normal_calculate': True, 'use_merge_vertices': True}),
- ModifierSpec("weld", 'WELD', {"merge_threshold": 0.001})]),
+ [ModifierSpec("screw", 'SCREW',
+ {'angle': math.radians(360), 'steps': 12, 'iterations': 1, 'screw_offset': 1,
+ 'use_normal_calculate': True, 'use_merge_vertices': True}),
+ ModifierSpec("weld", 'WELD', {"merge_threshold": 0.001})]),
SpecMeshTest("T72380Weld", "regressT72380Weld", "expectedT72380Weld",
- [ModifierSpec('vedit', 'VERTEX_WEIGHT_EDIT',
- {'vertex_group': 'Group', 'use_remove': True, 'remove_threshold': 1}),
- ModifierSpec("weld", 'WELD', {"merge_threshold": 0.2, "vertex_group": "Group"})]),
+ [ModifierSpec('vedit', 'VERTEX_WEIGHT_EDIT',
+ {'vertex_group': 'Group', 'use_remove': True, 'remove_threshold': 1}),
+ ModifierSpec("weld", 'WELD', {"merge_threshold": 0.2, "vertex_group": "Group"})]),
SpecMeshTest("T72792Weld", "regressT72792Weld", "expectedT72792Weld",
- [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 2}),
- ModifierSpec("weld", 'WELD', {"merge_threshold": 0.1, "vertex_group": "Group"})]),
+ [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 2}),
+ ModifierSpec("weld", 'WELD', {"merge_threshold": 0.1, "vertex_group": "Group"})]),
############################################
# One 'Generate' modifier on primitive meshes
#############################################
# 12
SpecMeshTest("CubeArray", "testCubeArray", "expectedCubeArray",
- [ModifierSpec('array', 'ARRAY', {})]),
+ [ModifierSpec('array', 'ARRAY', {})]),
SpecMeshTest("CapArray", "testCapArray", "expectedCapArray",
- [ModifierSpec('array', 'ARRAY',
- {'fit_type': 'FIT_LENGTH', 'fit_length': 2.0,
- 'start_cap': bpy.data.objects["testCapStart"],
- 'end_cap': bpy.data.objects["testCapEnd"]})]),
+ [ModifierSpec('array', 'ARRAY',
+ {'fit_type': 'FIT_LENGTH', 'fit_length': 2.0,
+ 'start_cap': bpy.data.objects["testCapStart"],
+ 'end_cap': bpy.data.objects["testCapEnd"]})]),
SpecMeshTest("CurveArray", "testCurveArray", "expectedCurveArray",
- [ModifierSpec('array', 'ARRAY',
- {'fit_type': 'FIT_CURVE', 'curve': bpy.data.objects["testCurveArrayHelper"],
- 'use_relative_offset': False, 'use_constant_offset': True,
- 'constant_offset_displace': (0.5, 0, 0)})]),
+ [ModifierSpec('array', 'ARRAY',
+ {'fit_type': 'FIT_CURVE', 'curve': bpy.data.objects["testCurveArrayHelper"],
+ 'use_relative_offset': False, 'use_constant_offset': True,
+ 'constant_offset_displace': (0.5, 0, 0)})]),
SpecMeshTest("RadialArray", "testRadialArray", "expectedRadialArray",
- [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 3, 'use_merge_vertices': True,
- 'use_merge_vertices_cap': True, 'use_relative_offset': False,
- 'use_object_offset': True,
- 'offset_object': bpy.data.objects["testRadialArrayHelper"]})]),
+ [ModifierSpec('array', 'ARRAY', {'fit_type': 'FIXED_COUNT', 'count': 3, 'use_merge_vertices': True,
+ 'use_merge_vertices_cap': True, 'use_relative_offset': False,
+ 'use_object_offset': True,
+ 'offset_object': bpy.data.objects["testRadialArrayHelper"]})]),
SpecMeshTest("CylinderBuild", "testCylinderBuild", "expectedCylinderBuild",
- [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]),
+ [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]),
# 17
SpecMeshTest("ConeDecimate", "testConeDecimate", "expectedConeDecimate",
- [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]),
+ [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]),
SpecMeshTest("CubeEdgeSplit", "testCubeEdgeSplit", "expectedCubeEdgeSplit",
- [ModifierSpec('edge split', 'EDGE_SPLIT', {})]),
+ [ModifierSpec('edge split', 'EDGE_SPLIT', {})]),
SpecMeshTest("SphereMirror", "testSphereMirror", "expectedSphereMirror",
- [ModifierSpec('mirror', 'MIRROR', {})]),
+ [ModifierSpec('mirror', 'MIRROR', {})]),
SpecMeshTest("LocalMirror", "testLocalMirror", "expectedLocalMirror",
- [ModifierSpec('mirror', 'MIRROR', {'use_clip': True})]),
+ [ModifierSpec('mirror', 'MIRROR', {'use_clip': True})]),
SpecMeshTest("ObjectOffsetMirror", "testObjectOffsetMirror", "expectedObjectOffsetMirror",
- [ModifierSpec('mirror', 'MIRROR',
- {'mirror_object': bpy.data.objects["testObjectOffsetMirrorHelper"]})]),
+ [ModifierSpec('mirror', 'MIRROR',
+ {'mirror_object': bpy.data.objects["testObjectOffsetMirrorHelper"]})]),
SpecMeshTest("CylinderMask", "testCylinderMask", "expectedCylinderMask",
- [ModifierSpec('mask', 'MASK', {'vertex_group': "mask_vertex_group"})]),
+ [ModifierSpec('mask', 'MASK', {'vertex_group': "mask_vertex_group"})]),
SpecMeshTest("ConeMultiRes", "testConeMultiRes", "expectedConeMultiRes",
- [ModifierSpec('multires', 'MULTIRES', {})]),
+ [ModifierSpec('multires', 'MULTIRES', {})]),
# 24
SpecMeshTest("CubeScrew", "testCubeScrew", "expectedCubeScrew",
- [ModifierSpec('screw', 'SCREW', {})]),
+ [ModifierSpec('screw', 'SCREW', {})]),
SpecMeshTest("CubeSolidify", "testCubeSolidify", "expectedCubeSolidify",
- [ModifierSpec('solidify', 'SOLIDIFY', {})]),
+ [ModifierSpec('solidify', 'SOLIDIFY', {})]),
SpecMeshTest("ComplexSolidify", "testComplexSolidify", "expectedComplexSolidify",
- [ModifierSpec('solidify', 'SOLIDIFY', {'solidify_mode': 'NON_MANIFOLD', 'thickness': 0.05, 'offset': 0,
- 'nonmanifold_thickness_mode': 'CONSTRAINTS'})]),
+ [ModifierSpec('solidify', 'SOLIDIFY', {'solidify_mode': 'NON_MANIFOLD', 'thickness': 0.05, 'offset': 0,
+ 'nonmanifold_thickness_mode': 'CONSTRAINTS'})]),
SpecMeshTest("T63063Solidify", "regressT63063Solidify", "expectedT63063Solidify",
- [ModifierSpec('solid', 'SOLIDIFY', {'thickness': 0.1, 'offset': 0.7})]),
+ [ModifierSpec('solid', 'SOLIDIFY', {'thickness': 0.1, 'offset': 0.7})]),
SpecMeshTest("T61979Solidify", "regressT61979Solidify", "expectedT61979Solidify",
- [ModifierSpec('solid', 'SOLIDIFY',
- {'thickness': -0.25, 'use_even_offset': True, 'use_quality_normals': True})]),
+ [ModifierSpec('solid', 'SOLIDIFY',
+ {'thickness': -0.25, 'use_even_offset': True, 'use_quality_normals': True})]),
SpecMeshTest("MonkeySubsurf", "testMonkeySubsurf", "expectedMonkeySubsurf",
- [ModifierSpec('subsurf', 'SUBSURF', {})]),
+ [ModifierSpec('subsurf', 'SUBSURF', {})]),
SpecMeshTest("CatmullClarkSubdivisionSurface", "testCatmullClarkSubdivisionSurface",
- "expectedCatmullClarkSubdivisionSurface",
- [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]),
+ "expectedCatmullClarkSubdivisionSurface",
+ [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]),
SpecMeshTest("SimpleSubdivisionSurface", "testSimpleSubdivisionSurface", "expectedSimpleSubdivisionSurface",
- [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2, 'subdivision_type': 'SIMPLE'})]),
+ [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2, 'subdivision_type': 'SIMPLE'})]),
SpecMeshTest("Crease2dSubdivisionSurface", "testCrease2dSubdivisionSurface", "expectedCrease2dSubdivisionSurface",
- [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]),
+ [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]),
SpecMeshTest("Crease3dSubdivisionSurface", "testCrease3dSubdivisionSurface", "expectedCrease3dSubdivisionSurface",
- [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]),
+ [ModifierSpec("subdivision", 'SUBSURF', {"levels": 2})]),
# 34
SpecMeshTest("SphereTriangulate", "testSphereTriangulate", "expectedSphereTriangulate",
- [ModifierSpec('triangulate', 'TRIANGULATE', {})]),
+ [ModifierSpec('triangulate', 'TRIANGULATE', {})]),
SpecMeshTest("MonkeyWireframe", "testMonkeyWireframe", "expectedMonkeyWireframe",
- [ModifierSpec('wireframe', 'WIREFRAME', {})]),
+ [ModifierSpec('wireframe', 'WIREFRAME', {})]),
# Duplicate the object, test object and expected object have same world coordinates.
SpecMeshTest("Skin", "testObjPlaneSkin", "expObjPlaneSkin",
- [ModifierSpec('skin', 'SKIN', {})]),
+ [ModifierSpec('skin', 'SKIN', {})]),
SpecMeshTest("MergedWeld", "testMergedWeld", "expectedMergedWeld",
- [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.021})]),
+ [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.021})]),
SpecMeshTest("MergedAllWeld", "testMergedAllWeld", "expectedMergedAllWeld",
- [ModifierSpec("weld", 'WELD', {"merge_threshold": 1.8})]),
+ [ModifierSpec("weld", 'WELD', {"merge_threshold": 1.8})]),
SpecMeshTest("MergedNoneWeld", "testMergedNoneWeld", "expectedMergedNoneWeld",
- [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.019})]),
+ [ModifierSpec("weld", 'WELD', {"merge_threshold": 0.019})]),
#############################################
@@ -221,14 +221,14 @@ def main():
#############################################
# 39
SpecMeshTest("MonkeyArmature", "testMonkeyArmature", "expectedMonkeyArmature",
- [ModifierSpec('armature', 'ARMATURE',
- {'object': bpy.data.objects['testArmature'], 'use_vertex_groups': True})]),
+ [ModifierSpec('armature', 'ARMATURE',
+ {'object': bpy.data.objects['testArmature'], 'use_vertex_groups': True})]),
SpecMeshTest("TorusCast", "testTorusCast", "expectedTorusCast",
- [ModifierSpec('cast', 'CAST', {'factor': 2.64})]),
+ [ModifierSpec('cast', 'CAST', {'factor': 2.64})]),
SpecMeshTest("CubeCurve", "testCubeCurve", "expectedCubeCurve",
- [ModifierSpec('curve', 'CURVE', {'object': bpy.data.objects['testBezierCurve']})]),
+ [ModifierSpec('curve', 'CURVE', {'object': bpy.data.objects['testBezierCurve']})]),
SpecMeshTest("MonkeyDisplace", "testMonkeyDisplace", "expectedMonkeyDisplace",
- [ModifierSpec('displace', "DISPLACE", {})]),
+ [ModifierSpec('displace', "DISPLACE", {})]),
# Hook modifier requires moving the hook object to get a mesh change
# so can't test it with the current framework
@@ -239,91 +239,91 @@ def main():
# 43
# ModifierSpec('laplacian_deform', 'LAPLACIANDEFORM', {}) Laplacian requires a more complex mesh
SpecMeshTest("CubeLattice", "testCubeLattice", "expectedCubeLattice",
- [ModifierSpec('lattice', 'LATTICE', {'object': bpy.data.objects["testLattice"]})]),
+ [ModifierSpec('lattice', 'LATTICE', {'object': bpy.data.objects["testLattice"]})]),
SpecMeshTest("PlaneShrinkWrap", "testPlaneShrinkWrap", "expectedPlaneShrinkWrap",
- [ModifierSpec('shrinkwrap', 'SHRINKWRAP',
- {'target': bpy.data.objects["testCubeWrap"], 'offset': 0.5})]),
+ [ModifierSpec('shrinkwrap', 'SHRINKWRAP',
+ {'target': bpy.data.objects["testCubeWrap"], 'offset': 0.5})]),
SpecMeshTest("CylinderSimpleDeform", "testCylinderSimpleDeform", "expectedCylinderSimpleDeform",
- [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(180), 'deform_axis': 'Z'})]),
+ [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(180), 'deform_axis': 'Z'})]),
SpecMeshTest("PlaneSmooth", "testPlaneSmooth", "expectedPlaneSmooth",
- [ModifierSpec('smooth', 'SMOOTH', {'iterations': 11})]),
+ [ModifierSpec('smooth', 'SMOOTH', {'iterations': 11})]),
# Smooth corrective requires a complex mesh.
SpecMeshTest("BalloonLaplacianSmooth", "testBalloonLaplacianSmooth", "expectedBalloonLaplacianSmooth",
- [ModifierSpec('laplaciansmooth', 'LAPLACIANSMOOTH', {'lambda_factor': 12, 'lambda_border': 12})]),
+ [ModifierSpec('laplaciansmooth', 'LAPLACIANSMOOTH', {'lambda_factor': 12, 'lambda_border': 12})]),
# Gets updated often
SpecMeshTest("WavePlane", "testObjPlaneWave", "expObjPlaneWave",
- [ModifierSpec('wave', 'WAVE', {})]),
+ [ModifierSpec('wave', 'WAVE', {})]),
#############################################
# CURVES Generate Modifiers
#############################################
# Caution: Make sure test object has no modifier in "added" state, the test may fail.
SpecMeshTest("BezCurveArray", "testObjBezierCurveArray", "expObjBezierCurveArray",
- [ModifierSpec('array', 'ARRAY', {})]),
+ [ModifierSpec('array', 'ARRAY', {})]),
SpecMeshTest("CurveBevel", "testObjBezierCurveBevel", "expObjBezierCurveBevel",
- [ModifierSpec('bevel', 'BEVEL', {'limit_method': 'NONE'})]),
+ [ModifierSpec('bevel', 'BEVEL', {'limit_method': 'NONE'})]),
SpecMeshTest("CurveBuild", "testObjBezierCurveBuild", "expObjBezierCurveBuild",
- [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]),
+ [ModifierSpec('build', 'BUILD', {'frame_start': 1, 'frame_duration': 1}, 2)]),
SpecMeshTest("CurveDecimate", "testObjBezierCurveDecimate", "expObjBezierCurveDecimate",
- [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]),
+ [ModifierSpec('decimate', 'DECIMATE', {'ratio': 0.5})]),
SpecMeshTest("CurveEdgeSplit", "testObjBezierCurveEdgeSplit", "expObjBezierCurveEdgeSplit",
- [ModifierSpec('edgeSplit', 'EDGE_SPLIT', {})]),
+ [ModifierSpec('edgeSplit', 'EDGE_SPLIT', {})]),
SpecMeshTest("CurveMirror", "testObjBezierCurveMirror", "expObjBezierCurveMirror",
- [ModifierSpec('mirror', 'MIRROR', {'use_axis': (True, True, False)})]),
+ [ModifierSpec('mirror', 'MIRROR', {'use_axis': (True, True, False)})]),
SpecMeshTest("CurveScrew", "testObjBezierCurveScrew", "expObjBezierCurveScrew",
- [ModifierSpec('screw', 'SCREW', {})]),
+ [ModifierSpec('screw', 'SCREW', {})]),
SpecMeshTest("CurveSolidify", "testObjBezierCurveSolidify", "expObjBezierCurveSolidify",
- [ModifierSpec('solidify', 'SOLIDIFY', {'thickness': 1})]),
+ [ModifierSpec('solidify', 'SOLIDIFY', {'thickness': 1})]),
SpecMeshTest("CurveSubSurf", "testObjBezierCurveSubSurf", "expObjBezierCurveSubSurf",
- [ModifierSpec('subSurf', 'SUBSURF', {})]),
+ [ModifierSpec('subSurf', 'SUBSURF', {})]),
SpecMeshTest("CurveTriangulate", "testObjBezierCurveTriangulate", "expObjBezierCurveTriangulate",
- [ModifierSpec('triangulate', 'TRIANGULATE', {})]),
+ [ModifierSpec('triangulate', 'TRIANGULATE', {})]),
# Test 60
# Caution Weld: if the distance is increased beyond a limit, the object disappears
SpecMeshTest("CurveWeld", "testObjBezierCurveWeld", "expObjBezierCurveWeld",
- [ModifierSpec('weld', 'WELD', {})]),
+ [ModifierSpec('weld', 'WELD', {})]),
SpecMeshTest("CurveWeld2", "testObjBezierCurveWeld2", "expObjBezierCurveWeld2",
- [ModifierSpec('weld', 'WELD', {})]),
+ [ModifierSpec('weld', 'WELD', {})]),
#############################################
# Curves Deform Modifiers
#############################################
# Test 62
SpecMeshTest("CurveCast", "testObjBezierCurveCast", "expObjBezierCurveCast",
- [ModifierSpec('Cast', 'CAST', {'cast_type': 'CYLINDER', 'factor': 10})]),
+ [ModifierSpec('Cast', 'CAST', {'cast_type': 'CYLINDER', 'factor': 10})]),
SpecMeshTest("CurveShrinkWrap", "testObjBezierCurveShrinkWrap", "expObjBezierCurveShrinkWrap",
- [ModifierSpec('ShrinkWrap', 'SHRINKWRAP',
- {'target': bpy.data.objects['testShrinkWrapHelperSuzanne']})]),
+ [ModifierSpec('ShrinkWrap', 'SHRINKWRAP',
+ {'target': bpy.data.objects['testShrinkWrapHelperSuzanne']})]),
SpecMeshTest("CurveSimpleDeform", "testObjBezierCurveSimpleDeform", "expObjBezierCurveSimpleDeform",
- [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(90)})]),
+ [ModifierSpec('simple_deform', 'SIMPLE_DEFORM', {'angle': math.radians(90)})]),
SpecMeshTest("CurveSmooth", "testObjBezierCurveSmooth", "expObjBezierCurveSmooth",
- [ModifierSpec('smooth', 'SMOOTH', {'factor': 10})]),
+ [ModifierSpec('smooth', 'SMOOTH', {'factor': 10})]),
SpecMeshTest("CurveWave", "testObjBezierCurveWave", "expObjBezierCurveWave",
- [ModifierSpec('curve_wave', 'WAVE', {'time_offset': -1.5})]),
+ [ModifierSpec('curve_wave', 'WAVE', {'time_offset': -1.5})]),
SpecMeshTest("CurveCurve", "testObjBezierCurveCurve", "expObjBezierCurveCurve",
- [ModifierSpec('curve_Curve', 'CURVE', {'object': bpy.data.objects['NurbsCurve']})]),
+ [ModifierSpec('curve_Curve', 'CURVE', {'object': bpy.data.objects['NurbsCurve']})]),
]
diff --git a/tests/python/modules/mesh_test.py b/tests/python/modules/mesh_test.py
index 730c0d61a26..873ab779d65 100644
--- a/tests/python/modules/mesh_test.py
+++ b/tests/python/modules/mesh_test.py
@@ -87,6 +87,7 @@ class OperatorSpecEditMode:
"""
Holds one operator and its parameters.
"""
+
def __init__(
self,
operator_name: str,
@@ -206,7 +207,7 @@ class MeshTest(ABC):
self.expected_object = self.evaluated_object
self.expected_object.name = self.exp_object_name
x, y, z = self.test_object.location
- self.expected_object.location = (x, y+10, z)
+ self.expected_object.location = (x, y + 10, z)
bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
def create_evaluated_object(self):
@@ -261,7 +262,6 @@ class MeshTest(ABC):
if not inside_loop_flag:
success = False
-
if success:
self.print_passed_test_result(result)
# Clean up.
@@ -320,7 +320,7 @@ class MeshTest(ABC):
bm = bmesh.from_edit_mesh(mesh)
- #bpy.ops.object.mode_set(mode='OBJECT')
+ # bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.tool_settings.mesh_select_mode = (select_mode == 'VERT',
select_mode == 'EDGE',
diff --git a/tests/python/modules/test_utils.py b/tests/python/modules/test_utils.py
index 18b4eb78f4b..d5cd743cde9 100755
--- a/tests/python/modules/test_utils.py
+++ b/tests/python/modules/test_utils.py
@@ -43,7 +43,7 @@ class AbstractBlenderRunnerTest(unittest.TestCase):
blender: pathlib.Path = None
testdir: pathlib.Path = None
- def run_blender(self, filepath: str, python_script: str, timeout: int=300) -> str:
+ def run_blender(self, filepath: str, python_script: str, timeout: int = 300) -> str:
"""Runs Blender by opening a blendfile and executing a script.
Returns Blender's stdout + stderr combined into one string.
diff --git a/tests/python/operators.py b/tests/python/operators.py
index 645ce31390d..6ccc96dba5d 100644
--- a/tests/python/operators.py
+++ b/tests/python/operators.py
@@ -22,104 +22,154 @@ MONKEY_LOOP_EDGE = {131, 278, 299, 305, 307, 334, 337, 359, 384, 396, 399, 412,
def main():
tests = [
# bisect
- SpecMeshTest("CubeBisect", "testCubeBisect", "expectedCubeBisect",
- [OperatorSpecEditMode("bisect",
- {"plane_co": (0, 0, 0), "plane_no": (0, 1, 1), "clear_inner": True,
- "use_fill": True}, 'FACE', {0, 1, 2, 3, 4, 5}, )]),
+ SpecMeshTest(
+ "CubeBisect", "testCubeBisect", "expectedCubeBisect",
+ [OperatorSpecEditMode("bisect",
+ {"plane_co": (0, 0, 0), "plane_no": (0, 1, 1), "clear_inner": True,
+ "use_fill": True}, 'FACE', {0, 1, 2, 3, 4, 5}, )],
+ ),
# blend from shape
- SpecMeshTest("CubeBlendFromShape", "testCubeBlendFromShape", "expectedCubeBlendFromShape",
- [OperatorSpecEditMode("blend_from_shape", {"shape": "Key 1"}, 'FACE', {0, 1, 2, 3, 4, 5})]),
+ SpecMeshTest(
+ "CubeBlendFromShape", "testCubeBlendFromShape", "expectedCubeBlendFromShape",
+ [OperatorSpecEditMode("blend_from_shape", {"shape": "Key 1"}, 'FACE', {0, 1, 2, 3, 4, 5})],
+ ),
# bridge edge loops
- SpecMeshTest("CubeBridgeEdgeLoop", "testCubeBrigeEdgeLoop", "expectedCubeBridgeEdgeLoop",
- [OperatorSpecEditMode("bridge_edge_loops", {}, "FACE", {0, 1})]),
+ SpecMeshTest(
+ "CubeBridgeEdgeLoop", "testCubeBrigeEdgeLoop", "expectedCubeBridgeEdgeLoop",
+ [OperatorSpecEditMode("bridge_edge_loops", {}, "FACE", {0, 1})],
+ ),
# decimate
- SpecMeshTest("MonkeyDecimate", "testMonkeyDecimate", "expectedMonkeyDecimate",
- [OperatorSpecEditMode("decimate",
- {"ratio": 0.1}, "FACE", {i for i in range(500)})]),
+ SpecMeshTest(
+ "MonkeyDecimate", "testMonkeyDecimate", "expectedMonkeyDecimate",
+ [OperatorSpecEditMode("decimate",
+ {"ratio": 0.1}, "FACE", {i for i in range(500)})],
+ ),
# delete
- SpecMeshTest("CubeDeleteVertices", "testCubeDeleteVertices", "expectedCubeDeleteVertices",
- [OperatorSpecEditMode("delete", {}, "VERT", {3})]),
- SpecMeshTest("CubeDeleteFaces", "testCubeDeleteFaces", "expectedCubeDeleteFaces",
- [OperatorSpecEditMode("delete", {}, "FACE", {0})]),
- SpecMeshTest("CubeDeleteEdges", "testCubeDeleteEdges", "expectedCubeDeleteEdges",
- [OperatorSpecEditMode("delete", {}, "EDGE", {0, 1, 2, 3})]),
+ SpecMeshTest(
+ "CubeDeleteVertices", "testCubeDeleteVertices", "expectedCubeDeleteVertices",
+ [OperatorSpecEditMode("delete", {}, "VERT", {3})],
+ ),
+ SpecMeshTest(
+ "CubeDeleteFaces", "testCubeDeleteFaces", "expectedCubeDeleteFaces",
+ [OperatorSpecEditMode("delete", {}, "FACE", {0})],
+ ),
+ SpecMeshTest(
+ "CubeDeleteEdges", "testCubeDeleteEdges", "expectedCubeDeleteEdges",
+ [OperatorSpecEditMode("delete", {}, "EDGE", {0, 1, 2, 3})],
+ ),
# delete edge loop
- SpecMeshTest("MonkeyDeleteEdgeLoopVertices", "testMokneyDeleteEdgeLoopVertices",
- "expectedMonkeyDeleteEdgeLoopVertices",
- [OperatorSpecEditMode("delete_edgeloop", {}, "VERT", MONKEY_LOOP_VERT)]),
+ SpecMeshTest(
+ "MonkeyDeleteEdgeLoopVertices", "testMokneyDeleteEdgeLoopVertices",
+ "expectedMonkeyDeleteEdgeLoopVertices",
+ [OperatorSpecEditMode("delete_edgeloop", {}, "VERT", MONKEY_LOOP_VERT)],
+ ),
- SpecMeshTest("MonkeyDeleteEdgeLoopEdges", "testMokneyDeleteEdgeLoopEdges",
- "expectedMonkeyDeleteEdgeLoopEdges",
- [OperatorSpecEditMode("delete_edgeloop", {}, "EDGE", MONKEY_LOOP_EDGE)]),
+ SpecMeshTest(
+ "MonkeyDeleteEdgeLoopEdges", "testMokneyDeleteEdgeLoopEdges",
+ "expectedMonkeyDeleteEdgeLoopEdges",
+ [OperatorSpecEditMode("delete_edgeloop", {}, "EDGE", MONKEY_LOOP_EDGE)],
+ ),
# delete loose
- SpecMeshTest("CubeDeleteLooseVertices", "testCubeDeleteLooseVertices",
- "expectedCubeDeleteLooseVertices",
- [OperatorSpecEditMode("delete_loose", {"use_verts": True, "use_edges": False, "use_faces": False},
- "VERT",
- {i for i in range(12)})]),
- SpecMeshTest("CubeDeleteLooseEdges", "testCubeDeleteLooseEdges",
- "expectedCubeDeleteLooseEdges",
- [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": True, "use_faces": False},
- "EDGE",
- {i for i in range(14)})]),
- SpecMeshTest("CubeDeleteLooseFaces", "testCubeDeleteLooseFaces",
- "expectedCubeDeleteLooseFaces",
- [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": False, "use_faces": True},
- "FACE",
- {i for i in range(7)})]),
+ SpecMeshTest(
+ "CubeDeleteLooseVertices", "testCubeDeleteLooseVertices",
+ "expectedCubeDeleteLooseVertices",
+ [OperatorSpecEditMode("delete_loose", {"use_verts": True, "use_edges": False, "use_faces": False},
+ "VERT",
+ {i for i in range(12)})],
+ ),
+ SpecMeshTest(
+ "CubeDeleteLooseEdges", "testCubeDeleteLooseEdges",
+ "expectedCubeDeleteLooseEdges",
+ [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": True, "use_faces": False},
+ "EDGE",
+ {i for i in range(14)})],
+ ),
+ SpecMeshTest(
+ "CubeDeleteLooseFaces", "testCubeDeleteLooseFaces",
+ "expectedCubeDeleteLooseFaces",
+ [OperatorSpecEditMode("delete_loose", {"use_verts": False, "use_edges": False, "use_faces": True},
+ "FACE",
+ {i for i in range(7)})],
+ ),
# dissolve degenerate
- SpecMeshTest("CubeDissolveDegenerate", "testCubeDissolveDegenerate",
- "expectedCubeDissolveDegenerate",
- [OperatorSpecEditMode("dissolve_degenerate", {}, "VERT", {i for i in range(8)})]),
+ SpecMeshTest(
+ "CubeDissolveDegenerate", "testCubeDissolveDegenerate",
+ "expectedCubeDissolveDegenerate",
+ [OperatorSpecEditMode("dissolve_degenerate", {}, "VERT", {i for i in range(8)})],
+ ),
# dissolve edges
- SpecMeshTest("CylinderDissolveEdges", "testCylinderDissolveEdges", "expectedCylinderDissolveEdges",
- [OperatorSpecEditMode("dissolve_edges", {}, "EDGE", {0, 5, 6, 9})]),
+ SpecMeshTest(
+ "CylinderDissolveEdges", "testCylinderDissolveEdges", "expectedCylinderDissolveEdges",
+ [OperatorSpecEditMode("dissolve_edges", {}, "EDGE", {0, 5, 6, 9})],
+ ),
# dissolve faces
- SpecMeshTest("CubeDissolveFaces", "testCubeDissolveFaces", "expectedCubeDissolveFaces",
- [OperatorSpecEditMode("dissolve_faces", {}, "VERT", {5, 34, 47, 49, 83, 91, 95})]),
+ SpecMeshTest(
+ "CubeDissolveFaces", "testCubeDissolveFaces", "expectedCubeDissolveFaces",
+ [OperatorSpecEditMode("dissolve_faces", {}, "VERT", {5, 34, 47, 49, 83, 91, 95})],
+ ),
# dissolve verts
- SpecMeshTest("CubeDissolveVerts", "testCubeDissolveVerts", "expectedCubeDissolveVerts",
- [OperatorSpecEditMode("dissolve_verts", {}, "VERT", {16, 20, 22, 23, 25})]),
+ SpecMeshTest(
+ "CubeDissolveVerts", "testCubeDissolveVerts", "expectedCubeDissolveVerts",
+ [OperatorSpecEditMode("dissolve_verts", {}, "VERT", {16, 20, 22, 23, 25})],
+ ),
# duplicate
- SpecMeshTest("ConeDuplicateVertices", "testConeDuplicateVertices",
- "expectedConeDuplicateVertices",
- [OperatorSpecEditMode("duplicate", {}, "VERT", {i for i in range(33)} - {23})]),
+ SpecMeshTest(
+ "ConeDuplicateVertices", "testConeDuplicateVertices",
+ "expectedConeDuplicateVertices",
+ [OperatorSpecEditMode("duplicate", {}, "VERT", {i for i in range(33)} - {23})],
+ ),
- SpecMeshTest("ConeDuplicateOneVertex", "testConeDuplicateOneVertex", "expectedConeDuplicateOneVertex",
- [OperatorSpecEditMode("duplicate", {}, "VERT", {23})]),
- SpecMeshTest("ConeDuplicateFaces", "testConeDuplicateFaces", "expectedConeDuplicateFaces",
- [OperatorSpecEditMode("duplicate", {}, "FACE", {6, 9})]),
- SpecMeshTest("ConeDuplicateEdges", "testConeDuplicateEdges", "expectedConeDuplicateEdges",
- [OperatorSpecEditMode("duplicate", {}, "EDGE", {i for i in range(64)})]),
+ SpecMeshTest(
+ "ConeDuplicateOneVertex", "testConeDuplicateOneVertex", "expectedConeDuplicateOneVertex",
+ [OperatorSpecEditMode("duplicate", {}, "VERT", {23})],
+ ),
+ SpecMeshTest(
+ "ConeDuplicateFaces", "testConeDuplicateFaces", "expectedConeDuplicateFaces",
+ [OperatorSpecEditMode("duplicate", {}, "FACE", {6, 9})],
+ ),
+ SpecMeshTest(
+ "ConeDuplicateEdges", "testConeDuplicateEdges", "expectedConeDuplicateEdges",
+ [OperatorSpecEditMode("duplicate", {}, "EDGE", {i for i in range(64)})],
+ ),
# edge collapse
- SpecMeshTest("CylinderEdgeCollapse", "testCylinderEdgeCollapse", "expectedCylinderEdgeCollapse",
- [OperatorSpecEditMode("edge_collapse", {}, "EDGE", {1, 9, 4})]),
+ SpecMeshTest(
+ "CylinderEdgeCollapse", "testCylinderEdgeCollapse", "expectedCylinderEdgeCollapse",
+ [OperatorSpecEditMode("edge_collapse", {}, "EDGE", {1, 9, 4})],
+ ),
# edge face add
- SpecMeshTest("CubeEdgeFaceAddFace", "testCubeEdgeFaceAddFace", "expectedCubeEdgeFaceAddFace",
- [OperatorSpecEditMode("edge_face_add", {}, "VERT", {1, 3, 4, 5, 7})]),
- SpecMeshTest("CubeEdgeFaceAddEdge", "testCubeEdgeFaceAddEdge", "expectedCubeEdgeFaceAddEdge",
- [OperatorSpecEditMode("edge_face_add", {}, "VERT", {4, 5})]),
+ SpecMeshTest(
+ "CubeEdgeFaceAddFace", "testCubeEdgeFaceAddFace", "expectedCubeEdgeFaceAddFace",
+ [OperatorSpecEditMode("edge_face_add", {}, "VERT", {1, 3, 4, 5, 7})],
+ ),
+ SpecMeshTest(
+ "CubeEdgeFaceAddEdge", "testCubeEdgeFaceAddEdge", "expectedCubeEdgeFaceAddEdge",
+ [OperatorSpecEditMode("edge_face_add", {}, "VERT", {4, 5})],
+ ),
# edge rotate
- SpecMeshTest("CubeEdgeRotate", "testCubeEdgeRotate", "expectedCubeEdgeRotate",
- [OperatorSpecEditMode("edge_rotate", {}, "EDGE", {1})]),
+ SpecMeshTest(
+ "CubeEdgeRotate", "testCubeEdgeRotate", "expectedCubeEdgeRotate",
+ [OperatorSpecEditMode("edge_rotate", {}, "EDGE", {1})],
+ ),
# edge split
- SpecMeshTest("CubeEdgeSplit", "testCubeEdgeSplit", "expectedCubeEdgeSplit",
- [OperatorSpecEditMode("edge_split", {}, "EDGE", {2, 5, 8, 11, 14, 17, 20, 23})]),
+ SpecMeshTest(
+ "CubeEdgeSplit", "testCubeEdgeSplit", "expectedCubeEdgeSplit",
+ [OperatorSpecEditMode("edge_split", {}, "EDGE", {2, 5, 8, 11, 14, 17, 20, 23})],
+ ),
# edge ring select - Cannot be tested. Need user input.
# SpecMeshTest("CubeEdgeRingSelect", "testCubeEdgeRingSelect", "expectedCubeEdgeRingSelect",
@@ -130,113 +180,173 @@ def main():
# [OperatorSpecEditMode("edgering_select", {}, "VERT", {})]),
# edges select sharp
- SpecMeshTest("CubeEdgesSelectSharp", "testCubeEdgeSelectSharp", "expectedCubeEdgeSelectSharp",
- [OperatorSpecEditMode("edges_select_sharp", {}, "EDGE", {20})]),
- SpecMeshTest("SphereEdgesSelectSharp", "testSphereEdgesSelectSharp", "expectedSphereEdgeSelectSharp",
- [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.25}, "EDGE", {288})]),
- SpecMeshTest("HoledSphereEdgesSelectSharp", "testHoledSphereEdgesSelectSharp", "expectedHoledSphereEdgeSelectSharp",
- [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.18}, "VERT", {})]),
- SpecMeshTest("EmptyMeshEdgesSelectSharp", "testEmptyMeshEdgeSelectSharp", "expectedEmptyMeshEdgeSelectSharp",
- [OperatorSpecEditMode("edges_select_sharp", {}, "VERT", {})]),
+ SpecMeshTest(
+ "CubeEdgesSelectSharp", "testCubeEdgeSelectSharp", "expectedCubeEdgeSelectSharp",
+ [OperatorSpecEditMode("edges_select_sharp", {}, "EDGE", {20})],
+ ),
+ SpecMeshTest(
+ "SphereEdgesSelectSharp", "testSphereEdgesSelectSharp", "expectedSphereEdgeSelectSharp",
+ [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.25}, "EDGE", {288})],
+ ),
+ SpecMeshTest(
+ "HoledSphereEdgesSelectSharp", "testHoledSphereEdgesSelectSharp", "expectedHoledSphereEdgeSelectSharp",
+ [OperatorSpecEditMode("edges_select_sharp", {"sharpness": 0.18}, "VERT", {})],
+ ),
+ SpecMeshTest(
+ "EmptyMeshEdgesSelectSharp", "testEmptyMeshEdgeSelectSharp", "expectedEmptyMeshEdgeSelectSharp",
+ [OperatorSpecEditMode("edges_select_sharp", {}, "VERT", {})],
+ ),
# face make planar
- SpecMeshTest("MonkeyFaceMakePlanar", "testMonkeyFaceMakePlanar",
- "expectedMonkeyFaceMakePlanar",
- [OperatorSpecEditMode("face_make_planar", {}, "FACE", {i for i in range(500)})]),
+ SpecMeshTest(
+ "MonkeyFaceMakePlanar", "testMonkeyFaceMakePlanar",
+ "expectedMonkeyFaceMakePlanar",
+ [OperatorSpecEditMode("face_make_planar", {}, "FACE", {i for i in range(500)})],
+ ),
# face split by edges
- SpecMeshTest("PlaneFaceSplitByEdges", "testPlaneFaceSplitByEdges",
- "expectedPlaneFaceSplitByEdges",
- [OperatorSpecEditMode("face_split_by_edges", {}, "VERT", {i for i in range(6)})]),
+ SpecMeshTest(
+ "PlaneFaceSplitByEdges", "testPlaneFaceSplitByEdges",
+ "expectedPlaneFaceSplitByEdges",
+ [OperatorSpecEditMode("face_split_by_edges", {}, "VERT", {i for i in range(6)})],
+ ),
# faces select linked flat
- SpecMeshTest("CubeFacesSelectLinkedFlat", "testCubeFaceSelectLinkedFlat", "expectedCubeFaceSelectLinkedFlat",
- [OperatorSpecEditMode("faces_select_linked_flat", {}, "FACE", {7})]),
- SpecMeshTest("PlaneFacesSelectLinkedFlat", "testPlaneFaceSelectLinkedFlat", "expectedPlaneFaceSelectLinkedFlat",
- [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {1})]),
- SpecMeshTest("EmptyMeshFacesSelectLinkedFlat", "testEmptyMeshFaceSelectLinkedFlat",
- "expectedEmptyMeshFaceSelectLinkedFlat",
- [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {})]),
+ SpecMeshTest(
+ "CubeFacesSelectLinkedFlat", "testCubeFaceSelectLinkedFlat", "expectedCubeFaceSelectLinkedFlat",
+ [OperatorSpecEditMode("faces_select_linked_flat", {}, "FACE", {7})],
+ ),
+ SpecMeshTest(
+ "PlaneFacesSelectLinkedFlat", "testPlaneFaceSelectLinkedFlat", "expectedPlaneFaceSelectLinkedFlat",
+ [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {1})],
+ ),
+ SpecMeshTest(
+ "EmptyMeshFacesSelectLinkedFlat", "testEmptyMeshFaceSelectLinkedFlat",
+ "expectedEmptyMeshFaceSelectLinkedFlat",
+ [OperatorSpecEditMode("faces_select_linked_flat", {}, "VERT", {})],
+ ),
# fill
- SpecMeshTest("IcosphereFill", "testIcosphereFill", "expectedIcosphereFill",
- [OperatorSpecEditMode("fill", {}, "EDGE", {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})]),
- SpecMeshTest("IcosphereFillUseBeautyFalse",
- "testIcosphereFillUseBeautyFalse", "expectedIcosphereFillUseBeautyFalse",
- [OperatorSpecEditMode("fill", {"use_beauty": False}, "EDGE",
- {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})]),
+ SpecMeshTest(
+ "IcosphereFill", "testIcosphereFill", "expectedIcosphereFill",
+ [OperatorSpecEditMode("fill", {}, "EDGE", {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})],
+ ),
+ SpecMeshTest(
+ "IcosphereFillUseBeautyFalse",
+ "testIcosphereFillUseBeautyFalse", "expectedIcosphereFillUseBeautyFalse",
+ [OperatorSpecEditMode("fill", {"use_beauty": False}, "EDGE",
+ {20, 21, 22, 23, 24, 45, 46, 47, 48, 49})],
+ ),
# fill grid
- SpecMeshTest("PlaneFillGrid", "testPlaneFillGrid",
- "expectedPlaneFillGrid",
- [OperatorSpecEditMode("fill_grid", {}, "EDGE", {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})]),
- SpecMeshTest("PlaneFillGridSimpleBlending",
- "testPlaneFillGridSimpleBlending",
- "expectedPlaneFillGridSimpleBlending",
- [OperatorSpecEditMode("fill_grid", {"use_interp_simple": True}, "EDGE",
- {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})]),
+ SpecMeshTest(
+ "PlaneFillGrid", "testPlaneFillGrid",
+ "expectedPlaneFillGrid",
+ [OperatorSpecEditMode("fill_grid", {}, "EDGE", {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})],
+ ),
+ SpecMeshTest(
+ "PlaneFillGridSimpleBlending",
+ "testPlaneFillGridSimpleBlending",
+ "expectedPlaneFillGridSimpleBlending",
+ [OperatorSpecEditMode("fill_grid", {"use_interp_simple": True}, "EDGE",
+ {1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 15})],
+ ),
# fill holes
- SpecMeshTest("SphereFillHoles", "testSphereFillHoles", "expectedSphereFillHoles",
- [OperatorSpecEditMode("fill_holes", {"sides": 9}, "VERT", {i for i in range(481)})]),
+ SpecMeshTest(
+ "SphereFillHoles", "testSphereFillHoles", "expectedSphereFillHoles",
+ [OperatorSpecEditMode("fill_holes", {"sides": 9}, "VERT", {i for i in range(481)})],
+ ),
# face shade smooth (not a real test)
- SpecMeshTest("CubeShadeSmooth", "testCubeShadeSmooth", "expectedCubeShadeSmooth",
- [OperatorSpecEditMode("faces_shade_smooth", {}, "VERT", {i for i in range(8)})]),
+ SpecMeshTest(
+ "CubeShadeSmooth", "testCubeShadeSmooth", "expectedCubeShadeSmooth",
+ [OperatorSpecEditMode("faces_shade_smooth", {}, "VERT", {i for i in range(8)})],
+ ),
# faces shade flat (not a real test)
- SpecMeshTest("CubeShadeFlat", "testCubeShadeFlat", "expectedCubeShadeFlat",
- [OperatorSpecEditMode("faces_shade_flat", {}, "FACE", {i for i in range(6)})]),
+ SpecMeshTest(
+ "CubeShadeFlat", "testCubeShadeFlat", "expectedCubeShadeFlat",
+ [OperatorSpecEditMode("faces_shade_flat", {}, "FACE", {i for i in range(6)})],
+ ),
# hide
- SpecMeshTest("HideFace", "testCubeHideFace", "expectedCubeHideFace",
- [OperatorSpecEditMode("hide", {}, "FACE", {3})]),
- SpecMeshTest("HideEdge", "testCubeHideEdge", "expectedCubeHideEdge",
- [OperatorSpecEditMode("hide", {}, "EDGE", {1})]),
- SpecMeshTest("HideVertex", "testCubeHideVertex", "expectedCubeHideVertex",
- [OperatorSpecEditMode("hide", {}, "VERT", {0})]),
+ SpecMeshTest(
+ "HideFace", "testCubeHideFace", "expectedCubeHideFace",
+ [OperatorSpecEditMode("hide", {}, "FACE", {3})],
+ ),
+ SpecMeshTest(
+ "HideEdge", "testCubeHideEdge", "expectedCubeHideEdge",
+ [OperatorSpecEditMode("hide", {}, "EDGE", {1})],
+ ),
+ SpecMeshTest(
+ "HideVertex", "testCubeHideVertex", "expectedCubeHideVertex",
+ [OperatorSpecEditMode("hide", {}, "VERT", {0})],
+ ),
# inset faces
- SpecMeshTest("CubeInset",
- "testCubeInset", "expectedCubeInset", [OperatorSpecEditMode("inset", {"thickness": 0.2}, "VERT",
- {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50,
- 52,
- 59, 61, 62, 65, 83, 91, 95})]),
- SpecMeshTest("CubeInsetEvenOffsetFalse",
- "testCubeInsetEvenOffsetFalse", "expectedCubeInsetEvenOffsetFalse",
- [OperatorSpecEditMode("inset", {"thickness": 0.2, "use_even_offset": False}, "VERT",
- {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, 62, 65, 83, 91, 95})]),
- SpecMeshTest("CubeInsetDepth",
- "testCubeInsetDepth",
- "expectedCubeInsetDepth", [OperatorSpecEditMode("inset", {"thickness": 0.2, "depth": 0.2}, "VERT",
- {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61,
- 62,
- 65, 83, 91, 95})]),
- SpecMeshTest("GridInsetRelativeOffset", "testGridInsetRelativeOffset",
- "expectedGridInsetRelativeOffset",
- [OperatorSpecEditMode("inset", {"thickness": 0.4,
- "use_relative_offset": True}, "FACE",
- {35, 36, 37, 45, 46, 47, 55, 56, 57})]),
+ SpecMeshTest(
+ "CubeInset",
+ "testCubeInset", "expectedCubeInset", [OperatorSpecEditMode("inset", {"thickness": 0.2}, "VERT",
+ {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50,
+ 52,
+ 59, 61, 62, 65, 83, 91, 95})],
+ ),
+ SpecMeshTest(
+ "CubeInsetEvenOffsetFalse",
+ "testCubeInsetEvenOffsetFalse", "expectedCubeInsetEvenOffsetFalse",
+ [OperatorSpecEditMode("inset", {"thickness": 0.2, "use_even_offset": False}, "VERT",
+ {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61, 62, 65, 83, 91, 95})],
+ ),
+ SpecMeshTest(
+ "CubeInsetDepth",
+ "testCubeInsetDepth",
+ "expectedCubeInsetDepth", [OperatorSpecEditMode("inset", {"thickness": 0.2, "depth": 0.2}, "VERT",
+ {5, 16, 17, 19, 20, 22, 23, 34, 47, 49, 50, 52, 59, 61,
+ 62,
+ 65, 83, 91, 95})],
+ ),
+ SpecMeshTest(
+ "GridInsetRelativeOffset", "testGridInsetRelativeOffset",
+ "expectedGridInsetRelativeOffset",
+ [OperatorSpecEditMode("inset", {"thickness": 0.4,
+ "use_relative_offset": True}, "FACE",
+ {35, 36, 37, 45, 46, 47, 55, 56, 57})],
+ ),
# loop multi select
- SpecMeshTest("MokeyLoopMultiSelect", "testMonkeyLoopMultiSelect", "expectedMonkeyLoopMultiSelect",
- [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {355, 359, 73, 301, 302})]),
- SpecMeshTest("HoledGridLoopMultiSelect", "testGridLoopMultiSelect", "expectedGridLoopMultiSelect",
- [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {257, 169, 202, 207, 274, 278, 63})]),
- SpecMeshTest("EmptyMeshLoopMultiSelect", "testEmptyMeshLoopMultiSelect", "expectedEmptyMeshLoopMultiSelect",
- [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {})]),
+ SpecMeshTest(
+ "MokeyLoopMultiSelect", "testMonkeyLoopMultiSelect", "expectedMonkeyLoopMultiSelect",
+ [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {355, 359, 73, 301, 302})],
+ ),
+ SpecMeshTest(
+ "HoledGridLoopMultiSelect", "testGridLoopMultiSelect", "expectedGridLoopMultiSelect",
+ [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {257, 169, 202, 207, 274, 278, 63})],
+ ),
+ SpecMeshTest(
+ "EmptyMeshLoopMultiSelect", "testEmptyMeshLoopMultiSelect", "expectedEmptyMeshLoopMultiSelect",
+ [OperatorSpecEditMode("loop_multi_select", {}, "VERT", {})],
+ ),
# mark seam
- SpecMeshTest("CubeMarkSeam", "testCubeMarkSeam", "expectedCubeMarkSeam",
- [OperatorSpecEditMode("mark_seam", {}, "EDGE", {1})]),
+ SpecMeshTest(
+ "CubeMarkSeam", "testCubeMarkSeam", "expectedCubeMarkSeam",
+ [OperatorSpecEditMode("mark_seam", {}, "EDGE", {1})],
+ ),
# select all
- SpecMeshTest("CircleSelectAll", "testCircleSelectAll", "expectedCircleSelectAll",
- [OperatorSpecEditMode("select_all", {}, "VERT", {1})]),
- SpecMeshTest("IsolatedVertsSelectAll", "testIsolatedVertsSelectAll", "expectedIsolatedVertsSelectAll",
- [OperatorSpecEditMode("select_all", {}, "VERT", {})]),
- SpecMeshTest("EmptyMeshSelectAll", "testEmptyMeshSelectAll", "expectedEmptyMeshSelectAll",
- [OperatorSpecEditMode("select_all", {}, "VERT", {})]),
+ SpecMeshTest(
+ "CircleSelectAll", "testCircleSelectAll", "expectedCircleSelectAll",
+ [OperatorSpecEditMode("select_all", {}, "VERT", {1})],
+ ),
+ SpecMeshTest(
+ "IsolatedVertsSelectAll", "testIsolatedVertsSelectAll", "expectedIsolatedVertsSelectAll",
+ [OperatorSpecEditMode("select_all", {}, "VERT", {})],
+ ),
+ SpecMeshTest(
+ "EmptyMeshSelectAll", "testEmptyMeshSelectAll", "expectedEmptyMeshSelectAll",
+ [OperatorSpecEditMode("select_all", {}, "VERT", {})],
+ ),
# select axis - Cannot be tested. Needs active vert selection
# SpecMeshTest("MonkeySelectAxisX", "testMonkeySelectAxisX", "expectedMonkeySelectAxisX",
@@ -249,120 +359,183 @@ def main():
# OperatorSpecEditMode("select_axis", {"axis": "Z", "sign": "NEG"}, "FACE", {})]),
# select faces by sides
- SpecMeshTest("CubeSelectFacesBySide", "testCubeSelectFacesBySide", "expectedCubeSelectFacesBySide",
- [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]),
- SpecMeshTest("CubeSelectFacesBySideGreater", "testCubeSelectFacesBySideGreater", "expectedCubeSelectFacesBySideGreater",
- [OperatorSpecEditMode("select_face_by_sides", {"number": 4, "type": "GREATER", "extend": True}, "FACE", {})]),
- SpecMeshTest("CubeSelectFacesBySideLess", "testCubeSelectFacesBySideLess", "expectedCubeSelectFacesBySideLess",
- [OperatorSpecEditMode("select_face_by_sides", {"number": 4, "type": "GREATER", "extend": True}, "FACE", {})]),
+ SpecMeshTest(
+ "CubeSelectFacesBySide", "testCubeSelectFacesBySide", "expectedCubeSelectFacesBySide",
+ [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})],
+ ),
+ SpecMeshTest(
+ "CubeSelectFacesBySideGreater", "testCubeSelectFacesBySideGreater", "expectedCubeSelectFacesBySideGreater",
+ [OperatorSpecEditMode("select_face_by_sides", {"number": 4,
+ "type": "GREATER", "extend": True}, "FACE", {})],
+ ),
+ SpecMeshTest(
+ "CubeSelectFacesBySideLess", "testCubeSelectFacesBySideLess", "expectedCubeSelectFacesBySideLess",
+ [OperatorSpecEditMode("select_face_by_sides", {"number": 4,
+ "type": "GREATER", "extend": True}, "FACE", {})],
+ ),
# select interior faces
- SpecMeshTest("CubeSelectInteriorFaces", "testCubeSelectInteriorFaces", "expectedCubeSelectInteriorFaces",
- [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]),
- SpecMeshTest("HoledCubeSelectInteriorFaces", "testHoledCubeSelectInteriorFaces", "expectedHoledCubeSelectInteriorFaces",
- [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]),
- SpecMeshTest("EmptyMeshSelectInteriorFaces", "testEmptyMeshSelectInteriorFaces", "expectedEmptyMeshSelectInteriorFaces",
- [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})]),
+ SpecMeshTest(
+ "CubeSelectInteriorFaces", "testCubeSelectInteriorFaces", "expectedCubeSelectInteriorFaces",
+ [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})],
+ ),
+ SpecMeshTest(
+ "HoledCubeSelectInteriorFaces", "testHoledCubeSelectInteriorFaces", "expectedHoledCubeSelectInteriorFaces",
+ [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})],
+ ),
+ SpecMeshTest(
+ "EmptyMeshSelectInteriorFaces", "testEmptyMeshSelectInteriorFaces", "expectedEmptyMeshSelectInteriorFaces",
+ [OperatorSpecEditMode("select_face_by_sides", {"number": 4}, "FACE", {})],
+ ),
# select less
- SpecMeshTest("MonkeySelectLess", "testMonkeySelectLess", "expectedMonkeySelectLess",
- [OperatorSpecEditMode("select_less", {}, "VERT", {2, 8, 24, 34, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 68,
- 69, 70, 71, 74, 75, 78, 80, 81, 82, 83, 90, 91, 93, 95, 97, 99,
- 101, 109, 111, 115, 117, 119, 121, 123, 125, 127, 129, 130, 131,
- 132, 133, 134, 135, 136, 138, 141, 143, 145, 147, 149, 151, 153,
- 155, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174,
- 175, 176, 177, 178, 181, 182, 184, 185, 186, 187, 188, 189, 190,
- 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204,
- 206, 207, 208, 210, 216, 217, 218, 219, 220, 221, 222, 229, 230,
- 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255,
- 257, 259, 263, 267, 269, 271, 275, 277, 289, 291, 293, 295, 309,
- 310, 311, 312, 316, 317, 318, 319, 320, 323, 325, 327, 329, 331,
- 341, 347, 349, 350, 351, 354, 356, 359, 361, 363, 365, 367, 369,
- 375, 379, 381, 382, 385, 386, 387, 388, 389, 390, 391, 392, 393,
- 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406,
- 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419,
- 420, 421, 423, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434,
- 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447,
- 448, 449, 450, 451, 452, 454, 455, 456, 457, 458, 459, 460, 461,
- 462, 463, 464, 471, 473, 474, 475, 476, 477, 478, 479, 480, 481,
- 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 495,
- 496, 497, 498, 499, 502, 505})]),
- SpecMeshTest("HoledCubeSelectLess", "testHoledCubeSelectLess", "expectedHoledCubeSelectLess",
- [OperatorSpecEditMode("select_face_by_sides", {}, "FACE", {})]),
- SpecMeshTest("EmptyMeshSelectLess", "testEmptyMeshSelectLess", "expectedEmptyMeshSelectLess",
- [OperatorSpecEditMode("select_face_by_sides", {}, "VERT", {})]),
+ SpecMeshTest(
+ "MonkeySelectLess", "testMonkeySelectLess", "expectedMonkeySelectLess",
+ [OperatorSpecEditMode("select_less", {}, "VERT", {
+ 2, 8, 24, 34, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 68,
+ 69, 70, 71, 74, 75, 78, 80, 81, 82, 83, 90, 91, 93, 95, 97, 99,
+ 101, 109, 111, 115, 117, 119, 121, 123, 125, 127, 129, 130, 131,
+ 132, 133, 134, 135, 136, 138, 141, 143, 145, 147, 149, 151, 153,
+ 155, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174,
+ 175, 176, 177, 178, 181, 182, 184, 185, 186, 187, 188, 189, 190,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204,
+ 206, 207, 208, 210, 216, 217, 218, 219, 220, 221, 222, 229, 230,
+ 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255,
+ 257, 259, 263, 267, 269, 271, 275, 277, 289, 291, 293, 295, 309,
+ 310, 311, 312, 316, 317, 318, 319, 320, 323, 325, 327, 329, 331,
+ 341, 347, 349, 350, 351, 354, 356, 359, 361, 363, 365, 367, 369,
+ 375, 379, 381, 382, 385, 386, 387, 388, 389, 390, 391, 392, 393,
+ 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406,
+ 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419,
+ 420, 421, 423, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434,
+ 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447,
+ 448, 449, 450, 451, 452, 454, 455, 456, 457, 458, 459, 460, 461,
+ 462, 463, 464, 471, 473, 474, 475, 476, 477, 478, 479, 480, 481,
+ 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 495,
+ 496, 497, 498, 499, 502, 505,
+ })],
+ ),
+ SpecMeshTest(
+ "HoledCubeSelectLess", "testHoledCubeSelectLess", "expectedHoledCubeSelectLess",
+ [OperatorSpecEditMode("select_face_by_sides", {}, "FACE", {})],
+ ),
+ SpecMeshTest(
+ "EmptyMeshSelectLess", "testEmptyMeshSelectLess", "expectedEmptyMeshSelectLess",
+ [OperatorSpecEditMode("select_face_by_sides", {}, "VERT", {})],
+ ),
# select linked
- SpecMeshTest("PlanesSelectLinked", "testPlanesSelectLinked", "expectedPlanesSelectedLinked",
- [OperatorSpecEditMode("select_linked", {}, "VERT", {7})]),
- SpecMeshTest("CubesSelectLinked", "testCubesSelectLinked", "expectedCubesSelectLinked",
- [OperatorSpecEditMode("select_linked", {}, "VERT", {11})]),
- SpecMeshTest("EmptyMeshSelectLinked", "testEmptyMeshSelectLinked", "expectedEmptyMeshSelectLinked",
- [OperatorSpecEditMode("select_linked", {}, "VERT", {})]),
+ SpecMeshTest(
+ "PlanesSelectLinked", "testPlanesSelectLinked", "expectedPlanesSelectedLinked",
+ [OperatorSpecEditMode("select_linked", {}, "VERT", {7})],
+ ),
+ SpecMeshTest(
+ "CubesSelectLinked", "testCubesSelectLinked", "expectedCubesSelectLinked",
+ [OperatorSpecEditMode("select_linked", {}, "VERT", {11})],
+ ),
+ SpecMeshTest(
+ "EmptyMeshSelectLinked", "testEmptyMeshSelectLinked", "expectedEmptyMeshSelectLinked",
+ [OperatorSpecEditMode("select_linked", {}, "VERT", {})],
+ ),
# select nth (checkered deselect)
- SpecMeshTest("CircleSelect2nd", "testCircleSelect2nd", "expectedCircleSelect2nd",
- [OperatorSpecEditMode("select_nth", {}, "VERT", {i for i in range(32)})]),
+ SpecMeshTest(
+ "CircleSelect2nd", "testCircleSelect2nd", "expectedCircleSelect2nd",
+ [OperatorSpecEditMode("select_nth", {}, "VERT", {i for i in range(32)})],
+ ),
# Subdivide edgering - Not currently functional, operator returns inconsistently
- #SpecMeshTest("SubdivideEdgeringSurface", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringSurface",
+ # SpecMeshTest("SubdivideEdgeringSurface", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringSurface",
# [OperatorSpecEditMode("subdivide_edgering", {"number_cuts": 5, "interpolation": 'SURFACE', "profile_shape_factor": 0.1}, "EDGE", {0, (i for i in range(96) if (i % 3))})]),
- #SpecMeshTest("SubdivideEdgeringPath", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringPath",
+ # SpecMeshTest("SubdivideEdgeringPath", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringPath",
# [OperatorSpecEditMode("subdivide_edgering", {"number_cuts": 5, "interpolation": 'PATH', "profile_shape_factor": 0.1}, "EDGE", {0, (i for i in range(96) if (i % 3))})]),
- #SpecMeshTest("SubdivideEdgeringLinear", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringLinear",
+ # SpecMeshTest("SubdivideEdgeringLinear", "testCylinderSubdivideEdgering", "expectedCylinderSubdivideEdgeringLinear",
# [OperatorSpecEditMode("subdivide_edgering", {"number_cuts": 5, "interpolation": 'LINEAR', "profile_shape_factor": 0.1}, "EDGE", {0, (i for i in range(96) if (i % 3))})]),
# Symmetry Snap
- SpecMeshTest("SymmetrySnap", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnap",
- [OperatorSpecEditMode("symmetry_snap", {"direction": 'POSITIVE_X', "threshold": 1, "factor": 0.75,
- "use_center": False}, "VERT", {i for i in range(5)})]),
- SpecMeshTest("SymmetrySnapCenter", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnapCenter",
- [OperatorSpecEditMode("symmetry_snap", {"direction": 'NEGATIVE_X', "threshold": 1, "factor": 0.75,
- "use_center": True}, "VERT", {i for i in range(5)})]),
+ SpecMeshTest(
+ "SymmetrySnap", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnap",
+ [OperatorSpecEditMode("symmetry_snap", {"direction": 'POSITIVE_X', "threshold": 1, "factor": 0.75,
+ "use_center": False}, "VERT", {i for i in range(5)})],
+ ),
+ SpecMeshTest(
+ "SymmetrySnapCenter", "testPlaneSymmetrySnap", "expectedPlaneSymmetrySnapCenter",
+ [OperatorSpecEditMode("symmetry_snap", {"direction": 'NEGATIVE_X', "threshold": 1, "factor": 0.75,
+ "use_center": True}, "VERT", {i for i in range(5)})],
+ ),
# Tris to Quads
- SpecMeshTest("TrisToQuads", "testPlanesTrisToQuad", "expectedPlanesTrisToQuad",
- [OperatorSpecEditMode("tris_convert_to_quads", {"face_threshold":0.174533, "shape_threshold":0.174533,
- "uvs":True, "vcols":True, "seam":True, "sharp":True, "materials":True}, "VERT", {i for i in range(32)})]),
+ SpecMeshTest(
+ "TrisToQuads", "testPlanesTrisToQuad", "expectedPlanesTrisToQuad",
+ [OperatorSpecEditMode("tris_convert_to_quads", {"face_threshold": 0.174533, "shape_threshold": 0.174533,
+ "uvs": True, "vcols": True, "seam": True, "sharp": True, "materials": True}, "VERT", {i for i in range(32)})],
+ ),
# unsubdivide
# normal case
- SpecMeshTest("CubeFaceUnsubdivide", "testCubeUnsubdivide", "expectedCubeUnsubdivide",
- [OperatorSpecEditMode("unsubdivide", {}, "FACE", {i for i in range(6)})]),
+ SpecMeshTest(
+ "CubeFaceUnsubdivide", "testCubeUnsubdivide", "expectedCubeUnsubdivide",
+ [OperatorSpecEditMode("unsubdivide", {}, "FACE", {i for i in range(6)})],
+ ),
# UV Manipulation
- SpecMeshTest("UVRotate", "testCubeUV", "expectedCubeUVRotate",
- [OperatorSpecEditMode("uvs_rotate", {}, "FACE", {2})]),
- SpecMeshTest("UVRotateCCW", "testCubeUV", "expectedCubeUVRotateCCW",
- [OperatorSpecEditMode("uvs_rotate", {"use_ccw": True}, "FACE", {2})]),
- SpecMeshTest("UVReverse", "testCubeUV", "expectedCubeUVReverse",
- [OperatorSpecEditMode("uvs_reverse", {}, "FACE", {2})]),
- SpecMeshTest("UVAdd", "testCubeUV", "expectedCubeUVAdd",
- [OperatorSpecEditMode("uv_texture_add", {}, "FACE", {})]),
- SpecMeshTest("UVRemove", "testCubeUV", "expectedCubeUVRemove",
- [OperatorSpecEditMode("uv_texture_remove", {}, "FACE", {})]),
+ SpecMeshTest(
+ "UVRotate", "testCubeUV", "expectedCubeUVRotate",
+ [OperatorSpecEditMode("uvs_rotate", {}, "FACE", {2})],
+ ),
+ SpecMeshTest(
+ "UVRotateCCW", "testCubeUV", "expectedCubeUVRotateCCW",
+ [OperatorSpecEditMode("uvs_rotate", {"use_ccw": True}, "FACE", {2})],
+ ),
+ SpecMeshTest(
+ "UVReverse", "testCubeUV", "expectedCubeUVReverse",
+ [OperatorSpecEditMode("uvs_reverse", {}, "FACE", {2})],
+ ),
+ SpecMeshTest(
+ "UVAdd", "testCubeUV", "expectedCubeUVAdd",
+ [OperatorSpecEditMode("uv_texture_add", {}, "FACE", {})],
+ ),
+ SpecMeshTest(
+ "UVRemove", "testCubeUV", "expectedCubeUVRemove",
+ [OperatorSpecEditMode("uv_texture_remove", {}, "FACE", {})],
+ ),
# Vert Connect Concave
- SpecMeshTest("VertexConnectConcave", "testPlaneVertConnectConcave", "expectedPlaneVertConnectConcave",
- [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})]),
- SpecMeshTest("VertexConnectConcaveConvexPentagon", "testPentagonVertConnectConcave", "expectedPentagonVertConnectConcave",
- [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})]),
- SpecMeshTest("VertexConnectConcaveQuad", "testPlaneVertConnectConcaveQuad", "expectedPlaneVertConnectConcaveQuad",
- [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})]),
+ SpecMeshTest(
+ "VertexConnectConcave", "testPlaneVertConnectConcave", "expectedPlaneVertConnectConcave",
+ [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})],
+ ),
+ SpecMeshTest(
+ "VertexConnectConcaveConvexPentagon", "testPentagonVertConnectConcave", "expectedPentagonVertConnectConcave",
+ [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})],
+ ),
+ SpecMeshTest(
+ "VertexConnectConcaveQuad", "testPlaneVertConnectConcaveQuad", "expectedPlaneVertConnectConcaveQuad",
+ [OperatorSpecEditMode("vert_connect_concave", {}, "FACE", {0})],
+ ),
# Vert Connect Nonplanar
- SpecMeshTest("VertexConnectNonplanar", "testPlaneVertConnectNonplanar", "expectedPlaneVertConnectNonplanar",
- [OperatorSpecEditMode("vert_connect_nonplanar", {"angle_limit": 0.17453292}, "VERT", {i for i in range(9)})]),
- SpecMeshTest("VertexConnectNonplanarNgon", "testPlaneVertConnectNonplanarNgon", "expectedPlaneVertConnectNonplanarNgon",
- [OperatorSpecEditMode("vert_connect_nonplanar", {"angle_limit": 0.218166}, "VERT", {i for i in range(6)})]),
+ SpecMeshTest(
+ "VertexConnectNonplanar", "testPlaneVertConnectNonplanar", "expectedPlaneVertConnectNonplanar",
+ [OperatorSpecEditMode("vert_connect_nonplanar", {
+ "angle_limit": 0.17453292}, "VERT", {i for i in range(9)})],
+ ),
+ SpecMeshTest(
+ "VertexConnectNonplanarNgon", "testPlaneVertConnectNonplanarNgon", "expectedPlaneVertConnectNonplanarNgon",
+ [OperatorSpecEditMode("vert_connect_nonplanar", {"angle_limit": 0.218166}, "VERT", {i for i in range(6)})],
+ ),
# T87259 - test cases
- SpecMeshTest("CubeEdgeUnsubdivide", "testCubeEdgeUnsubdivide", "expectedCubeEdgeUnsubdivide",
- [OperatorSpecEditMode("unsubdivide", {}, "EDGE", {i for i in range(6)})]),
- SpecMeshTest("UVSphereUnsubdivide", "testUVSphereUnsubdivide", "expectedUVSphereUnsubdivide",
- [OperatorSpecEditMode("unsubdivide", {'iterations': 9}, "FACE", {i for i in range(512)})]),
+ SpecMeshTest(
+ "CubeEdgeUnsubdivide", "testCubeEdgeUnsubdivide", "expectedCubeEdgeUnsubdivide",
+ [OperatorSpecEditMode("unsubdivide", {}, "EDGE", {i for i in range(6)})],
+ ),
+ SpecMeshTest(
+ "UVSphereUnsubdivide", "testUVSphereUnsubdivide", "expectedUVSphereUnsubdivide",
+ [OperatorSpecEditMode("unsubdivide", {'iterations': 9}, "FACE", {i for i in range(512)})],
+ ),
# vert connect path
# Tip: It works only if there is an already existing face or more than 2 vertices.
@@ -395,23 +568,32 @@ def main():
# Laplacian Smooth
SpecMeshTest(
"LaplacianSmoothDefault", "testSphereLaplacianSmoothDefault", "expectedSphereLaplacianSmoothDefault",
- [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False}, "VERT", {i for i in range(482)})],
+ [OperatorSpecEditMode("vertices_smooth_laplacian", {
+ "preserve_volume": False}, "VERT", {i for i in range(482)})],
),
SpecMeshTest(
"LaplacianSmoothHighValues", "testSphereLaplacianSmoothHigh", "expectedSphereLaplacianSmoothHigh",
- [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False, "repeat": 100, "lambda_factor": 10.0}, "VERT", {i for i in range(482)})],
+ [OperatorSpecEditMode("vertices_smooth_laplacian",
+ {"preserve_volume": False,
+ "repeat": 100,
+ "lambda_factor": 10.0},
+ "VERT",
+ {i for i in range(482)})],
),
SpecMeshTest(
"LaplacianSmoothBorder", "testCubeLaplacianSmoothBorder", "expectedCubeLaplacianSmoothBorder",
- [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False, "lambda_border": 1.0}, "VERT", {i for i in range(25)})],
+ [OperatorSpecEditMode("vertices_smooth_laplacian", {
+ "preserve_volume": False, "lambda_border": 1.0}, "VERT", {i for i in range(25)})],
),
SpecMeshTest(
"LaplacianSmoothHighBorder", "testCubeLaplacianSmoothHighBorder", "expectedCubeLaplacianSmoothHighBorder",
- [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": False, "lambda_border": 100.0}, "VERT", {i for i in range(25)})],
+ [OperatorSpecEditMode("vertices_smooth_laplacian", {
+ "preserve_volume": False, "lambda_border": 100.0}, "VERT", {i for i in range(25)})],
),
SpecMeshTest(
"LaplacianSmoothPreserveVolume", "testSphereLaplacianSmoothPreserveVol", "expectedSphereLaplacianSmoothPreserveVol",
- [OperatorSpecEditMode("vertices_smooth_laplacian", {"preserve_volume": True}, "VERT", {i for i in range(482)})],
+ [OperatorSpecEditMode("vertices_smooth_laplacian", {
+ "preserve_volume": True}, "VERT", {i for i in range(482)})],
),
diff --git a/tests/python/physics_cloth.py b/tests/python/physics_cloth.py
index c507a85c087..e453b4dd68b 100644
--- a/tests/python/physics_cloth.py
+++ b/tests/python/physics_cloth.py
@@ -15,7 +15,7 @@ def main():
test = [
SpecMeshTest("ClothSimple", "testClothPlane", "expectedClothPlane",
- [ModifierSpec('Cloth', 'CLOTH', {'settings': {'quality': 5}}, 15)], threshold=1e-3),
+ [ModifierSpec('Cloth', 'CLOTH', {'settings': {'quality': 5}}, 15)], threshold=1e-3),
# Not reproducible
# SpecMeshTest("ClothPressure", "testObjClothPressure", "expObjClothPressure",
@@ -27,7 +27,7 @@ def main():
# [ModifierSpec('Cloth', 'CLOTH', {'collision_settings': {'use_self_collision': True}}, 67)]),
SpecMeshTest("ClothSpring", "testTorusClothSpring", "expTorusClothSpring",
- [ModifierSpec('Cloth2', 'CLOTH', {'settings': {'use_internal_springs': True}}, 10)], threshold=1e-3),
+ [ModifierSpec('Cloth2', 'CLOTH', {'settings': {'use_internal_springs': True}}, 10)], threshold=1e-3),
]
cloth_test = RunTest(test)
diff --git a/tests/python/physics_dynamic_paint.py b/tests/python/physics_dynamic_paint.py
index f5fabb23adc..57b96ccffba 100644
--- a/tests/python/physics_dynamic_paint.py
+++ b/tests/python/physics_dynamic_paint.py
@@ -15,10 +15,10 @@ def main():
test = [
SpecMeshTest("DynamicPaintSimple", "testObjDynamicPaintPlane", "expObjDynamicPaintPlane",
- [ModifierSpec('dynamic_paint', 'DYNAMIC_PAINT',
- {'ui_type': 'CANVAS',
- 'canvas_settings': {'canvas_surfaces': {'surface_type': 'WAVE', 'frame_end': 15}}},
- 15)]),
+ [ModifierSpec('dynamic_paint', 'DYNAMIC_PAINT',
+ {'ui_type': 'CANVAS',
+ 'canvas_settings': {'canvas_surfaces': {'surface_type': 'WAVE', 'frame_end': 15}}},
+ 15)]),
]
dynamic_paint_test = RunTest(test)
diff --git a/tests/python/physics_ocean.py b/tests/python/physics_ocean.py
index 7a02fc9de4b..20d563f782b 100644
--- a/tests/python/physics_ocean.py
+++ b/tests/python/physics_ocean.py
@@ -15,7 +15,7 @@ def main():
test = [
# World coordinates of test and expected object should be same.
SpecMeshTest("PlaneOcean", "testObjPlaneOcean", "expObjPlaneOcean",
- [ModifierSpec('Ocean', 'OCEAN', {})]),
+ [ModifierSpec('Ocean', 'OCEAN', {})]),
]
ocean_test = RunTest(test)
diff --git a/tests/python/physics_particle_instance.py b/tests/python/physics_particle_instance.py
index d31ce68c7b2..353c0d868c8 100644
--- a/tests/python/physics_particle_instance.py
+++ b/tests/python/physics_particle_instance.py
@@ -15,8 +15,8 @@ def main():
test = [
SpecMeshTest("ParticleInstanceSimple", "testParticleInstance", "expectedParticleInstance",
- [ModifierSpec('ParticleInstance', 'PARTICLE_INSTANCE', {'object': bpy.data.objects['Cube']})],
- threshold=1e-3),
+ [ModifierSpec('ParticleInstance', 'PARTICLE_INSTANCE', {'object': bpy.data.objects['Cube']})],
+ threshold=1e-3),
]
particle_instance_test = RunTest(test)
diff --git a/tests/python/physics_particle_system.py b/tests/python/physics_particle_system.py
index d304d11a894..f558df0aeb7 100644
--- a/tests/python/physics_particle_system.py
+++ b/tests/python/physics_particle_system.py
@@ -13,8 +13,8 @@ from modules.mesh_test import RunTest, ParticleSystemSpec, SpecMeshTest
def main():
test = [
SpecMeshTest("ParticleSystemTest", "testParticleSystem", "expParticleSystem",
- [ParticleSystemSpec('Particles', 'PARTICLE_SYSTEM', {'render_type': "OBJECT",
- 'instance_object': bpy.data.objects['Cube']}, 20)], threshold=1e-3),
+ [ParticleSystemSpec('Particles', 'PARTICLE_SYSTEM', {'render_type': "OBJECT",
+ 'instance_object': bpy.data.objects['Cube']}, 20)], threshold=1e-3),
]
particle_test = RunTest(test)
diff --git a/tests/python/physics_softbody.py b/tests/python/physics_softbody.py
index b167529ccca..00d2a637cf7 100644
--- a/tests/python/physics_softbody.py
+++ b/tests/python/physics_softbody.py
@@ -15,9 +15,9 @@ def main():
test = [
SpecMeshTest("SoftBodySimple", "testSoftBody", "expectedSoftBody",
- [ModifierSpec('Softbody', 'SOFT_BODY',
- {'settings': {'use_goal': False, 'bend': 8, 'pull': 0.8, 'push': 0.8}},
- 45)]),
+ [ModifierSpec('Softbody', 'SOFT_BODY',
+ {'settings': {'use_goal': False, 'bend': 8, 'pull': 0.8, 'push': 0.8}},
+ 45)]),
]
soft_body_test = RunTest(test)