WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 351 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
149 changed files with 3239 additions and 877 deletions
Showing only changes of commit 036d47af63 - Show all commits

View File

@ -3920,27 +3920,18 @@ def km_grease_pencil_stroke_paint_draw_brush(params):
)
# Draw
if params.use_experimental_grease_pencil_version3:
items.extend([
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
])
else:
items.extend([
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
# Draw - straight lines
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'DRAW_STRAIGHT'), ("wait_for_input", False)]}),
# Erase
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'ERASER'), ("wait_for_input", False)]}),
])
items.extend([
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS'},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'DRAW'), ("wait_for_input", False)]}),
# Draw - straight lines
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'DRAW_STRAIGHT'), ("wait_for_input", False)]}),
# Erase
("gpencil.draw", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'ERASER'), ("wait_for_input", False)]}),
])
items.extend([
# Tablet Mappings for Drawing ------------------ */
@ -4566,7 +4557,8 @@ def km_grease_pencil_stroke_vertex_replace(_params):
return keymap
def km_grease_pencil_paint(_params):
# Grease Pencil v3
def km_grease_pencil_paint_mode(_params):
items = []
keymap = (
"Grease Pencil Paint Mode",
@ -4575,7 +4567,6 @@ def km_grease_pencil_paint(_params):
)
items.extend([
*_template_paint_radial_control("gpencil_paint"),
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 0.9)]}),
("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True},
@ -4585,6 +4576,7 @@ def km_grease_pencil_paint(_params):
{"properties": [("mode", 'INVERT')]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
*_template_paint_radial_control("gpencil_paint"),
# Active material
op_menu("VIEW3D_MT_greasepencil_material_active", {"type": 'U', "value": 'PRESS'}),
# Active layer
@ -4599,7 +4591,7 @@ def km_grease_pencil_paint(_params):
return keymap
def km_grease_pencil_edit(params):
def km_grease_pencil_edit_mode(params):
items = []
keymap = (
"Grease Pencil Edit Mode",
@ -8588,8 +8580,9 @@ def generate_keymaps(params=None):
km_grease_pencil_stroke_vertex_average(params),
km_grease_pencil_stroke_vertex_smear(params),
km_grease_pencil_stroke_vertex_replace(params),
km_grease_pencil_paint(params),
km_grease_pencil_edit(params),
# Grease Pencil v3
km_grease_pencil_paint_mode(params),
km_grease_pencil_edit_mode(params),
# Object mode.
km_object_mode(params),
km_object_non_modal(params),

View File

@ -524,48 +524,6 @@ class ARMATURE_OT_copy_bone_color_to_selected(Operator):
return {'FINISHED'}
class ARMATURE_OT_collection_solo_visibility(Operator):
"""Hide all other bone collections and show the active one. """ \
"""Note that it is necessary to also show the ancestors of the active """ \
"""bone collection in order to ensure its visibility"""
bl_idname = "armature.collection_solo_visibility"
bl_label = "Solo Visibility"
bl_options = {'REGISTER', 'UNDO'}
name: StringProperty(name='Bone Collection')
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE' and context.object.data
def execute(self, context):
arm = context.object.data
# Find the named bone collection.
if self.name:
try:
solo_bcoll = arm.collections[self.name]
except KeyError:
self.report({'ERROR'}, "Bone collection %r not found" % self.name)
return {'CANCELLED'}
else:
solo_bcoll = arm.collections.active
if not solo_bcoll:
self.report({'ERROR'}, "Armature has no active Bone collection, nothing to solo")
return {'CANCELLED'}
# Hide everything first.
for bcoll in arm.collections_all:
bcoll.is_visible = False
# Show the named bone collection and all its ancestors.
while solo_bcoll:
solo_bcoll.is_visible = True
solo_bcoll = solo_bcoll.parent
return {'FINISHED'}
class ARMATURE_OT_collection_show_all(Operator):
"""Show all bone collections"""
bl_idname = "armature.collection_show_all"
@ -583,6 +541,28 @@ class ARMATURE_OT_collection_show_all(Operator):
return {'FINISHED'}
class ARMATURE_OT_collection_unsolo_all(Operator):
"""Clear the 'solo' setting on all bone collections"""
bl_idname = "armature.collection_unsolo_all"
bl_label = "Un-solo All"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not (context.object and context.object.type == 'ARMATURE' and context.object.data):
return False
if not context.object.data.collections.is_solo_active:
cls.poll_message_set("None of the bone collections is marked 'solo'")
return False
return True
def execute(self, context):
arm = context.object.data
for bcoll in arm.collections_all:
bcoll.is_solo = False
return {'FINISHED'}
class ARMATURE_OT_collection_remove_unused(Operator):
"""Remove all bone collections that have neither bones nor children. """ \
"""This is done recursively, so bone collections that only have unused children are also removed"""
@ -673,7 +653,7 @@ classes = (
ClearUselessActions,
UpdateAnimatedTransformConstraint,
ARMATURE_OT_copy_bone_color_to_selected,
ARMATURE_OT_collection_solo_visibility,
ARMATURE_OT_collection_show_all,
ARMATURE_OT_collection_unsolo_all,
ARMATURE_OT_collection_remove_unused,
)

View File

@ -386,6 +386,61 @@ class NODE_OT_interface_item_remove(NodeInterfaceOperator, Operator):
return {'FINISHED'}
class NODE_OT_enum_definition_item_add(Operator):
'''Add an enum item to the definition'''
bl_idname = "node.enum_definition_item_add"
bl_label = "Add Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
item = enum_def.enum_items.new("Item")
enum_def.active_index = enum_def.enum_items[:].index(item)
return {'FINISHED'}
class NODE_OT_enum_definition_item_remove(Operator):
'''Remove the selected enum item from the definition'''
bl_idname = "node.enum_definition_item_remove"
bl_label = "Remove Item"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
item = enum_def.active_item
if item:
enum_def.enum_items.remove(item)
enum_def.active_index = min(max(enum_def.active_index, 0), len(enum_def.enum_items) - 1)
return {'FINISHED'}
class NODE_OT_enum_definition_item_move(Operator):
'''Remove the selected enum item from the definition'''
bl_idname = "node.enum_definition_item_move"
bl_label = "Move Item"
bl_options = {'REGISTER', 'UNDO'}
direction: EnumProperty(
name="Direction",
description="Move up or down",
items=[("UP", "Up", ""), ("DOWN", "Down", "")]
)
def execute(self, context):
node = context.active_node
enum_def = node.enum_definition
index = enum_def.active_index
if self.direction == 'UP':
enum_def.enum_items.move(index, index - 1)
enum_def.active_index = min(max(index - 1, 0), len(enum_def.enum_items) - 1)
else:
enum_def.enum_items.move(index, index + 1)
enum_def.active_index = min(max(index + 1, 0), len(enum_def.enum_items) - 1)
return {'FINISHED'}
classes = (
NodeSetting,
@ -397,4 +452,7 @@ classes = (
NODE_OT_interface_item_duplicate,
NODE_OT_interface_item_remove,
NODE_OT_tree_path_parent,
NODE_OT_enum_definition_item_add,
NODE_OT_enum_definition_item_remove,
NODE_OT_enum_definition_item_move,
)

View File

@ -543,6 +543,7 @@ class NODE_MT_category_GEO_UTILITIES(Menu):
layout.menu("NODE_MT_category_GEO_UTILITIES_MATH")
layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION")
layout.separator()
node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch")
node_add_menu.add_node_type(layout, "FunctionNodeRandomValue")
node_add_menu.add_repeat_zone(layout, label="Repeat Zone")
node_add_menu.add_node_type(layout, "GeometryNodeSwitch")

View File

@ -143,15 +143,9 @@ class ARMATURE_MT_collection_context_menu(Menu):
def draw(self, context):
layout = self.layout
arm = context.armature
active_bcoll = arm.collections.active
props = layout.operator("armature.collection_solo_visibility")
props.name = active_bcoll.name if active_bcoll else ""
layout.operator("armature.collection_show_all")
layout.operator("armature.collection_unsolo_all")
layout.separator()
layout.operator("armature.collection_remove_unused", text="Remove Unused")
@ -175,8 +169,8 @@ class ARMATURE_MT_collection_tree_context_menu(Menu):
layout.separator()
layout.operator("armature.collection_solo_visibility")
layout.operator("armature.collection_show_all")
layout.operator("armature.collection_unsolo_all")
layout.separator()

View File

@ -1213,6 +1213,60 @@ class NODE_PT_index_switch_node_items(Panel):
row.operator("node.index_switch_item_remove", icon='REMOVE', text="").index = i
class NODE_UL_enum_definition_items(bpy.types.UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
layout.prop(item, "name", text="", emboss=False, icon_value=icon)
class NODE_PT_menu_switch_items(Panel):
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_category = "Node"
bl_label = "Menu Switch"
@classmethod
def poll(cls, context):
snode = context.space_data
if snode is None:
return False
node = context.active_node
if node is None or node.bl_idname != "GeometryNodeMenuSwitch":
return False
return True
def draw(self, context):
node = context.active_node
layout = self.layout
split = layout.row()
split.template_list(
"NODE_UL_enum_definition_items",
"",
node.enum_definition,
"enum_items",
node.enum_definition,
"active_index")
ops_col = split.column()
add_remove_col = ops_col.column(align=True)
add_remove_col.operator("node.enum_definition_item_add", icon='ADD', text="")
add_remove_col.operator("node.enum_definition_item_remove", icon='REMOVE', text="")
ops_col.separator()
up_down_col = ops_col.column(align=True)
props = up_down_col.operator("node.enum_definition_item_move", icon='TRIA_UP', text="")
props.direction = 'UP'
props = up_down_col.operator("node.enum_definition_item_move", icon='TRIA_DOWN', text="")
props.direction = 'DOWN'
active_item = node.enum_definition.active_item
if active_item is not None:
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(active_item, "description")
# Grease Pencil properties
class NODE_PT_annotation(AnnotationDataPanel, Panel):
bl_space_type = 'NODE_EDITOR'
@ -1285,6 +1339,8 @@ classes = (
NODE_PT_bake_node_items,
NODE_PT_index_switch_node_items,
NODE_PT_repeat_zone_items,
NODE_UL_enum_definition_items,
NODE_PT_menu_switch_items,
NODE_PT_active_node_properties,
node_panel(EEVEE_MATERIAL_PT_settings),

View File

@ -1772,6 +1772,19 @@ class _defs_weight_paint:
class _defs_paint_grease_pencil:
# FIXME: Replace brush tools with code below once they are all implemented:
#
# @staticmethod
# def generate_from_brushes(context):
# return generate_from_enum_ex(
# context,
# idname_prefix="builtin_brush.",
# icon_prefix="brush.gpencil_draw.",
# type=bpy.types.Brush,
# attr="gpencil_tool",
# cursor='DOT',
# )
@ToolDef.from_fn
def draw():
return dict(

View File

@ -239,6 +239,23 @@ void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
BoneCollection *bcoll,
bool is_visible);
/**
* Set or clear this bone collection's solo flag.
*/
void ANIM_armature_bonecoll_solo_set(bArmature *armature, BoneCollection *bcoll, bool is_solo);
/**
* Refresh the ARM_BCOLL_SOLO_ACTIVE flag.
*/
void ANIM_armature_refresh_solo_active(bArmature *armature);
/**
* Determine whether this bone collection is visible, taking into account the visibility of its
* ancestors and the "solo" flags that are in use.
*/
bool ANIM_armature_bonecoll_is_visible_effectively(const bArmature *armature,
const BoneCollection *bcoll);
/**
* Assign the bone to the bone collection.
*

View File

@ -696,7 +696,13 @@ void ANIM_armature_bonecoll_remove_from_index(bArmature *armature, int index)
}
}
const bool is_solo = bcoll->is_solo();
internal::bonecoll_unassign_and_free(armature, bcoll);
if (is_solo) {
/* This might have been the last solo'ed bone collection, so check whether
* solo'ing should still be active on the armature. */
ANIM_armature_refresh_solo_active(armature);
}
}
void ANIM_armature_bonecoll_remove(bArmature *armature, BoneCollection *bcoll)
@ -738,7 +744,7 @@ static void ancestors_visible_descendants_clear(bArmature *armature, BoneCollect
/** Set or clear #BONE_COLLECTION_ANCESTORS_VISIBLE on all descendants of this bone collection. */
static void ancestors_visible_descendants_update(bArmature *armature, BoneCollection *parent_bcoll)
{
if (!parent_bcoll->is_visible_effectively()) {
if (!parent_bcoll->is_visible_with_ancestors()) {
/* If this bone collection is not visible itself, or any of its ancestors are
* invisible, all descendants have an invisible ancestor. */
ancestors_visible_descendants_clear(armature, parent_bcoll);
@ -759,7 +765,7 @@ static void ancestors_visible_update(bArmature *armature,
const BoneCollection *parent_bcoll,
BoneCollection *bcoll)
{
if (parent_bcoll == nullptr || parent_bcoll->is_visible_effectively()) {
if (parent_bcoll == nullptr || parent_bcoll->is_visible_with_ancestors()) {
bcoll->flags |= BONE_COLLECTION_ANCESTORS_VISIBLE;
}
else {
@ -792,6 +798,54 @@ void ANIM_armature_bonecoll_is_visible_set(bArmature *armature,
}
}
void ANIM_armature_bonecoll_solo_set(bArmature *armature,
BoneCollection *bcoll,
const bool is_solo)
{
if (is_solo) {
/* Enabling solo is simple. */
bcoll->flags |= BONE_COLLECTION_SOLO;
armature->flag |= ARM_BCOLL_SOLO_ACTIVE;
return;
}
/* Disabling is harder, as the armature flag can only be disabled when there
* are no more bone collections with the SOLO flag set. */
bcoll->flags &= ~BONE_COLLECTION_SOLO;
ANIM_armature_refresh_solo_active(armature);
}
void ANIM_armature_refresh_solo_active(bArmature *armature)
{
bool any_bcoll_solo = false;
for (const BoneCollection *bcoll : armature->collections_span()) {
if (bcoll->flags & BONE_COLLECTION_SOLO) {
any_bcoll_solo = true;
break;
}
}
if (any_bcoll_solo) {
armature->flag |= ARM_BCOLL_SOLO_ACTIVE;
}
else {
armature->flag &= ~ARM_BCOLL_SOLO_ACTIVE;
}
}
bool ANIM_armature_bonecoll_is_visible_effectively(const bArmature *armature,
const BoneCollection *bcoll)
{
const bool is_solo_active = armature->flag & ARM_BCOLL_SOLO_ACTIVE;
if (is_solo_active) {
/* If soloing is active, nothing in the hierarchy matters except the solo flag. */
return bcoll->is_solo();
}
return bcoll->is_visible_with_ancestors();
}
/* Store the bone's membership on the collection. */
static void add_membership(BoneCollection *bcoll, Bone *bone)
{
@ -925,7 +979,8 @@ void ANIM_armature_bonecoll_reconstruct(bArmature *armature)
});
}
static bool any_bone_collection_visible(const ListBase /*BoneCollectionRef*/ *collection_refs)
static bool any_bone_collection_visible(const bArmature *armature,
const ListBase /*BoneCollectionRef*/ *collection_refs)
{
/* Special case: when a bone is not in any collection, it is visible. */
if (BLI_listbase_is_empty(collection_refs)) {
@ -934,23 +989,20 @@ static bool any_bone_collection_visible(const ListBase /*BoneCollectionRef*/ *co
LISTBASE_FOREACH (const BoneCollectionReference *, bcoll_ref, collection_refs) {
const BoneCollection *bcoll = bcoll_ref->bcoll;
if (bcoll->is_visible_effectively()) {
if (ANIM_armature_bonecoll_is_visible_effectively(armature, bcoll)) {
return true;
}
}
return false;
}
/* TODO: these two functions were originally implemented for armature layers, hence the armature
* parameters. These should be removed at some point. */
bool ANIM_bone_in_visible_collection(const bArmature * /*armature*/, const Bone *bone)
bool ANIM_bone_in_visible_collection(const bArmature *armature, const Bone *bone)
{
return any_bone_collection_visible(&bone->runtime.collections);
return any_bone_collection_visible(armature, &bone->runtime.collections);
}
bool ANIM_bonecoll_is_visible_editbone(const bArmature * /*armature*/, const EditBone *ebone)
bool ANIM_bonecoll_is_visible_editbone(const bArmature *armature, const EditBone *ebone)
{
return any_bone_collection_visible(&ebone->bone_collections);
return any_bone_collection_visible(armature, &ebone->bone_collections);
}
void ANIM_armature_bonecoll_show_all(bArmature *armature)

View File

@ -43,7 +43,7 @@ TEST(ANIM_bone_collections, bonecoll_default_name)
}
}
class ANIM_armature_bone_collections : public testing::Test {
class ArmatureBoneCollections : public testing::Test {
protected:
bArmature arm;
Bone bone1, bone2, bone3;
@ -78,7 +78,7 @@ class ANIM_armature_bone_collections : public testing::Test {
}
};
TEST_F(ANIM_armature_bone_collections, armature_owned_collections)
TEST_F(ArmatureBoneCollections, armature_owned_collections)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "collection");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "collection");
@ -90,7 +90,7 @@ TEST_F(ANIM_armature_bone_collections, armature_owned_collections)
ANIM_armature_bonecoll_remove(&arm, bcoll2);
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_creation)
TEST_F(ArmatureBoneCollections, collection_hierarchy_creation)
{
/* Implicit root: */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "wortel");
@ -146,7 +146,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_creation)
/* TODO: test with deeper hierarchy. */
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal)
TEST_F(ArmatureBoneCollections, collection_hierarchy_removal)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -240,8 +240,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal)
EXPECT_EQ(0, arm.collection_array[2]->child_count);
}
TEST_F(ANIM_armature_bone_collections,
collection_hierarchy_removal__more_complex_remove_inner_child)
TEST_F(ArmatureBoneCollections, collection_hierarchy_removal__more_complex_remove_inner_child)
{
/* Set up a slightly bigger hierarchy. Contrary to the other tests these are
* actually declared in array order. */
@ -339,7 +338,7 @@ TEST_F(ANIM_armature_bone_collections,
EXPECT_EQ(0, arm.collection_array[5]->child_count);
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal__more_complex_remove_root)
TEST_F(ArmatureBoneCollections, collection_hierarchy_removal__more_complex_remove_root)
{
/* Set up a slightly bigger hierarchy. Contrary to the other tests these are
* actually declared in array order. */
@ -411,7 +410,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_removal__more_comple
EXPECT_EQ(0, arm.collection_array[6]->child_count);
}
TEST_F(ANIM_armature_bone_collections, find_parent_index)
TEST_F(ArmatureBoneCollections, find_parent_index)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -455,7 +454,7 @@ TEST_F(ANIM_armature_bone_collections, find_parent_index)
EXPECT_EQ(2, armature_bonecoll_find_parent_index(&arm, 5));
}
TEST_F(ANIM_armature_bone_collections, collection_hierarchy_visibility)
TEST_F(ArmatureBoneCollections, collection_hierarchy_visibility)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root0 = ANIM_armature_bonecoll_new(&arm, "root0");
@ -520,7 +519,7 @@ TEST_F(ANIM_armature_bone_collections, collection_hierarchy_visibility)
EXPECT_FALSE(bcoll_r0_child2->flags & BONE_COLLECTION_ANCESTORS_VISIBLE);
}
TEST_F(ANIM_armature_bone_collections, bones_assign_unassign)
TEST_F(ArmatureBoneCollections, bones_assign_unassign)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_new(&arm, "collection");
@ -547,7 +546,7 @@ TEST_F(ANIM_armature_bone_collections, bones_assign_unassign)
ANIM_armature_bonecoll_remove(&arm, bcoll);
}
TEST_F(ANIM_armature_bone_collections, bones_assign_remove)
TEST_F(ArmatureBoneCollections, bones_assign_remove)
{
BoneCollection *bcoll = ANIM_armature_bonecoll_new(&arm, "collection");
@ -563,7 +562,7 @@ TEST_F(ANIM_armature_bone_collections, bones_assign_remove)
"removed";
}
TEST_F(ANIM_armature_bone_collections, active_set_clear_by_pointer)
TEST_F(ArmatureBoneCollections, active_set_clear_by_pointer)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
@ -592,7 +591,7 @@ TEST_F(ANIM_armature_bone_collections, active_set_clear_by_pointer)
ANIM_bonecoll_free(bcoll3);
}
TEST_F(ANIM_armature_bone_collections, active_set_clear_by_index)
TEST_F(ArmatureBoneCollections, active_set_clear_by_index)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
@ -618,7 +617,7 @@ TEST_F(ANIM_armature_bone_collections, active_set_clear_by_index)
EXPECT_STREQ("", arm.active_collection_name);
}
TEST_F(ANIM_armature_bone_collections, bcoll_is_editable)
TEST_F(ArmatureBoneCollections, bcoll_is_editable)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
@ -652,7 +651,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_is_editable)
<< "Expecting local bone collection on local armature override to be editable";
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__roots)
TEST_F(ArmatureBoneCollections, bcoll_move_to_index__roots)
{
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "collection");
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "collection");
@ -687,7 +686,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__roots)
EXPECT_EQ(arm.collection_array[3], bcoll1);
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__siblings)
TEST_F(ArmatureBoneCollections, bcoll_move_to_index__siblings)
{
BoneCollection *root = ANIM_armature_bonecoll_new(&arm, "root");
BoneCollection *child0 = ANIM_armature_bonecoll_new(&arm, "child0", 0);
@ -738,7 +737,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_index__siblings)
EXPECT_STREQ(child1_0->name, arm.collection_array[4]->name);
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent)
TEST_F(ArmatureBoneCollections, bcoll_move_to_parent)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -877,7 +876,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent)
EXPECT_EQ(0, arm.collection_array[5]->child_count);
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__root_unroot)
TEST_F(ArmatureBoneCollections, bcoll_move_to_parent__root_unroot)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -965,7 +964,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__root_unroot)
// TODO: test with circular parenthood.
}
TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__within_siblings)
TEST_F(ArmatureBoneCollections, bcoll_move_to_parent__within_siblings)
{
/* Set up a small hierarchy. */
auto bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -1218,7 +1217,7 @@ TEST_F(ANIM_armature_bone_collections, bcoll_move_to_parent__within_siblings)
EXPECT_EQ(0, arm.collection_array[6]->child_count);
}
TEST_F(ANIM_armature_bone_collections, internal__bonecolls_rotate_block)
TEST_F(ArmatureBoneCollections, internal__bonecolls_rotate_block)
{
/* Set up a small hierarchy. */
BoneCollection *bcoll_root_0 = ANIM_armature_bonecoll_new(&arm, "root_0");
@ -1283,7 +1282,7 @@ TEST_F(ANIM_armature_bone_collections, internal__bonecolls_rotate_block)
EXPECT_EQ(0, arm.collection_array[5]->child_index);
}
class ANIM_armature_bone_collections_testlist : public testing::Test {
class ArmatureBoneCollectionsTestList : public testing::Test {
protected:
bArmature arm;
@ -1352,7 +1351,7 @@ class ANIM_armature_bone_collections_testlist : public testing::Test {
}
};
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_first_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__before_first_sibling)
{
/* Set the active index to be one of the affected bone collections. */
ANIM_armature_bonecoll_active_name_set(&arm, "child2");
@ -1368,14 +1367,14 @@ TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_
EXPECT_STREQ("child2", arm.active_collection_name);
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__after_first_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__after_first_sibling)
{
EXPECT_EQ(2, ANIM_armature_bonecoll_move_before_after_index(&arm, 3, 1, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child0", "child2", "child1", "child1_0"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 2));
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_last_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__before_last_sibling)
{
/* Set the active index to be one of the affected bone collections. */
ANIM_armature_bonecoll_active_name_set(&arm, "child1");
@ -1391,60 +1390,56 @@ TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__before_
EXPECT_STREQ("child1", arm.active_collection_name);
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__after_last_sibling)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__after_last_sibling)
{
EXPECT_EQ(3, ANIM_armature_bonecoll_move_before_after_index(&arm, 1, 3, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child0", "child1_0"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 3));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_before__move_left)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_before__move_left)
{
EXPECT_EQ(1, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 1, MoveLocation::Before));
EXPECT_TRUE(expect_bcolls({"root", "child1_0", "child0", "child1", "child2"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 1));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_after__move_left)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_after__move_left)
{
EXPECT_EQ(2, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 1, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child0", "child1_0", "child1", "child2"}));
EXPECT_EQ(0, armature_bonecoll_find_parent_index(&arm, 2));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_before__move_right)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_before__move_right)
{
EXPECT_EQ(3, ANIM_armature_bonecoll_move_before_after_index(&arm, 1, 4, MoveLocation::Before));
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child0", "child1_0"}));
EXPECT_EQ(1, armature_bonecoll_find_parent_index(&arm, 3));
}
TEST_F(ANIM_armature_bone_collections_testlist,
move_before_after_index__other_parent_after__move_right)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__other_parent_after__move_right)
{
EXPECT_EQ(4, ANIM_armature_bonecoll_move_before_after_index(&arm, 1, 4, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child1_0", "child0"}));
EXPECT_EQ(1, armature_bonecoll_find_parent_index(&arm, 4));
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__to_root__before)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__to_root__before)
{
EXPECT_EQ(0, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 0, MoveLocation::Before));
EXPECT_TRUE(expect_bcolls({"child1_0", "root", "child0", "child1", "child2"}));
EXPECT_EQ(-1, armature_bonecoll_find_parent_index(&arm, 0));
}
TEST_F(ANIM_armature_bone_collections_testlist, move_before_after_index__to_root__after)
TEST_F(ArmatureBoneCollectionsTestList, move_before_after_index__to_root__after)
{
EXPECT_EQ(1, ANIM_armature_bonecoll_move_before_after_index(&arm, 4, 0, MoveLocation::After));
EXPECT_TRUE(expect_bcolls({"root", "child1_0", "child0", "child1", "child2"}));
EXPECT_EQ(-1, armature_bonecoll_find_parent_index(&arm, 1));
}
TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__roots)
TEST_F(ArmatureBoneCollectionsTestList, child_number_set__roots)
{
/* Test with only one root. */
EXPECT_EQ(0, armature_bonecoll_child_number_set(&arm, root, 0));
@ -1474,7 +1469,7 @@ TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__roots)
EXPECT_TRUE(expect_bcolls({"root1", "root2", "root", "child0", "child1", "child2", "child1_0"}));
}
TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__siblings)
TEST_F(ArmatureBoneCollectionsTestList, child_number_set__siblings)
{
/* Move child0 to itself. */
EXPECT_EQ(1, armature_bonecoll_child_number_set(&arm, child0, 0));
@ -1497,8 +1492,36 @@ TEST_F(ANIM_armature_bone_collections_testlist, child_number_set__siblings)
EXPECT_TRUE(expect_bcolls({"root", "child1", "child2", "child0", "child1_0"}));
}
class ANIM_armature_bone_collections_liboverrides
: public ANIM_armature_bone_collections_testlist {
TEST_F(ArmatureBoneCollectionsTestList, bone_collection_solo)
{
EXPECT_FALSE(arm.flag & ARM_BCOLL_SOLO_ACTIVE) << "By default no solo'ing should be active";
/* Enable solo. */
EXPECT_FALSE(child1->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1, true);
EXPECT_TRUE(child1->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Enable solo on another bone collection. */
EXPECT_FALSE(child1_0->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1_0, true);
EXPECT_TRUE(child1_0->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Disable the first solo flag. */
EXPECT_TRUE(child1->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1, false);
EXPECT_FALSE(child1->flags & BONE_COLLECTION_SOLO);
EXPECT_TRUE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
/* Disable the second solo flag. This should also disable the ARM_BCOLL_SOLO_ACTIVE flag. */
EXPECT_TRUE(child1_0->flags & BONE_COLLECTION_SOLO);
ANIM_armature_bonecoll_solo_set(&arm, child1_0, false);
EXPECT_FALSE(child1_0->flags & BONE_COLLECTION_SOLO);
EXPECT_FALSE(arm.flag & ARM_BCOLL_SOLO_ACTIVE);
}
class ArmatureBoneCollectionsLiboverrides : public ArmatureBoneCollectionsTestList {
protected:
bArmature dst_arm;
@ -1510,7 +1533,7 @@ class ANIM_armature_bone_collections_liboverrides
void SetUp() override
{
ANIM_armature_bone_collections_testlist::SetUp();
ArmatureBoneCollectionsTestList::SetUp();
/* TODO: make this clone `arm` into `dst_arm`, instead of assuming the below
* code is still in sync with the super-class. */
@ -1535,12 +1558,12 @@ class ANIM_armature_bone_collections_liboverrides
void TearDown() override
{
ANIM_armature_bone_collections_testlist::TearDown();
ArmatureBoneCollectionsTestList::TearDown();
BKE_libblock_free_datablock(&dst_arm.id, 0);
}
};
TEST_F(ANIM_armature_bone_collections_liboverrides, bcoll_insert_copy_after)
TEST_F(ArmatureBoneCollectionsLiboverrides, bcoll_insert_copy_after)
{
/* Mimic that a new root, two children, and two grandchildren were added via library overrides.
* These were saved in `arm`, and now need to be copied into `dst_arm`. */

View File

@ -77,13 +77,12 @@ uint64_t BoneColor::hash() const
/* For custom colors, hash everything together. */
/* The last byte of the color is skipped, as it is inconsistent (see note above). */
const uint64_t hash_solid = get_default_hash_3(
custom.solid[0], custom.solid[1], custom.solid[2]);
const uint64_t hash_select = get_default_hash_3(
const uint64_t hash_solid = get_default_hash(custom.solid[0], custom.solid[1], custom.solid[2]);
const uint64_t hash_select = get_default_hash(
custom.select[0], custom.select[1], custom.select[2]);
const uint64_t hash_active = get_default_hash_3(
const uint64_t hash_active = get_default_hash(
custom.active[0], custom.active[1], custom.active[2]);
return get_default_hash_4(hash_solid, hash_select, hash_active, custom.flag);
return get_default_hash(hash_solid, hash_select, hash_active, custom.flag);
}
const BoneColor &ANIM_bonecolor_posebone_get(const bPoseChannel *pose_bone)

View File

@ -1328,6 +1328,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_GET_NAMED_GRID 2121
#define GEO_NODE_STORE_NAMED_GRID 2122
#define GEO_NODE_SORT_ELEMENTS 2123
#define GEO_NODE_MENU_SWITCH 2124
/** \} */

View File

@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <string>
#include "BLI_implicit_sharing.hh"
#include "BLI_vector.hh"
namespace blender::bke {
/* Flags for #bNodeSocketValueMenu. */
enum NodeSocketValueMenuRuntimeFlag {
/* Socket has conflicting menu connections and cannot resolve items. */
NODE_MENU_ITEMS_CONFLICT = (1 << 0),
};
/* -------------------------------------------------------------------- */
/** \name Runtime enum items list.
* \{ */
/**
* Runtime copy of #NodeEnumItem for use in #RuntimeNodeEnumItems.
*/
struct RuntimeNodeEnumItem {
std::string name;
std::string description;
/* Immutable unique identifier. */
int identifier;
};
/**
* Shared immutable list of enum items.
* These are owned by a node and can be referenced by node sockets.
*/
struct RuntimeNodeEnumItems : ImplicitSharingMixin {
Vector<RuntimeNodeEnumItem> items;
void delete_self() override
{
delete this;
}
};
/** \} */
} // namespace blender::bke

View File

@ -158,6 +158,7 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = {
{"NodeSocketCollection", "NodeTreeInterfaceSocketCollection", SOCK_COLLECTION, PROP_NONE},
{"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE},
{"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE},
{"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE},
};
template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype type, const Fn &fn)
@ -199,6 +200,9 @@ template<typename Fn> bool socket_data_to_static_type(const eNodeSocketDatatype
case SOCK_MATERIAL:
fn.template operator()<bNodeSocketValueMaterial>();
return true;
case SOCK_MENU:
fn.template operator()<bNodeSocketValueMenu>();
return true;
case SOCK_CUSTOM:
case SOCK_SHADER:

View File

@ -285,7 +285,7 @@ void BKE_pbvh_node_mark_update_face_sets(PBVHNode *node);
void BKE_pbvh_node_mark_update_visibility(PBVHNode *node);
void BKE_pbvh_node_mark_rebuild_draw(PBVHNode *node);
void BKE_pbvh_node_mark_redraw(PBVHNode *node);
void BKE_pbvh_node_mark_normals_update(PBVHNode *node);
void BKE_pbvh_node_mark_positions_update(PBVHNode *node);
void BKE_pbvh_node_mark_topology_update(PBVHNode *node);
void BKE_pbvh_node_fully_hidden_set(PBVHNode *node, int fully_hidden);
bool BKE_pbvh_node_fully_hidden_get(const PBVHNode *node);

View File

@ -232,6 +232,7 @@ set(SRC
intern/multires_versioning.cc
intern/nla.cc
intern/node.cc
intern/node_enum_definition.cc
intern/node_runtime.cc
intern/node_socket_value.cc
intern/node_tree_anonymous_attributes.cc
@ -456,6 +457,7 @@ set(SRC
BKE_nla.h
BKE_node.h
BKE_node.hh
BKE_node_enum.hh
BKE_node_runtime.hh
BKE_node_socket_value.hh
BKE_node_tree_anonymous_attributes.hh

View File

@ -3182,9 +3182,13 @@ bool BoneCollection::is_visible_ancestors() const
{
return this->flags & BONE_COLLECTION_ANCESTORS_VISIBLE;
}
bool BoneCollection::is_visible_effectively() const
bool BoneCollection::is_visible_with_ancestors() const
{
return this->is_visible() && this->is_visible_ancestors();
}
bool BoneCollection::is_solo() const
{
return this->flags & BONE_COLLECTION_SOLO;
}
/** \} */

View File

@ -1060,7 +1060,7 @@ static bool data_transfer_layersmapping_generate(ListBase *r_map,
dst_data = static_cast<float3 *>(CustomData_add_layer(
&me_dst->corner_data, CD_NORMAL, CD_SET_DEFAULT, me_dst->corners_num));
}
if (mix_factor != 1.0f) {
if (mix_factor != 1.0f || mix_weights) {
MutableSpan(dst_data, me_dst->corners_num).copy_from(me_dst->corner_normals());
}
/* Post-process will convert it back to CD_CUSTOMLOOPNORMAL. */

View File

@ -431,7 +431,7 @@ std::string AttributeFieldInput::socket_inspection_name() const
uint64_t AttributeFieldInput::hash() const
{
return get_default_hash_2(name_, type_);
return get_default_hash(name_, type_);
}
bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
@ -513,7 +513,7 @@ std::string AnonymousAttributeFieldInput::socket_inspection_name() const
uint64_t AnonymousAttributeFieldInput::hash() const
{
return get_default_hash_2(anonymous_id_.get(), type_);
return get_default_hash(anonymous_id_.get(), type_);
}
bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
@ -575,7 +575,7 @@ GVArray NamedLayerSelectionFieldInput::get_varray_for_context(
uint64_t NamedLayerSelectionFieldInput::hash() const
{
return get_default_hash_2(layer_name_, type_);
return get_default_hash(layer_name_, type_);
}
bool NamedLayerSelectionFieldInput::is_equal_to(const fn::FieldNode &other) const

View File

@ -57,6 +57,7 @@ static size_t idp_size_table[] = {
sizeof(double), /* #IDP_DOUBLE */
0, /* #IDP_IDPARRAY (no fixed size). */
sizeof(int8_t), /* #IDP_BOOLEAN */
sizeof(int), /* #IDP_ENUM */
};
/* -------------------------------------------------------------------- */

View File

@ -343,6 +343,11 @@ void Mesh::tag_sharpness_changed()
this->runtime->corner_normals_cache.tag_dirty();
}
void Mesh::tag_custom_normals_changed()
{
this->runtime->corner_normals_cache.tag_dirty();
}
void Mesh::tag_face_winding_changed()
{
this->runtime->vert_normals_cache.tag_dirty();

View File

@ -67,6 +67,7 @@
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
#include "BKE_node_tree_interface.hh"
@ -357,6 +358,7 @@ static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
case SOCK_MENU:
break;
}
}
@ -701,6 +703,10 @@ static void write_node_socket_default_value(BlendWriter *writer, const bNodeSock
case SOCK_ROTATION:
BLO_write_struct(writer, bNodeSocketValueRotation, sock->default_value);
break;
case SOCK_MENU: {
BLO_write_struct(writer, bNodeSocketValueMenu, sock->default_value);
break;
}
case SOCK_CUSTOM:
/* Custom node sockets where default_value is defined uses custom properties for storage. */
break;
@ -853,6 +859,17 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
if (node->type == GEO_NODE_BAKE) {
blender::nodes::BakeItemsAccessor::blend_write(writer, *node);
}
if (node->type == GEO_NODE_MENU_SWITCH) {
const NodeMenuSwitch &storage = *static_cast<const NodeMenuSwitch *>(node->storage);
BLO_write_struct_array(writer,
NodeEnumItem,
storage.enum_definition.items_num,
storage.enum_definition.items_array);
for (const NodeEnumItem &item : storage.enum_definition.items()) {
BLO_write_string(writer, item.name);
BLO_write_string(writer, item.description);
}
}
}
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
@ -921,6 +938,7 @@ static bool is_node_socket_supported(const bNodeSocket *sock)
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_ROTATION:
case SOCK_MENU:
return true;
}
return false;
@ -937,6 +955,18 @@ static void direct_link_node_socket(BlendDataReader *reader, bNodeSocket *sock)
BLO_read_data_address(reader, &sock->default_value);
BLO_read_data_address(reader, &sock->default_attribute_name);
sock->runtime = MEM_new<bNodeSocketRuntime>(__func__);
switch (eNodeSocketDatatype(sock->type)) {
case SOCK_MENU: {
bNodeSocketValueMenu &default_value = *sock->default_value_typed<bNodeSocketValueMenu>();
/* Clear runtime data. */
default_value.enum_items = nullptr;
default_value.runtime_flag = 0;
break;
}
default:
break;
}
}
static void direct_link_node_socket_list(BlendDataReader *reader, ListBase *socket_list)
@ -1099,6 +1129,15 @@ void ntreeBlendReadData(BlendDataReader *reader, ID *owner_id, bNodeTree *ntree)
blender::nodes::BakeItemsAccessor::blend_read_data(reader, *node);
break;
}
case GEO_NODE_MENU_SWITCH: {
NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
BLO_read_data_address(reader, &storage.enum_definition.items_array);
for (const NodeEnumItem &item : storage.enum_definition.items()) {
BLO_read_data_address(reader, &item.name);
BLO_read_data_address(reader, &item.description);
}
break;
}
default:
break;
@ -1826,6 +1865,7 @@ static void socket_id_user_increment(bNodeSocket *sock)
case SOCK_ROTATION:
case SOCK_INT:
case SOCK_STRING:
case SOCK_MENU:
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
@ -1872,6 +1912,7 @@ static bool socket_id_user_decrement(bNodeSocket *sock)
case SOCK_ROTATION:
case SOCK_INT:
case SOCK_STRING:
case SOCK_MENU:
case SOCK_CUSTOM:
case SOCK_SHADER:
case SOCK_GEOMETRY:
@ -1931,6 +1972,7 @@ void nodeModifySocketType(bNodeTree *ntree,
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
break;
}
}
@ -2065,6 +2107,8 @@ const char *nodeStaticSocketType(const int type, const int subtype)
return "NodeSocketTexture";
case SOCK_MATERIAL:
return "NodeSocketMaterial";
case SOCK_MENU:
return "NodeSocketMenu";
case SOCK_CUSTOM:
break;
}
@ -2146,6 +2190,8 @@ const char *nodeStaticSocketInterfaceTypeNew(const int type, const int subtype)
return "NodeTreeInterfaceSocketTexture";
case SOCK_MATERIAL:
return "NodeTreeInterfaceSocketMaterial";
case SOCK_MENU:
return "NodeTreeInterfaceSocketMenu";
case SOCK_CUSTOM:
break;
}
@ -2183,6 +2229,8 @@ const char *nodeStaticSocketLabel(const int type, const int /*subtype*/)
return "Texture";
case SOCK_MATERIAL:
return "Material";
case SOCK_MENU:
return "Menu";
case SOCK_CUSTOM:
break;
}
@ -2222,6 +2270,13 @@ static void node_socket_free(bNodeSocket *sock, const bool do_id_user)
if (do_id_user) {
socket_id_user_decrement(sock);
}
if (sock->type == SOCK_MENU) {
auto &default_value_menu = *sock->default_value_typed<bNodeSocketValueMenu>();
if (default_value_menu.enum_items) {
/* Release shared data pointer. */
default_value_menu.enum_items->remove_user_and_delete_if_last();
}
}
MEM_freeN(sock->default_value);
}
if (sock->default_attribute_name) {
@ -2551,6 +2606,14 @@ static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src,
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
socket_id_user_increment(sock_dst);
}
if (sock_src->type == SOCK_MENU) {
auto &default_value_menu = *sock_dst->default_value_typed<bNodeSocketValueMenu>();
if (default_value_menu.enum_items) {
/* Copy of shared data pointer. */
default_value_menu.enum_items->add_user();
}
}
}
sock_dst->default_attribute_name = static_cast<char *>(
@ -2691,6 +2754,8 @@ static void *socket_value_storage(bNodeSocket &socket)
return &socket.default_value_typed<bNodeSocketValueMaterial>()->value;
case SOCK_ROTATION:
return &socket.default_value_typed<bNodeSocketValueRotation>()->value_euler;
case SOCK_MENU:
return &socket.default_value_typed<bNodeSocketValueMenu>()->value;
case SOCK_STRING:
/* We don't want do this now! */
return nullptr;

View File

@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "DNA_array_utils.hh"
#include "DNA_node_types.h"
#include "BKE_node.h"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
using blender::bke::NodeSocketValueMenuRuntimeFlag;
bool bNodeSocketValueMenu::has_conflict() const
{
return this->runtime_flag & NodeSocketValueMenuRuntimeFlag::NODE_MENU_ITEMS_CONFLICT;
}
blender::Span<NodeEnumItem> NodeEnumDefinition::items() const
{
return {this->items_array, this->items_num};
}
blender::MutableSpan<NodeEnumItem> NodeEnumDefinition::items_for_write()
{
return {this->items_array, this->items_num};
}
NodeEnumItem *NodeEnumDefinition::add_item(blender::StringRef name)
{
const int insert_index = this->items_num;
NodeEnumItem *old_items = this->items_array;
this->items_array = MEM_cnew_array<NodeEnumItem>(this->items_num + 1, __func__);
std::copy_n(old_items, insert_index, this->items_array);
NodeEnumItem &new_item = this->items_array[insert_index];
std::copy_n(old_items + insert_index + 1,
this->items_num - insert_index,
this->items_array + insert_index + 1);
new_item.identifier = this->next_identifier++;
this->set_item_name(new_item, name);
this->items_num++;
MEM_SAFE_FREE(old_items);
return &new_item;
}
static void free_enum_item(NodeEnumItem *item)
{
MEM_SAFE_FREE(item->name);
MEM_SAFE_FREE(item->description);
}
bool NodeEnumDefinition::remove_item(NodeEnumItem &item)
{
if (!this->items().contains_ptr(&item)) {
return false;
}
const int remove_index = &item - this->items().begin();
/* DNA fields are 16 bits, can't use directly. */
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::remove_index(
&this->items_array, &items_num, &active_index, remove_index, free_enum_item);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
return true;
}
void NodeEnumDefinition::clear()
{
/* DNA fields are 16 bits, can't use directly. */
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::clear(&this->items_array, &items_num, &active_index, free_enum_item);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
}
bool NodeEnumDefinition::move_item(uint16_t from_index, uint16_t to_index)
{
if (to_index < this->items_num) {
int items_num = this->items_num;
int active_index = this->active_index;
blender::dna::array::move_index(this->items_array, items_num, from_index, to_index);
this->items_num = int16_t(items_num);
this->active_index = int16_t(active_index);
}
return true;
}
const NodeEnumItem *NodeEnumDefinition::active_item() const
{
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
return &this->items()[this->active_index];
}
return nullptr;
}
NodeEnumItem *NodeEnumDefinition::active_item()
{
if (blender::IndexRange(this->items_num).contains(this->active_index)) {
return &this->items_for_write()[this->active_index];
}
return nullptr;
}
void NodeEnumDefinition::active_item_set(NodeEnumItem *item)
{
this->active_index = this->items().contains_ptr(item) ? item - this->items_array : -1;
}
void NodeEnumDefinition::set_item_name(NodeEnumItem &item, blender::StringRef name)
{
char unique_name[MAX_NAME + 4];
STRNCPY(unique_name, name.data());
struct Args {
NodeEnumDefinition *enum_def;
const NodeEnumItem *item;
} args = {this, &item};
const char *default_name = items().is_empty() ? "Name" : items().last().name;
BLI_uniquename_cb(
[](void *arg, const char *name) {
const Args &args = *static_cast<Args *>(arg);
for (const NodeEnumItem &item : args.enum_def->items()) {
if (&item != args.item) {
if (STREQ(item.name, name)) {
return true;
}
}
}
return false;
},
&args,
default_name,
'.',
unique_name,
ARRAY_SIZE(unique_name));
MEM_SAFE_FREE(item.name);
item.name = BLI_strdup(unique_name);
}

View File

@ -6,6 +6,7 @@
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_tree_interface.hh"
#include "BLI_math_vector.h"
@ -171,6 +172,12 @@ template<> void socket_data_init_impl(bNodeSocketValueMaterial &data)
{
data.value = nullptr;
}
template<> void socket_data_init_impl(bNodeSocketValueMenu &data)
{
data.value = -1;
data.enum_items = nullptr;
data.runtime_flag = 0;
}
static void *make_socket_data(const StringRef socket_type)
{
@ -191,6 +198,13 @@ static void *make_socket_data(const StringRef socket_type)
* \{ */
template<typename T> void socket_data_free_impl(T & /*data*/, const bool /*do_id_user*/) {}
template<> void socket_data_free_impl(bNodeSocketValueMenu &dst, const bool /*do_id_user*/)
{
if (dst.enum_items) {
/* Release shared data pointer. */
dst.enum_items->remove_user_and_delete_if_last();
}
}
static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_user)
{
@ -210,6 +224,14 @@ static void socket_data_free(bNodeTreeInterfaceSocket &socket, const bool do_id_
* \{ */
template<typename T> void socket_data_copy_impl(T & /*dst*/, const T & /*src*/) {}
template<>
void socket_data_copy_impl(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu & /*src*/)
{
/* Copy of shared data pointer. */
if (dst.enum_items) {
dst.enum_items->add_user();
}
}
static void socket_data_copy(bNodeTreeInterfaceSocket &dst,
const bNodeTreeInterfaceSocket &src,
@ -304,6 +326,10 @@ inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMaterial
{
BLO_write_struct(writer, bNodeSocketValueMaterial, &data);
}
inline void socket_data_write_impl(BlendWriter *writer, bNodeSocketValueMenu &data)
{
BLO_write_struct(writer, bNodeSocketValueMenu, &data);
}
static void socket_data_write(BlendWriter *writer, bNodeTreeInterfaceSocket &socket)
{
@ -323,6 +349,13 @@ template<typename T> void socket_data_read_data_impl(BlendDataReader *reader, T
{
BLO_read_data_address(reader, data);
}
template<> void socket_data_read_data_impl(BlendDataReader *reader, bNodeSocketValueMenu **data)
{
BLO_read_data_address(reader, data);
/* Clear runtime data. */
(*data)->enum_items = nullptr;
(*data)->runtime_flag = 0;
}
static void socket_data_read_data(BlendDataReader *reader, bNodeTreeInterfaceSocket &socket)
{

View File

@ -19,6 +19,7 @@
#include "BKE_image.h"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_anonymous_attributes.hh"
#include "BKE_node_tree_update.hh"
@ -485,6 +486,9 @@ class NodeTreeMainUpdater {
this->propagate_runtime_flags(ntree);
if (ntree.type == NTREE_GEOMETRY) {
if (this->propagate_enum_definitions(ntree)) {
result.interface_changed = true;
}
if (node_field_inferencing::update_field_inferencing(ntree)) {
result.interface_changed = true;
}
@ -799,6 +803,209 @@ class NodeTreeMainUpdater {
}
}
bool propagate_enum_definitions(bNodeTree &ntree)
{
ntree.ensure_interface_cache();
/* Propagation from right to left to determine which enum
* definition to use for menu sockets. */
for (bNode *node : ntree.toposort_right_to_left()) {
const bool node_updated = this->should_update_individual_node(ntree, *node);
if (node->typeinfo->type == GEO_NODE_MENU_SWITCH) {
/* Generate new enum items when the node has changed, otherwise keep existing items. */
if (node_updated) {
const NodeMenuSwitch &storage = *static_cast<NodeMenuSwitch *>(node->storage);
const RuntimeNodeEnumItems *enum_items = this->create_runtime_enum_items(
storage.enum_definition);
bNodeSocket &input = *node->input_sockets()[0];
BLI_assert(input.is_available() && input.type == SOCK_MENU);
this->set_enum_ptr(*input.default_value_typed<bNodeSocketValueMenu>(), enum_items);
}
continue;
}
else {
/* Clear current enum references. */
for (bNodeSocket *socket : node->input_sockets()) {
if (socket->is_available() && socket->type == SOCK_MENU) {
clear_enum_reference(*socket);
}
}
for (bNodeSocket *socket : node->output_sockets()) {
if (socket->is_available() && socket->type == SOCK_MENU) {
clear_enum_reference(*socket);
}
}
}
/* Propagate enum references from output links. */
for (bNodeSocket *output : node->output_sockets()) {
if (output->is_available() && output->type == SOCK_MENU) {
for (const bNodeSocket *input : output->directly_linked_sockets()) {
this->update_socket_enum_definition(
*output->default_value_typed<bNodeSocketValueMenu>(),
*input->default_value_typed<bNodeSocketValueMenu>());
}
}
}
if (node->is_group()) {
/* Node groups expose internal enum definitions. */
if (node->id == nullptr) {
continue;
}
const bNodeTree *group_tree = reinterpret_cast<bNodeTree *>(node->id);
group_tree->ensure_interface_cache();
for (const int socket_i : group_tree->interface_inputs().index_range()) {
bNodeSocket &input = *node->input_sockets()[socket_i];
const bNodeTreeInterfaceSocket &iosocket = *group_tree->interface_inputs()[socket_i];
BLI_assert(STREQ(input.identifier, iosocket.identifier));
if (input.is_available() && input.type == SOCK_MENU) {
BLI_assert(STREQ(iosocket.socket_type, "NodeSocketMenu"));
this->update_socket_enum_definition(
*input.default_value_typed<bNodeSocketValueMenu>(),
*static_cast<bNodeSocketValueMenu *>(iosocket.socket_data));
}
}
}
else if (node->type == GEO_NODE_MENU_SWITCH) {
/* First input is always the node's own menu, propagate only to the enum case inputs. */
const bNodeSocket *output = node->output_sockets().first();
for (bNodeSocket *input : node->input_sockets().drop_front(1)) {
if (input->is_available() && input->type == SOCK_MENU) {
this->update_socket_enum_definition(
*input->default_value_typed<bNodeSocketValueMenu>(),
*output->default_value_typed<bNodeSocketValueMenu>());
}
}
}
else {
/* Propagate over internal relations. */
/* XXX Placeholder implementation just propagates all outputs
* to all inputs for built-in nodes This could perhaps use
* input/output relations to handle propagation generically? */
for (bNodeSocket *input : node->input_sockets()) {
if (input->is_available() && input->type == SOCK_MENU) {
for (const bNodeSocket *output : node->output_sockets()) {
if (output->is_available() && output->type == SOCK_MENU) {
this->update_socket_enum_definition(
*input->default_value_typed<bNodeSocketValueMenu>(),
*output->default_value_typed<bNodeSocketValueMenu>());
}
}
}
}
}
}
/* Build list of new enum items for the node tree interface. */
Vector<bNodeSocketValueMenu> interface_enum_items(ntree.interface_inputs().size(), {0});
for (const bNode *group_input_node : ntree.group_input_nodes()) {
for (const int socket_i : ntree.interface_inputs().index_range()) {
const bNodeSocket &output = *group_input_node->output_sockets()[socket_i];
if (output.is_available() && output.type == SOCK_MENU) {
this->update_socket_enum_definition(interface_enum_items[socket_i],
*output.default_value_typed<bNodeSocketValueMenu>());
}
}
}
/* Move enum items to the interface and detect if anything changed. */
bool changed = false;
for (const int socket_i : ntree.interface_inputs().index_range()) {
bNodeTreeInterfaceSocket &iosocket = *ntree.interface_inputs()[socket_i];
if (STREQ(iosocket.socket_type, "NodeSocketMenu")) {
bNodeSocketValueMenu &dst = *static_cast<bNodeSocketValueMenu *>(iosocket.socket_data);
const bNodeSocketValueMenu &src = interface_enum_items[socket_i];
if (dst.enum_items != src.enum_items || dst.has_conflict() != src.has_conflict()) {
changed = true;
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
}
/* Items are moved, no need to change user count. */
dst.enum_items = src.enum_items;
SET_FLAG_FROM_TEST(dst.runtime_flag, src.has_conflict(), NODE_MENU_ITEMS_CONFLICT);
}
}
}
return changed;
}
/**
* Make a runtime copy of the DNA enum items.
* The runtime items list is shared by sockets.
*/
const RuntimeNodeEnumItems *create_runtime_enum_items(const NodeEnumDefinition &enum_def)
{
RuntimeNodeEnumItems *enum_items = new RuntimeNodeEnumItems();
enum_items->items.reinitialize(enum_def.items_num);
for (const int i : enum_def.items().index_range()) {
const NodeEnumItem &src = enum_def.items()[i];
RuntimeNodeEnumItem &dst = enum_items->items[i];
dst.identifier = src.identifier;
dst.name = src.name ? src.name : "";
dst.description = src.description ? src.description : "";
}
return enum_items;
}
void clear_enum_reference(bNodeSocket &socket)
{
BLI_assert(socket.is_available() && socket.type == SOCK_MENU);
bNodeSocketValueMenu &default_value = *socket.default_value_typed<bNodeSocketValueMenu>();
this->reset_enum_ptr(default_value);
default_value.runtime_flag &= ~NODE_MENU_ITEMS_CONFLICT;
}
void update_socket_enum_definition(bNodeSocketValueMenu &dst, const bNodeSocketValueMenu &src)
{
if (dst.has_conflict()) {
/* Target enum already has a conflict. */
BLI_assert(dst.enum_items == nullptr);
return;
}
if (src.has_conflict()) {
/* Target conflict if any source enum has a conflict. */
this->reset_enum_ptr(dst);
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
}
else if (!dst.enum_items) {
/* First connection, set the reference. */
this->set_enum_ptr(dst, src.enum_items);
}
else if (src.enum_items && dst.enum_items != src.enum_items) {
/* Error if enum ref does not match other connections. */
this->reset_enum_ptr(dst);
dst.runtime_flag |= NODE_MENU_ITEMS_CONFLICT;
}
}
void reset_enum_ptr(bNodeSocketValueMenu &dst)
{
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
dst.enum_items = nullptr;
}
}
void set_enum_ptr(bNodeSocketValueMenu &dst, const RuntimeNodeEnumItems *enum_items)
{
if (dst.enum_items) {
dst.enum_items->remove_user_and_delete_if_last();
dst.enum_items = nullptr;
}
if (enum_items) {
enum_items->add_user();
dst.enum_items = enum_items;
}
}
void update_link_validation(bNodeTree &ntree)
{
const Span<const bNode *> toposort = ntree.toposort_left_to_right();
@ -810,12 +1017,24 @@ class NodeTreeMainUpdater {
toposort_indices[node.index()] = i;
}
/* Tests if enum references are undefined. */
const auto is_invalid_enum_ref = [](const bNodeSocket &socket) -> bool {
if (socket.type == SOCK_MENU) {
return socket.default_value_typed<bNodeSocketValueMenu>()->enum_items == nullptr;
}
return false;
};
LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
link->flag |= NODE_LINK_VALID;
if (!link->fromsock->is_available() || !link->tosock->is_available()) {
link->flag &= ~NODE_LINK_VALID;
continue;
}
if (is_invalid_enum_ref(*link->fromsock) || is_invalid_enum_ref(*link->tosock)) {
link->flag &= ~NODE_LINK_VALID;
continue;
}
const bNode &from_node = *link->fromnode;
const bNode &to_node = *link->tonode;
if (toposort_indices[from_node.index()] > toposort_indices[to_node.index()]) {
@ -837,8 +1056,8 @@ class NodeTreeMainUpdater {
{
tree.ensure_topology_cache();
/* Compute a hash that represents the node topology connected to the output. This always has to
* be updated even if it is not used to detect changes right now. Otherwise
/* Compute a hash that represents the node topology connected to the output. This always has
* to be updated even if it is not used to detect changes right now. Otherwise
* #btree.runtime.output_topology_hash will go out of date. */
const Vector<const bNodeSocket *> tree_output_sockets = this->find_output_sockets(tree);
const uint32_t old_topology_hash = tree.runtime->output_topology_hash;
@ -852,8 +1071,8 @@ class NodeTreeMainUpdater {
* be used without causing updates all the time currently. In the future we could try to
* handle other drivers better as well.
* Note that this optimization only works in practice when the depsgraph didn't also get a
* copy-on-write tag for the node tree (which happens when changing node properties). It does
* work in a few situations like adding reroutes and duplicating nodes though. */
* copy-on-write tag for the node tree (which happens when changing node properties). It
* does work in a few situations like adding reroutes and duplicating nodes though. */
LISTBASE_FOREACH (const FCurve *, fcurve, &adt->drivers) {
const ChannelDriver *driver = fcurve->driver;
const StringRef expression = driver->expression;
@ -876,7 +1095,8 @@ class NodeTreeMainUpdater {
return true;
}
/* The topology hash can only be used when only topology-changing operations have been done. */
/* The topology hash can only be used when only topology-changing operations have been done.
*/
if (tree.runtime->changed_flag ==
(tree.runtime->changed_flag & (NTREE_CHANGED_LINK | NTREE_CHANGED_REMOVED_NODE)))
{
@ -929,15 +1149,15 @@ class NodeTreeMainUpdater {
}
/**
* Computes a hash that changes when the node tree topology connected to an output node changes.
* Adding reroutes does not have an effect on the hash.
* Computes a hash that changes when the node tree topology connected to an output node
* changes. Adding reroutes does not have an effect on the hash.
*/
uint32_t get_combined_socket_topology_hash(const bNodeTree &tree,
Span<const bNodeSocket *> sockets)
{
if (tree.has_available_link_cycle()) {
/* Return dummy value when the link has any cycles. The algorithm below could be improved to
* handle cycles more gracefully. */
/* Return dummy value when the link has any cycles. The algorithm below could be improved
* to handle cycles more gracefully. */
return 0;
}
Array<uint32_t> hashes = this->get_socket_topology_hashes(tree, sockets);
@ -1121,8 +1341,8 @@ class NodeTreeMainUpdater {
}
}
}
/* The Normal node has a special case, because the value stored in the first output socket
* is used as input in the node. */
/* The Normal node has a special case, because the value stored in the first output
* socket is used as input in the node. */
if (node.type == SH_NODE_NORMAL && socket.index() == 1) {
BLI_assert(STREQ(socket.name, "Dot"));
const bNodeSocket &normal_output = node.output_socket(0);
@ -1381,7 +1601,7 @@ void BKE_ntree_update_tag_image_user_changed(bNodeTree *ntree, ImageUser * /*ius
uint64_t bNestedNodePath::hash() const
{
return blender::get_default_hash_2(this->node_id, this->id_in_node);
return blender::get_default_hash(this->node_id, this->id_in_node);
}
bool operator==(const bNestedNodePath &a, const bNestedNodePath &b)

View File

@ -69,7 +69,7 @@ struct ZoneRelation {
uint64_t hash() const
{
return get_default_hash_2(this->parent, this->child);
return get_default_hash(this->parent, this->child);
}
BLI_STRUCT_EQUALITY_OPERATORS_2(ZoneRelation, parent, child)

View File

@ -70,7 +70,7 @@ TreeStoreElemKey::TreeStoreElemKey(ID *id, short type, short nr) : id(id), type(
uint64_t TreeStoreElemKey::hash() const
{
return get_default_hash_3(id, type, nr);
return get_default_hash(id, type, nr);
}
bool operator==(const TreeStoreElemKey &a, const TreeStoreElemKey &b)

View File

@ -1729,7 +1729,7 @@ void BKE_pbvh_node_mark_redraw(PBVHNode *node)
node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw;
}
void BKE_pbvh_node_mark_normals_update(PBVHNode *node)
void BKE_pbvh_node_mark_positions_update(PBVHNode *node)
{
node->flag |= PBVH_UpdateNormals | PBVH_UpdateDrawBuffers | PBVH_UpdateRedraw | PBVH_UpdateBB;
}

View File

@ -2237,22 +2237,22 @@ uint64_t BKE_tracking_camera_distortion_hash(const MovieTrackingCamera *camera)
using namespace blender;
switch (camera->distortion_model) {
case TRACKING_DISTORTION_MODEL_POLYNOMIAL:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float3(camera->k1, camera->k2, camera->k3));
return get_default_hash(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float3(camera->k1, camera->k2, camera->k3));
case TRACKING_DISTORTION_MODEL_DIVISION:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->division_k1, camera->division_k2));
return get_default_hash(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->division_k1, camera->division_k2));
case TRACKING_DISTORTION_MODEL_NUKE:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->nuke_k1, camera->nuke_k2));
return get_default_hash(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->nuke_k1, camera->nuke_k2));
case TRACKING_DISTORTION_MODEL_BROWN:
return get_default_hash_4(
return get_default_hash(
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float4(camera->brown_k1, camera->brown_k2, camera->brown_k3, camera->brown_k4),

View File

@ -220,7 +220,7 @@ template<typename T> uint64_t get_default_hash(const T &v)
return DefaultHash<std::decay_t<T>>{}(v);
}
template<typename T1, typename T2> uint64_t get_default_hash_2(const T1 &v1, const T2 &v2)
template<typename T1, typename T2> uint64_t get_default_hash(const T1 &v1, const T2 &v2)
{
const uint64_t h1 = get_default_hash(v1);
const uint64_t h2 = get_default_hash(v2);
@ -228,7 +228,7 @@ template<typename T1, typename T2> uint64_t get_default_hash_2(const T1 &v1, con
}
template<typename T1, typename T2, typename T3>
uint64_t get_default_hash_3(const T1 &v1, const T2 &v2, const T3 &v3)
uint64_t get_default_hash(const T1 &v1, const T2 &v2, const T3 &v3)
{
const uint64_t h1 = get_default_hash(v1);
const uint64_t h2 = get_default_hash(v2);
@ -237,7 +237,7 @@ uint64_t get_default_hash_3(const T1 &v1, const T2 &v2, const T3 &v3)
}
template<typename T1, typename T2, typename T3, typename T4>
uint64_t get_default_hash_4(const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4)
uint64_t get_default_hash(const T1 &v1, const T2 &v2, const T3 &v3, const T4 &v4)
{
const uint64_t h1 = get_default_hash(v1);
const uint64_t h2 = get_default_hash(v2);
@ -267,7 +267,7 @@ template<typename T> struct DefaultHash<std::reference_wrapper<T>> {
template<typename T1, typename T2> struct DefaultHash<std::pair<T1, T2>> {
uint64_t operator()(const std::pair<T1, T2> &value) const
{
return get_default_hash_2(value.first, value.second);
return get_default_hash(value.first, value.second);
}
};

View File

@ -203,6 +203,27 @@ void interpolate_bilinear_wrap_fl(const float *buffer,
void interpolate_cubic_bspline_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v);
/**
* Cubic Mitchell sampling.
*
* Takes 4x4 image samples at floor(u,v)-1 .. floor(u,v)+2, and blends them
* based on fractional parts of u,v. Uses Mitchell-Netravali filter (B=C=1/3),
* which has a good compromise between blur and ringing.
* Samples outside the image are clamped to texels at image edge.
*
* Note that you probably want to subtract 0.5 from u,v before this function,
* to get proper filtering.
*/
[[nodiscard]] uchar4 interpolate_cubic_mitchell_byte(
const uchar *buffer, int width, int height, float u, float v);
[[nodiscard]] float4 interpolate_cubic_mitchell_fl(
const float *buffer, int width, int height, float u, float v);
void interpolate_cubic_mitchell_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v);
} // namespace blender::math
#define EWA_MAXIDX 255

View File

@ -10,24 +10,41 @@
#include <cstring>
#include "BLI_math_base.h"
#include "BLI_math_base.hh"
#include "BLI_math_interp.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_simd.h"
#include "BLI_strict_flags.h"
/* Cubic B-Spline coefficients. f is offset from texel center in pixel space.
* This is Mitchell-Netravali filter with B=1, C=0 parameters. */
static blender::float4 cubic_bspline_coefficients(float f)
enum class eCubicFilter {
BSpline,
Mitchell,
};
/* Calculate cubic filter coefficients, for samples at -1,0,+1,+2.
* f is 0..1 offset from texel center in pixel space. */
template<enum eCubicFilter filter> static blender::float4 cubic_filter_coefficients(float f)
{
float f2 = f * f;
float f3 = f2 * f;
float w3 = f3 / 6.0f;
float w0 = -w3 + f2 * 0.5f - f * 0.5f + 1.0f / 6.0f;
float w1 = f3 * 0.5f - f2 * 1.0f + 2.0f / 3.0f;
float w2 = 1.0f - w0 - w1 - w3;
return blender::float4(w0, w1, w2, w3);
if constexpr (filter == eCubicFilter::BSpline) {
/* Cubic B-Spline (Mitchell-Netravali filter with B=1, C=0 parameters). */
float w3 = f3 * (1.0f / 6.0f);
float w0 = -w3 + f2 * 0.5f - f * 0.5f + 1.0f / 6.0f;
float w1 = f3 * 0.5f - f2 * 1.0f + 2.0f / 3.0f;
float w2 = 1.0f - w0 - w1 - w3;
return blender::float4(w0, w1, w2, w3);
}
else if constexpr (filter == eCubicFilter::Mitchell) {
/* Cubic Mitchell-Netravali filter with B=1/3, C=1/3 parameters. */
float w0 = -7.0f / 18.0f * f3 + 5.0f / 6.0f * f2 - 0.5f * f + 1.0f / 18.0f;
float w1 = 7.0f / 6.0f * f3 - 2.0f * f2 + 8.0f / 9.0f;
float w2 = -7.0f / 6.0f * f3 + 3.0f / 2.0f * f2 + 0.5f * f + 1.0f / 18.0f;
float w3 = 7.0f / 18.0f * f3 - 1.0f / 3.0f * f2;
return blender::float4(w0, w1, w2, w3);
}
}
#if BLI_HAVE_SSE2
@ -51,6 +68,7 @@ BLI_INLINE __m128 floor_simd(__m128 v)
return v_floor;
}
template<eCubicFilter filter>
BLI_INLINE void bicubic_interpolation_uchar_simd(
const uchar *src_buffer, uchar *output, int width, int height, float u, float v)
{
@ -72,8 +90,8 @@ BLI_INLINE void bicubic_interpolation_uchar_simd(
__m128 frac_uv = _mm_sub_ps(uv, uv_floor);
/* Calculate pixel weights. */
blender::float4 wx = cubic_bspline_coefficients(_mm_cvtss_f32(frac_uv));
blender::float4 wy = cubic_bspline_coefficients(
blender::float4 wx = cubic_filter_coefficients<filter>(_mm_cvtss_f32(frac_uv));
blender::float4 wy = cubic_filter_coefficients<filter>(
_mm_cvtss_f32(_mm_shuffle_ps(frac_uv, frac_uv, 1)));
/* Read 4x4 source pixels and blend them. */
@ -102,7 +120,8 @@ BLI_INLINE void bicubic_interpolation_uchar_simd(
}
/* Pack and write to destination: pack to 16 bit signed, then to 8 bit
* unsigned, then write resulting 32-bit value. */
* unsigned, then write resulting 32-bit value. This will clamp
* out of range values too. */
out = _mm_add_ps(out, _mm_set1_ps(0.5f));
__m128i rgba32 = _mm_cvttps_epi32(out);
__m128i rgba16 = _mm_packs_epi32(rgba32, _mm_setzero_si128());
@ -111,7 +130,7 @@ BLI_INLINE void bicubic_interpolation_uchar_simd(
}
#endif /* BLI_HAVE_SSE2 */
template<typename T>
template<typename T, eCubicFilter filter>
static void bicubic_interpolation(
const T *src_buffer, T *output, int width, int height, int components, float u, float v)
{
@ -122,7 +141,7 @@ static void bicubic_interpolation(
#if BLI_HAVE_SSE2
if constexpr (std::is_same_v<T, uchar>) {
if (components == 4) {
bicubic_interpolation_uchar_simd(src_buffer, output, width, height, u, v);
bicubic_interpolation_uchar_simd<filter>(src_buffer, output, width, height, u, v);
return;
}
}
@ -143,8 +162,8 @@ static void bicubic_interpolation(
float4 out{0.0f};
/* Calculate pixel weights. */
float4 wx = cubic_bspline_coefficients(frac_u);
float4 wy = cubic_bspline_coefficients(frac_v);
float4 wx = cubic_filter_coefficients<filter>(frac_u);
float4 wy = cubic_filter_coefficients<filter>(frac_v);
/* Read 4x4 source pixels and blend them. */
for (int n = 0; n < 4; n++) {
@ -175,6 +194,16 @@ static void bicubic_interpolation(
}
}
/* Mitchell filter has negative lobes; prevent output from going out of range. */
if constexpr (filter == eCubicFilter::Mitchell) {
for (int i = 0; i < components; i++) {
out[i] = math::max(out[i], 0.0f);
if constexpr (std::is_same_v<T, uchar>) {
out[i] = math::min(out[i], 255.0f);
}
}
}
/* Write result. */
if constexpr (std::is_same_v<T, float>) {
if (components == 1) {
@ -551,21 +580,44 @@ float4 interpolate_bilinear_wrap_fl(const float *buffer, int width, int height,
uchar4 interpolate_cubic_bspline_byte(const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
bicubic_interpolation<uchar>(buffer, res, width, height, 4, u, v);
bicubic_interpolation<uchar, eCubicFilter::BSpline>(buffer, res, width, height, 4, u, v);
return res;
}
float4 interpolate_cubic_bspline_fl(const float *buffer, int width, int height, float u, float v)
{
float4 res;
bicubic_interpolation<float>(buffer, res, width, height, 4, u, v);
bicubic_interpolation<float, eCubicFilter::BSpline>(buffer, res, width, height, 4, u, v);
return res;
}
void interpolate_cubic_bspline_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
bicubic_interpolation<float>(buffer, output, width, height, components, u, v);
bicubic_interpolation<float, eCubicFilter::BSpline>(
buffer, output, width, height, components, u, v);
}
uchar4 interpolate_cubic_mitchell_byte(
const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
bicubic_interpolation<uchar, eCubicFilter::Mitchell>(buffer, res, width, height, 4, u, v);
return res;
}
float4 interpolate_cubic_mitchell_fl(const float *buffer, int width, int height, float u, float v)
{
float4 res;
bicubic_interpolation<float, eCubicFilter::Mitchell>(buffer, res, width, height, 4, u, v);
return res;
}
void interpolate_cubic_mitchell_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
bicubic_interpolation<float, eCubicFilter::Mitchell>(
buffer, output, width, height, components, u, v);
}
} // namespace blender::math

View File

@ -88,7 +88,7 @@ class Edge {
uint64_t hash() const
{
return get_default_hash_2(v_[0]->id, v_[1]->id);
return get_default_hash(v_[0]->id, v_[1]->id);
}
};

View File

@ -256,3 +256,65 @@ TEST(math_interp, CubicBSplineCharFullyOutsideImage)
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 500.0f);
EXPECT_EQ(exp, res);
}
TEST(math_interp, CubicMitchellCharExactSamples)
{
uchar4 res;
uchar4 exp1 = {72, 101, 140, 223};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 1.0f, 2.0f);
EXPECT_EQ(int4(exp1), int4(res));
uchar4 exp2 = {233, 162, 99, 37};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f);
EXPECT_EQ(int4(exp2), int4(res));
}
TEST(math_interp, CubicMitchellCharSamples)
{
uchar4 res;
uchar4 exp1 = {135, 132, 130, 127};
res = interpolate_cubic_mitchell_byte(
image_char[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_EQ(int4(exp1), int4(res));
uchar4 exp2 = {216, 189, 167, 143};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_EQ(int4(exp2), int4(res));
}
TEST(math_interp, CubicMitchellFloatSamples)
{
float4 res;
float4 exp1 = {134.5659f, 131.91309f, 130.17685f, 126.66989f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {216.27115f, 189.30673f, 166.93599f, 143.31964f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
}
TEST(math_interp, CubicMitchellCharPartiallyOutsideImage)
{
uchar4 res;
uchar4 exp1 = {0, 0, 0, 0};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_EQ(int4(exp1), int4(res));
uchar4 exp2 = {88, 116, 151, 228};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_EQ(int4(exp2), int4(res));
uchar4 exp3 = {239, 159, 89, 19};
res = interpolate_cubic_mitchell_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_EQ(int4(exp3), int4(res));
}
TEST(math_interp, CubicMitchellFloatPartiallyOutsideImage)
{
float4 res;
float4 exp1 = {0, 0, 0, 0};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {87.98676f, 115.63634f, 151.13014f, 228.19823f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
float4 exp3 = {238.6136f, 158.58293f, 88.55761f, 18.53225f};
res = interpolate_cubic_mitchell_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_V4_NEAR(exp3, res, float_tolerance);
}

View File

@ -4888,7 +4888,7 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain)
}
LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_DRAW_RELATION_FROM_HEAD | ARM_FLAG_UNUSED_6 |
arm->flag &= ~(ARM_FLAG_UNUSED_1 | ARM_DRAW_RELATION_FROM_HEAD | ARM_BCOLL_SOLO_ACTIVE |
ARM_FLAG_UNUSED_7 | ARM_FLAG_UNUSED_12);
}
@ -5367,7 +5367,7 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain)
}
LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
arm->flag &= ~(ARM_FLAG_UNUSED_6);
arm->flag &= ~(ARM_BCOLL_SOLO_ACTIVE);
}
}

View File

@ -42,7 +42,7 @@ template<> struct blender::DefaultHash<blender::Set<const BMVert *>> {
{
uint64_t hash = 0;
for (const BMVert *vert : value) {
hash = get_default_hash_2(hash, vert);
hash = get_default_hash(hash, vert);
}
return hash;
}

View File

@ -41,7 +41,7 @@ const float *NodeOperation::get_constant_elem_default(const float *default_elem)
std::optional<NodeOperationHash> NodeOperation::generate_hash()
{
params_hash_ = get_default_hash_2(canvas_.xmin, canvas_.xmax);
params_hash_ = get_default_hash(canvas_.xmin, canvas_.xmax);
/* Hash subclasses params. */
is_hash_output_params_implemented_ = true;

View File

@ -672,12 +672,12 @@ class NodeOperation {
template<typename T1, typename T2> void hash_params(T1 param1, T2 param2)
{
combine_hashes(params_hash_, get_default_hash_2(param1, param2));
combine_hashes(params_hash_, get_default_hash(param1, param2));
}
template<typename T1, typename T2, typename T3> void hash_params(T1 param1, T2 param2, T3 param3)
{
combine_hashes(params_hash_, get_default_hash_3(param1, param2, param3));
combine_hashes(params_hash_, get_default_hash(param1, param2, param3));
}
void add_input_socket(DataType datatype, ResizeMode resize_mode = ResizeMode::Center);

View File

@ -36,7 +36,7 @@ BokehKernelKey::BokehKernelKey(
uint64_t BokehKernelKey::hash() const
{
return get_default_hash_3(
return get_default_hash(
size, size, get_default_hash(float4(rotation, roundness, catadioptric, lens_shift)));
}

View File

@ -42,7 +42,7 @@ CachedImageKey::CachedImageKey(ImageUser image_user, std::string pass_name)
uint64_t CachedImageKey::hash() const
{
return get_default_hash_4(image_user.framenr, image_user.layer, image_user.view, pass_name);
return get_default_hash(image_user.framenr, image_user.layer, image_user.view, pass_name);
}
bool operator==(const CachedImageKey &a, const CachedImageKey &b)

View File

@ -45,7 +45,7 @@ CachedMaskKey::CachedMaskKey(int2 size,
uint64_t CachedMaskKey::hash() const
{
return get_default_hash_4(
return get_default_hash(
size, use_feather, motion_blur_samples, float2(motion_blur_shutter, aspect_ratio));
}

View File

@ -29,7 +29,7 @@ CachedShaderKey::CachedShaderKey(const char *info_name, ResultPrecision precisio
uint64_t CachedShaderKey::hash() const
{
return get_default_hash_2(info_name, precision);
return get_default_hash(info_name, precision);
}
bool operator==(const CachedShaderKey &a, const CachedShaderKey &b)

View File

@ -40,7 +40,7 @@ CachedTextureKey::CachedTextureKey(int2 size, float3 offset, float3 scale)
uint64_t CachedTextureKey::hash() const
{
return get_default_hash_3(size, offset, scale);
return get_default_hash(size, offset, scale);
}
bool operator==(const CachedTextureKey &a, const CachedTextureKey &b)

View File

@ -40,7 +40,7 @@ DistortionGridKey::DistortionGridKey(MovieTrackingCamera camera,
uint64_t DistortionGridKey::hash() const
{
return get_default_hash_4(
return get_default_hash(
BKE_tracking_camera_distortion_hash(&camera), size, type, calibration_size);
}

View File

@ -44,7 +44,7 @@ KeyingScreenKey::KeyingScreenKey(int frame, float smoothness)
uint64_t KeyingScreenKey::hash() const
{
return get_default_hash_2(frame, smoothness);
return get_default_hash(frame, smoothness);
}
bool operator==(const KeyingScreenKey &a, const KeyingScreenKey &b)

View File

@ -35,7 +35,7 @@ MorphologicalDistanceFeatherWeightsKey::MorphologicalDistanceFeatherWeightsKey(i
uint64_t MorphologicalDistanceFeatherWeightsKey::hash() const
{
return get_default_hash_2(type, radius);
return get_default_hash(type, radius);
}
bool operator==(const MorphologicalDistanceFeatherWeightsKey &a,

View File

@ -43,7 +43,7 @@ OCIOColorSpaceConversionShaderKey::OCIOColorSpaceConversionShaderKey(std::string
uint64_t OCIOColorSpaceConversionShaderKey::hash() const
{
return get_default_hash_3(source, target, config_cache_id);
return get_default_hash(source, target, config_cache_id);
}
bool operator==(const OCIOColorSpaceConversionShaderKey &a,

View File

@ -33,7 +33,7 @@ SymmetricBlurWeightsKey::SymmetricBlurWeightsKey(int type, float2 radius)
uint64_t SymmetricBlurWeightsKey::hash() const
{
return get_default_hash_3(type, radius.x, radius.y);
return get_default_hash(type, radius.x, radius.y);
}
bool operator==(const SymmetricBlurWeightsKey &a, const SymmetricBlurWeightsKey &b)

View File

@ -32,7 +32,7 @@ SymmetricSeparableBlurWeightsKey::SymmetricSeparableBlurWeightsKey(int type, flo
uint64_t SymmetricSeparableBlurWeightsKey::hash() const
{
return get_default_hash_2(type, radius);
return get_default_hash(type, radius);
}
bool operator==(const SymmetricSeparableBlurWeightsKey &a,

View File

@ -29,7 +29,7 @@ TexturePoolKey::TexturePoolKey(const GPUTexture *texture)
uint64_t TexturePoolKey::hash() const
{
return get_default_hash_3(size.x, size.y, format);
return get_default_hash(size.x, size.y, format);
}
bool operator==(const TexturePoolKey &a, const TexturePoolKey &b)

View File

@ -139,8 +139,8 @@ bool LightSet::operator==(const LightSet &other) const
uint64_t LightSet::hash() const
{
return get_default_hash_2(get_default_hash(include_collection_mask),
get_default_hash(exclude_collection_mask));
return get_default_hash(get_default_hash(include_collection_mask),
get_default_hash(exclude_collection_mask));
}
/* EmitterSetMembership */

View File

@ -19,7 +19,9 @@ void HiZBuffer::sync()
{
int2 render_extent = inst_.film.render_extent_get();
/* Padding to avoid complexity during down-sampling and screen tracing. */
int2 hiz_extent = math::ceil_to_multiple(render_extent, int2(1u << (HIZ_MIP_COUNT - 1)));
int2 probe_extent = int2(inst_.reflection_probes.probe_render_extent());
int2 hiz_extent = math::ceil_to_multiple(math::max(render_extent, probe_extent),
int2(1u << (HIZ_MIP_COUNT - 1)));
int2 dispatch_size = math::divide_ceil(hiz_extent, int2(HIZ_GROUP_SIZE));
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE;

View File

@ -140,6 +140,11 @@ eLightProbeResolution ReflectionProbeModule::reflection_probe_resolution() const
return LIGHT_PROBE_RESOLUTION_2048;
}
int ReflectionProbeModule::probe_render_extent() const
{
return instance_.scene->eevee.gi_cubemap_resolution / 2;
}
void ReflectionProbeModule::init()
{
if (!is_initialized) {

View File

@ -243,6 +243,13 @@ class ReflectionProbeModule {
return probes_tx_.width();
}
/**
* Get the resolution of a single cubemap side when rendering probes.
*
* The cubemaps are rendered half size of the size of the octahedral texture.
*/
int probe_render_extent() const;
ReflectionProbeAtlasCoordinate world_atlas_coord_get() const;
private:

View File

@ -16,6 +16,7 @@
#include "BLT_translation.h"
#include "DNA_material_types.h"
#include "DNA_scene_types.h"
#include "BKE_attribute.hh"
#include "BKE_context.hh"
@ -33,7 +34,6 @@
#include "ED_curves.hh"
#include "ED_grease_pencil.hh"
#include "ED_screen.hh"
#include "GEO_smooth_curves.hh"
#include "GEO_subdivide_curves.hh"
@ -44,71 +44,6 @@
namespace blender::ed::greasepencil {
bool active_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
return true;
}
bool editable_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
if (!ED_operator_object_active_editable_ex(C, object)) {
return false;
}
if ((object->mode & OB_MODE_EDIT) == 0) {
return false;
}
return true;
}
bool editable_grease_pencil_point_selection_poll(bContext *C)
{
if (!editable_grease_pencil_poll(C)) {
return false;
}
/* Allowed: point and segment selection mode, not allowed: stroke selection mode. */
ToolSettings *ts = CTX_data_tool_settings(C);
return (ts->gpencil_selectmode_edit != GP_SELECTMODE_STROKE);
}
bool grease_pencil_painting_poll(bContext *C)
{
if (!active_grease_pencil_poll(C)) {
return false;
}
Object *object = CTX_data_active_object(C);
if ((object->mode & OB_MODE_PAINT_GREASE_PENCIL) == 0) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
if (!ts || !ts->gp_paint) {
return false;
}
return true;
}
static void keymap_grease_pencil_editing(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Edit Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = editable_grease_pencil_poll;
}
static void keymap_grease_pencil_painting(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Paint Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = grease_pencil_painting_poll;
}
/* -------------------------------------------------------------------- */
/** \name Smooth Stroke Operator
* \{ */
@ -1602,18 +1537,6 @@ static void GREASE_PENCIL_OT_stroke_subdivide(wmOperatorType *ot)
/** \} */
static void grease_pencil_operatormarcos_define()
{
wmOperatorType *ot;
ot = WM_operatortype_append_macro("GREASE_PENCIL_OT_stroke_subdivide_smooth",
"Subdivide and Smooth",
"Subdivide strokes and smooth them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_subdivide");
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_smooth");
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_edit()
@ -1635,13 +1558,4 @@ void ED_operatortypes_grease_pencil_edit()
WM_operatortype_append(GREASE_PENCIL_OT_set_material);
WM_operatortype_append(GREASE_PENCIL_OT_clean_loose);
WM_operatortype_append(GREASE_PENCIL_OT_stroke_subdivide);
grease_pencil_operatormarcos_define();
}
void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
{
using namespace blender::ed::greasepencil;
keymap_grease_pencil_editing(keyconf);
keymap_grease_pencil_painting(keyconf);
}

View File

@ -6,13 +6,87 @@
* \ingroup edgreasepencil
*/
#include "BKE_context.hh"
#include "DNA_scene_types.h"
#include "ED_grease_pencil.hh"
#include "ED_screen.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
namespace blender::ed::greasepencil {
bool active_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
return true;
}
bool editable_grease_pencil_poll(bContext *C)
{
Object *object = CTX_data_active_object(C);
if (object == nullptr || object->type != OB_GREASE_PENCIL) {
return false;
}
if (!ED_operator_object_active_editable_ex(C, object)) {
return false;
}
if ((object->mode & OB_MODE_EDIT) == 0) {
return false;
}
return true;
}
bool editable_grease_pencil_point_selection_poll(bContext *C)
{
if (!editable_grease_pencil_poll(C)) {
return false;
}
/* Allowed: point and segment selection mode, not allowed: stroke selection mode. */
ToolSettings *ts = CTX_data_tool_settings(C);
return (ts->gpencil_selectmode_edit != GP_SELECTMODE_STROKE);
}
bool grease_pencil_painting_poll(bContext *C)
{
if (!active_grease_pencil_poll(C)) {
return false;
}
Object *object = CTX_data_active_object(C);
if ((object->mode & OB_MODE_PAINT_GREASE_PENCIL) == 0) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
if (!ts || !ts->gp_paint) {
return false;
}
return true;
}
static void keymap_grease_pencil_edit_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Edit Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = editable_grease_pencil_poll;
}
static void keymap_grease_pencil_paint_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Paint Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = grease_pencil_painting_poll;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil()
{
ED_operatortypes_grease_pencil_draw();
@ -38,4 +112,19 @@ void ED_operatormacros_grease_pencil()
otmacro = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate");
RNA_boolean_set(otmacro->ptr, "use_proportional_edit", false);
RNA_boolean_set(otmacro->ptr, "mirror", false);
/* Subdivide and Smooth. */
ot = WM_operatortype_append_macro("GREASE_PENCIL_OT_stroke_subdivide_smooth",
"Subdivide and Smooth",
"Subdivide strokes and smooth them",
OPTYPE_UNDO | OPTYPE_REGISTER);
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_subdivide");
WM_operatortype_macro_define(ot, "GREASE_PENCIL_OT_stroke_smooth");
}
void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
{
using namespace blender::ed::greasepencil;
keymap_grease_pencil_edit_mode(keyconf);
keymap_grease_pencil_paint_mode(keyconf);
}

View File

@ -209,7 +209,7 @@ static int select_random_exec(bContext *C, wmOperator *op)
curves,
selectable_elements,
selection_domain,
blender::get_default_hash_2<int>(seed, info.layer_index),
blender::get_default_hash<int>(seed, info.layer_index),
ratio,
memory);

View File

@ -2280,6 +2280,11 @@ Panel *uiLayoutGetRootPanel(uiLayout *layout);
uiLayout *uiLayoutRow(uiLayout *layout, bool align);
struct PanelLayout {
uiLayout *header;
uiLayout *body;
};
/**
* Create a "layout panel" which is a panel that is defined as part of the `uiLayout`. This allows
* creating expandable sections which can also be nested.
@ -2291,10 +2296,23 @@ uiLayout *uiLayoutRow(uiLayout *layout, bool align);
* context even of the open-property is `false`. This can happen with e.g. property search.
* \param layout: The `uiLayout` that should contain the sub-panel.
* Only layouts that span the full width of the region are supported for now.
* \param name: Text that's shown in the panel header. It should already be translated.
* \param open_prop_owner: Data that contains the open-property.
* \param open_prop_name: Name of the open-property in `open_prop_owner`.
*
* \return A #PanelLayout containing layouts for both the header row and the panel body. If the
* panel is closed and should not be drawn, the body layout will be NULL.
*/
PanelLayout uiLayoutPanelWithHeader(const bContext *C,
uiLayout *layout,
PointerRNA *open_prop_owner,
const char *open_prop_name);
/**
* Variant of #uiLayoutPanelWithHeader() that automatically creates the header row with the
* given label name and only returns the body layout.
*
* \param name: Text that's shown in the panel header. It should already be translated.
*
* \return NULL if the panel is closed and should not be drawn, otherwise the layout where the
* sub-panel should be inserted into.
*/

View File

@ -6396,41 +6396,43 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
if ((event->modifier & KM_CTRL) == 0) {
float color[3];
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
if (paint != nullptr) {
Brush *brush = BKE_paint_brush(paint);
if (brush->flag & BRUSH_USE_GRADIENT) {
float *target = &brush->gradient->data[brush->gradient->cur].r;
if (brush->flag & BRUSH_USE_GRADIENT) {
float *target = &brush->gradient->data[brush->gradient->cur].r;
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
IMB_colormanagement_srgb_to_scene_linear_v3(target, target);
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
IMB_colormanagement_srgb_to_scene_linear_v3(target, target);
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
}
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
}
}
else {
Scene *scene = CTX_data_scene(C);
bool updated = false;
else {
Scene *scene = CTX_data_scene(C);
bool updated = false;
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
IMB_colormanagement_scene_linear_to_srgb_v3(color, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
IMB_colormanagement_scene_linear_to_srgb_v3(color, color);
BKE_brush_color_set(scene, brush, color);
updated = true;
}
if (updated) {
PropertyRNA *brush_color_prop;
if (updated) {
PropertyRNA *brush_color_prop;
PointerRNA brush_ptr = RNA_id_pointer_create(&brush->id);
brush_color_prop = RNA_struct_find_property(&brush_ptr, "color");
RNA_property_update(C, &brush_ptr, brush_color_prop);
PointerRNA brush_ptr = RNA_id_pointer_create(&brush->id);
brush_color_prop = RNA_struct_find_property(&brush_ptr, "color");
RNA_property_update(C, &brush_ptr, brush_color_prop);
}
}
}

View File

@ -4974,11 +4974,10 @@ uiLayout *uiLayoutRow(uiLayout *layout, bool align)
return litem;
}
uiLayout *uiLayoutPanel(const bContext *C,
uiLayout *layout,
const char *name,
PointerRNA *open_prop_owner,
const char *open_prop_name)
PanelLayout uiLayoutPanelWithHeader(const bContext *C,
uiLayout *layout,
PointerRNA *open_prop_owner,
const char *open_prop_name)
{
const ARegion *region = CTX_wm_region(C);
@ -4986,6 +4985,7 @@ uiLayout *uiLayoutPanel(const bContext *C,
const bool search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE;
const bool is_open = is_real_open || search_filter_active;
PanelLayout panel_layout{};
{
uiLayoutItemPanelHeader *header_litem = MEM_cnew<uiLayoutItemPanelHeader>(__func__);
uiLayout *litem = &header_litem->litem;
@ -4995,30 +4995,33 @@ uiLayout *uiLayoutPanel(const bContext *C,
header_litem->open_prop_owner = *open_prop_owner;
STRNCPY(header_litem->open_prop_name, open_prop_name);
UI_block_layout_set_current(layout->root->block, litem);
uiLayout *row = uiLayoutRow(litem, true);
uiLayoutSetUnitsY(row, 1.2f);
uiBlock *block = uiLayoutGetBlock(layout);
uiBlock *block = uiLayoutGetBlock(row);
const int icon = is_open ? ICON_DOWNARROW_HLT : ICON_RIGHTARROW;
const int width = ui_text_icon_width(layout, name, icon, false);
const int width = ui_text_icon_width(layout, "", icon, false);
uiDefIconTextBut(block,
UI_BTYPE_LABEL,
0,
icon,
name,
"",
0,
0,
width,
UI_UNIT_Y * 1.2f,
UI_UNIT_Y,
nullptr,
0.0,
0.0,
0.0,
0.0,
0.0f,
0.0f,
0.0f,
0.0f,
"");
panel_layout.header = row;
}
if (!is_open) {
return nullptr;
return panel_layout;
}
uiLayoutItemPanelBody *body_litem = MEM_cnew<uiLayoutItemPanelBody>(__func__);
@ -5027,7 +5030,21 @@ uiLayout *uiLayoutPanel(const bContext *C,
litem->space = layout->root->style->templatespace;
ui_litem_init_from_parent(litem, layout, false);
UI_block_layout_set_current(layout->root->block, litem);
return litem;
panel_layout.body = litem;
return panel_layout;
}
uiLayout *uiLayoutPanel(const bContext *C,
uiLayout *layout,
const char *name,
PointerRNA *open_prop_owner,
const char *open_prop_name)
{
PanelLayout panel = uiLayoutPanelWithHeader(C, layout, open_prop_owner, open_prop_name);
uiItemL(panel.header, name, ICON_NONE);
return panel.body;
}
bool uiLayoutEndsWithPanelHeader(const uiLayout &layout)

View File

@ -236,13 +236,22 @@ class BoneCollectionItem : public AbstractTreeViewItem {
/* Visibility eye icon. */
{
const bool is_solo_active = armature_.flag & ARM_BCOLL_SOLO_ACTIVE;
uiLayout *visibility_sub = uiLayoutRow(sub, true);
uiLayoutSetActive(visibility_sub, bone_collection_.is_visible_ancestors());
uiLayoutSetActive(visibility_sub,
!is_solo_active && bone_collection_.is_visible_ancestors());
const int icon = bone_collection_.is_visible() ? ICON_HIDE_OFF : ICON_HIDE_ON;
PointerRNA bcoll_ptr = rna_pointer();
uiItemR(visibility_sub, &bcoll_ptr, "is_visible", UI_ITEM_R_ICON_ONLY, "", icon);
}
/* Solo icon. */
{
const int icon = bone_collection_.is_solo() ? ICON_SOLO_ON : ICON_SOLO_OFF;
PointerRNA bcoll_ptr = rna_pointer();
uiItemR(sub, &bcoll_ptr, "is_solo", UI_ITEM_R_ICON_ONLY, "", icon);
}
}
void build_context_menu(bContext &C, uiLayout &column) const override

View File

@ -579,7 +579,7 @@ static int mesh_customdata_clear_exec__internal(bContext *C,
CustomData_free_layers(data, type, tot);
}
DEG_id_tag_update(&mesh->id, 0);
DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh);
return OPERATOR_FINISHED;
@ -761,14 +761,28 @@ static int mesh_customdata_custom_splitnormals_clear_exec(bContext *C, wmOperato
{
Mesh *mesh = ED_mesh_context(C);
if (BKE_mesh_has_custom_loop_normals(mesh)) {
BMEditMesh *em = mesh->edit_mesh;
if (em != nullptr && em->bm->lnor_spacearr != nullptr) {
BKE_lnor_spacearr_clear(em->bm->lnor_spacearr);
if (BMEditMesh *em = mesh->edit_mesh) {
BMesh &bm = *em->bm;
if (!CustomData_has_layer(&bm.ldata, CD_CUSTOMLOOPNORMAL)) {
return OPERATOR_CANCELLED;
}
BM_data_layer_free(&bm, &bm.ldata, CD_CUSTOMLOOPNORMAL);
if (bm.lnor_spacearr) {
BKE_lnor_spacearr_clear(bm.lnor_spacearr);
}
return mesh_customdata_clear_exec__internal(C, BM_LOOP, CD_CUSTOMLOOPNORMAL);
}
return OPERATOR_CANCELLED;
else {
if (!CustomData_has_layer(&mesh->corner_data, CD_CUSTOMLOOPNORMAL)) {
return OPERATOR_CANCELLED;
}
CustomData_free_layers(&mesh->corner_data, CD_CUSTOMLOOPNORMAL, mesh->corners_num);
}
mesh->tag_custom_normals_changed();
DEG_id_tag_update(&mesh->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh);
return OPERATOR_FINISHED;
}
void MESH_OT_customdata_custom_splitnormals_clear(wmOperatorType *ot)

View File

@ -120,7 +120,18 @@ static void stroke_done(const bContext *C, PaintStroke *stroke)
operation->~GreasePencilStrokeOperation();
}
static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static bool grease_pencil_brush_stroke_poll(bContext *C)
{
if (!ed::greasepencil::grease_pencil_painting_poll(C)) {
return false;
}
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
return true;
}
static int grease_pencil_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const Scene *scene = CTX_data_scene(C);
const Object *object = CTX_data_active_object(C);
@ -192,12 +203,12 @@ static int grease_pencil_stroke_invoke(bContext *C, wmOperator *op, const wmEven
return OPERATOR_RUNNING_MODAL;
}
static int grease_pencil_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
static int grease_pencil_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
return paint_stroke_modal(C, op, event, reinterpret_cast<PaintStroke **>(&op->customdata));
}
static void grease_pencil_stroke_cancel(bContext *C, wmOperator *op)
static void grease_pencil_brush_stroke_cancel(bContext *C, wmOperator *op)
{
paint_stroke_cancel(C, op, static_cast<PaintStroke *>(op->customdata));
}
@ -208,9 +219,10 @@ static void GREASE_PENCIL_OT_brush_stroke(wmOperatorType *ot)
ot->idname = "GREASE_PENCIL_OT_brush_stroke";
ot->description = "Draw a new stroke in the active Grease Pencil object";
ot->invoke = grease_pencil_stroke_invoke;
ot->modal = grease_pencil_stroke_modal;
ot->cancel = grease_pencil_stroke_cancel;
ot->poll = grease_pencil_brush_stroke_poll;
ot->invoke = grease_pencil_brush_stroke_invoke;
ot->modal = grease_pencil_brush_stroke_modal;
ot->cancel = grease_pencil_brush_stroke_cancel;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -223,9 +235,9 @@ static void GREASE_PENCIL_OT_brush_stroke(wmOperatorType *ot)
/** \name Toggle Draw Mode
* \{ */
static bool grease_pencil_mode_poll_view3d(bContext *C)
static bool grease_pencil_mode_poll_paint_cursor(bContext *C)
{
if (!ed::greasepencil::grease_pencil_painting_poll(C)) {
if (!grease_pencil_brush_stroke_poll(C)) {
return false;
}
if (CTX_wm_region_view3d(C) == nullptr) {
@ -246,7 +258,7 @@ static void grease_pencil_draw_mode_enter(bContext *C)
ob->mode = OB_MODE_PAINT_GREASE_PENCIL;
/* TODO: Setup cursor color. BKE_paint_init() could be used, but creates an additional brush. */
ED_paint_cursor_start(&grease_pencil_paint->paint, grease_pencil_mode_poll_view3d);
ED_paint_cursor_start(&grease_pencil_paint->paint, grease_pencil_mode_poll_paint_cursor);
paint_init_pivot(ob, scene);
/* Necessary to change the object mode on the evaluated object. */

View File

@ -1225,7 +1225,7 @@ static void mask_gesture_apply_task(SculptGestureContext *sgcontext,
undo::push_node(ob, node, undo::Type::Mask);
if (is_multires) {
BKE_pbvh_node_mark_normals_update(node);
BKE_pbvh_node_mark_positions_update(node);
}
}
const float new_mask = mask_flood_fill_get_new_value_for_elem(

View File

@ -121,7 +121,7 @@ void cache_init(bContext *C,
pbvh, [&](PBVHNode &node) { return !node_fully_masked_or_hidden(node); });
for (PBVHNode *node : ss->filter_cache->nodes) {
BKE_pbvh_node_mark_normals_update(node);
BKE_pbvh_node_mark_positions_update(node);
}
/* `mesh->runtime.subdiv_ccg` is not available. Updating of the normals is done during drawing.

View File

@ -295,7 +295,7 @@ static void update_modified_node_mesh(PBVHNode &node, PartialUpdateData &data)
if (!data.modified_position_verts.is_empty()) {
for (const int vert : verts) {
if (data.modified_position_verts[vert]) {
BKE_pbvh_node_mark_normals_update(&node);
BKE_pbvh_node_mark_positions_update(&node);
break;
}
}

View File

@ -523,7 +523,7 @@ static bool clip_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*eve
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
if (ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
return true;
}
}

View File

@ -99,7 +99,7 @@ struct PaintTileKey {
uint64_t hash() const
{
return blender::get_default_hash_4(x_tile, y_tile, image, ibuf);
return blender::get_default_hash(x_tile, y_tile, image, ibuf);
}
bool operator==(const PaintTileKey &other) const
{

View File

@ -258,7 +258,7 @@ static bool image_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
}
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
if (ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
return true;
}
}

View File

@ -23,6 +23,7 @@
#include "BKE_image.h"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_enum.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_scene.h"
@ -1199,6 +1200,7 @@ static const float std_node_socket_colors[][4] = {
{0.62, 0.31, 0.64, 1.0}, /* SOCK_TEXTURE */
{0.92, 0.46, 0.51, 1.0}, /* SOCK_MATERIAL */
{0.65, 0.39, 0.78, 1.0}, /* SOCK_ROTATION */
{0.40, 0.40, 0.40, 1.0}, /* SOCK_MENU */
};
/* Callback for colors that does not depend on the socket pointer argument to get the type. */
@ -1233,6 +1235,7 @@ static const SocketColorFn std_node_socket_color_funcs[] = {
std_node_socket_color_fn<SOCK_TEXTURE>,
std_node_socket_color_fn<SOCK_MATERIAL>,
std_node_socket_color_fn<SOCK_ROTATION>,
std_node_socket_color_fn<SOCK_MENU>,
};
/* draw function for file output node sockets,
@ -1371,6 +1374,27 @@ static void std_node_socket_draw(
break;
}
case SOCK_MENU: {
const bNodeSocketValueMenu *default_value =
sock->default_value_typed<bNodeSocketValueMenu>();
if (default_value->enum_items) {
if (default_value->enum_items->items.is_empty()) {
uiLayout *row = uiLayoutSplit(layout, 0.4f, false);
uiItemL(row, text, ICON_NONE);
uiItemL(row, "No Items", ICON_NONE);
}
else {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, "", ICON_NONE);
}
}
else if (default_value->has_conflict()) {
uiItemL(layout, IFACE_("Menu Error"), ICON_ERROR);
}
else {
uiItemL(layout, IFACE_("Menu Undefined"), ICON_QUESTION);
}
break;
}
case SOCK_OBJECT: {
uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, ICON_NONE);
break;
@ -1498,6 +1522,10 @@ static void std_node_socket_interface_draw(ID *id,
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_MENU: {
uiItemR(col, &ptr, "default_value", DEFAULT_FLAGS, IFACE_("Default"), ICON_NONE);
break;
}
case SOCK_SHADER:
case SOCK_GEOMETRY:
break;

View File

@ -207,7 +207,8 @@ void NODE_OT_group_edit(wmOperatorType *ot)
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(ot->srna, "exit", false, "Exit", "");
PropertyRNA *prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */

View File

@ -434,7 +434,8 @@ static bool socket_can_be_viewed(const bNode &node, const bNodeSocket &socket)
SOCK_INT,
SOCK_BOOLEAN,
SOCK_ROTATION,
SOCK_RGBA);
SOCK_RGBA,
SOCK_MENU);
}
/**
@ -2245,6 +2246,7 @@ static int get_main_socket_priority(const bNodeSocket *socket)
case SOCK_COLLECTION:
case SOCK_TEXTURE:
case SOCK_MATERIAL:
case SOCK_MENU:
return 6;
}
return -1;

View File

@ -394,6 +394,9 @@ static Vector<NodeLinkItem> ui_node_link_items(NodeLinkArg *arg,
else if (dynamic_cast<const decl::String *>(&socket_decl)) {
item.socket_type = SOCK_STRING;
}
else if (dynamic_cast<const decl::Menu *>(&socket_decl)) {
item.socket_type = SOCK_MENU;
}
else if (dynamic_cast<const decl::Image *>(&socket_decl)) {
item.socket_type = SOCK_IMAGE;
}
@ -994,6 +997,9 @@ static void ui_node_draw_input(uiLayout &layout,
split_wrapper.decorate_column, &inputptr, "default_value", RNA_NO_INDEX);
break;
}
case SOCK_MENU:
uiItemL(sub, RPT_("Unsupported Menu Socket"), ICON_NONE);
break;
case SOCK_CUSTOM:
input.typeinfo->draw(&C, sub, &inputptr, &nodeptr, input.name);
break;

View File

@ -888,7 +888,7 @@ static bool node_ima_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = static_cast<eFileSel_File_Types>(
WM_drag_get_path_file_type(drag));
return ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
return ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
}
return WM_drag_is_ID_type(drag, ID_IM);
}

View File

@ -1287,7 +1287,7 @@ static std::string collection_drop_tooltip(bContext *C,
}
}
}
return nullptr;
return {};
}
static int collection_drop_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)

View File

@ -80,7 +80,7 @@ static bool image_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent *event
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE)) {
if (file_type == FILE_TYPE_IMAGE) {
generic_poll_operations(event, TH_SEQ_IMAGE);
return true;
}
@ -98,7 +98,7 @@ static bool is_movie(wmDrag *drag)
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_MOVIE)) {
if (file_type == FILE_TYPE_MOVIE) {
return true;
}
}
@ -122,7 +122,7 @@ static bool is_sound(wmDrag *drag)
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_SOUND)) {
if (file_type == FILE_TYPE_SOUND) {
return true;
}
}
@ -690,7 +690,7 @@ static bool image_drop_preview_poll(bContext * /*C*/, wmDrag *drag, const wmEven
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_IMAGE)) {
if (file_type == FILE_TYPE_IMAGE) {
return true;
}
}
@ -702,7 +702,7 @@ static bool movie_drop_preview_poll(bContext * /*C*/, wmDrag *drag, const wmEven
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_MOVIE)) {
if (file_type == FILE_TYPE_MOVIE) {
return true;
}
}
@ -714,7 +714,7 @@ static bool sound_drop_preview_poll(bContext * /*C*/, wmDrag *drag, const wmEven
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_SOUND)) {
if (file_type == FILE_TYPE_SOUND) {
return true;
}
}

View File

@ -307,7 +307,7 @@ static bool text_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*eve
{
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, 0, FILE_TYPE_PYSCRIPT, FILE_TYPE_TEXT)) {
if (ELEM(file_type, FILE_TYPE_PYSCRIPT, FILE_TYPE_TEXT)) {
return true;
}
}

View File

@ -633,7 +633,7 @@ static bool view3d_ima_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event
}
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
return ELEM(file_type, 0, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
return ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE);
}
return WM_drag_is_ID_type(drag, ID_IM);

View File

@ -126,7 +126,7 @@ template<typename NodePtr> class GFieldBase {
uint64_t hash() const
{
return get_default_hash_2(*node_, node_output_index_);
return get_default_hash(*node_, node_output_index_);
}
const CPPType &cpp_type() const

View File

@ -96,7 +96,7 @@ class DataType {
uint64_t hash() const
{
return get_default_hash_2(*type_, category_);
return get_default_hash(*type_, category_);
}
};

View File

@ -177,9 +177,9 @@ Batch *VKBackend::batch_alloc()
return new VKBatch();
}
DrawList *VKBackend::drawlist_alloc(int /*list_length*/)
DrawList *VKBackend::drawlist_alloc(int list_length)
{
return new VKDrawList();
return new VKDrawList(list_length);
}
Fence *VKBackend::fence_alloc()

View File

@ -31,4 +31,9 @@ class VKBatch : public Batch {
void draw_setup();
};
BLI_INLINE VKBatch *unwrap(GPUBatch *batch)
{
return static_cast<VKBatch *>(batch);
}
} // namespace blender::gpu

View File

@ -8,15 +8,81 @@
#include "GPU_batch.h"
#include "vk_batch.hh"
#include "vk_common.hh"
#include "vk_drawlist.hh"
#include "vk_index_buffer.hh"
#include "vk_vertex_buffer.hh"
namespace blender::gpu {
void VKDrawList::append(GPUBatch *batch, int instance_first, int instance_count)
VKDrawList::VKDrawList(int list_length)
: command_buffer_(
list_length * sizeof(VkDrawIndexedIndirectCommand), GPU_USAGE_STREAM, __func__),
length_(list_length)
{
GPU_batch_draw_advanced(batch, 0, 0, instance_first, instance_count);
command_buffer_.ensure_allocated();
}
void VKDrawList::submit() {}
void VKDrawList::append(GPUBatch *gpu_batch, int instance_first, int instance_count)
{
/* Check for different batch. When batch is different the previous commands should be flushed to
* the gpu. */
VKBatch *batch = unwrap(gpu_batch);
if (batch_ != batch) {
submit();
batch_ = batch;
}
/* Record the new command */
const VKIndexBuffer *index_buffer = batch_->index_buffer_get();
const bool is_indexed = index_buffer != nullptr;
if (is_indexed) {
VkDrawIndexedIndirectCommand &command = get_command<VkDrawIndexedIndirectCommand>();
command.firstIndex = index_buffer->index_base_get();
command.vertexOffset = index_buffer->index_start_get();
command.indexCount = index_buffer->index_len_get();
command.firstInstance = instance_first;
command.instanceCount = instance_count;
}
else {
const VKVertexBuffer *vertex_buffer = batch_->vertex_buffer_get(0);
if (vertex_buffer == nullptr) {
batch_ = nullptr;
return;
}
VkDrawIndirectCommand &command = get_command<VkDrawIndirectCommand>();
command.vertexCount = vertex_buffer->vertex_len;
command.instanceCount = instance_count;
command.firstVertex = 0;
command.firstInstance = instance_first;
}
command_index_++;
/* Submit commands when command buffer is full. */
if (command_index_ == length_) {
submit();
}
}
void VKDrawList::submit()
{
if (batch_ == nullptr || command_index_ == 0) {
command_index_ = 0;
batch_ = nullptr;
return;
}
const VKIndexBuffer *index_buffer = batch_->index_buffer_get();
const bool is_indexed = index_buffer != nullptr;
command_buffer_.buffer_get().flush();
batch_->multi_draw_indirect(wrap(wrap(&command_buffer_)),
command_index_,
0,
is_indexed ? sizeof(VkDrawIndexedIndirectCommand) :
sizeof(VkDrawIndirectCommand));
command_index_ = 0;
batch_ = nullptr;
}
} // namespace blender::gpu

View File

@ -10,12 +10,68 @@
#include "gpu_drawlist_private.hh"
#include "vk_storage_buffer.hh"
namespace blender::gpu {
class VKBatch;
class VKDrawList : public DrawList {
private:
/**
* Batch from who the commands are being recorded.
*/
VKBatch *batch_ = nullptr;
/**
* Storage buffer containing the commands.
*
* The storage buffer is host visible and new commands are directly added to the buffer. Reducing
* the need to copy the commands from an intermediate buffer to the GPU. The commands are only
* written once and used once.
*
* The data can be used to record VkDrawIndirectCommands or VkDrawIndirectIndexedCommands.
*/
VKStorageBuffer command_buffer_;
/**
* Maximum number of commands that can be recorded per batch. Commands will be flushed when this
* number of commands are added.
*/
const int length_;
/**
* Current number of recorded commands.
*/
int command_index_ = 0;
public:
VKDrawList(int list_length);
/**
* Append a new command for the given batch to the draw list.
*
* Will flush when batch is different than the previous one or when the command_buffer_ is full.
*/
void append(GPUBatch *batch, int instance_first, int instance_count) override;
/**
* Submit buffered commands to the GPU.
*
* NOTE: after calling this method the command_index_ and the batch_ are reset.
*/
void submit() override;
private:
/**
* Retrieve command to write to. The returned memory is part of the mapped memory of the
* commands_buffer_.
*/
template<typename CommandType> CommandType &get_command() const
{
return MutableSpan<CommandType>(
static_cast<CommandType *>(command_buffer_.buffer_get().mapped_memory_get()),
length_)[command_index_];
}
};
} // namespace blender::gpu

View File

@ -38,12 +38,14 @@ void VKStorageBuffer::ensure_allocated()
void VKStorageBuffer::allocate()
{
const bool is_host_visible = false;
buffer_.create(size_in_bytes_,
usage_,
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
is_host_visible);
const bool is_host_visible = ELEM(usage_, GPU_USAGE_STREAM);
VkBufferUsageFlags buffer_usage_flags = ELEM(usage_, GPU_USAGE_STREAM) ?
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT :
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT;
buffer_.create(size_in_bytes_, usage_, buffer_usage_flags, is_host_visible);
debug::object_label(buffer_.vk_handle(), name_);
}

View File

@ -49,6 +49,11 @@ class VKStorageBuffer : public StorageBuf, public VKBindableResource {
void ensure_allocated();
const VKBuffer &buffer_get() const
{
return buffer_;
}
private:
void allocate();
};
@ -57,5 +62,9 @@ BLI_INLINE VKStorageBuffer *unwrap(StorageBuf *storage_buffer)
{
return static_cast<VKStorageBuffer *>(storage_buffer);
}
BLI_INLINE StorageBuf *wrap(VKStorageBuffer *storage_buffer)
{
return static_cast<StorageBuf *>(storage_buffer);
}
} // namespace blender::gpu

View File

@ -264,7 +264,8 @@ void IMB_rectblend_threaded(ImBuf *dbuf,
enum eIMBInterpolationFilterMode {
IMB_FILTER_NEAREST,
IMB_FILTER_BILINEAR,
IMB_FILTER_BICUBIC,
IMB_FILTER_CUBIC_BSPLINE,
IMB_FILTER_CUBIC_MITCHELL,
};
/**

View File

@ -91,6 +91,16 @@ inline void interpolate_cubic_bspline_fl(const ImBuf *in, float output[4], float
memcpy(output, &col, sizeof(col));
}
[[nodiscard]] inline uchar4 interpolate_cubic_mitchell_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_cubic_mitchell_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
inline void interpolate_cubic_mitchell_byte(const ImBuf *in, uchar output[4], float u, float v)
{
uchar4 col = math::interpolate_cubic_mitchell_byte(in->byte_buffer.data, in->x, in->y, u, v);
memcpy(output, &col, sizeof(col));
}
} // namespace blender::imbuf
/**

View File

@ -146,10 +146,10 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
u = wrap_uv(u, source->x);
v = wrap_uv(v, source->y);
}
/* BLI_bilinear_interpolation functions use `floor(uv)` and `floor(uv)+1`
/* Bilinear/cubic interpolation functions use `floor(uv)` and `floor(uv)+1`
* texels. For proper mapping between pixel and texel spaces, need to
* subtract 0.5. Same for bicubic. */
if constexpr (ELEM(Filter, IMB_FILTER_BILINEAR, IMB_FILTER_BICUBIC)) {
* subtract 0.5. */
if constexpr (Filter != IMB_FILTER_NEAREST) {
u -= 0.5f;
v -= 0.5f;
}
@ -185,14 +185,24 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
math::interpolate_nearest_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, float>) {
else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, float>) {
math::interpolate_cubic_bspline_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, uchar> && NumChannels == 4)
else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, uchar> &&
NumChannels == 4)
{
interpolate_cubic_bspline_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, float>) {
math::interpolate_cubic_mitchell_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, uchar> &&
NumChannels == 4)
{
interpolate_cubic_mitchell_byte(source, r_sample, u, v);
}
else {
/* Unsupported sampler. */
BLI_assert_unreachable();
@ -385,8 +395,11 @@ void IMB_transform(const ImBuf *src,
else if (filter == IMB_FILTER_BILINEAR) {
transform_scanlines_filter<IMB_FILTER_BILINEAR>(ctx, y_range);
}
else if (filter == IMB_FILTER_BICUBIC) {
transform_scanlines_filter<IMB_FILTER_BICUBIC>(ctx, y_range);
else if (filter == IMB_FILTER_CUBIC_BSPLINE) {
transform_scanlines_filter<IMB_FILTER_CUBIC_BSPLINE>(ctx, y_range);
}
else if (filter == IMB_FILTER_CUBIC_MITCHELL) {
transform_scanlines_filter<IMB_FILTER_CUBIC_MITCHELL>(ctx, y_range);
}
});
}

View File

@ -87,9 +87,9 @@ TEST(imbuf_transform, bilinear_2x_smaller)
IMB_freeImBuf(res);
}
TEST(imbuf_transform, bicubic_2x_smaller)
TEST(imbuf_transform, cubic_bspline_2x_smaller)
{
ImBuf *res = transform_2x_smaller(IMB_FILTER_BICUBIC, 1);
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_BSPLINE, 1);
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
EXPECT_EQ(got[0], ColorTheme4b(189, 126, 62, 250));
EXPECT_EQ(got[1], ColorTheme4b(134, 57, 33, 26));
@ -97,16 +97,26 @@ TEST(imbuf_transform, bicubic_2x_smaller)
IMB_freeImBuf(res);
}
TEST(imbuf_transform, bicubic_fractional_larger)
TEST(imbuf_transform, cubic_mitchell_2x_smaller)
{
ImBuf *res = transform_fractional_larger(IMB_FILTER_BICUBIC, 1);
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_MITCHELL, 1);
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
EXPECT_EQ(got[0 + 0 * res->x], ColorTheme4b(35, 11, 1, 255));
EXPECT_EQ(got[1 + 0 * res->x], ColorTheme4b(131, 12, 6, 250));
EXPECT_EQ(got[7 + 0 * res->x], ColorTheme4b(54, 93, 19, 249));
EXPECT_EQ(got[2 + 2 * res->x], ColorTheme4b(206, 70, 56, 192));
EXPECT_EQ(got[3 + 2 * res->x], ColorTheme4b(165, 60, 42, 78));
EXPECT_EQ(got[8 + 6 * res->x], ColorTheme4b(57, 1, 90, 252));
EXPECT_EQ(got[0], ColorTheme4b(195, 130, 67, 255));
EXPECT_EQ(got[1], ColorTheme4b(132, 51, 28, 0));
EXPECT_EQ(got[2], ColorTheme4b(52, 52, 48, 255));
IMB_freeImBuf(res);
}
TEST(imbuf_transform, cubic_mitchell_fractional_larger)
{
ImBuf *res = transform_fractional_larger(IMB_FILTER_CUBIC_MITCHELL, 1);
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
EXPECT_EQ(got[0 + 0 * res->x], ColorTheme4b(0, 0, 0, 255));
EXPECT_EQ(got[1 + 0 * res->x], ColorTheme4b(127, 0, 0, 255));
EXPECT_EQ(got[7 + 0 * res->x], ColorTheme4b(49, 109, 13, 255));
EXPECT_EQ(got[2 + 2 * res->x], ColorTheme4b(236, 53, 50, 215));
EXPECT_EQ(got[3 + 2 * res->x], ColorTheme4b(155, 55, 35, 54));
EXPECT_EQ(got[8 + 6 * res->x], ColorTheme4b(57, 0, 98, 252));
IMB_freeImBuf(res);
}

View File

@ -78,7 +78,7 @@ struct uv_vertex_key {
uint64_t hash() const
{
return get_default_hash_3(uv.x, uv.y, vertex_index);
return get_default_hash(uv.x, uv.y, vertex_index);
}
};

View File

@ -25,7 +25,7 @@
namespace blender::io::ply {
class ply_export_test : public BlendfileLoadingBaseTest {
class PLYExportTest : public BlendfileLoadingBaseTest {
public:
bool load_file_and_depsgraph(const std::string &filepath,
const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT)
@ -132,7 +132,7 @@ static std::vector<char> read_temp_file_in_vectorchar(const std::string &file_pa
return res;
}
TEST_F(ply_export_test, WriteHeaderAscii)
TEST_F(PLYExportTest, WriteHeaderAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -170,7 +170,7 @@ TEST_F(ply_export_test, WriteHeaderAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(ply_export_test, WriteHeaderBinary)
TEST_F(PLYExportTest, WriteHeaderBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -208,7 +208,7 @@ TEST_F(ply_export_test, WriteHeaderBinary)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(ply_export_test, WriteVerticesAscii)
TEST_F(PLYExportTest, WriteVerticesAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -240,7 +240,7 @@ TEST_F(ply_export_test, WriteVerticesAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(ply_export_test, WriteVerticesBinary)
TEST_F(PLYExportTest, WriteVerticesBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -276,7 +276,7 @@ TEST_F(ply_export_test, WriteVerticesBinary)
}
}
TEST_F(ply_export_test, WriteFacesAscii)
TEST_F(PLYExportTest, WriteFacesAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -306,7 +306,7 @@ TEST_F(ply_export_test, WriteFacesAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(ply_export_test, WriteFacesBinary)
TEST_F(PLYExportTest, WriteFacesBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -342,7 +342,7 @@ TEST_F(ply_export_test, WriteFacesBinary)
}
}
TEST_F(ply_export_test, WriteVertexNormalsAscii)
TEST_F(PLYExportTest, WriteVertexNormalsAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -374,7 +374,7 @@ TEST_F(ply_export_test, WriteVertexNormalsAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(ply_export_test, WriteVertexNormalsBinary)
TEST_F(PLYExportTest, WriteVertexNormalsBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -416,7 +416,7 @@ TEST_F(ply_export_test, WriteVertexNormalsBinary)
}
}
class ply_exporter_ply_data_test : public ply_export_test {
class PLYExportPLYDataTest : public PLYExportTest {
public:
PlyData load_ply_data_from_blendfile(const std::string &blendfile, PLYExportParams &params)
{
@ -431,7 +431,7 @@ class ply_exporter_ply_data_test : public ply_export_test {
}
};
TEST_F(ply_exporter_ply_data_test, CubeLoadPLYData)
TEST_F(PLYExportPLYDataTest, CubeLoadPLYData)
{
PLYExportParams params = {};
PlyData plyData = load_ply_data_from_blendfile("io_tests/blend_geometry/cube_all_data.blend",
@ -439,7 +439,7 @@ TEST_F(ply_exporter_ply_data_test, CubeLoadPLYData)
EXPECT_EQ(plyData.vertices.size(), 8);
EXPECT_EQ(plyData.uv_coordinates.size(), 0);
}
TEST_F(ply_exporter_ply_data_test, CubeLoadPLYDataUV)
TEST_F(PLYExportPLYDataTest, CubeLoadPLYDataUV)
{
PLYExportParams params = {};
params.export_uv = true;
@ -448,7 +448,7 @@ TEST_F(ply_exporter_ply_data_test, CubeLoadPLYDataUV)
EXPECT_EQ(plyData.vertices.size(), 8);
EXPECT_EQ(plyData.uv_coordinates.size(), 8);
}
TEST_F(ply_exporter_ply_data_test, CubeLooseEdgesLoadPLYData)
TEST_F(PLYExportPLYDataTest, CubeLooseEdgesLoadPLYData)
{
PLYExportParams params = {};
params.forward_axis = IO_AXIS_Y;
@ -479,7 +479,7 @@ TEST_F(ply_exporter_ply_data_test, CubeLooseEdgesLoadPLYData)
EXPECT_EQ_ARRAY(exp_face_sizes, plyData.face_sizes.data(), ARRAY_SIZE(exp_face_sizes));
EXPECT_EQ_ARRAY(exp_faces, plyData.face_vertices.data(), ARRAY_SIZE(exp_faces));
}
TEST_F(ply_exporter_ply_data_test, CubeLooseEdgesLoadPLYDataUV)
TEST_F(PLYExportPLYDataTest, CubeLooseEdgesLoadPLYDataUV)
{
PLYExportParams params = {};
params.forward_axis = IO_AXIS_Y;
@ -525,7 +525,7 @@ TEST_F(ply_exporter_ply_data_test, CubeLooseEdgesLoadPLYDataUV)
EXPECT_EQ_ARRAY(exp_faces, plyData.face_vertices.data(), ARRAY_SIZE(exp_faces));
}
TEST_F(ply_exporter_ply_data_test, CubesVertexAttrs)
TEST_F(PLYExportPLYDataTest, CubesVertexAttrs)
{
PLYExportParams params = {};
params.export_uv = true;
@ -537,7 +537,7 @@ TEST_F(ply_exporter_ply_data_test, CubesVertexAttrs)
EXPECT_EQ(plyData.vertex_custom_attr[0].data.size(), 28);
}
TEST_F(ply_exporter_ply_data_test, SuzanneLoadPLYDataUV)
TEST_F(PLYExportPLYDataTest, SuzanneLoadPLYDataUV)
{
PLYExportParams params = {};
params.export_uv = true;

View File

@ -22,7 +22,7 @@ struct Expectation {
float4 color_first = {-1, -1, -1, -1};
};
class ply_import_test : public testing::Test {
class PLYImportTest : public testing::Test {
public:
void import_and_check(const char *path, const Expectation &exp)
{
@ -90,7 +90,7 @@ class ply_import_test : public testing::Test {
}
};
TEST_F(ply_import_test, PLYImportCube)
TEST_F(PLYImportTest, PLYImportCube)
{
Expectation expect = {24,
6,
@ -106,21 +106,21 @@ TEST_F(ply_import_test, PLYImportCube)
import_and_check("cube_ascii.ply", expect);
}
TEST_F(ply_import_test, PLYImportWireframeCube)
TEST_F(PLYImportTest, PLYImportWireframeCube)
{
Expectation expect = {8, 0, 0, 12, 0, 31435, float3(-1, -1, -1), float3(1, 1, 1)};
import_and_check("ASCII_wireframe_cube.ply", expect);
import_and_check("wireframe_cube.ply", expect);
}
TEST_F(ply_import_test, PlyImportBinaryDataStartsWithLF)
TEST_F(PLYImportTest, PlyImportBinaryDataStartsWithLF)
{
Expectation expect = {4, 1, 4, 0, 37235, 0, float3(-1, -1, 0), float3(-1, 1, 0)};
import_and_check("bin_data_starts_with_lf.ply", expect);
import_and_check("bin_data_starts_with_lf_header_crlf.ply", expect);
}
TEST_F(ply_import_test, PLYImportBunny)
TEST_F(PLYImportTest, PLYImportBunny)
{
Expectation expect = {1623,
1000,
@ -133,7 +133,7 @@ TEST_F(ply_import_test, PLYImportBunny)
import_and_check("bunny2.ply", expect);
}
TEST_F(ply_import_test, PlyImportManySmallHoles)
TEST_F(PLYImportTest, PlyImportManySmallHoles)
{
Expectation expect = {2004,
3524,
@ -149,14 +149,14 @@ TEST_F(ply_import_test, PlyImportManySmallHoles)
import_and_check("many_small_holes.ply", expect);
}
TEST_F(ply_import_test, PlyImportColorNotFull)
TEST_F(PLYImportTest, PlyImportColorNotFull)
{
Expectation expect = {4, 1, 4, 0, 37235, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("color_not_full_a.ply", expect);
import_and_check("color_not_full_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportCustomDataElements)
TEST_F(PLYImportTest, PlyImportCustomDataElements)
{
Expectation expect = {600,
0,
@ -172,7 +172,7 @@ TEST_F(ply_import_test, PlyImportCustomDataElements)
import_and_check("custom_data_elements.ply", expect);
}
TEST_F(ply_import_test, PlyImportDoubleXYZ)
TEST_F(PLYImportTest, PlyImportDoubleXYZ)
{
Expectation expect = {4,
1,
@ -189,28 +189,28 @@ TEST_F(ply_import_test, PlyImportDoubleXYZ)
import_and_check("double_xyz_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFaceIndicesNotFirstProp)
TEST_F(PLYImportTest, PlyImportFaceIndicesNotFirstProp)
{
Expectation expect = {4, 2, 6, 0, 4136, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("face_indices_not_first_prop_a.ply", expect);
import_and_check("face_indices_not_first_prop_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFaceIndicesPrecededByList)
TEST_F(PLYImportTest, PlyImportFaceIndicesPrecededByList)
{
Expectation expect = {4, 2, 6, 0, 4136, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("face_indices_preceded_by_list_a.ply", expect);
import_and_check("face_indices_preceded_by_list_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFaceUVsColors)
TEST_F(PLYImportTest, PlyImportFaceUVsColors)
{
Expectation expect = {4, 1, 4, 0, 37235, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("face_uvs_colors_a.ply", expect);
import_and_check("face_uvs_colors_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFacesFirst)
TEST_F(PLYImportTest, PlyImportFacesFirst)
{
Expectation expect = {4,
1,
@ -227,7 +227,7 @@ TEST_F(ply_import_test, PlyImportFacesFirst)
import_and_check("faces_first_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFloatFormats)
TEST_F(PLYImportTest, PlyImportFloatFormats)
{
Expectation expect = {4,
1,
@ -244,21 +244,21 @@ TEST_F(ply_import_test, PlyImportFloatFormats)
import_and_check("float_formats_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportPositionNotFull)
TEST_F(PLYImportTest, PlyImportPositionNotFull)
{
Expectation expect = {0, 0, 0, 0};
import_and_check("position_not_full_a.ply", expect);
import_and_check("position_not_full_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportTristrips)
TEST_F(PLYImportTest, PlyImportTristrips)
{
Expectation expect = {6, 4, 12, 0, 3404, 0, float3(1, 0, 1), float3(-3, 0, 1)};
import_and_check("tristrips_a.ply", expect);
import_and_check("tristrips_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportTypeAliases)
TEST_F(PLYImportTest, PlyImportTypeAliases)
{
Expectation expect = {4,
1,
@ -276,7 +276,7 @@ TEST_F(ply_import_test, PlyImportTypeAliases)
import_and_check("type_aliases_be_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportVertexCompOrder)
TEST_F(PLYImportTest, PlyImportVertexCompOrder)
{
Expectation expect = {4,
1,

View File

@ -34,7 +34,7 @@ static std::string read_temp_file_in_string(const std::string &file_path)
return res;
}
class stl_export_test : public BlendfileLoadingBaseTest {
class STLExportTest : public BlendfileLoadingBaseTest {
public:
bool load_file_and_depsgraph(const std::string &filepath)
{
@ -46,7 +46,7 @@ class stl_export_test : public BlendfileLoadingBaseTest {
}
protected:
stl_export_test()
STLExportTest()
{
_params = {};
_params.forward_axis = IO_AXIS_Y;
@ -105,25 +105,25 @@ class stl_export_test : public BlendfileLoadingBaseTest {
STLExportParams _params;
};
TEST_F(stl_export_test, all_tris)
TEST_F(STLExportTest, all_tris)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "all_tris.blend",
"io_tests" SEP_STR "stl" SEP_STR "all_tris.stl");
}
TEST_F(stl_export_test, all_quads)
TEST_F(STLExportTest, all_quads)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "all_quads.blend",
"io_tests" SEP_STR "stl" SEP_STR "all_quads.stl");
}
TEST_F(stl_export_test, non_uniform_scale)
TEST_F(STLExportTest, non_uniform_scale)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "non_uniform_scale.blend",
"io_tests" SEP_STR "stl" SEP_STR "non_uniform_scale.stl");
}
TEST_F(stl_export_test, cubes_positioned)
TEST_F(STLExportTest, cubes_positioned)
{
compare_to_golden("io_tests" SEP_STR "blend_geometry" SEP_STR "cubes_positioned.blend",
"io_tests" SEP_STR "stl" SEP_STR "cubes_positioned.stl");

View File

@ -62,10 +62,10 @@ void OBJWriter::write_vert_uv_normal_indices(FormatHandler &fh,
const int uv_offset = offsets.uv_vertex_offset + 1;
const int normal_offset = offsets.normal_offset + 1;
const int n = vert_indices.size();
fh.write_obj_poly_begin();
fh.write_obj_face_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write_obj_poly_v_uv_normal(vert_indices[j] + vertex_offset,
fh.write_obj_face_v_uv_normal(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset,
normal_indices[j] + normal_offset);
}
@ -76,12 +76,12 @@ void OBJWriter::write_vert_uv_normal_indices(FormatHandler &fh,
* then go backwards. Same logic in other write_*_indices functions below. */
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write_obj_poly_v_uv_normal(vert_indices[j] + vertex_offset,
fh.write_obj_face_v_uv_normal(vert_indices[j] + vertex_offset,
uv_indices[j] + uv_offset,
normal_indices[j] + normal_offset);
}
}
fh.write_obj_poly_end();
fh.write_obj_face_end();
}
void OBJWriter::write_vert_normal_indices(FormatHandler &fh,
@ -95,21 +95,21 @@ void OBJWriter::write_vert_normal_indices(FormatHandler &fh,
const int vertex_offset = offsets.vertex_offset + 1;
const int normal_offset = offsets.normal_offset + 1;
const int n = vert_indices.size();
fh.write_obj_poly_begin();
fh.write_obj_face_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write_obj_poly_v_normal(vert_indices[j] + vertex_offset,
fh.write_obj_face_v_normal(vert_indices[j] + vertex_offset,
normal_indices[j] + normal_offset);
}
}
else {
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write_obj_poly_v_normal(vert_indices[j] + vertex_offset,
fh.write_obj_face_v_normal(vert_indices[j] + vertex_offset,
normal_indices[j] + normal_offset);
}
}
fh.write_obj_poly_end();
fh.write_obj_face_end();
}
void OBJWriter::write_vert_uv_indices(FormatHandler &fh,
@ -123,19 +123,19 @@ void OBJWriter::write_vert_uv_indices(FormatHandler &fh,
const int vertex_offset = offsets.vertex_offset + 1;
const int uv_offset = offsets.uv_vertex_offset + 1;
const int n = vert_indices.size();
fh.write_obj_poly_begin();
fh.write_obj_face_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write_obj_poly_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
fh.write_obj_face_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
}
}
else {
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write_obj_poly_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
fh.write_obj_face_v_uv(vert_indices[j] + vertex_offset, uv_indices[j] + uv_offset);
}
}
fh.write_obj_poly_end();
fh.write_obj_face_end();
}
void OBJWriter::write_vert_indices(FormatHandler &fh,
@ -147,19 +147,19 @@ void OBJWriter::write_vert_indices(FormatHandler &fh,
{
const int vertex_offset = offsets.vertex_offset + 1;
const int n = vert_indices.size();
fh.write_obj_poly_begin();
fh.write_obj_face_begin();
if (!flip) {
for (int j = 0; j < n; ++j) {
fh.write_obj_poly_v(vert_indices[j] + vertex_offset);
fh.write_obj_face_v(vert_indices[j] + vertex_offset);
}
}
else {
for (int k = 0; k < n; ++k) {
int j = k == 0 ? 0 : n - k;
fh.write_obj_poly_v(vert_indices[j] + vertex_offset);
fh.write_obj_face_v(vert_indices[j] + vertex_offset);
}
}
fh.write_obj_poly_end();
fh.write_obj_face_end();
}
void OBJWriter::write_header() const
@ -295,7 +295,7 @@ void OBJWriter::write_uv_coords(FormatHandler &fh, OBJMesh &r_obj_mesh_data) con
});
}
void OBJWriter::write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
void OBJWriter::write_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
{
/* Poly normals should be calculated earlier via store_normal_coords_and_indices. */
const Span<float3> normal_coords = obj_mesh_data.get_normal_coords();
@ -305,7 +305,7 @@ void OBJWriter::write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
});
}
OBJWriter::func_vert_uv_normal_indices OBJWriter::get_poly_element_writer(
OBJWriter::func_vert_uv_normal_indices OBJWriter::get_face_element_writer(
const int total_uv_vertices) const
{
if (export_params_.export_normals) {
@ -330,18 +330,18 @@ static int get_smooth_group(const OBJMesh &mesh, const OBJExportParams &params,
return NEGATIVE_INIT;
}
int group = SMOOTH_GROUP_DISABLED;
if (mesh.is_ith_poly_smooth(face_idx)) {
if (mesh.is_ith_face_smooth(face_idx)) {
group = !params.export_smooth_groups ? SMOOTH_GROUP_DEFAULT : mesh.ith_smooth_group(face_idx);
}
return group;
}
void OBJWriter::write_poly_elements(FormatHandler &fh,
void OBJWriter::write_face_elements(FormatHandler &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data,
FunctionRef<const char *(int)> matname_fn)
{
const func_vert_uv_normal_indices poly_element_writer = get_poly_element_writer(
const func_vert_uv_normal_indices face_element_writer = get_face_element_writer(
obj_mesh_data.tot_uv_vertices());
const int tot_faces = obj_mesh_data.tot_faces();
@ -358,9 +358,9 @@ void OBJWriter::write_poly_elements(FormatHandler &fh,
int prev_i = obj_mesh_data.remap_face_index(idx - 1);
int i = obj_mesh_data.remap_face_index(idx);
Span<int> poly_vertex_indices = obj_mesh_data.calc_poly_vertex_indices(i);
Span<int> poly_uv_indices = obj_mesh_data.calc_poly_uv_indices(i);
Vector<int> poly_normal_indices = obj_mesh_data.calc_poly_normal_indices(i);
const Span<int> face_vertex_indices = obj_mesh_data.calc_face_vert_indices(i);
const Span<int> face_uv_indices = obj_mesh_data.get_face_uv_indices(i);
const Span<int> face_normal_indices = obj_mesh_data.get_face_normal_indices(i);
/* Write smoothing group if different from previous. */
{
@ -376,12 +376,12 @@ void OBJWriter::write_poly_elements(FormatHandler &fh,
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(
obj_mesh_data.get_face_deform_group_index(
prev_i, local_weights);
const int16_t group = obj_mesh_data.get_poly_deform_group_index(i, local_weights);
const int16_t group = obj_mesh_data.get_face_deform_group_index(i, local_weights);
if (group != prev_group) {
buf.write_obj_group(group == NOT_FOUND ? DEFORM_GROUP_DISABLED :
obj_mesh_data.get_poly_deform_group_name(group));
obj_mesh_data.get_face_deform_group_name(group));
}
}
@ -409,11 +409,11 @@ void OBJWriter::write_poly_elements(FormatHandler &fh,
}
/* Write face elements. */
(this->*poly_element_writer)(buf,
(this->*face_element_writer)(buf,
offsets,
poly_vertex_indices,
poly_uv_indices,
poly_normal_indices,
face_vertex_indices,
face_uv_indices,
face_normal_indices,
obj_mesh_data.is_mirrored_transform());
});
}
@ -465,7 +465,7 @@ void OBJWriter::write_nurbs_curve(FormatHandler &fh, const OBJCurve &obj_nurbs_d
for (int i = 0; i < total_control_points; i++) {
/* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the
* last vertex coordinate, -2 second last. */
fh.write_obj_poly_v(-((i % total_vertices) + 1));
fh.write_obj_face_v(-((i % total_vertices) + 1));
}
fh.write_obj_curve_end();

View File

@ -81,22 +81,22 @@ class OBJWriter : NonMovable, NonCopyable {
bool write_colors) const;
/**
* Write UV vertex coordinates for all vertices as `vt u v`.
* \note UV indices are stored here, but written with polygons later.
* \note UV indices are stored here, but written with faces later.
*/
void write_uv_coords(FormatHandler &fh, OBJMesh &obj_mesh_data) const;
/**
* Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z".
* \note Normal indices ares stored here, but written with polygons later.
* Write corner normals for smooth-shaded faces, and face normals otherwise, as "vn x y z".
* \note Normal indices ares stored here, but written with faces later.
*/
void write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data);
void write_normals(FormatHandler &fh, OBJMesh &obj_mesh_data);
/**
* Write polygon elements with at least vertex indices, and conditionally with UV vertex
* indices and polygon normal indices. Also write groups: smooth, vertex, material.
* Write face elements with at least vertex indices, and conditionally with UV vertex
* indices and face normal indices. Also write groups: smooth, vertex, material.
* The matname_fn turns a 0-indexed material slot number in an Object into the
* name used in the .obj file.
* \note UV indices were stored while writing UV vertices.
*/
void write_poly_elements(FormatHandler &fh,
void write_face_elements(FormatHandler &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data,
FunctionRef<const char *(int)> matname_fn);
@ -119,12 +119,12 @@ class OBJWriter : NonMovable, NonCopyable {
Span<int> normal_indices,
bool flip) const;
/**
* \return Writer function with appropriate polygon-element syntax.
* \return Writer function with appropriate face-element syntax.
*/
func_vert_uv_normal_indices get_poly_element_writer(int total_uv_vertices) const;
func_vert_uv_normal_indices get_face_element_writer(int total_uv_vertices) const;
/**
* Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...".
* Write one line of face indices as "f v1/vt1/vn1 v2/vt2/vn2 ...".
*/
void write_vert_uv_normal_indices(FormatHandler &fh,
const IndexOffsets &offsets,
@ -133,7 +133,7 @@ class OBJWriter : NonMovable, NonCopyable {
Span<int> normal_indices,
bool flip) const;
/**
* Write one line of polygon indices as "f v1//vn1 v2//vn2 ...".
* Write one line of face indices as "f v1//vn1 v2//vn2 ...".
*/
void write_vert_normal_indices(FormatHandler &fh,
const IndexOffsets &offsets,
@ -142,7 +142,7 @@ class OBJWriter : NonMovable, NonCopyable {
Span<int> normal_indices,
bool flip) const;
/**
* Write one line of polygon indices as "f v1/vt1 v2/vt2 ...".
* Write one line of face indices as "f v1/vt1 v2/vt2 ...".
*/
void write_vert_uv_indices(FormatHandler &fh,
const IndexOffsets &offsets,
@ -151,7 +151,7 @@ class OBJWriter : NonMovable, NonCopyable {
Span<int> /*normal_indices*/,
bool flip) const;
/**
* Write one line of polygon indices as "f v1 v2 ...".
* Write one line of face indices as "f v1 v2 ...".
*/
void write_vert_indices(FormatHandler &fh,
const IndexOffsets &offsets,

View File

@ -85,27 +85,27 @@ class FormatHandler : NonCopyable, NonMovable {
{
write_impl("vn {:.4f} {:.4f} {:.4f}\n", x, y, z);
}
void write_obj_poly_begin()
void write_obj_face_begin()
{
write_impl("f");
}
void write_obj_poly_end()
void write_obj_face_end()
{
write_obj_newline();
}
void write_obj_poly_v_uv_normal(int v, int uv, int n)
void write_obj_face_v_uv_normal(int v, int uv, int n)
{
write_impl(" {}/{}/{}", v, uv, n);
}
void write_obj_poly_v_normal(int v, int n)
void write_obj_face_v_normal(int v, int n)
{
write_impl(" {}//{}", v, n);
}
void write_obj_poly_v_uv(int v, int uv)
void write_obj_face_v_uv(int v, int uv)
{
write_impl(" {}/{}", v, uv);
}
void write_obj_poly_v(int v)
void write_obj_face_v(int v)
{
write_impl(" {}", v);
}

View File

@ -15,12 +15,14 @@
#include "BKE_mesh_mapping.hh"
#include "BKE_object.hh"
#include "BLI_array_utils.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_sort.hh"
#include "BLI_vector_set.hh"
#include "DEG_depsgraph_query.hh"
@ -48,7 +50,6 @@ OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Obj
}
if (export_mesh_) {
mesh_positions_ = export_mesh_->vert_positions();
mesh_edges_ = export_mesh_->edges();
mesh_faces_ = export_mesh_->faces();
mesh_corner_verts_ = export_mesh_->corner_verts();
@ -89,7 +90,6 @@ void OBJMesh::set_mesh(Mesh *mesh)
}
owned_export_mesh_ = mesh;
export_mesh_ = owned_export_mesh_;
mesh_positions_ = mesh->vert_positions();
mesh_edges_ = mesh->edges();
mesh_faces_ = mesh->faces();
mesh_corner_verts_ = mesh->corner_verts();
@ -104,14 +104,14 @@ void OBJMesh::clear()
owned_export_mesh_ = nullptr;
}
export_mesh_ = nullptr;
loop_to_uv_index_ = {};
corner_to_uv_index_ = {};
uv_coords_.clear_and_shrink();
loop_to_normal_index_ = {};
normal_coords_.clear_and_shrink();
poly_order_ = {};
if (poly_smooth_groups_) {
MEM_freeN(poly_smooth_groups_);
poly_smooth_groups_ = nullptr;
corner_to_normal_index_ = {};
normal_coords_ = {};
face_order_ = {};
if (face_smooth_groups_) {
MEM_freeN(face_smooth_groups_);
face_smooth_groups_ = nullptr;
}
}
@ -195,17 +195,12 @@ int16_t OBJMesh::tot_materials() const
return this->materials.size();
}
int OBJMesh::tot_normal_indices() const
{
return tot_normal_indices_;
}
int OBJMesh::ith_smooth_group(const int face_index) const
{
/* Calculate smooth groups first: #OBJMesh::calc_smooth_groups. */
BLI_assert(tot_smooth_groups_ != -NEGATIVE_INIT);
BLI_assert(poly_smooth_groups_);
return poly_smooth_groups_[face_index];
BLI_assert(face_smooth_groups_);
return face_smooth_groups_[face_index];
}
void OBJMesh::calc_smooth_groups(const bool use_bitflags)
@ -213,7 +208,7 @@ void OBJMesh::calc_smooth_groups(const bool use_bitflags)
const bke::AttributeAccessor attributes = export_mesh_->attributes();
const VArraySpan sharp_edges = *attributes.lookup<bool>("sharp_edge", bke::AttrDomain::Edge);
const VArraySpan sharp_faces = *attributes.lookup<bool>("sharp_face", bke::AttrDomain::Face);
poly_smooth_groups_ = BKE_mesh_calc_smoothgroups(mesh_edges_.size(),
face_smooth_groups_ = BKE_mesh_calc_smoothgroups(mesh_edges_.size(),
mesh_faces_,
export_mesh_->corner_edges(),
sharp_edges,
@ -222,7 +217,7 @@ void OBJMesh::calc_smooth_groups(const bool use_bitflags)
use_bitflags);
}
void OBJMesh::calc_poly_order()
void OBJMesh::calc_face_order()
{
const bke::AttributeAccessor attributes = export_mesh_->attributes();
const VArray<int> material_indices = *attributes.lookup_or_default<int>(
@ -232,13 +227,10 @@ void OBJMesh::calc_poly_order()
}
const VArraySpan<int> material_indices_span(material_indices);
poly_order_.reinitialize(material_indices_span.size());
for (const int i : material_indices_span.index_range()) {
poly_order_[i] = i;
}
/* Sort polygons by their material index. */
blender::parallel_sort(poly_order_.begin(), poly_order_.end(), [&](int a, int b) {
/* Sort faces by their material index. */
face_order_.reinitialize(material_indices_span.size());
array_utils::fill_index_range(face_order_.as_mutable_span());
blender::parallel_sort(face_order_.begin(), face_order_.end(), [&](int a, int b) {
int mat_a = material_indices_span[a];
int mat_b = material_indices_span[b];
if (mat_a != mat_b) {
@ -248,26 +240,21 @@ void OBJMesh::calc_poly_order()
});
}
bool OBJMesh::is_ith_poly_smooth(const int face_index) const
bool OBJMesh::is_ith_face_smooth(const int face_index) const
{
return !sharp_faces_[face_index];
}
const char *OBJMesh::get_object_name() const
StringRef OBJMesh::get_object_name() const
{
return object_name_.c_str();
return object_name_;
}
const char *OBJMesh::get_object_mesh_name() const
StringRef OBJMesh::get_object_mesh_name() const
{
return export_mesh_->id.name + 2;
}
Span<int> OBJMesh::calc_poly_vertex_indices(const int face_index) const
{
return mesh_corner_verts_.slice(mesh_faces_[face_index]);
}
void OBJMesh::store_uv_coords_and_indices()
{
const StringRef active_uv_name = CustomData_get_active_layer_name(&export_mesh_->corner_data,
@ -289,7 +276,7 @@ void OBJMesh::store_uv_coords_and_indices()
uv_to_index.reserve(export_mesh_->verts_num);
uv_coords_.reserve(export_mesh_->verts_num);
loop_to_uv_index_.reinitialize(uv_map.size());
corner_to_uv_index_.reinitialize(uv_map.size());
for (int index = 0; index < int(uv_map.size()); index++) {
float2 uv = uv_map[index];
@ -299,26 +286,10 @@ void OBJMesh::store_uv_coords_and_indices()
uv_to_index.add(uv, uv_index);
uv_coords_.append(uv);
}
loop_to_uv_index_[index] = uv_index;
corner_to_uv_index_[index] = uv_index;
}
}
Span<int> OBJMesh::calc_poly_uv_indices(const int face_index) const
{
if (uv_coords_.is_empty()) {
return {};
}
BLI_assert(face_index < export_mesh_->faces_num);
return loop_to_uv_index_.as_span().slice(mesh_faces_[face_index]);
}
float3 OBJMesh::calc_poly_normal(const int face_index) const
{
const Span<int> face_verts = mesh_corner_verts_.slice(mesh_faces_[face_index]);
const float3 normal = bke::mesh::face_normal_calc(mesh_positions_, face_verts);
return math::normalize(world_and_axes_normal_transform_ * normal);
}
/** Round \a f to \a round_digits decimal digits. */
static float round_float_to_n_digits(const float f, int round_digits)
{
@ -343,68 +314,59 @@ void OBJMesh::store_normal_coords_and_indices()
* Since normals are normalized, there will be no perceptible loss
* of precision when rounding to 4 digits. */
constexpr int round_digits = 4;
int cur_normal_index = 0;
Map<float3, int> normal_to_index;
VectorSet<float3> unique_normals;
/* We don't know how many unique normals there will be, but this is a guess. */
normal_to_index.reserve(export_mesh_->faces_num);
loop_to_normal_index_.reinitialize(export_mesh_->corners_num);
loop_to_normal_index_.fill(-1);
unique_normals.reserve(export_mesh_->faces_num);
corner_to_normal_index_.reinitialize(export_mesh_->corners_num);
Span<float3> corner_normals;
if (ELEM(export_mesh_->normals_domain(),
bke::MeshNormalDomain::Point,
bke::MeshNormalDomain::Corner))
{
corner_normals = export_mesh_->corner_normals();
}
/* Normals need inverse transpose of the regular matrix to handle non-uniform scale. */
const float3x3 transform = world_and_axes_normal_transform_;
auto add_normal = [&](const float3 &normal) {
const float3 transformed = math::normalize(transform * normal);
const float3 rounded = round_float3_to_n_digits(transformed, round_digits);
return unique_normals.index_of_or_add(rounded);
};
for (int face_index = 0; face_index < export_mesh_->faces_num; ++face_index) {
const IndexRange face = mesh_faces_[face_index];
bool need_per_loop_normals = !corner_normals.is_empty() || !(sharp_faces_[face_index]);
if (need_per_loop_normals) {
for (const int corner : face) {
BLI_assert(corner < export_mesh_->corners_num);
const float3 normal = math::normalize(world_and_axes_normal_transform_ *
corner_normals[corner]);
const float3 rounded = round_float3_to_n_digits(normal, round_digits);
int loop_norm_index = normal_to_index.lookup_default(rounded, -1);
if (loop_norm_index == -1) {
loop_norm_index = cur_normal_index++;
normal_to_index.add(rounded, loop_norm_index);
normal_coords_.append(rounded);
switch (export_mesh_->normals_domain()) {
case bke::MeshNormalDomain::Face: {
const Span<float3> face_normals = export_mesh_->face_normals();
for (const int face : mesh_faces_.index_range()) {
const int index = add_normal(face_normals[face]);
corner_to_normal_index_.as_mutable_span().slice(mesh_faces_[face]).fill(index);
}
break;
}
case bke::MeshNormalDomain::Point: {
const Span<float3> vert_normals = export_mesh_->vert_normals();
Array<int> vert_normal_indices(vert_normals.size());
const bke::LooseVertCache &verts_no_face = export_mesh_->verts_no_face();
if (verts_no_face.count == 0) {
for (const int vert : vert_normals.index_range()) {
vert_normal_indices[vert] = add_normal(vert_normals[vert]);
}
loop_to_normal_index_[corner] = loop_norm_index;
}
else {
for (const int vert : vert_normals.index_range()) {
if (!verts_no_face.is_loose_bits[vert]) {
vert_normal_indices[vert] = add_normal(vert_normals[vert]);
}
}
}
array_utils::gather(vert_normal_indices.as_span(),
mesh_corner_verts_,
corner_to_normal_index_.as_mutable_span());
break;
}
else {
float3 poly_normal = calc_poly_normal(face_index);
float3 rounded_poly_normal = round_float3_to_n_digits(poly_normal, round_digits);
int poly_norm_index = normal_to_index.lookup_default(rounded_poly_normal, -1);
if (poly_norm_index == -1) {
poly_norm_index = cur_normal_index++;
normal_to_index.add(rounded_poly_normal, poly_norm_index);
normal_coords_.append(rounded_poly_normal);
}
for (const int corner : face) {
BLI_assert(corner < export_mesh_->corners_num);
loop_to_normal_index_[corner] = poly_norm_index;
case bke::MeshNormalDomain::Corner: {
const Span<float3> corner_normals = export_mesh_->corner_normals();
for (const int corner : corner_normals.index_range()) {
corner_to_normal_index_[corner] = add_normal(corner_normals[corner]);
}
break;
}
}
tot_normal_indices_ = cur_normal_index;
}
Vector<int> OBJMesh::calc_poly_normal_indices(const int face_index) const
{
if (loop_to_normal_index_.is_empty()) {
return {};
}
const IndexRange face = mesh_faces_[face_index];
Vector<int> r_poly_normal_indices(face.size());
for (const int i : IndexRange(face.size())) {
r_poly_normal_indices[i] = loop_to_normal_index_[face[i]];
}
return r_poly_normal_indices;
normal_coords_ = unique_normals.as_span();
}
int OBJMesh::tot_deform_groups() const
@ -412,7 +374,7 @@ int OBJMesh::tot_deform_groups() const
return BLI_listbase_count(&export_mesh_->vertex_group_names);
}
int16_t OBJMesh::get_poly_deform_group_index(const int face_index,
int16_t OBJMesh::get_face_deform_group_index(const int face_index,
MutableSpan<float> group_weights) const
{
BLI_assert(face_index < export_mesh_->faces_num);
@ -444,7 +406,7 @@ int16_t OBJMesh::get_poly_deform_group_index(const int face_index,
return max_idx;
}
const char *OBJMesh::get_poly_deform_group_name(const int16_t def_group_index) const
const char *OBJMesh::get_face_deform_group_name(const int16_t def_group_index) const
{
const bDeformGroup &vertex_group = *(static_cast<bDeformGroup *>(
BLI_findlink(&export_mesh_->vertex_group_names, def_group_index)));

View File

@ -35,7 +35,6 @@ class OBJMesh : NonCopyable {
const Mesh *export_mesh_;
/** A mesh owned here, if created or modified for the export. May be null. */
Mesh *owned_export_mesh_ = nullptr;
Span<float3> mesh_positions_;
Span<int2> mesh_edges_;
OffsetIndices<int> mesh_faces_;
Span<int> mesh_corner_verts_;
@ -49,28 +48,15 @@ class OBJMesh : NonCopyable {
float3x3 world_and_axes_normal_transform_;
bool mirrored_transform_;
/**
* Per-loop UV index.
*/
Array<int> loop_to_uv_index_;
/*
* UV vertices.
*/
/** Per-corner UV index. */
Array<int> corner_to_uv_index_;
/** UV vertices. */
Vector<float2> uv_coords_;
/**
* Per-loop normal index.
*/
Array<int> loop_to_normal_index_;
/*
* Normal coords.
*/
Vector<float3> normal_coords_;
/*
* Total number of normal indices (maximum entry, plus 1, in
* the loop_to_norm_index_ vector).
*/
int tot_normal_indices_ = 0;
/** Index into #normal_coords_ for every face corner. */
Array<int> corner_to_normal_index_;
/** De-duplicated normals, indexed by #corner_to_normal_index_. */
Array<float3> normal_coords_;
/**
* Total smooth groups in an object.
*/
@ -78,11 +64,11 @@ class OBJMesh : NonCopyable {
/**
* Polygon aligned array of their smooth groups.
*/
int *poly_smooth_groups_ = nullptr;
int *face_smooth_groups_ = nullptr;
/**
* Order in which the polygons should be written into the file (sorted by material index).
* Order in which the faces should be written into the file (sorted by material index).
*/
Array<int> poly_order_;
Array<int> face_order_;
public:
Array<const Material *> materials;
@ -100,7 +86,6 @@ class OBJMesh : NonCopyable {
int tot_vertices() const;
int tot_faces() const;
int tot_uv_vertices() const;
int tot_normal_indices() const;
int tot_edges() const;
int tot_deform_groups() const;
bool is_mirrored_transform() const
@ -115,23 +100,23 @@ class OBJMesh : NonCopyable {
/**
* Calculate smooth groups of a smooth-shaded object.
* \return A polygon aligned array of smooth group numbers.
* \return A face aligned array of smooth group numbers.
*/
void calc_smooth_groups(bool use_bitflags);
/**
* \return Smooth group of the polygon at the given index.
* \return Smooth group of the face at the given index.
*/
int ith_smooth_group(int face_index) const;
bool is_ith_poly_smooth(int face_index) const;
bool is_ith_face_smooth(int face_index) const;
/**
* Get object name as it appears in the outliner.
*/
const char *get_object_name() const;
StringRef get_object_name() const;
/**
* Get Object's Mesh's name.
*/
const char *get_object_mesh_name() const;
StringRef get_object_mesh_name() const;
const float4x4 &get_world_axes_transform() const
{
@ -139,72 +124,86 @@ class OBJMesh : NonCopyable {
}
/**
* Calculate vertex indices of all vertices of the polygon at the given index.
* Calculate vertex indices of all vertices of the face at the given index.
*/
Span<int> calc_poly_vertex_indices(int face_index) const;
Span<int> calc_face_vert_indices(const int face_index) const
{
return mesh_corner_verts_.slice(mesh_faces_[face_index]);
}
/**
* Calculate UV vertex coordinates of an Object.
* Stores the coordinates and UV vertex indices in the member variables.
*/
void store_uv_coords_and_indices();
/* Get UV coordinates computed by store_uv_coords_and_indices. */
const Vector<float2> &get_uv_coords() const
const Span<float2> get_uv_coords() const
{
return uv_coords_;
}
Span<int> calc_poly_uv_indices(int face_index) const;
/**
* Calculate polygon normal of a polygon at given index.
*
* Should be used for flat-shaded polygons.
*/
float3 calc_poly_normal(int face_index) const;
Span<int> get_face_uv_indices(const int face_index) const
{
if (uv_coords_.is_empty()) {
return {};
}
BLI_assert(face_index < export_mesh_->faces_num);
return corner_to_uv_index_.as_span().slice(mesh_faces_[face_index]);
}
/**
* Find the unique normals of the mesh and stores them in a member variable.
* Also stores the indices into that vector with for each loop.
* Also stores the indices into that vector with for each corner.
*/
void store_normal_coords_and_indices();
/* Get normals calculate by store_normal_coords_and_indices. */
const Vector<float3> &get_normal_coords() const
Span<float3> get_normal_coords() const
{
return normal_coords_;
}
/**
* Calculate a polygon's polygon/loop normal indices.
* \param face_index: Index of the polygon to calculate indices for.
* \return Vector of normal indices, aligned with vertices of polygon.
* Calculate a face's face/corner normal indices.
* \param face_index: Index of the face to calculate indices for.
* \return Span of normal indices, aligned with vertices of face.
*/
Vector<int> calc_poly_normal_indices(int face_index) const;
Span<int> get_face_normal_indices(const int face_index) const
{
if (corner_to_normal_index_.is_empty()) {
return {};
}
const IndexRange face = mesh_faces_[face_index];
return corner_to_normal_index_.as_span().slice(face);
}
/**
* Find the most representative vertex group of a polygon.
* Find the most representative vertex group of a face.
*
* This adds up vertex group weights, and the group with the largest
* weight sum across the polygon is the one returned.
* weight sum across the face is the one returned.
*
* 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 face_index, MutableSpan<float> group_weights) const;
int16_t get_face_deform_group_index(int face_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.
*/
const char *get_poly_deform_group_name(int16_t def_group_index) const;
const char *get_face_deform_group_name(int16_t def_group_index) const;
/**
* Calculate the order in which the polygons should be written into the file (sorted by material
* Calculate the order in which the faces should be written into the file (sorted by material
* index).
*/
void calc_poly_order();
void calc_face_order();
/**
* Remap polygon index according to polygon writing order.
* When materials are not being written, the polygon order array
* Remap face index according to face writing order.
* When materials are not being written, the face order array
* might be empty, in which case remap is a no-op.
*/
int remap_face_index(int i) const
{
return i < 0 || i >= poly_order_.size() ? i : poly_order_[i];
return i < 0 || i >= face_order_.size() ? i : face_order_[i];
}
const Mesh *get_mesh() const

View File

@ -185,7 +185,7 @@ static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_a
index_offsets.append(offsets);
offsets.vertex_offset += obj.tot_vertices();
offsets.uv_vertex_offset += obj.tot_uv_vertices();
offsets.normal_offset += obj.tot_normal_indices();
offsets.normal_offset += obj.get_normal_coords().size();
}
/* Parallel over meshes: main result writing. */
@ -202,10 +202,10 @@ static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_a
obj.calc_smooth_groups(export_params.smooth_groups_bitflags);
}
if (export_params.export_materials) {
obj.calc_poly_order();
obj.calc_face_order();
}
if (export_params.export_normals) {
obj_writer.write_poly_normals(fh, obj);
obj_writer.write_normals(fh, obj);
}
if (export_params.export_uv) {
obj_writer.write_uv_coords(fh, obj);
@ -219,7 +219,7 @@ static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_a
}
return mtl_writer->mtlmaterial_name((*obj_mtlindices)[s]);
};
obj_writer.write_poly_elements(fh, index_offsets[i], obj, matname_fn);
obj_writer.write_face_elements(fh, index_offsets[i], obj, matname_fn);
}
obj_writer.write_edges_indices(fh, index_offsets[i], obj);

Some files were not shown because too many files have changed in this diff Show More