Brush Assets: Switch to a single "Brush" active tool #117491

Merged
Hans Goudey merged 11 commits from HooglyBoogly/blender:brush-assets-single-tool into brush-assets-project 2024-01-26 21:08:24 +01:00
149 changed files with 3239 additions and 877 deletions
Showing only changes of commit ac29b9cf1b - 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

@ -1778,6 +1778,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