Anim: run bezier handle calculation in parallel #119388

Merged
Christoph Lendenfeld merged 21 commits from ChrisLend/blender:thread_recalc_handles into main 2024-05-07 10:43:03 +02:00
92 changed files with 1472 additions and 655 deletions
Showing only changes of commit 3aa506f119 - Show all commits

5
.gitmodules vendored
View File

@ -18,6 +18,11 @@
path = lib/windows_x64
url = https://projects.blender.org/blender/lib-windows_x64.git
branch = main
[submodule "lib/windows_arm64"]
update = none
path = lib/windows_arm64
url = https://projects.blender.org/blender/lib-windows_arm64.git
branch = main
[submodule "release/datafiles/assets"]
path = release/datafiles/assets
url = https://projects.blender.org/blender/blender-assets.git

View File

@ -1,6 +1,5 @@
"""
Using Python Argument Parsing
-----------------------------
**Using Python Argument Parsing**
This example shows how the Python ``argparse`` module can be used with a custom command.

View File

@ -1,6 +1,5 @@
"""
Custom Commands
---------------
**Custom Commands**
Registering commands makes it possible to conveniently expose command line
functionality via commands passed to (``-c`` / ``--command``).

View File

@ -252,8 +252,7 @@ def main():
name, tp = arg
tp_sub = None
else:
print(arg)
assert 0
assert False, "unreachable, unsupported 'arg' length found %d" % len(arg)
tp_str = ""
@ -322,8 +321,7 @@ def main():
# but think the idea is that that pointer is for any type?
tp_str = ":class:`bpy.types.bpy_struct`"
else:
print("Can't find", vars_dict_reverse[tp_sub])
assert 0
assert False, "unreachable, unknown type %r" % vars_dict_reverse[tp_sub]
elif tp == BMO_OP_SLOT_ELEMENT_BUF:
assert tp_sub is not None
@ -362,11 +360,9 @@ def main():
elif tp_sub == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL:
tp_str += "unknown internal data, not compatible with python"
else:
print("Can't find", vars_dict_reverse[tp_sub])
assert 0
assert False, "unreachable, unknown type %r" % vars_dict_reverse[tp_sub]
else:
print("Can't find", vars_dict_reverse[tp])
assert 0
assert False, "unreachable, unknown type %r" % vars_dict_reverse[tp]
args_wash.append((name, default_value, tp_str, comment))
return args_wash

View File

@ -198,7 +198,7 @@ void device_cuda_info(vector<DeviceInfo> &devices)
VLOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE)
VLOG_INFO << "Device with id \"" << info.id << "\" is supporting "
VLOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}

View File

@ -211,7 +211,7 @@ void device_hip_info(vector<DeviceInfo> &devices)
VLOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE)
VLOG_INFO << "Device with id \"" << info.id << "\" is supporting "
VLOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}

View File

@ -89,7 +89,7 @@ void device_metal_info(vector<DeviceInfo> &devices)
VLOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE)
VLOG_INFO << "Device with id \"" << info.id << "\" is supporting "
VLOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}
}

View File

@ -134,7 +134,7 @@ static void device_iterator_cb(
VLOG_INFO << "Added device \"" << info.description << "\" with id \"" << info.id << "\".";
if (info.denoisers & DENOISER_OPENIMAGEDENOISE)
VLOG_INFO << "Device with id \"" << info.id << "\" is supporting "
VLOG_INFO << "Device with id \"" << info.id << "\" supports "
<< denoiserTypeToHumanReadable(DENOISER_OPENIMAGEDENOISE) << ".";
}
#endif

1
lib/windows_arm64 Submodule

@ -0,0 +1 @@
Subproject commit dc33a8704935c49dd7a8cf49197fdf42ff52622d

View File

@ -297,7 +297,7 @@ def axis_conversion(from_forward='Y', from_up='Z', to_forward='Y', to_up='Z'):
for i, axis_lut in enumerate(_axis_convert_lut):
if value in axis_lut:
return Matrix(_axis_convert_matrix[i])
assert 0
assert False, "unreachable"
def axis_conversion_ensure(operator, forward_attr, up_attr):

View File

@ -375,7 +375,7 @@ def draw_keymaps(context, layout):
rowsub.menu("USERPREF_MT_keyconfigs", text=text)
rowsub.operator("wm.keyconfig_preset_add", text="", icon='ADD')
rowsub.operator("wm.keyconfig_preset_add", text="", icon='REMOVE').remove_active = True
rowsub.operator("wm.keyconfig_preset_remove", text="", icon='REMOVE')
rowsub = split.row(align=True)
rowsub.operator("preferences.keyconfig_import", text="Import...", icon='IMPORT')

View File

@ -768,7 +768,7 @@ def km_window(params):
("wm.search_menu", {"type": 'SPACE', "value": 'PRESS'}, None),
)
else:
assert False
assert False, "unreachable"
return keymap
@ -3530,7 +3530,7 @@ def km_frames(params):
("screen.animation_play", {"type": 'SPACE', "value": 'PRESS'}, None),
)
else:
assert False
assert False, "unreachable"
items.extend([
("screen.animation_play", {"type": 'SPACE', "value": 'PRESS', "shift": True, "ctrl": True},

View File

@ -147,8 +147,7 @@ class AddTorus(Operator, object_utils.AddObjectHelper):
)
major_radius: FloatProperty(
name="Major Radius",
description=("Radius from the origin to the "
"center of the cross sections"),
description="Radius from the origin to the center of the cross sections",
soft_min=0.0, soft_max=100.0,
min=0.0, max=10_000.0,
default=1.0,

View File

@ -760,8 +760,7 @@ class CLIP_OT_setup_tracking_scene(Operator):
def setup_space(space):
space.show_backdrop = True
CLIP_spaces_walk(context, True, 'NODE_EDITOR', 'NODE_EDITOR',
setup_space)
CLIP_spaces_walk(context, True, 'NODE_EDITOR', 'NODE_EDITOR', setup_space)
sc = context.space_data
scene = context.scene
@ -812,8 +811,7 @@ class CLIP_OT_setup_tracking_scene(Operator):
tree.links.new(movieclip.outputs["Image"], distortion.inputs["Image"])
if need_stabilization:
tree.links.new(distortion.outputs["Image"],
stabilize.inputs["Image"])
tree.links.new(distortion.outputs["Image"], stabilize.inputs["Image"])
tree.links.new(stabilize.outputs["Image"], scale.inputs["Image"])
else:
tree.links.new(distortion.outputs["Image"], scale.inputs["Image"])

View File

@ -42,8 +42,7 @@ class ConsoleExec(Operator):
if execute is not None:
return execute(context, self.interactive)
else:
print("Error: bpy.ops.console.execute_%s - not found" %
sc.language)
print("Error: bpy.ops.console.execute_%s - not found" % sc.language)
return {'FINISHED'}
@ -65,8 +64,7 @@ class ConsoleAutocomplete(Operator):
if autocomplete:
return autocomplete(context)
else:
print("Error: bpy.ops.console.autocomplete_%s - not found" %
sc.language)
print("Error: bpy.ops.console.autocomplete_%s - not found" % sc.language)
return {'FINISHED'}
@ -88,8 +86,7 @@ class ConsoleCopyAsScript(Operator):
if copy_as_script:
return copy_as_script(context)
else:
print("Error: copy_as_script - not found for %r" %
sc.language)
print("Error: copy_as_script - not found for %r" % sc.language)
return {'FINISHED'}
@ -115,8 +112,7 @@ class ConsoleBanner(Operator):
if banner:
return banner(context)
else:
print("Error: bpy.ops.console.banner_%s - not found" %
sc.language)
print("Error: bpy.ops.console.banner_%s - not found" % sc.language)
return {'FINISHED'}
@ -144,8 +140,7 @@ class ConsoleLanguage(Operator):
bpy.ops.console.banner()
# insert a new blank line
bpy.ops.console.history_append(text="", current_character=0,
remove_duplicates=True)
bpy.ops.console.history_append(text="", current_character=0, remove_duplicates=True)
return {'FINISHED'}

View File

@ -171,9 +171,7 @@ class MeshMirrorUV(Operator):
self.report({'WARNING'},
rpt_("%d mesh(es) with no active UV layer, "
"%d duplicates found in %d mesh(es), mirror may be incomplete")
% (total_no_active_UV,
total_duplicates,
meshes_with_duplicates))
% (total_no_active_UV, total_duplicates, meshes_with_duplicates))
elif total_no_active_UV:
self.report({'WARNING'},
rpt_("%d mesh(es) with no active UV layer")

View File

@ -58,8 +58,7 @@ class NodeAddOperator:
# convert mouse position to the View2D for later node placement
if context.region.type == 'WINDOW':
# convert mouse position to the View2D for later node placement
space.cursor_location_from_region(
event.mouse_region_x, event.mouse_region_y)
space.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y)
else:
space.cursor_location = tree.view_center

View File

@ -223,8 +223,7 @@ class SubdivisionSet(Operator):
)
relative: BoolProperty(
name="Relative",
description=("Apply the subdivision surface level as an offset "
"relative to the current level"),
description="Apply the subdivision surface level as an offset relative to the current level",
default=False,
)
@ -288,8 +287,7 @@ class SubdivisionSet(Operator):
mod = obj.modifiers.new("Subdivision", 'SUBSURF')
mod.levels = level
except BaseException:
self.report({'WARNING'},
"Modifiers cannot be added to object: " + obj.name)
self.report({'WARNING'}, "Modifiers cannot be added to object: " + obj.name)
for obj in context.selected_editable_objects:
set_object_subd(obj)
@ -325,8 +323,7 @@ class ShapeTransfer(Operator):
)
use_clamp: BoolProperty(
name="Clamp Offset",
description=("Clamp the transformation to the distance each "
"vertex moves in the original shape"),
description="Clamp the transformation to the distance each vertex moves in the original shape",
default=False,
)
@ -447,8 +444,7 @@ class ShapeTransfer(Operator):
if use_clamp:
# clamp to the same movement as the original
# breaks copy between different scaled meshes.
len_from = (orig_shape_coords[i] -
orig_coords[i]).length
len_from = (orig_shape_coords[i] - orig_coords[i]).length
ofs = co - target_coords[i]
ofs.length = len_from
co = target_coords[i] + ofs

View File

@ -101,13 +101,7 @@ def worldspace_bounds_from_object_data(depsgraph, obj):
return Vector((left, front, up)), Vector((right, back, down))
def align_objects(context,
align_x,
align_y,
align_z,
align_mode,
relative_to,
bb_quality):
def align_objects(context, align_x, align_y, align_z, align_mode, relative_to, bb_quality):
depsgraph = context.evaluated_depsgraph_get()
scene = context.scene
@ -115,8 +109,7 @@ def align_objects(context,
cursor = scene.cursor.location
# We are accessing runtime data such as evaluated bounding box, so we need to
# be sure it is properly updated and valid (bounding box might be lost on operator
# redo).
# be sure it is properly updated and valid (bounding box might be lost on operator redo).
context.view_layer.update()
Left_Front_Up_SEL = [0.0, 0.0, 0.0]

View File

@ -277,9 +277,7 @@ class QuickExplode(ObjectModeOperator, Operator):
for obj in mesh_objects:
if obj.particle_systems:
self.report({'ERROR'},
rpt_("Object %r already has a "
"particle system") % obj.name)
self.report({'ERROR'}, rpt_("Object %r already has a " "particle system") % obj.name)
return {'CANCELLED'}
@ -518,8 +516,7 @@ class QuickSmoke(ObjectModeOperator, Operator):
# Add Principled Volume
node_principled = nodes.new(type='ShaderNodeVolumePrincipled')
node_principled.location = grid_location(4, 1)
links.new(node_principled.outputs["Volume"],
node_out.inputs["Volume"])
links.new(node_principled.outputs["Volume"], node_out.inputs["Volume"])
node_principled.inputs["Density"].default_value = 5.0

View File

@ -6,8 +6,7 @@ from bpy.types import Operator
from mathutils import Vector
def randomize_selected(context, seed, delta,
loc, rot, scale, scale_even, _scale_min):
def randomize_selected(context, seed, delta, loc, rot, scale, scale_even, _scale_min):
import random
from random import uniform
@ -100,8 +99,7 @@ class RandomizeLocRotSize(Operator):
)
use_delta: BoolProperty(
name="Transform Delta",
description=("Randomize delta transform values "
"instead of regular transform"),
description="Randomize delta transform values instead of regular transform",
default=False,
)
use_loc: BoolProperty(
@ -111,8 +109,7 @@ class RandomizeLocRotSize(Operator):
)
loc: FloatVectorProperty(
name="Location",
description=("Maximum distance the objects "
"can spread over each axis"),
description="Maximum distance the objects can spread over each axis",
min=-100.0,
max=100.0,
default=(0.0, 0.0, 0.0),
@ -174,8 +171,7 @@ class RandomizeLocRotSize(Operator):
# scale_min = self.scale_min
scale_min = 0
randomize_selected(context, seed, delta,
loc, rot, scale, scale_even, scale_min)
randomize_selected(context, seed, delta, loc, rot, scale, scale_even, scale_min)
return {'FINISHED'}

View File

@ -120,9 +120,7 @@ class AddPresetBase:
if is_xml:
import rna_xml
rna_xml.xml_file_write(context,
filepath,
preset_menu_class.preset_xml_map)
rna_xml.xml_file_write(context, filepath, preset_menu_class.preset_xml_map)
else:
def rna_recursive_attr_expand(value, rna_path_step, level):
@ -169,15 +167,10 @@ class AddPresetBase:
name = preset_menu_class.bl_label
# fairly sloppy but convenient.
filepath = bpy.utils.preset_find(name,
self.preset_subdir,
ext=ext)
filepath = bpy.utils.preset_find(name, self.preset_subdir, ext=ext)
if not filepath:
filepath = bpy.utils.preset_find(name,
self.preset_subdir,
display_name=True,
ext=ext)
filepath = bpy.utils.preset_find(name, self.preset_subdir, display_name=True, ext=ext)
if not filepath:
return {'CANCELLED'}
@ -257,9 +250,7 @@ class ExecutePreset(Operator):
elif ext == ".xml":
import rna_xml
rna_xml.xml_file_run(context,
filepath,
preset_class.preset_xml_map)
rna_xml.xml_file_run(context, filepath, preset_class.preset_xml_map)
if hasattr(preset_class, "post_cb"):
preset_class.post_cb(context)
@ -563,17 +554,44 @@ class AddPresetNodeColor(AddPresetBase, Operator):
class AddPresetInterfaceTheme(AddPresetBase, Operator):
"""Add or remove a theme preset"""
"""Add a custom theme to the preset list"""
bl_idname = "wm.interface_theme_preset_add"
bl_label = "Add Theme Preset"
bl_label = "Add Theme"
preset_menu = "USERPREF_MT_interface_theme_presets"
preset_subdir = "interface_theme"
class RemovePresetInterfaceTheme(AddPresetBase, Operator):
"""Remove a custom theme from the preset list"""
bl_idname = "wm.interface_theme_preset_remove"
bl_label = "Remove Theme"
preset_menu = "USERPREF_MT_interface_theme_presets"
preset_subdir = "interface_theme"
remove_active: BoolProperty(
default=True,
options={'HIDDEN', 'SKIP_SAVE'},
)
@classmethod
def poll(cls, context):
from bpy.utils import is_path_builtin
preset_menu_class = getattr(bpy.types, cls.preset_menu)
name = preset_menu_class.bl_label
filepath = bpy.utils.preset_find(name, cls.preset_subdir, ext=".xml")
if not bool(filepath) or is_path_builtin(filepath):
cls.poll_message_set("Built-in themes cannot be removed")
return False
return True
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event, title="Remove Custom Theme", confirm_text="Delete")
class AddPresetKeyconfig(AddPresetBase, Operator):
"""Add or remove a Key-config Preset"""
"""Add a custom keymap configuration to the preset list"""
bl_idname = "wm.keyconfig_preset_add"
bl_label = "Add Keyconfig Preset"
bl_label = "Add Custom Keymap Configuration"
preset_menu = "USERPREF_MT_keyconfigs"
preset_subdir = "keyconfig"
@ -581,16 +599,43 @@ class AddPresetKeyconfig(AddPresetBase, Operator):
bpy.ops.preferences.keyconfig_export(filepath=filepath)
bpy.utils.keyconfig_set(filepath)
class RemovePresetKeyconfig(AddPresetBase, Operator):
"""Remove a custom keymap configuration from the preset list"""
bl_idname = "wm.keyconfig_preset_remove"
bl_label = "Remove Keymap Configuration"
preset_menu = "USERPREF_MT_keyconfigs"
preset_subdir = "keyconfig"
remove_active: BoolProperty(
default=True,
options={'HIDDEN', 'SKIP_SAVE'},
)
@classmethod
def poll(cls, context):
from bpy.utils import is_path_builtin
keyconfigs = bpy.context.window_manager.keyconfigs
preset_menu_class = getattr(bpy.types, cls.preset_menu)
name = keyconfigs.active.name
filepath = bpy.utils.preset_find(name, cls.preset_subdir, ext=".py")
if not bool(filepath) or is_path_builtin(filepath):
cls.poll_message_set("Built-in keymap configurations cannot be removed")
return False
return True
def pre_cb(self, context):
keyconfigs = bpy.context.window_manager.keyconfigs
if self.remove_active:
preset_menu_class = getattr(bpy.types, self.preset_menu)
preset_menu_class.bl_label = keyconfigs.active.name
preset_menu_class = getattr(bpy.types, self.preset_menu)
preset_menu_class.bl_label = keyconfigs.active.name
def post_cb(self, context):
keyconfigs = bpy.context.window_manager.keyconfigs
if self.remove_active:
keyconfigs.remove(keyconfigs.active)
keyconfigs.remove(keyconfigs.active)
def invoke(self, context, event):
return context.window_manager.invoke_confirm(
self, event, title="Remove Keymap Configuration", confirm_text="Delete")
class AddPresetOperator(AddPresetBase, Operator):
@ -811,7 +856,9 @@ classes = (
AddPresetFluid,
AddPresetHairDynamics,
AddPresetInterfaceTheme,
RemovePresetInterfaceTheme,
AddPresetKeyconfig,
RemovePresetKeyconfig,
AddPresetNodeColor,
AddPresetOperator,
AddPresetRender,

View File

@ -434,8 +434,7 @@ class RandomizeUVTransform(Operator):
)
loc: FloatVectorProperty(
name="Location",
description=("Maximum distance the objects "
"can spread over each axis"),
description="Maximum distance the objects can spread over each axis",
min=-100.0,
max=100.0,
size=2,

View File

@ -155,8 +155,7 @@ class VIEW3D_OT_edit_mesh_extrude_move(Operator):
return {'FINISHED'}
def execute(self, context):
return VIEW3D_OT_edit_mesh_extrude_move.extrude_region(
self, context, False, self.dissolve_and_intersect)
return VIEW3D_OT_edit_mesh_extrude_move.extrude_region(self, context, False, self.dissolve_and_intersect)
def invoke(self, context, _event):
return self.execute(context)

View File

@ -2084,8 +2084,7 @@ class WM_OT_properties_edit_value(Operator):
rna_item = eval("context.%s" % self.data_path)
if WM_OT_properties_edit.get_property_type(rna_item, self.property_name) == 'PYTHON':
self.eval_string = WM_OT_properties_edit.convert_custom_property_to_string(rna_item,
self.property_name)
self.eval_string = WM_OT_properties_edit.convert_custom_property_to_string(rna_item, self.property_name)
else:
self.eval_string = ""
@ -2960,7 +2959,7 @@ class WM_OT_batch_rename(Operator):
elif method == 'SUFFIX':
name = name + text
else:
assert 0
assert False, "unreachable"
elif ty == 'STRIP':
chars = action.strip_chars
@ -3005,9 +3004,9 @@ class WM_OT_batch_rename(Operator):
elif method == 'TITLE':
name = name.title()
else:
assert 0
assert False, "unreachable"
else:
assert 0
assert False, "unreachable"
return name
def _data_update(self, context):

View File

@ -24,10 +24,8 @@ class CollectionButtonsPanel:
def lineart_make_line_type_entry(col, line_type, text_disp, expand, search_from):
col.prop(line_type, "use", text=text_disp)
if line_type.use and expand:
col.prop_search(line_type, "layer", search_from,
"layers", icon='GREASEPENCIL')
col.prop_search(line_type, "material", search_from,
"materials", icon='SHADING_TEXTURE')
col.prop_search(line_type, "layer", search_from, "layers", icon='GREASEPENCIL')
col.prop_search(line_type, "material", search_from, "materials", icon='SHADING_TEXTURE')
class COLLECTION_PT_collection_flags(CollectionButtonsPanel, Panel):

View File

@ -87,20 +87,13 @@ class DATA_UL_bone_collections(UIList):
active_bone = armature.edit_bones.active or armature.bones.active
has_active_bone = active_bone and bcoll.name in active_bone.collections
layout.prop(bcoll, "name", text="", emboss=False,
icon='DOT' if has_active_bone else 'BLANK1')
layout.prop(bcoll, "name", text="", emboss=False, icon='DOT' if has_active_bone else 'BLANK1')
if armature.override_library:
icon = 'LIBRARY_DATA_OVERRIDE' if bcoll.is_local_override else 'BLANK1'
layout.prop(
bcoll,
"is_local_override",
text="",
emboss=False,
icon=icon)
layout.prop(bcoll, "is_local_override", text="", emboss=False, icon=icon)
layout.prop(bcoll, "is_visible", text="", emboss=False,
icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
layout.prop(bcoll, "is_visible", text="", emboss=False, icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
class DATA_PT_bone_collections(ArmatureButtonsPanel, Panel):

View File

@ -289,15 +289,12 @@ class BONE_PT_collections(BoneButtonsPanel, Panel):
# Sub-layout that's dimmed when the bone collection's own visibility flag doesn't matter.
sub_visible = row.row(align=True)
sub_visible.active = (not is_solo_active) and bcoll.is_visible_ancestors
sub_visible.prop(bcoll, "is_visible", text="",
icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
sub_visible.prop(bcoll, "is_visible", text="", icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
row.prop(bcoll, "is_solo", text="",
icon='SOLO_ON' if bcoll.is_solo else 'SOLO_OFF')
row.prop(bcoll, "is_solo", text="", icon='SOLO_ON' if bcoll.is_solo else 'SOLO_OFF')
# Unassignment operator, less safe so with a bit of spacing.
props = bcoll_row.operator("armature.collection_unassign_named",
text="", icon='X')
props = bcoll_row.operator("armature.collection_unassign_named", text="", icon='X')
props.name = bcoll.name
props.bone_name = bone.name
@ -410,8 +407,7 @@ class BONE_PT_display_custom_shape(BoneButtonsPanel, Panel):
sub.prop(pchan, "custom_shape_translation", text="Translation")
sub.prop(pchan, "custom_shape_rotation_euler", text="Rotation")
sub.prop_search(pchan, "custom_shape_transform",
ob.pose, "bones", text="Override Transform")
sub.prop_search(pchan, "custom_shape_transform", ob.pose, "bones", text="Override Transform")
sub.prop(pchan, "use_custom_shape_bone_size")
sub.separator()
@ -559,8 +555,7 @@ class BONE_PT_custom_props(BoneButtonsPanel, rna_prop_ui.PropertyPanel, Panel):
@classmethod
def _poll(cls, context):
context_path = cls._get_context_path(context)
rna_item, _context_member = rna_prop_ui.rna_idprop_context_value(
context, context_path, cls._property_type)
rna_item, _context_member = rna_prop_ui.rna_idprop_context_value(context, context_path, cls._property_type)
return bool(rna_item)
def draw(self, context):

View File

@ -156,8 +156,7 @@ class DATA_PT_camera_stereoscopy(CameraButtonsPanel, Panel):
@classmethod
def poll(cls, context):
render = context.scene.render
return (super().poll(context) and render.use_multiview and
render.views_format == 'STEREO_3D')
return (super().poll(context) and render.use_multiview and render.views_format == 'STEREO_3D')
def draw(self, context):
layout = self.layout

View File

@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Panel, Menu
from bpy.types import Panel, Menu, UIList
from rna_prop_ui import PropertyPanel
@ -27,6 +27,39 @@ class LayerDataButtonsPanel:
return grease_pencil and grease_pencil.layers.active
class GREASE_PENCIL_UL_masks(UIList):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
mask = item
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row(align=True)
row.prop(mask, "name", text="", emboss=False, icon_value=icon)
row.prop(mask, "invert", text="", emboss=False)
row.prop(mask, "hide", text="", emboss=False)
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.prop(mask, "name", text="", emboss=False, icon_value=icon)
class GREASE_PENCIL_MT_layer_mask_add(Menu):
bl_label = "Add Mask"
def draw(self, context):
layout = self.layout
grease_pencil = context.grease_pencil
active_layer = grease_pencil.layers.active
found = False
for layer in grease_pencil.layers:
if layer == active_layer or layer.name in active_layer.mask_layers:
continue
found = True
layout.operator("grease_pencil.layer_mask_add", text=layer.name).name = layer.name
if not found:
layout.label(text="No layers to add")
class DATA_PT_context_grease_pencil(DataButtonsPanel, Panel):
bl_label = ""
bl_options = {'HIDE_HEADER'}
@ -91,14 +124,58 @@ class DATA_PT_grease_pencil_layers(DataButtonsPanel, Panel):
col.operator("grease_pencil.layer_remove", icon='REMOVE', text="")
# Layer main properties
if layer:
layout.use_property_split = True
layout.use_property_decorate = True
col = layout.column(align=True)
if not layer:
return
row = layout.row(align=True)
row.prop(layer, "opacity", text="Opacity", slider=True)
layout.use_property_split = True
layout.use_property_decorate = True
col = layout.column(align=True)
# Layer main properties
row = layout.row(align=True)
row.prop(layer, "blend_mode", text="Blend Mode")
row = layout.row(align=True)
row.prop(layer, "opacity", text="Opacity", slider=True)
class DATA_PT_grease_pencil_layer_masks(LayerDataButtonsPanel, Panel):
bl_label = "Masks"
bl_parent_id = "DATA_PT_grease_pencil_layers"
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
grease_pencil = context.grease_pencil
layer = grease_pencil.layers.active
self.layout.prop(layer, "use_masks", text="")
def draw(self, context):
layout = self.layout
grease_pencil = context.grease_pencil
layer = grease_pencil.layers.active
layout = self.layout
layout.enabled = layer.use_masks
if not layer:
return
rows = 4
row = layout.row()
col = row.column()
col.template_list("GREASE_PENCIL_UL_masks", "", layer, "mask_layers", layer.mask_layers,
"active_mask_index", rows=rows, sort_lock=True)
col = row.column(align=True)
col.menu("GREASE_PENCIL_MT_layer_mask_add", icon='ADD', text="")
col.operator("grease_pencil.layer_mask_remove", icon='REMOVE', text="")
col.separator()
sub = col.column(align=True)
sub.operator("grease_pencil.layer_mask_reorder", icon='TRIA_UP', text="").direction = 'UP'
sub.operator("grease_pencil.layer_mask_reorder", icon='TRIA_DOWN', text="").direction = 'DOWN'
class DATA_PT_grease_pencil_layer_transform(LayerDataButtonsPanel, Panel):
@ -153,16 +230,33 @@ class DATA_PT_grease_pencil_layer_relations(LayerDataButtonsPanel, Panel):
col.prop_search(layer, "viewlayer_render", context.scene, "view_layers", text="View Layer")
class DATA_PT_grease_pencil_settings(DataButtonsPanel, Panel):
bl_label = "Settings"
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
grease_pencil = context.grease_pencil
col = layout.column(align=True)
col.prop(grease_pencil, "stroke_depth_order", text="Stroke Depth Order")
class DATA_PT_grease_pencil_custom_props(DataButtonsPanel, PropertyPanel, Panel):
_context_path = "object.data"
_property_type = bpy.types.GreasePencilv3
classes = (
GREASE_PENCIL_UL_masks,
GREASE_PENCIL_MT_layer_mask_add,
DATA_PT_context_grease_pencil,
DATA_PT_grease_pencil_layers,
DATA_PT_grease_pencil_layer_masks,
DATA_PT_grease_pencil_layer_transform,
DATA_PT_grease_pencil_layer_relations,
DATA_PT_grease_pencil_settings,
DATA_PT_grease_pencil_custom_props,
GREASE_PENCIL_MT_grease_pencil_add_layer_extra,
)

View File

@ -637,8 +637,7 @@ class MESH_UL_color_attributes(UIList, ColorAttributesListBase):
sub = split.row()
sub.alignment = 'RIGHT'
sub.active = False
sub.label(text="%s%s" % (iface_(domain_name), iface_(data_type.name)),
translate=False)
sub.label(text="%s%s" % (iface_(domain_name), iface_(data_type.name)), translate=False)
active_render = _index == data.color_attributes.render_color_index

View File

@ -754,8 +754,7 @@ class VIEWLAYER_PT_freestyle_linestyle_color(ViewLayerFreestyleLineStyle, Panel)
elif modifier.type == 'MATERIAL':
row = box.row()
row.prop(modifier, "material_attribute",
text="Material Attribute")
row.prop(modifier, "material_attribute", text="Material Attribute")
sub = box.column()
sub.prop(modifier, "use_ramp")
if modifier.material_attribute in {'LINE', 'DIFF', 'SPEC'}:

View File

@ -36,8 +36,7 @@ def draw_mask_context_menu(layout, _context):
class MASK_UL_layers(UIList):
def draw_item(self, _context, layout, _data, item, icon,
_active_data, _active_propname, _index):
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
# assert(isinstance(item, bpy.types.MaskLayer)
mask = item
if self.layout_type in {'DEFAULT', 'COMPACT'}:
@ -204,8 +203,7 @@ class MASK_PT_point:
row = col.row()
row.prop(parent, "type", expand=True)
col.prop_search(parent, "parent", tracking,
"objects", icon='OBJECT_DATA', text="Object")
col.prop_search(parent, "parent", tracking, "objects", icon='OBJECT_DATA', text="Object")
tracks_list = "tracks" if parent.type == 'POINT_TRACK' else "plane_tracks"

View File

@ -391,8 +391,7 @@ class SmoothStrokePanel(BrushPanel):
brush = settings.brush
self.layout.use_property_split = False
self.layout.prop(brush, "use_smooth_stroke",
text=self.bl_label if self.is_popover else "")
self.layout.prop(brush, "use_smooth_stroke", text=self.bl_label if self.is_popover else "")
def draw(self, context):
layout = self.layout
@ -1344,8 +1343,7 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
if gp_settings.use_pressure and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True, use_negative_slope=True)
row = layout.row(align=True)
row.prop(gp_settings, "pen_strength", slider=True)
@ -1353,8 +1351,7 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
if gp_settings.use_strength_pressure and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_strength", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_strength", brush=True, use_negative_slope=True)
if brush.gpencil_tool == 'TINT':
row = layout.row(align=True)
@ -1415,8 +1412,7 @@ def brush_basic_grease_pencil_paint_settings(layout, context, brush, *, compact=
if brush.use_pressure_size and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True, use_negative_slope=True)
UnifiedPaintPanel.prop_unified(
layout,
@ -1431,8 +1427,7 @@ def brush_basic_grease_pencil_paint_settings(layout, context, brush, *, compact=
if brush.use_pressure_strength and not compact:
col = layout.column()
col.template_curve_mapping(gp_settings, "curve_strength", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_strength", brush=True, use_negative_slope=True)
if grease_pencil_tool == 'DRAW':
layout.prop(gp_settings, "active_smooth_factor")

View File

@ -87,9 +87,7 @@ class PARTICLE_MT_context_menu(Menu):
props.remove_target_particles = True
if psys is not None and psys.settings.type == 'HAIR':
layout.operator(
"curves.convert_from_particle_system",
text="Convert to Curves")
layout.operator("curves.convert_from_particle_system", text="Convert to Curves")
layout.separator()

View File

@ -151,8 +151,7 @@ class VIEWLAYER_PT_eevee_layer_passes_light(ViewLayerButtonsPanel, Panel):
col.prop(view_layer, "use_pass_emit", text="Emission")
col.prop(view_layer, "use_pass_environment")
col.prop(view_layer, "use_pass_shadow")
col.prop(view_layer, "use_pass_ambient_occlusion",
text="Ambient Occlusion")
col.prop(view_layer, "use_pass_ambient_occlusion", text="Ambient Occlusion")
class VIEWLAYER_PT_eevee_next_layer_passes_light(ViewLayerButtonsPanel, Panel):
@ -184,8 +183,7 @@ class VIEWLAYER_PT_eevee_next_layer_passes_light(ViewLayerButtonsPanel, Panel):
col.prop(view_layer, "use_pass_emit", text="Emission")
col.prop(view_layer, "use_pass_environment")
col.prop(view_layer, "use_pass_shadow")
col.prop(view_layer, "use_pass_ambient_occlusion",
text="Ambient Occlusion")
col.prop(view_layer, "use_pass_ambient_occlusion", text="Ambient Occlusion")
col = layout.column()
col.active = view_layer.use_pass_ambient_occlusion
@ -230,8 +228,7 @@ class ViewLayerAOVPanel(ViewLayerButtonsPanel, Panel):
row = layout.row()
col = row.column()
col.template_list("VIEWLAYER_UL_aov", "aovs", view_layer,
"aovs", view_layer, "active_aov_index", rows=3)
col.template_list("VIEWLAYER_UL_aov", "aovs", view_layer, "aovs", view_layer, "active_aov_index", rows=3)
col = row.column()
sub = col.column(align=True)
@ -240,8 +237,7 @@ class ViewLayerAOVPanel(ViewLayerButtonsPanel, Panel):
aov = view_layer.active_aov
if aov and not aov.is_valid:
layout.label(
text="Conflicts with another render pass with the same name", icon='ERROR')
layout.label(text="Conflicts with another render pass with the same name", icon='ERROR')
class VIEWLAYER_PT_layer_passes_aov(ViewLayerAOVPanel):
@ -271,8 +267,7 @@ class ViewLayerCryptomattePanel(ViewLayerButtonsPanel, Panel):
col.prop(view_layer, "pass_cryptomatte_depth", text="Levels")
if context.engine == 'BLENDER_EEVEE':
col.prop(view_layer, "use_pass_cryptomatte_accurate",
text="Accurate Mode")
col.prop(view_layer, "use_pass_cryptomatte_accurate", text="Accurate Mode")
class VIEWLAYER_PT_layer_passes_cryptomatte(ViewLayerCryptomattePanel, Panel):

View File

@ -17,8 +17,7 @@ from bl_ui.properties_grease_pencil_common import (
class CLIP_UL_tracking_objects(UIList):
def draw_item(self, _context, layout, _data, item, _icon,
_active_data, _active_propname, _index):
def draw_item(self, _context, layout, _data, item, _icon, _active_data, _active_propname, _index):
# assert(isinstance(item, bpy.types.MovieTrackingObject)
tobj = item
if self.layout_type in {'DEFAULT', 'COMPACT'}:
@ -142,8 +141,7 @@ class CLIP_HT_header(Header):
props = row.operator("clip.track_markers", text="", icon='TRACKING_BACKWARDS_SINGLE')
props.backwards = True
props.sequence = False
props = row.operator("clip.track_markers", text="",
icon='TRACKING_BACKWARDS')
props = row.operator("clip.track_markers", text="", icon='TRACKING_BACKWARDS')
props.backwards = True
props.sequence = True
props = row.operator("clip.track_markers", text="", icon='TRACKING_FORWARDS')
@ -171,9 +169,7 @@ class CLIP_HT_header(Header):
r = active_object.reconstruction
if r.is_valid and sc.view == 'CLIP':
layout.label(text=rpt_("Solve error: %.2f px") %
(r.average_error),
translate=False)
layout.label(text=rpt_("Solve error: %.2f px") % (r.average_error), translate=False)
row = layout.row()
row.prop(sc, "pivot_point", text="", icon_only=True)
@ -433,16 +429,12 @@ class CLIP_PT_tracking_settings(CLIP_PT_tracking_panel, Panel):
row = col.row(align=True)
row.use_property_split = False
row.prop(settings, "use_default_red_channel",
text="R", toggle=True)
row.prop(settings, "use_default_green_channel",
text="G", toggle=True)
row.prop(settings, "use_default_blue_channel",
text="B", toggle=True)
row.prop(settings, "use_default_red_channel", text="R", toggle=True)
row.prop(settings, "use_default_green_channel", text="G", toggle=True)
row.prop(settings, "use_default_blue_channel", text="B", toggle=True)
col.separator()
col.operator("clip.track_settings_as_default",
text="Copy from Active Track")
col.operator("clip.track_settings_as_default", text="Copy from Active Track")
class CLIP_PT_tracking_settings_extras(CLIP_PT_tracking_panel, Panel):
@ -486,8 +478,7 @@ class CLIP_PT_tools_tracking(CLIP_PT_tracking_panel, Panel):
props = row.operator("clip.track_markers", text="", icon='TRACKING_BACKWARDS_SINGLE')
props.backwards = True
props.sequence = False
props = row.operator("clip.track_markers", text="",
icon='TRACKING_BACKWARDS')
props = row.operator("clip.track_markers", text="", icon='TRACKING_BACKWARDS')
props.backwards = True
props.sequence = True
props = row.operator("clip.track_markers", text="", icon='TRACKING_FORWARDS')
@ -712,8 +703,7 @@ class CLIP_PT_objects(CLIP_PT_clip_view_panel, Panel):
tracking = sc.clip.tracking
row = layout.row()
row.template_list("CLIP_UL_tracking_objects", "", tracking, "objects",
tracking, "active_object_index", rows=1)
row.template_list("CLIP_UL_tracking_objects", "", tracking, "objects", tracking, "active_object_index", rows=1)
sub = row.column(align=True)
@ -765,8 +755,7 @@ class CLIP_PT_track(CLIP_PT_tracking_panel, Panel):
row.prop(act_track, "use_grayscale_preview", text="B/W", toggle=True)
row.separator()
row.prop(act_track, "use_alpha_preview",
text="", toggle=True, icon='IMAGE_ALPHA')
row.prop(act_track, "use_alpha_preview", text="", toggle=True, icon='IMAGE_ALPHA')
layout.prop(act_track, "weight")
layout.prop(act_track, "weight_stab")
@ -810,8 +799,7 @@ class CLIP_PT_plane_track(CLIP_PT_tracking_panel, Panel):
layout.prop(active_track, "name")
layout.prop(active_track, "use_auto_keying")
row = layout.row()
row.template_ID(
active_track, "image", new="image.new", open="image.open")
row.template_ID(active_track, "image", new="image.new", open="image.open")
row.menu("CLIP_MT_plane_track_image_context_menu", icon='DOWNARROW_HLT', text="")
row = layout.row()
@ -1642,10 +1630,8 @@ class CLIP_MT_tracking_context_menu(Menu):
layout.separator()
layout.operator("clip.disable_markers",
text="Disable Markers").action = 'DISABLE'
layout.operator("clip.disable_markers",
text="Enable Markers").action = 'ENABLE'
layout.operator("clip.disable_markers", text="Disable Markers").action = 'DISABLE'
layout.operator("clip.disable_markers", text="Enable Markers").action = 'ENABLE'
layout.separator()
@ -1655,8 +1641,7 @@ class CLIP_MT_tracking_context_menu(Menu):
layout.separator()
layout.operator("clip.lock_tracks", text="Lock Tracks").action = 'LOCK'
layout.operator("clip.lock_tracks",
text="Unlock Tracks").action = 'UNLOCK'
layout.operator("clip.lock_tracks", text="Unlock Tracks").action = 'UNLOCK'
layout.separator()

View File

@ -73,9 +73,7 @@ class CONSOLE_MT_language(Menu):
languages.sort()
for language in languages:
layout.operator("console.language",
text=language.title(),
translate=False).language = language
layout.operator("console.language", text=language.title(), translate=False).language = language
class CONSOLE_MT_console(Menu):

View File

@ -179,8 +179,7 @@ class DOPESHEET_PT_filters(DopesheetFilterPopoverBase, Panel):
if ds_mode in {'DOPESHEET', 'ACTION', 'GPENCIL'}:
layout.separator()
generic_filters_only = ds_mode != 'DOPESHEET'
DopesheetFilterPopoverBase.draw_search_filters(context, layout,
generic_filters_only=generic_filters_only)
DopesheetFilterPopoverBase.draw_search_filters(context, layout, generic_filters_only=generic_filters_only)
if ds_mode == 'DOPESHEET':
layout.separator()

View File

@ -142,12 +142,10 @@ class FILEBROWSER_PT_filter(FileBrowserPanel, Panel):
else:
row = col.row()
row.label(icon='FILE_BLEND')
row.prop(params, "use_filter_blender",
text=".blend Files", toggle=False)
row.prop(params, "use_filter_blender", text=".blend Files", toggle=False)
row = col.row()
row.label(icon='FILE_BACKUP')
row.prop(params, "use_filter_backup",
text="Backup .blend Files", toggle=False)
row.prop(params, "use_filter_backup", text="Backup .blend Files", toggle=False)
row = col.row()
row.label(icon='FILE_IMAGE')
row.prop(params, "use_filter_image", text="Image Files", toggle=False)
@ -156,8 +154,7 @@ class FILEBROWSER_PT_filter(FileBrowserPanel, Panel):
row.prop(params, "use_filter_movie", text="Movie Files", toggle=False)
row = col.row()
row.label(icon='FILE_SCRIPT')
row.prop(params, "use_filter_script",
text="Script Files", toggle=False)
row.prop(params, "use_filter_script", text="Script Files", toggle=False)
row = col.row()
row.label(icon='FILE_FONT')
row.prop(params, "use_filter_font", text="Font Files", toggle=False)
@ -176,8 +173,7 @@ class FILEBROWSER_PT_filter(FileBrowserPanel, Panel):
if is_lib_browser:
row = col.row()
row.label(icon='BLANK1') # Indentation
row.prop(params, "use_filter_blendid",
text="Blender IDs", toggle=False)
row.prop(params, "use_filter_blendid", text="Blender IDs", toggle=False)
if params.use_filter_blendid:
row = col.row()
row.label(icon='BLANK1') # Indentation
@ -281,10 +277,8 @@ class FILEBROWSER_MT_bookmarks_context_menu(Menu):
layout.operator("file.bookmark_cleanup", icon='X', text="Cleanup")
layout.separator()
layout.operator("file.bookmark_move", icon='TRIA_UP_BAR',
text="Move to Top").direction = 'TOP'
layout.operator("file.bookmark_move", icon='TRIA_DOWN_BAR',
text="Move to Bottom").direction = 'BOTTOM'
layout.operator("file.bookmark_move", icon='TRIA_UP_BAR', text="Move to Top").direction = 'TOP'
layout.operator("file.bookmark_move", icon='TRIA_DOWN_BAR', text="Move to Bottom").direction = 'BOTTOM'
class FILEBROWSER_PT_bookmarks_favorites(FileBrowserPanel, Panel):
@ -314,15 +308,12 @@ class FILEBROWSER_PT_bookmarks_favorites(FileBrowserPanel, Panel):
col = row.column(align=True)
col.operator("file.bookmark_add", icon='ADD', text="")
col.operator("file.bookmark_delete", icon='REMOVE', text="")
col.menu("FILEBROWSER_MT_bookmarks_context_menu",
icon='DOWNARROW_HLT', text="")
col.menu("FILEBROWSER_MT_bookmarks_context_menu", icon='DOWNARROW_HLT', text="")
if num_rows > 1:
col.separator()
col.operator("file.bookmark_move", icon='TRIA_UP',
text="").direction = 'UP'
col.operator("file.bookmark_move", icon='TRIA_DOWN',
text="").direction = 'DOWN'
col.operator("file.bookmark_move", icon='TRIA_UP', text="").direction = 'UP'
col.operator("file.bookmark_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
else:
layout.operator("file.bookmark_add", icon='ADD')
@ -553,10 +544,8 @@ class FILEBROWSER_MT_context_menu(FileBrowserMenu, Menu):
layout.separator()
layout.operator("file.filenum", text="Increase Number",
icon='ADD').increment = 1
layout.operator("file.filenum", text="Decrease Number",
icon='REMOVE').increment = -1
layout.operator("file.filenum", text="Increase Number", icon='ADD').increment = 1
layout.operator("file.filenum", text="Decrease Number", icon='REMOVE').increment = -1
layout.separator()

View File

@ -194,8 +194,7 @@ class IMAGE_MT_image(Menu):
ima = sima.image
show_render = sima.show_render
layout.operator("image.new", text="New",
text_ctxt=i18n_contexts.id_image)
layout.operator("image.new", text="New", text_ctxt=i18n_contexts.id_image)
layout.operator("image.open", text="Open...", icon='FILE_FOLDER')
layout.operator("image.read_viewlayers")

View File

@ -81,9 +81,7 @@ class INFO_MT_area(Menu):
layout.separator()
layout.operator("screen.screen_full_area")
layout.operator(
"screen.screen_full_area",
text="Toggle Fullscreen Area").use_hide_panels = True
layout.operator("screen.screen_full_area", text="Toggle Fullscreen Area").use_hide_panels = True
layout.operator("screen.area_dupli")
layout.separator()

View File

@ -223,8 +223,7 @@ class OUTLINER_MT_collection(Menu):
space = context.space_data
layout.operator("outliner.collection_new", text="New",
text_ctxt=i18n_contexts.id_collection).nested = True
layout.operator("outliner.collection_new", text="New", text_ctxt=i18n_contexts.id_collection).nested = True
layout.operator("outliner.collection_duplicate", text="Duplicate Collection")
layout.operator("outliner.collection_duplicate_linked", text="Duplicate Linked")
layout.operator("outliner.id_copy", text="Copy", icon='COPYDOWN')

View File

@ -31,14 +31,12 @@ class TEXT_HT_header(Header):
row.operator("text.resolve_conflict", text="", icon='QUESTION')
row = layout.row(align=True)
row.template_ID(st, "text", new="text.new",
unlink="text.unlink", open="text.open")
row.template_ID(st, "text", new="text.new", unlink="text.unlink", open="text.open")
if text:
is_osl = text.name.endswith((".osl", ".osl"))
if is_osl:
row.operator("node.shader_script_update",
text="", icon='FILE_REFRESH')
row.operator("node.shader_script_update", text="", icon='FILE_REFRESH')
else:
row = layout.row()
row.active = is_syntax_highlight_supported
@ -174,10 +172,8 @@ class TEXT_PT_find(Panel):
row = layout.row(align=True)
if not st.text:
row.active = False
row.prop(st, "use_match_case", text="Case",
text_ctxt=i18n_contexts.id_text, toggle=True)
row.prop(st, "use_find_wrap", text="Wrap",
text_ctxt=i18n_contexts.id_text, toggle=True)
row.prop(st, "use_match_case", text="Case", text_ctxt=i18n_contexts.id_text, toggle=True)
row.prop(st, "use_find_wrap", text="Wrap", text_ctxt=i18n_contexts.id_text, toggle=True)
row.prop(st, "use_find_all", text="All", toggle=True)
@ -253,8 +249,7 @@ class TEXT_MT_text(Menu):
st = context.space_data
text = st.text
layout.operator("text.new", text="New",
text_ctxt=i18n_contexts.id_text, icon='FILE_NEW')
layout.operator("text.new", text="New", text_ctxt=i18n_contexts.id_text, icon='FILE_NEW')
layout.operator("text.open", text="Open...", icon='FILE_FOLDER')
if text:

View File

@ -228,7 +228,7 @@ class TimelinePanelButtons:
class TIME_PT_playback(TimelinePanelButtons, Panel):
bl_label = "Playback"
bl_region_type = 'HEADER'
bl_ui_units_x = 11
bl_ui_units_x = 13
def draw(self, context):
layout = self.layout

View File

@ -226,8 +226,7 @@ class ToolSelectPanelHelper:
@staticmethod
def _tool_class_from_space_type(space_type):
return next(
(cls for cls in ToolSelectPanelHelper.__subclasses__()
if cls.bl_space_type == space_type),
(cls for cls in ToolSelectPanelHelper.__subclasses__() if cls.bl_space_type == space_type),
None,
)

View File

@ -33,17 +33,9 @@ class TOPBAR_HT_upper_bar(Header):
layout.separator()
if not screen.show_fullscreen:
layout.template_ID_tabs(
window, "workspace",
new="workspace.add",
menu="TOPBAR_MT_workspace_menu",
)
layout.template_ID_tabs(window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu")
else:
layout.operator(
"screen.back_to_previous",
icon='SCREEN_BACK',
text="Back to Previous",
)
layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")
def draw_right(self, context):
layout = self.layout
@ -58,8 +50,7 @@ class TOPBAR_HT_upper_bar(Header):
layout.template_running_jobs()
# Active workspace view-layer is retrieved through window, not through workspace.
layout.template_ID(window, "scene", new="scene.new",
unlink="scene.delete")
layout.template_ID(window, "scene", new="scene.new", unlink="scene.delete")
row = layout.row(align=True)
row.template_search(
@ -84,11 +75,9 @@ class TOPBAR_PT_tool_settings_extra(Panel):
layout = self.layout
# Get the active tool
space_type, mode = ToolSelectPanelHelper._tool_key_from_context(
context)
space_type, mode = ToolSelectPanelHelper._tool_key_from_context(context)
cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
item, tool, _ = cls._tool_get_active(
context, space_type, mode, with_icon=True)
item, tool, _ = cls._tool_get_active(context, space_type, mode, with_icon=True)
if item is None:
return
@ -110,8 +99,7 @@ class TOPBAR_PT_tool_fallback(Panel):
ToolSelectPanelHelper.draw_fallback_tool_items(layout, context)
if tool_settings.workspace_tool_type == 'FALLBACK':
tool = context.tool
ToolSelectPanelHelper.draw_active_tool_fallback(
context, layout, tool)
ToolSelectPanelHelper.draw_active_tool_fallback(context, layout, tool)
class TOPBAR_PT_gpencil_layers(Panel):
@ -156,8 +144,7 @@ class TOPBAR_PT_gpencil_layers(Panel):
srow = col.row(align=True)
srow.prop(gpl, "opacity", text="Opacity", slider=True)
srow.prop(gpl, "use_mask_layer", text="",
icon='CLIPUV_DEHLT' if gpl.use_mask_layer else 'CLIPUV_HLT')
srow.prop(gpl, "use_mask_layer", text="", icon='CLIPUV_DEHLT' if gpl.use_mask_layer else 'CLIPUV_HLT')
srow = col.row(align=True)
srow.prop(gpl, "use_lights", text="Lights")
@ -170,25 +157,20 @@ class TOPBAR_PT_gpencil_layers(Panel):
gpl = context.active_gpencil_layer
if gpl:
sub.menu("GPENCIL_MT_layer_context_menu",
icon='DOWNARROW_HLT', text="")
sub.menu("GPENCIL_MT_layer_context_menu", icon='DOWNARROW_HLT', text="")
if len(gpd.layers) > 1:
col.separator()
sub = col.column(align=True)
sub.operator("gpencil.layer_move",
icon='TRIA_UP', text="").type = 'UP'
sub.operator("gpencil.layer_move",
icon='TRIA_DOWN', text="").type = 'DOWN'
sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
col.separator()
sub = col.column(align=True)
sub.operator("gpencil.layer_isolate", icon='HIDE_OFF',
text="").affect_visibility = True
sub.operator("gpencil.layer_isolate", icon='LOCKED',
text="").affect_visibility = False
sub.operator("gpencil.layer_isolate", icon='HIDE_OFF', text="").affect_visibility = True
sub.operator("gpencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False
class TOPBAR_MT_editor_menus(Menu):
@ -224,8 +206,7 @@ class TOPBAR_MT_blender(Menu):
layout.separator()
layout.operator("preferences.app_template_install",
text="Install Application Template...")
layout.operator("preferences.app_template_install", text="Install Application Template...")
layout.separator()
@ -343,16 +324,11 @@ class TOPBAR_MT_file_new(Menu):
# Draw application templates.
if not use_more:
props = layout.operator(
"wm.read_homefile", text="General", icon=icon)
props = layout.operator("wm.read_homefile", text="General", icon=icon)
props.app_template = ""
for d in paths:
props = layout.operator(
"wm.read_homefile",
text=bpy.path.display_name(iface_(d)),
icon=icon,
)
props = layout.operator("wm.read_homefile", text=bpy.path.display_name(iface_(d)), icon=icon)
props.app_template = d
layout.operator_context = 'EXEC_DEFAULT'
@ -396,8 +372,7 @@ class TOPBAR_MT_file_defaults(Menu):
layout.operator("wm.save_homefile")
if app_template:
display_name = bpy.path.display_name(iface_(app_template))
props = layout.operator("wm.read_factory_settings",
text="Load Factory Blender Settings")
props = layout.operator("wm.read_factory_settings", text="Load Factory Blender Settings")
props.app_template = app_template
props = layout.operator("wm.read_factory_settings",
text=iface_("Load Factory %s Settings",
@ -435,8 +410,7 @@ class TOPBAR_MT_templates_more(Menu):
bl_label = "Templates"
def draw(self, context):
bpy.types.TOPBAR_MT_file_new.draw_ex(
self.layout, context, use_more=True)
bpy.types.TOPBAR_MT_file_new.draw_ex(self.layout, context, use_more=True)
class TOPBAR_MT_file_import(Menu):
@ -550,10 +524,8 @@ class TOPBAR_MT_render(Menu):
rd = context.scene.render
layout.operator("render.render", text="Render Image",
icon='RENDER_STILL').use_viewport = True
props = layout.operator(
"render.render", text="Render Animation", icon='RENDER_ANIMATION')
layout.operator("render.render", text="Render Image", icon='RENDER_STILL').use_viewport = True
props = layout.operator("render.render", text="Render Animation", icon='RENDER_ANIMATION')
props.animation = True
props.use_viewport = True
@ -612,8 +584,7 @@ class TOPBAR_MT_edit(Menu):
layout.separator()
layout.operator("screen.userpref_show",
text="Preferences...", icon='PREFERENCES')
layout.operator("screen.userpref_show", text="Preferences...", icon='PREFERENCES')
class TOPBAR_MT_window(Menu):
@ -634,10 +605,8 @@ class TOPBAR_MT_window(Menu):
layout.separator()
layout.operator("screen.workspace_cycle",
text="Next Workspace").direction = 'NEXT'
layout.operator("screen.workspace_cycle",
text="Previous Workspace").direction = 'PREV'
layout.operator("screen.workspace_cycle", text="Next Workspace").direction = 'NEXT'
layout.operator("screen.workspace_cycle", text="Previous Workspace").direction = 'PREV'
layout.separator()
@ -718,8 +687,7 @@ class TOPBAR_MT_file_context_menu(Menu):
layout.separator()
layout.operator("screen.userpref_show",
text="Preferences...", icon='PREFERENCES')
layout.operator("screen.userpref_show", text="Preferences...", icon='PREFERENCES')
class TOPBAR_MT_workspace_menu(Menu):
@ -728,26 +696,21 @@ class TOPBAR_MT_workspace_menu(Menu):
def draw(self, _context):
layout = self.layout
layout.operator("workspace.duplicate",
text="Duplicate", icon='DUPLICATE')
layout.operator("workspace.duplicate", text="Duplicate", icon='DUPLICATE')
if len(bpy.data.workspaces) > 1:
layout.operator("workspace.delete", text="Delete", icon='REMOVE')
layout.separator()
layout.operator("workspace.reorder_to_front",
text="Reorder to Front", icon='TRIA_LEFT_BAR')
layout.operator("workspace.reorder_to_back",
text="Reorder to Back", icon='TRIA_RIGHT_BAR')
layout.operator("workspace.reorder_to_front", text="Reorder to Front", icon='TRIA_LEFT_BAR')
layout.operator("workspace.reorder_to_back", text="Reorder to Back", icon='TRIA_RIGHT_BAR')
layout.separator()
# For key binding discoverability.
props = layout.operator("screen.workspace_cycle",
text="Previous Workspace")
props = layout.operator("screen.workspace_cycle", text="Previous Workspace")
props.direction = 'PREV'
props = layout.operator(
"screen.workspace_cycle", text="Next Workspace")
props = layout.operator("screen.workspace_cycle", text="Next Workspace")
props.direction = 'NEXT'
@ -762,8 +725,7 @@ class TOPBAR_PT_gpencil_primitive(Panel):
layout = self.layout
# Curve
layout.template_curve_mapping(
settings, "thickness_primitive_curve", brush=True)
layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
# Only a popover

View File

@ -247,7 +247,7 @@ class USERPREF_PT_interface_text(InterfacePanel, CenterAlignMixIn, Panel):
class USERPREF_PT_interface_translation(InterfacePanel, CenterAlignMixIn, Panel):
bl_label = "Translation"
bl_label = "Language"
bl_translation_context = i18n_contexts.id_windowmanager
@classmethod
@ -260,7 +260,7 @@ class USERPREF_PT_interface_translation(InterfacePanel, CenterAlignMixIn, Panel)
layout.prop(view, "language")
col = layout.column(heading="Affect")
col = layout.column(heading="Translate")
col.active = (bpy.app.translations.locale != "en_US")
col.prop(view, "use_translate_tooltips", text="Tooltips")
col.prop(view, "use_translate_interface", text="Interface")
@ -876,7 +876,7 @@ class USERPREF_PT_theme(ThemePanel, Panel):
row = split.row(align=True)
row.menu("USERPREF_MT_interface_theme_presets", text=USERPREF_MT_interface_theme_presets.bl_label)
row.operator("wm.interface_theme_preset_add", text="", icon='ADD')
row.operator("wm.interface_theme_preset_add", text="", icon='REMOVE').remove_active = True
row.operator("wm.interface_theme_preset_remove", text="", icon='REMOVE')
row = split.row(align=True)
row.operator("preferences.theme_install", text="Install...", icon='IMPORT')
@ -1021,8 +1021,7 @@ class USERPREF_PT_theme_interface_transparent_checker(ThemePanel, CenterAlignMix
theme = context.preferences.themes[0]
ui = theme.user_interface
flow = layout.grid_flow(
row_major=False, columns=0, even_columns=True, even_rows=False, align=False)
flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=False)
col = flow.column(align=True)
col.prop(ui, "transparent_checker_primary")

View File

@ -843,8 +843,7 @@ class VIEW3D_HT_header(Header):
# Curve edit sub-mode.
row = layout.row(align=True)
row.prop(gpd, "use_curve_edit", text="",
icon='IPO_BEZIER')
row.prop(gpd, "use_curve_edit", text="", icon='IPO_BEZIER')
sub = row.row(align=True)
sub.active = gpd.use_curve_edit
sub.popover(
@ -974,8 +973,7 @@ class VIEW3D_HT_header(Header):
row.popover(panel="VIEW3D_PT_slots_projectpaint", icon=icon)
row.popover(
panel="VIEW3D_PT_mask",
icon=VIEW3D_HT_header._texture_mask_icon(
tool_settings.image_paint),
icon=VIEW3D_HT_header._texture_mask_icon(tool_settings.image_paint),
text="")
else:
# Transform settings depending on tool header visibility
@ -3138,9 +3136,7 @@ class VIEW3D_MT_object_apply(Menu):
text_ctxt=i18n_contexts.default,
).target = 'MESH'
layout.operator("object.duplicates_make_real")
layout.operator("object.parent_inverse_apply",
text="Parent Inverse",
text_ctxt=i18n_contexts.default)
layout.operator("object.parent_inverse_apply", text="Parent Inverse", text_ctxt=i18n_contexts.default)
layout.template_node_operator_asset_menu_items(catalog_path="Object/Apply")
@ -4113,8 +4109,7 @@ class VIEW3D_MT_bone_collections(Menu):
layout.separator()
layout.operator("armature.collection_show_all")
props = layout.operator("armature.collection_create_and_assign",
text="Assign to New Collection")
props = layout.operator("armature.collection_create_and_assign", text="Assign to New Collection")
props.name = "New Collection"
@ -4496,12 +4491,9 @@ class VIEW3D_MT_edit_mesh_context_menu(Menu):
col.separator()
col.operator("view3d.edit_mesh_extrude_move_normal",
text="Extrude Faces")
col.operator("view3d.edit_mesh_extrude_move_shrink_fatten",
text="Extrude Faces Along Normals")
col.operator("mesh.extrude_faces_move",
text="Extrude Individual Faces")
col.operator("view3d.edit_mesh_extrude_move_normal", text="Extrude Faces")
col.operator("view3d.edit_mesh_extrude_move_shrink_fatten", text="Extrude Faces Along Normals")
col.operator("mesh.extrude_faces_move", text="Extrude Individual Faces")
col.operator("mesh.inset")
col.operator("mesh.poke")
@ -4561,23 +4553,16 @@ class VIEW3D_MT_edit_mesh_extrude(Menu):
mesh = context.object.data
if mesh.total_face_sel:
layout.operator("view3d.edit_mesh_extrude_move_normal",
text="Extrude Faces")
layout.operator("view3d.edit_mesh_extrude_move_shrink_fatten",
text="Extrude Faces Along Normals")
layout.operator(
"mesh.extrude_faces_move",
text="Extrude Individual Faces")
layout.operator("view3d.edit_mesh_extrude_manifold_normal",
text="Extrude Manifold")
layout.operator("view3d.edit_mesh_extrude_move_normal", text="Extrude Faces")
layout.operator("view3d.edit_mesh_extrude_move_shrink_fatten", text="Extrude Faces Along Normals")
layout.operator("mesh.extrude_faces_move", text="Extrude Individual Faces")
layout.operator("view3d.edit_mesh_extrude_manifold_normal", text="Extrude Manifold")
if mesh.total_edge_sel and (select_mode[0] or select_mode[1]):
layout.operator("mesh.extrude_edges_move",
text="Extrude Edges")
layout.operator("mesh.extrude_edges_move", text="Extrude Edges")
if mesh.total_vert_sel and select_mode[0]:
layout.operator("mesh.extrude_vertices_move",
text="Extrude Vertices")
layout.operator("mesh.extrude_vertices_move", text="Extrude Vertices")
layout.separator()
@ -4742,10 +4727,8 @@ class VIEW3D_MT_edit_mesh_faces(Menu):
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("view3d.edit_mesh_extrude_move_normal",
text="Extrude Faces")
layout.operator("view3d.edit_mesh_extrude_move_shrink_fatten",
text="Extrude Faces Along Normals")
layout.operator("view3d.edit_mesh_extrude_move_normal", text="Extrude Faces")
layout.operator("view3d.edit_mesh_extrude_move_shrink_fatten", text="Extrude Faces Along Normals")
layout.operator("mesh.extrude_faces_move", text="Extrude Individual Faces")
layout.separator()
@ -5665,8 +5648,7 @@ class VIEW3D_MT_edit_gpencil_stroke(Menu):
layout.separator()
layout.operator_menu_enum("gpencil.stroke_join", "type", text="Join",
text_ctxt=i18n_contexts.id_gpencil)
layout.operator_menu_enum("gpencil.stroke_join", "type", text="Join", text_ctxt=i18n_contexts.id_gpencil)
layout.separator()
@ -8156,8 +8138,7 @@ class VIEW3D_MT_gpencil_edit_context_menu(Menu):
# Removal Operators
col.operator("gpencil.stroke_merge_by_distance").use_unselected = True
col.operator_menu_enum("gpencil.stroke_join", "type", text="Join",
text_ctxt=i18n_contexts.id_gpencil)
col.operator_menu_enum("gpencil.stroke_join", "type", text="Join", text_ctxt=i18n_contexts.id_gpencil)
col.operator("gpencil.stroke_split", text="Split")
col.operator("gpencil.stroke_separate", text="Separate").mode = 'STROKE'

View File

@ -743,8 +743,7 @@ class VIEW3D_PT_stencil_projectpaint(Panel):
def draw_header(self, context):
ipaint = context.tool_settings.image_paint
self.layout.prop(ipaint, "use_stencil_layer",
text=self.bl_label if self.is_popover else "")
self.layout.prop(ipaint, "use_stencil_layer", text=self.bl_label if self.is_popover else "")
def draw(self, context):
layout = self.layout
@ -926,8 +925,7 @@ class VIEW3D_PT_tools_brush_falloff_frontface(View3DPaintPanel, Panel):
settings = self.paint_settings(context)
brush = settings.brush
self.layout.prop(brush, "use_frontface_falloff",
text=self.bl_label if self.is_popover else "")
self.layout.prop(brush, "use_frontface_falloff", text=self.bl_label if self.is_popover else "")
def draw(self, context):
settings = self.paint_settings(context)
@ -956,8 +954,7 @@ class VIEW3D_PT_tools_brush_falloff_normal(View3DPaintPanel, Panel):
tool_settings = context.tool_settings
ipaint = tool_settings.image_paint
self.layout.prop(ipaint, "use_normal_falloff",
text=self.bl_label if self.is_popover else "")
self.layout.prop(ipaint, "use_normal_falloff", text=self.bl_label if self.is_popover else "")
def draw(self, context):
tool_settings = context.tool_settings
@ -1406,8 +1403,7 @@ class VIEW3D_PT_tools_imagepaint_options_cavity(Panel):
tool_settings = context.tool_settings
ipaint = tool_settings.image_paint
self.layout.prop(ipaint, "use_cavity",
text=self.bl_label if self.is_popover else "")
self.layout.prop(ipaint, "use_cavity", text=self.bl_label if self.is_popover else "")
def draw(self, context):
layout = self.layout
@ -1417,8 +1413,7 @@ class VIEW3D_PT_tools_imagepaint_options_cavity(Panel):
layout.active = ipaint.use_cavity
layout.template_curve_mapping(ipaint, "cavity_curve", brush=True,
use_negative_slope=True)
layout.template_curve_mapping(ipaint, "cavity_curve", brush=True, use_negative_slope=True)
# TODO, move to space_view3d.py
@ -1706,8 +1701,7 @@ class VIEW3D_PT_tools_grease_pencil_brush_advanced(View3DPanel, Panel):
elif brush.gpencil_tool == 'FILL':
row = col.row(align=True)
row.prop(gp_settings, "fill_draw_mode", text="Boundary",
text_ctxt=i18n_contexts.id_gpencil)
row.prop(gp_settings, "fill_draw_mode", text="Boundary", text_ctxt=i18n_contexts.id_gpencil)
row.prop(
gp_settings,
"show_fill_boundary",
@ -1771,8 +1765,7 @@ class VIEW3D_PT_tools_grease_pencil_brush_stabilizer(Panel, View3DPanel):
brush = context.tool_settings.gpencil_paint.brush
gp_settings = brush.gpencil_settings
self.layout.use_property_split = False
self.layout.prop(gp_settings, "use_settings_stabilizer",
text=self.bl_label if self.is_popover else "")
self.layout.prop(gp_settings, "use_settings_stabilizer", text=self.bl_label if self.is_popover else "")
def draw(self, context):
layout = self.layout
@ -1805,8 +1798,7 @@ class VIEW3D_PT_tools_grease_pencil_brush_post_processing(View3DPanel, Panel):
brush = context.tool_settings.gpencil_paint.brush
gp_settings = brush.gpencil_settings
self.layout.use_property_split = False
self.layout.prop(gp_settings, "use_settings_postprocess",
text=self.bl_label if self.is_popover else "")
self.layout.prop(gp_settings, "use_settings_postprocess", text=self.bl_label if self.is_popover else "")
def draw(self, context):
layout = self.layout
@ -1861,8 +1853,7 @@ class VIEW3D_PT_tools_grease_pencil_brush_random(View3DPanel, Panel):
brush = context.tool_settings.gpencil_paint.brush
gp_settings = brush.gpencil_settings
self.layout.use_property_split = False
self.layout.prop(gp_settings, "use_settings_random",
text=self.bl_label if self.is_popover else "")
self.layout.prop(gp_settings, "use_settings_random", text=self.bl_label if self.is_popover else "")
def draw(self, context):
layout = self.layout
@ -1882,24 +1873,21 @@ class VIEW3D_PT_tools_grease_pencil_brush_random(View3DPanel, Panel):
row.prop(gp_settings, "use_stroke_random_radius", text="", icon='GP_SELECT_STROKES')
row.prop(gp_settings, "use_random_press_radius", text="", icon='STYLUS_PRESSURE')
if gp_settings.use_random_press_radius and self.is_popover is False:
col.template_curve_mapping(gp_settings, "curve_random_pressure", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_random_pressure", brush=True, use_negative_slope=True)
row = col.row(align=True)
row.prop(gp_settings, "random_strength", text="Strength", slider=True)
row.prop(gp_settings, "use_stroke_random_strength", text="", icon='GP_SELECT_STROKES')
row.prop(gp_settings, "use_random_press_strength", text="", icon='STYLUS_PRESSURE')
if gp_settings.use_random_press_strength and self.is_popover is False:
col.template_curve_mapping(gp_settings, "curve_random_strength", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_random_strength", brush=True, use_negative_slope=True)
row = col.row(align=True)
row.prop(gp_settings, "uv_random", text="UV", slider=True)
row.prop(gp_settings, "use_stroke_random_uv", text="", icon='GP_SELECT_STROKES')
row.prop(gp_settings, "use_random_press_uv", text="", icon='STYLUS_PRESSURE')
if gp_settings.use_random_press_uv and self.is_popover is False:
col.template_curve_mapping(gp_settings, "curve_random_uv", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_random_uv", brush=True, use_negative_slope=True)
col.separator()
@ -1910,24 +1898,21 @@ class VIEW3D_PT_tools_grease_pencil_brush_random(View3DPanel, Panel):
row.prop(gp_settings, "use_stroke_random_hue", text="", icon='GP_SELECT_STROKES')
row.prop(gp_settings, "use_random_press_hue", text="", icon='STYLUS_PRESSURE')
if gp_settings.use_random_press_hue and self.is_popover is False:
col1.template_curve_mapping(gp_settings, "curve_random_hue", brush=True,
use_negative_slope=True)
col1.template_curve_mapping(gp_settings, "curve_random_hue", brush=True, use_negative_slope=True)
row = col1.row(align=True)
row.prop(gp_settings, "random_saturation_factor", slider=True)
row.prop(gp_settings, "use_stroke_random_sat", text="", icon='GP_SELECT_STROKES')
row.prop(gp_settings, "use_random_press_sat", text="", icon='STYLUS_PRESSURE')
if gp_settings.use_random_press_sat and self.is_popover is False:
col1.template_curve_mapping(gp_settings, "curve_random_saturation", brush=True,
use_negative_slope=True)
col1.template_curve_mapping(gp_settings, "curve_random_saturation", brush=True, use_negative_slope=True)
row = col1.row(align=True)
row.prop(gp_settings, "random_value_factor", slider=True)
row.prop(gp_settings, "use_stroke_random_val", text="", icon='GP_SELECT_STROKES')
row.prop(gp_settings, "use_random_press_val", text="", icon='STYLUS_PRESSURE')
if gp_settings.use_random_press_val and self.is_popover is False:
col1.template_curve_mapping(gp_settings, "curve_random_value", brush=True,
use_negative_slope=True)
col1.template_curve_mapping(gp_settings, "curve_random_value", brush=True, use_negative_slope=True)
col.separator()
@ -1935,8 +1920,7 @@ class VIEW3D_PT_tools_grease_pencil_brush_random(View3DPanel, Panel):
row.prop(gp_settings, "pen_jitter", slider=True)
row.prop(gp_settings, "use_jitter_pressure", text="", icon='STYLUS_PRESSURE')
if gp_settings.use_jitter_pressure and self.is_popover is False:
col.template_curve_mapping(gp_settings, "curve_jitter", brush=True,
use_negative_slope=True)
col.template_curve_mapping(gp_settings, "curve_jitter", brush=True, use_negative_slope=True)
class VIEW3D_PT_tools_grease_pencil_brush_paint_falloff(GreasePencilBrushFalloff, Panel, View3DPaintPanel):

View File

@ -138,7 +138,7 @@ class AssetLibrary {
* Remove an asset from the library that was added using #add_external_asset() or
* #add_local_id_asset(). Can usually be expected to be constant time complexity (worst case may
* differ).
* \note This is save to call if \a asset is freed (dangling reference), will not perform any
* \note This is safe to call if \a asset is freed (dangling reference), will not perform any
* change then.
* \return True on success, false if the asset couldn't be found inside the library (also the
* case when the reference is dangling).

View File

@ -207,14 +207,14 @@ AssetRepresentation &AssetLibrary::add_external_asset(StringRef relative_asset_p
const int id_type,
std::unique_ptr<AssetMetaData> metadata)
{
AssetIdentifier identifier = asset_identifier_from_library(relative_asset_path);
AssetIdentifier identifier = this->asset_identifier_from_library(relative_asset_path);
return asset_storage_->add_external_asset(
std::move(identifier), name, id_type, std::move(metadata), *this);
}
AssetRepresentation &AssetLibrary::add_local_id_asset(StringRef relative_asset_path, ID &id)
{
AssetIdentifier identifier = asset_identifier_from_library(relative_asset_path);
AssetIdentifier identifier = this->asset_identifier_from_library(relative_asset_path);
return asset_storage_->add_local_id_asset(std::move(identifier), id, *this);
}

View File

@ -43,7 +43,7 @@ bool AssetStorage::remove_asset(AssetRepresentation &asset)
return external_assets_.remove_as(&asset);
}
void AssetStorage::remap_ids_and_remove_invalid(const blender::bke::id::IDRemapper &mappings)
void AssetStorage::remap_ids_and_remove_invalid(const bke::id::IDRemapper &mappings)
{
Set<AssetRepresentation *> removed_assets;
@ -61,7 +61,7 @@ void AssetStorage::remap_ids_and_remove_invalid(const blender::bke::id::IDRemapp
}
for (AssetRepresentation *asset : removed_assets) {
remove_asset(*asset);
this->remove_asset(*asset);
}
}

View File

@ -148,6 +148,7 @@ class Layer;
bool is_selected() const; \
void set_selected(bool selected); \
bool use_onion_skinning() const; \
bool use_masks() const; \
bool is_child_of(const LayerGroup &group) const;
/* Implements the forwarding of the methods defined by #TREENODE_COMMON_METHODS. */
@ -192,6 +193,10 @@ class Layer;
{ \
return this->as_node().use_onion_skinning(); \
} \
inline bool class_name::use_masks() const \
{ \
return this->as_node().use_masks(); \
} \
inline bool class_name::is_child_of(const LayerGroup &group) const \
{ \
return this->as_node().is_child_of(group); \
@ -685,6 +690,11 @@ inline bool TreeNode::use_onion_skinning() const
{
return ((this->flag & GP_LAYER_TREE_NODE_USE_ONION_SKINNING) != 0);
}
inline bool TreeNode::use_masks() const
{
return ((this->flag & GP_LAYER_TREE_NODE_HIDE_MASKS) == 0) &&
(!this->parent_group() || this->parent_group()->as_node().use_masks());
}
inline bool TreeNode::is_child_of(const LayerGroup &group) const
{
if (const LayerGroup *parent = this->parent_group()) {

View File

@ -11,6 +11,7 @@
#include "BKE_action.h"
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
@ -628,14 +629,12 @@ LayerMask::LayerMask()
LayerMask::LayerMask(StringRefNull name) : LayerMask()
{
this->layer_name = BLI_strdup(name.c_str());
this->layer_name = BLI_strdup_null(name.c_str());
}
LayerMask::LayerMask(const LayerMask &other) : LayerMask()
{
if (other.layer_name) {
this->layer_name = BLI_strdup(other.layer_name);
}
this->layer_name = BLI_strdup_null(other.layer_name);
this->flag = other.flag;
}
@ -675,6 +674,7 @@ Layer::Layer()
this->viewlayername = nullptr;
BLI_listbase_clear(&this->masks);
this->active_mask_index = 0;
this->runtime = MEM_new<LayerRuntime>(__func__);
}
@ -688,7 +688,11 @@ Layer::Layer(const Layer &other) : Layer()
{
new (&this->base) TreeNode(other.base.wrap());
/* TODO: duplicate masks. */
LISTBASE_FOREACH (GreasePencilLayerMask *, other_mask, &other.masks) {
LayerMask *new_mask = MEM_new<LayerMask>(__func__, *reinterpret_cast<LayerMask *>(other_mask));
BLI_addtail(&this->masks, reinterpret_cast<GreasePencilLayerMask *>(new_mask));
}
this->active_mask_index = other.active_mask_index;
this->blend_mode = other.blend_mode;
this->opacity = other.opacity;
@ -719,9 +723,9 @@ Layer::~Layer()
MEM_SAFE_FREE(this->frames_storage.values);
LISTBASE_FOREACH_MUTABLE (GreasePencilLayerMask *, mask, &this->masks) {
MEM_SAFE_FREE(mask->layer_name);
MEM_freeN(mask);
MEM_delete(reinterpret_cast<LayerMask *>(mask));
}
BLI_listbase_clear(&this->masks);
MEM_SAFE_FREE(this->parsubstr);
MEM_SAFE_FREE(this->viewlayername);
@ -2525,8 +2529,21 @@ void GreasePencil::rename_node(blender::bke::greasepencil::TreeNode &node,
if (node.name() == new_name) {
return;
}
node.set_name(node.is_layer() ? unique_layer_name(*this, new_name) :
unique_layer_group_name(*this, new_name));
std::string old_name = node.name();
if (node.is_layer()) {
node.set_name(unique_layer_name(*this, new_name));
BKE_animdata_fix_paths_rename_all(&this->id, "layers", old_name.c_str(), node.name().c_str());
for (bke::greasepencil::Layer *layer : this->layers_for_write()) {
LISTBASE_FOREACH (GreasePencilLayerMask *, mask, &layer->masks) {
if (STREQ(mask->layer_name, old_name.c_str())) {
mask->layer_name = BLI_strdup(node.name().c_str());
}
}
}
}
else if (node.is_group()) {
node.set_name(unique_layer_group_name(*this, new_name));
}
}
static void shrink_customdata(CustomData &data, const int index_to_remove, const int size)

View File

@ -400,6 +400,15 @@ void legacy_gpencil_to_grease_pencil(Main &bmain, GreasePencil &grease_pencil, b
grease_pencil.id.properties = IDP_CopyProperty(gpd.id.properties);
}
/** Convert Grease Pencil data flag. */
SET_FLAG_FROM_TEST(
grease_pencil.flag, (gpd.flag & GP_DATA_EXPAND) != 0, GREASE_PENCIL_ANIM_CHANNEL_EXPANDED);
SET_FLAG_FROM_TEST(grease_pencil.flag,
(gpd.flag & GP_DATA_AUTOLOCK_LAYERS) != 0,
GREASE_PENCIL_AUTOLOCK_LAYERS);
SET_FLAG_FROM_TEST(
grease_pencil.flag, (gpd.draw_mode == GP_DRAWMODE_3D), GREASE_PENCIL_STROKE_ORDER_3D);
int num_drawings = 0;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd.layers) {
num_drawings += BLI_listbase_count(&gpl->frames);
@ -426,6 +435,8 @@ void legacy_gpencil_to_grease_pencil(Main &bmain, GreasePencil &grease_pencil, b
SET_FLAG_FROM_TEST(new_layer.base.flag,
(gpl->onion_flag & GP_LAYER_ONIONSKIN),
GP_LAYER_TREE_NODE_USE_ONION_SKINNING);
SET_FLAG_FROM_TEST(
new_layer.base.flag, (gpl->flag & GP_LAYER_USE_MASK) == 0, GP_LAYER_TREE_NODE_HIDE_MASKS);
new_layer.blend_mode = int8_t(gpl->blend_mode);
@ -440,7 +451,7 @@ void legacy_gpencil_to_grease_pencil(Main &bmain, GreasePencil &grease_pencil, b
/* Convert the layer masks. */
LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl->mask_layers) {
LayerMask *new_mask = MEM_new<LayerMask>(mask->name);
LayerMask *new_mask = MEM_new<LayerMask>(__func__, mask->name);
new_mask->flag = mask->flag;
BLI_addtail(&new_layer.masks, new_mask);
}

View File

@ -1190,7 +1190,7 @@ void blo_do_versions_290(FileData *fd, Library * /*lib*/, Main *bmain)
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Boolean) {
BooleanModifierData *bmd = (BooleanModifierData *)md;
bmd->solver = eBooleanModifierSolver_Fast;
bmd->solver = eBooleanModifierSolver_Float;
bmd->flag = eBooleanModifierFlag_Object;
}
}

View File

@ -34,6 +34,8 @@ void cryptomatte_clear_samples(FilmSample dst)
int layer_len = imageSize(cryptomatte_img).z;
for (int i = 0; i < layer_len; i++) {
imageStore(cryptomatte_img, ivec3(dst.texel, i), vec4(0.0));
/* Ensure stores are visible to later reads. */
imageFence(cryptomatte_img);
}
}
@ -71,4 +73,6 @@ void cryptomatte_store_film_sample(FilmSample dst,
imageStore(cryptomatte_img, img_co, sample_pair);
break;
}
/* Ensure stores are visible to later reads. */
imageFence(cryptomatte_img);
}

View File

@ -61,6 +61,8 @@ void cryptomatte_store_samples(ivec2 texel, int layer, vec2 samples[CRYPTOMATTE_
pass_sample.zw = samples[p * 2 + 1];
imageStore(cryptomatte_img, ivec3(texel, p + layer_id), pass_sample);
}
/* Ensure stores are visible to later reads. */
imageFence(cryptomatte_img);
}
void main()

View File

@ -142,17 +142,10 @@ void film_sample_accum_combined(FilmSample samp, inout vec4 accum, inout float w
weight_accum += weight;
}
#ifdef GPU_METAL
void film_sample_cryptomatte_accum(FilmSample samp,
int layer,
sampler2D tex,
thread vec2 *crypto_samples)
#else
void film_sample_cryptomatte_accum(FilmSample samp,
int layer,
sampler2D tex,
inout vec2 crypto_samples[4])
#endif
{
float hash = texelFetch(tex, samp.texel, 0)[layer];
/* Find existing entry. */
@ -247,11 +240,7 @@ vec2 film_pixel_history_motion_vector(ivec2 texel_sample)
/* \a t is inter-pixel position. 0 means perfectly on a pixel center.
* Returns weights in both dimensions.
* Multiply each dimension weights to get final pixel weights. */
#ifdef GPU_METAL
void film_get_catmull_rom_weights(vec2 t, thread vec2 *weights)
#else
void film_get_catmull_rom_weights(vec2 t, out vec2 weights[4])
#endif
{
vec2 t2 = t * t;
vec2 t3 = t2 * t;

View File

@ -136,6 +136,13 @@ SphericalHarmonicL1 lightprobe_irradiance_sample(
#ifdef IRRADIANCE_GRID_SAMPLING
float random = interlieved_gradient_noise(UTIL_TEXEL, 0.0, 0.0);
random = fract(random + sampling_rng_1D_get(SAMPLING_LIGHTPROBE));
#endif
#ifdef GPU_METAL
/* NOTE: Performs a chunked unroll to avoid the compiler unrolling the entire loop, avoiding
* very high instruction counts and long compilation time. Full unroll results in 90k +
* instructions. Chunked unroll is 5.1k instructions with reduced register pressure, while
* retaining most of the benefits of unrolling. */
# pragma clang loop unroll_count(16)
#endif
for (; i < IRRADIANCE_GRID_MAX; i++) {
/* Last grid is tagged as invalid to stop the iteration. */

View File

@ -17,7 +17,7 @@ void main()
int frame = gl_VertexID + cacheStart;
bool use_custom_color = customColor.x >= 0.0;
finalColor = (use_custom_color) ? vec4(customColor, 1.0) : vec4(1.0);
finalColor = (use_custom_color) ? vec4(customColor, 1.0) : colorVertex;
/* Bias to reduce z fighting with the path */
gl_Position.z -= 1e-4;

View File

@ -221,27 +221,30 @@ AnimData *ANIM_nla_mapping_get(bAnimContext *ac, bAnimListElem *ale)
/* apart from strictly keyframe-related contexts, this shouldn't even happen */
/* XXX: nla and channel here may not be necessary... */
if (ELEM(ac->datatype,
ANIMCONT_ACTION,
ANIMCONT_SHAPEKEY,
ANIMCONT_DOPESHEET,
ANIMCONT_FCURVES,
ANIMCONT_NLA,
ANIMCONT_CHANNEL,
ANIMCONT_TIMELINE))
if (!ELEM(ac->datatype,
ANIMCONT_ACTION,
ANIMCONT_SHAPEKEY,
ANIMCONT_DOPESHEET,
ANIMCONT_FCURVES,
ANIMCONT_NLA,
ANIMCONT_CHANNEL,
ANIMCONT_TIMELINE))
{
/* handling depends on the type of animation-context we've got */
if (ale) {
/* NLA Control Curves occur on NLA strips,
* and shouldn't be subjected to this kind of mapping. */
if (ale->type != ANIMTYPE_NLACURVE) {
return ale->adt;
}
}
return nullptr;
}
/* cannot handle... */
return nullptr;
/* handling depends on the type of animation-context we've got */
if (!ale) {
return nullptr;
}
/* NLA Control Curves occur on NLA strips,
* and shouldn't be subjected to this kind of mapping. */
if (ale->type == ANIMTYPE_NLACURVE) {
return nullptr;
}
return ale->adt;
}
/* ------------------- */

View File

@ -524,6 +524,169 @@ static void GREASE_PENCIL_OT_layer_duplicate(wmOperatorType *ot)
/* properties */
RNA_def_boolean(ot->srna, "empty_keyframes", false, "Empty Keyframes", "Add Empty Keyframes");
}
static int grease_pencil_layer_mask_add_exec(bContext *C, wmOperator *op)
{
using namespace ::blender::bke::greasepencil;
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
if (!grease_pencil.has_active_layer()) {
return OPERATOR_CANCELLED;
}
Layer &active_layer = *grease_pencil.get_active_layer();
int mask_name_length;
char *mask_name = RNA_string_get_alloc(op->ptr, "name", nullptr, 0, &mask_name_length);
BLI_SCOPED_DEFER([&] { MEM_SAFE_FREE(mask_name); });
if (TreeNode *node = grease_pencil.find_node_by_name(mask_name)) {
if (grease_pencil.is_layer_active(&node->as_layer())) {
BKE_report(op->reports, RPT_ERROR, "Cannot add active layer as mask");
return OPERATOR_CANCELLED;
}
if (BLI_findstring(&active_layer.masks,
mask_name,
offsetof(GreasePencilLayerMask, layer_name)) != nullptr)
{
BKE_report(op->reports, RPT_ERROR, "Layer already added");
return OPERATOR_CANCELLED;
}
LayerMask *new_mask = MEM_new<LayerMask>(__func__, mask_name);
BLI_addtail(&active_layer.masks, reinterpret_cast<GreasePencilLayerMask *>(new_mask));
/* Make the newly added mask active. */
active_layer.active_mask_index = BLI_listbase_count(&active_layer.masks) - 1;
}
else {
BKE_report(op->reports, RPT_ERROR, "Unable to find layer to add");
return OPERATOR_CANCELLED;
}
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_SELECTED, &grease_pencil);
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_layer_mask_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add New Mask Layer";
ot->idname = "GREASE_PENCIL_OT_layer_mask_add";
ot->description = "Add new layer as masking";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* callbacks */
ot->exec = grease_pencil_layer_mask_add_exec;
ot->poll = active_grease_pencil_layer_poll;
/* properties */
RNA_def_string(ot->srna, "name", nullptr, 0, "Layer", "Name of the layer");
}
static int grease_pencil_layer_mask_remove_exec(bContext *C, wmOperator * /*op*/)
{
using namespace ::blender::bke::greasepencil;
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
if (!grease_pencil.has_active_layer()) {
return OPERATOR_CANCELLED;
}
Layer &active_layer = *grease_pencil.get_active_layer();
if (GreasePencilLayerMask *mask = reinterpret_cast<GreasePencilLayerMask *>(
BLI_findlink(&active_layer.masks, active_layer.active_mask_index)))
{
BLI_remlink(&active_layer.masks, mask);
MEM_delete(reinterpret_cast<LayerMask *>(mask));
active_layer.active_mask_index = std::max(active_layer.active_mask_index - 1, 0);
}
else {
return OPERATOR_CANCELLED;
}
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_SELECTED, &grease_pencil);
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_layer_mask_remove(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Mask Layer";
ot->idname = "GREASE_PENCIL_OT_layer_mask_remove";
ot->description = "Remove Layer Mask";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* callbacks */
ot->exec = grease_pencil_layer_mask_remove_exec;
ot->poll = active_grease_pencil_layer_poll;
}
enum class LayerMaskMoveDirection : int8_t { Up = -1, Down = 1 };
static int grease_pencil_layer_mask_reorder_exec(bContext *C, wmOperator *op)
{
using namespace ::blender::bke::greasepencil;
Object *object = CTX_data_active_object(C);
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
if (!grease_pencil.has_active_layer()) {
return OPERATOR_CANCELLED;
}
Layer &active_layer = *grease_pencil.get_active_layer();
const int direction = RNA_enum_get(op->ptr, "direction");
bool changed = false;
if (GreasePencilLayerMask *mask = reinterpret_cast<GreasePencilLayerMask *>(
BLI_findlink(&active_layer.masks, active_layer.active_mask_index)))
{
if (BLI_listbase_link_move(&active_layer.masks, mask, direction)) {
active_layer.active_mask_index = std::max(active_layer.active_mask_index + direction, 0);
changed = true;
}
}
else {
return OPERATOR_CANCELLED;
}
if (changed) {
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_SELECTED, &grease_pencil);
}
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_layer_mask_reorder(wmOperatorType *ot)
{
static const EnumPropertyItem enum_direction[] = {
{int(LayerMaskMoveDirection::Up), "UP", 0, "Up", ""},
{int(LayerMaskMoveDirection::Down), "DOWN", 0, "Down", ""},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
ot->name = "Reorder Grease Pencil Layer Mask";
ot->idname = "GREASE_PENCIL_OT_layer_mask_reorder";
ot->description = "Reorder the active Grease Pencil mask layer up/down in the list";
/* api callbacks */
ot->exec = grease_pencil_layer_mask_reorder_exec;
ot->poll = active_grease_pencil_layer_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
ot->prop = RNA_def_enum(ot->srna, "direction", enum_direction, 0, "Direction", "");
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_layers()
@ -540,4 +703,8 @@ void ED_operatortypes_grease_pencil_layers()
WM_operatortype_append(GREASE_PENCIL_OT_layer_duplicate);
WM_operatortype_append(GREASE_PENCIL_OT_layer_group_add);
WM_operatortype_append(GREASE_PENCIL_OT_layer_mask_add);
WM_operatortype_append(GREASE_PENCIL_OT_layer_mask_remove);
WM_operatortype_append(GREASE_PENCIL_OT_layer_mask_reorder);
}

View File

@ -1441,6 +1441,10 @@ ARegion *UI_tooltip_create_from_button_or_extra_icon(
}
BLI_rcti_rctf_copy_round(&init_rect, &overlap_rect_fl);
}
else if (but->type == UI_BTYPE_LABEL && BLI_rctf_size_y(&but->rect) > UI_UNIT_Y) {
init_position[0] = win->eventstate->xy[0];
init_position[1] = win->eventstate->xy[1] - (UI_POPUP_MARGIN / 2);
}
else {
init_position[0] = BLI_rctf_cent_x(&but->rect);
init_position[1] = but->rect.ymin;

View File

@ -255,6 +255,27 @@ class LayerViewItem : public AbstractTreeViewItem {
PointerRNA layer_ptr = RNA_pointer_create(&grease_pencil_.id, &RNA_GreasePencilLayer, &layer_);
uiBlock *block = uiLayoutGetBlock(&row);
const int icon = (layer_.base.flag & GP_LAYER_TREE_NODE_HIDE_MASKS) == 0 ? ICON_CLIPUV_DEHLT :
ICON_CLIPUV_HLT;
but = uiDefIconButR(block,
UI_BTYPE_ICON_TOGGLE,
0,
icon,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
&layer_ptr,
"use_masks",
0,
0.0f,
0.0f,
nullptr);
if (layer_.parent_group().use_masks()) {
UI_but_flag_enable(but, UI_BUT_INACTIVE);
}
but = uiDefIconButR(block,
UI_BTYPE_ICON_TOGGLE,
0,
@ -359,6 +380,9 @@ class LayerGroupViewItem : public AbstractTreeViewItem {
PointerRNA group_ptr = RNA_pointer_create(
&grease_pencil_.id, &RNA_GreasePencilLayerGroup, &group_);
const int icon = (group_.base.flag & GP_LAYER_TREE_NODE_HIDE_MASKS) == 0 ? ICON_CLIPUV_DEHLT :
ICON_CLIPUV_HLT;
uiItemR(&row, &group_ptr, "use_masks", UI_ITEM_R_ICON_ONLY, nullptr, icon);
uiItemR(&row, &group_ptr, "hide", UI_ITEM_R_ICON_ONLY, nullptr, ICON_NONE);
uiItemR(&row, &group_ptr, "lock", UI_ITEM_R_ICON_ONLY, nullptr, ICON_NONE);
}

View File

@ -457,10 +457,16 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op)
BKE_sculpt_mask_layers_ensure(nullptr, nullptr, ob, nullptr);
bool create_new_object = RNA_boolean_get(op->ptr, "new_object");
bool fill_holes = RNA_boolean_get(op->ptr, "fill_holes");
float mask_threshold = RNA_float_get(op->ptr, "mask_threshold");
Mesh *mesh = static_cast<Mesh *>(ob->data);
Mesh *new_mesh = (Mesh *)BKE_id_copy(bmain, &mesh->id);
if (ob->mode == OB_MODE_SCULPT) {
/* Fix for #87243 */
/* Undo crashes when new object is created in the middle of a sculpt */
if (ob->mode == OB_MODE_SCULPT && !create_new_object) {
sculpt_paint::undo::geometry_begin(ob, op);
}
@ -473,15 +479,14 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op)
mesh_to_bm_params.calc_face_normal = true;
BM_mesh_bm_from_me(bm, new_mesh, &mesh_to_bm_params);
slice_paint_mask(
bm, false, RNA_boolean_get(op->ptr, "fill_holes"), RNA_float_get(op->ptr, "mask_threshold"));
slice_paint_mask(bm, false, fill_holes, mask_threshold);
BKE_id_free(bmain, new_mesh);
BMeshToMeshParams bm_to_mesh_params{};
bm_to_mesh_params.calc_object_remap = false;
new_mesh = BKE_mesh_from_bmesh_nomain(bm, &bm_to_mesh_params, mesh);
BM_mesh_free(bm);
if (RNA_boolean_get(op->ptr, "new_object")) {
if (create_new_object) {
ushort local_view_bits = 0;
if (v3d && v3d->localvd) {
local_view_bits = v3d->local_view_uid;
@ -495,10 +500,7 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op)
BM_mesh_bm_from_me(bm, new_ob_mesh, &mesh_to_bm_params);
slice_paint_mask(bm,
true,
RNA_boolean_get(op->ptr, "fill_holes"),
RNA_float_get(op->ptr, "mask_threshold"));
slice_paint_mask(bm, true, fill_holes, mask_threshold);
BKE_id_free(bmain, new_ob_mesh);
new_ob_mesh = BKE_mesh_from_bmesh_nomain(bm, &bm_to_mesh_params, mesh);
BM_mesh_free(bm);
@ -524,7 +526,9 @@ static int paint_mask_slice_exec(bContext *C, wmOperator *op)
const int next_face_set_id = sculpt_paint::face_set::find_next_available_id(*ob);
sculpt_paint::face_set::initialize_none_to_id(mesh, next_face_set_id);
}
sculpt_paint::undo::geometry_end(ob);
if (!create_new_object) {
sculpt_paint::undo::geometry_end(ob);
}
}
BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL);

View File

@ -99,7 +99,7 @@ void enable_ex(Main *bmain, Depsgraph *depsgraph, Object *ob)
BM_mesh_bm_from_me(ss->bm, mesh, &convert_params);
triangulate(ss->bm);
BM_data_layer_add_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
BM_data_layer_ensure_named(ss->bm, &ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
/* Make sure the data for existing faces are initialized. */
if (mesh->faces_num != ss->bm->totface) {

View File

@ -1293,6 +1293,10 @@ void file_draw_list(const bContext *C, ARegion *region)
nullptr);
UI_but_dragflag_enable(drag_but, UI_BUT_DRAG_FULL_BUT);
file_but_enable_drag(drag_but, sfile, file, path, nullptr, icon, UI_SCALE_FAC);
UI_but_func_tooltip_custom_set(drag_but,
file_draw_tooltip_custom_func,
file_tooltip_data_create(sfile, file),
MEM_freeN);
}
}

View File

@ -6,6 +6,7 @@ set(INC
.
../blenkernel
../blentranslation
../bmesh
../functions
../makesrna
../../../intern/eigen
@ -124,6 +125,10 @@ if(WITH_GMP)
list(APPEND INC_SYS
${GMP_INCLUDE_DIRS}
)
list(APPEND LIB
${GMP_LIBRARIES}
)
endif()
blender_add_lib(bf_geometry "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -6,29 +6,77 @@
#include "BLI_array.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_mesh_boolean.hh"
#include "BLI_span.hh"
struct Mesh;
namespace blender::meshintersect {
namespace blender::geometry::boolean {
/** Specifies which solver to use. */
enum class Solver {
/**
* The exact solver based on the Mesh Arrangments for Solid Geometry paper,
* by Zhou, Grinspun, Zorin, and Jacobson.
*/
MeshArr = 0,
/** The original BMesh floating point solver. */
Float = 1,
};
enum class Operation {
Intersect = 0,
Union = 1,
Difference = 2,
};
/**
* Do a mesh boolean operation directly on meshes (without going back and forth from BMesh).
* BooleanOpParameters bundles together the global parameters for the boolean operation.
* As well as saying which particular operation (intersect, difference, union) is desired,
* it also states some assumptions that the algorithm is allowed to make about the input
* (e.g., whether or not there are any self intersections).
*/
struct BooleanOpParameters {
Operation boolean_mode;
/** Can we assume there are no self-intersections in any of the operands? */
bool no_self_intersections = true;
/** Can we assume there are no nested components (e.g., a box inside a box) in any of the
* components? */
bool no_nested_components = true;
/** Can we assume the argument meshes are watertight volume enclosing? */
bool watertight = true;
};
/**
* Do a mesh boolean operation directly on meshes.
* Boolean operations operate on the volumes enclosed by the operands.
* If is only one operand, the non-float versions will do self-intersection and remove
* internal faces.
* If there are more than two meshes, the first mesh is operand 0 and the rest of the
* meshes are operand 1 (i.e., as if all of operands 1, ... are joined into one mesh.
* The exact solvers assume that the meshes are PWN (piecewise winding number,
* which approximately means that the meshes are enclosed watertight voluems,
* and all edges are manifold, though there are allowable exceptions to that last condition).
* If the meshes don't sastisfy those conditions, all solvers will try to use ray-shooting
* to determine whether particular faces survive or not. This may or may not work
* in the way the user hopes.
*
* \param meshes: The meshes that are operands of the boolean operation.
* \param transforms: An array of transform matrices used for each mesh's positions.
* \param target_transform: the result needs to be transformed by this.
* \param material_remaps: An array of maps from material slot numbers in the corresponding mesh
* to the material slot in the first mesh. It is OK for material_remaps or any of its constituent
* arrays to be empty. A -1 value means that the original index should be used with no mapping.
* \param r_intersecting_edges: Array to store indices of edges on the resulting mesh in. These
* \param op_params: Specifies the boolean operation and assumptions we can make.
* \param solver: which solver to use
* \param r_intersecting_edges: Vector to store indices of edges on the resulting mesh in. These
* 'new' edges are the result of the intersections.
*/
Mesh *direct_mesh_boolean(Span<const Mesh *> meshes,
Span<float4x4> transforms,
const float4x4 &target_transform,
Span<Array<short>> material_remaps,
bool use_self,
bool hole_tolerant,
int boolean_mode,
Vector<int> *r_intersecting_edges);
Mesh *mesh_boolean(Span<const Mesh *> meshes,
Span<float4x4> transforms,
const float4x4 &target_transform,
Span<Array<short>> material_remaps,
BooleanOpParameters op_params,
Solver solver,
Vector<int> *r_intersecting_edges);
} // namespace blender::meshintersect
} // namespace blender::geometry::boolean

View File

@ -2,10 +2,6 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <iostream>
#include "BKE_attribute.hh"
@ -25,9 +21,20 @@
#include "BLI_task.hh"
#include "BLI_virtual_array.hh"
#include "DNA_node_types.h"
#include "GEO_mesh_boolean.hh"
namespace blender::meshintersect {
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "tools/bmesh_boolean.hh"
#include "tools/bmesh_intersect.hh"
namespace blender::geometry::boolean {
/* -------------------------------------------------------------------- */
/** \name Mesh Arrangements (Old Exact Boolean)
* \{ */
#ifdef WITH_GMP
@ -78,9 +85,9 @@ class MeshesToIMeshInfo {
Array<int> mesh_face_offset;
/* For each Mesh vertex in all the meshes (with concatenated indexing),
* what is the IMesh Vert* allocated for it in the input IMesh? */
Array<const Vert *> mesh_to_imesh_vert;
Array<const meshintersect::Vert *> mesh_to_imesh_vert;
/* Similarly for each Mesh face. */
Array<Face *> mesh_to_imesh_face;
Array<meshintersect::Face *> mesh_to_imesh_face;
/* Transformation matrix to transform a coordinate in the corresponding
* Mesh to the local space of the first Mesh. */
Array<float4x4> to_target_transform;
@ -230,12 +237,12 @@ void MeshesToIMeshInfo::input_medge_for_orig_index(int orig_index,
* correspondence between the Mesh MVerts/MPolys and the IMesh Verts/Faces.
* All allocation of memory for the IMesh comes from `arena`.
*/
static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
Span<float4x4> obmats,
Span<Array<short>> material_remaps,
const float4x4 &target_transform,
IMeshArena &arena,
MeshesToIMeshInfo *r_info)
static meshintersect::IMesh meshes_to_imesh(Span<const Mesh *> meshes,
Span<float4x4> obmats,
Span<Array<short>> material_remaps,
const float4x4 &target_transform,
meshintersect::IMeshArena &arena,
MeshesToIMeshInfo *r_info)
{
int nmeshes = meshes.size();
BLI_assert(nmeshes > 0);
@ -272,7 +279,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
/* Put these Vectors here, with a size unlikely to need resizing,
* so that the loop to make new Faces will likely not need to allocate
* over and over. */
Vector<const Vert *, estimated_max_facelen> face_vert;
Vector<const meshintersect::Vert *, estimated_max_facelen> face_vert;
Vector<int, estimated_max_facelen> face_edge_orig;
/* To convert the coordinates of meshes 1, 2, etc. into the local space
@ -303,7 +310,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
* that would have a negative transform if you do that. */
bool need_face_flip = r_info->has_negative_transform[mi] != r_info->has_negative_transform[0];
Vector<Vert *> verts(mesh->verts_num);
Vector<meshintersect::Vert *> verts(mesh->verts_num);
const Span<float3> vert_positions = mesh->vert_positions();
const OffsetIndices faces = mesh->faces();
const Span<int> corner_verts = mesh->corner_verts();
@ -319,7 +326,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
float3 co = vert_positions[i];
mpq3 mco = mpq3(co.x, co.y, co.z);
double3 dco(mco[0].get_d(), mco[1].get_d(), mco[2].get_d());
verts[i] = new Vert(mco, dco, NO_INDEX, i);
verts[i] = new meshintersect::Vert(mco, dco, meshintersect::NO_INDEX, i);
}
});
}
@ -329,7 +336,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
float3 co = math::transform_point(r_info->to_target_transform[mi], vert_positions[i]);
mpq3 mco = mpq3(co.x, co.y, co.z);
double3 dco(mco[0].get_d(), mco[1].get_d(), mco[2].get_d());
verts[i] = new Vert(mco, dco, NO_INDEX, i);
verts[i] = new meshintersect::Vert(mco, dco, meshintersect::NO_INDEX, i);
}
});
}
@ -346,7 +353,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
for (int i = 0; i < flen; ++i) {
const int corner_i = face[i];
int mverti = r_info->mesh_vert_offset[mi] + corner_verts[corner_i];
const Vert *fv = r_info->mesh_to_imesh_vert[mverti];
const meshintersect::Vert *fv = r_info->mesh_to_imesh_vert[mverti];
if (need_face_flip) {
face_vert[flen - i - 1] = fv;
int iedge = i < flen - 1 ? flen - i - 2 : flen - 1;
@ -362,7 +369,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes,
}
e += mesh->edges_num;
}
return IMesh(r_info->mesh_to_imesh_face);
return meshintersect::IMesh(r_info->mesh_to_imesh_face);
}
/* Copy vertex attributes, including customdata, from `orig_mv` to `mv`.
@ -460,7 +467,7 @@ static void copy_edge_attributes(Mesh *dest_mesh,
* For now, we only try to do this if `face` and `orig_face` have the same size.
* Return the number of non-null MLoops filled in.
*/
static int fill_orig_loops(const Face *f,
static int fill_orig_loops(const meshintersect::Face *f,
const IndexRange orig_face,
const Mesh *orig_me,
int orig_me_index,
@ -482,7 +489,7 @@ static int fill_orig_loops(const Face *f,
* aligned loops is only an optimization to avoid some re-interpolation.
*/
int first_orig_v = f->vert[0]->orig;
if (first_orig_v == NO_INDEX) {
if (first_orig_v == meshintersect::NO_INDEX) {
return 0;
}
/* It is possible that the original vert was merged with another in another mesh. */
@ -509,20 +516,20 @@ static int fill_orig_loops(const Face *f,
int orig_mp_loop_index = (mp_loop_index + offset) % orig_mplen;
const int vert_i = orig_corner_verts[orig_face.start() + orig_mp_loop_index];
int fv_orig = f->vert[mp_loop_index]->orig;
if (fv_orig != NO_INDEX) {
if (fv_orig != meshintersect::NO_INDEX) {
fv_orig -= orig_me_vert_offset;
if (fv_orig < 0 || fv_orig >= orig_me->verts_num) {
fv_orig = NO_INDEX;
fv_orig = meshintersect::NO_INDEX;
}
}
if (vert_i == fv_orig) {
const int vert_next =
orig_corner_verts[orig_face.start() + ((orig_mp_loop_index + 1) % orig_mplen)];
int fvnext_orig = f->vert[(mp_loop_index + 1) % orig_mplen]->orig;
if (fvnext_orig != NO_INDEX) {
if (fvnext_orig != meshintersect::NO_INDEX) {
fvnext_orig -= orig_me_vert_offset;
if (fvnext_orig < 0 || fvnext_orig >= orig_me->verts_num) {
fvnext_orig = NO_INDEX;
fvnext_orig = meshintersect::NO_INDEX;
}
}
if (vert_next == fvnext_orig) {
@ -562,7 +569,7 @@ static void get_poly2d_cos(const Mesh *mesh,
* copy the Loop attributes from corresponding loops to corresponding loops.
* Otherwise, interpolate the Loop attributes in the face `orig_face`. */
static void copy_or_interp_loop_attributes(Mesh *dest_mesh,
const Face *f,
const meshintersect::Face *f,
const IndexRange face,
const IndexRange orig_face,
const Mesh *orig_me,
@ -701,7 +708,7 @@ static void merge_edge_customdata_layers(Mesh *target, MeshesToIMeshInfo &mim)
* Convert the output IMesh im to a Blender Mesh,
* using the information in mim to get all the attributes right.
*/
static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
static Mesh *imesh_to_mesh(meshintersect::IMesh *im, MeshesToIMeshInfo &mim)
{
constexpr int dbg_level = 0;
@ -709,7 +716,7 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
int out_totvert = im->vert_size();
int out_faces_num = im->face_size();
int out_totloop = 0;
for (const Face *f : im->faces()) {
for (const meshintersect::Face *f : im->faces()) {
out_totloop += f->size();
}
/* Will calculate edges later. */
@ -720,8 +727,8 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
/* Set the vertex coordinate values and other data. */
MutableSpan<float3> positions = result->vert_positions_for_write();
for (int vi : im->vert_index_range()) {
const Vert *v = im->vert(vi);
if (v->orig != NO_INDEX) {
const meshintersect::Vert *v = im->vert(vi);
if (v->orig != meshintersect::NO_INDEX) {
const Mesh *orig_me;
int index_in_orig_me;
mim.input_mvert_for_orig_index(v->orig, &orig_me, &index_in_orig_me);
@ -739,7 +746,7 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
MutableSpan<int> dst_corner_verts = result->corner_verts_for_write();
MutableSpan<int> dst_face_offsets = result->face_offsets_for_write();
for (int fi : im->face_index_range()) {
const Face *f = im->face(fi);
const meshintersect::Face *f = im->face(fi);
const Mesh *orig_me;
int index_in_orig_me;
int orig_me_index;
@ -747,7 +754,7 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
f->orig, &orig_me, &orig_me_index, &index_in_orig_me);
dst_face_offsets[fi] = cur_loop_index;
for (int j : f->index_range()) {
const Vert *vf = f->vert[j];
const meshintersect::Vert *vf = f->vert[j];
const int vfi = im->lookup_vert(vf);
dst_corner_verts[cur_loop_index] = vfi;
++cur_loop_index;
@ -779,10 +786,10 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
const OffsetIndices dst_polys = result->faces();
const Span<int> dst_corner_edges = result->corner_edges();
for (int fi : im->face_index_range()) {
const Face *f = im->face(fi);
const meshintersect::Face *f = im->face(fi);
const IndexRange face = dst_polys[fi];
for (int j : f->index_range()) {
if (f->edge_orig[j] != NO_INDEX) {
if (f->edge_orig[j] != meshintersect::NO_INDEX) {
const Mesh *orig_me;
int index_in_orig_me;
mim.input_medge_for_orig_index(f->edge_orig[j], &orig_me, &index_in_orig_me);
@ -798,18 +805,29 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim)
return result;
}
#endif // WITH_GMP
Mesh *direct_mesh_boolean(Span<const Mesh *> meshes,
Span<float4x4> transforms,
const float4x4 &target_transform,
Span<Array<short>> material_remaps,
const bool use_self,
const bool hole_tolerant,
const int boolean_mode,
Vector<int> *r_intersecting_edges)
static meshintersect::BoolOpType operation_to_mesh_arr_mode(const Operation operation)
{
switch (operation) {
case Operation::Intersect:
return meshintersect::BoolOpType::Intersect;
case Operation::Union:
return meshintersect::BoolOpType::Union;
case Operation::Difference:
return meshintersect::BoolOpType::Difference;
}
BLI_assert_unreachable();
return meshintersect::BoolOpType::None;
}
static Mesh *mesh_boolean_mesh_arr(Span<const Mesh *> meshes,
Span<float4x4> transforms,
const float4x4 &target_transform,
Span<Array<short>> material_remaps,
const bool use_self,
const bool hole_tolerant,
const meshintersect::BoolOpType boolean_mode,
Vector<int> *r_intersecting_edges)
{
#ifdef WITH_GMP
BLI_assert(transforms.is_empty() || meshes.size() == transforms.size());
BLI_assert(material_remaps.is_empty() || material_remaps.size() == meshes.size());
if (meshes.size() <= 0) {
@ -818,11 +836,12 @@ Mesh *direct_mesh_boolean(Span<const Mesh *> meshes,
const int dbg_level = 0;
if (dbg_level > 0) {
std::cout << "\nDIRECT_MESH_INTERSECT, nmeshes = " << meshes.size() << "\n";
std::cout << "\nOLD_MESH_INTERSECT, nmeshes = " << meshes.size() << "\n";
}
MeshesToIMeshInfo mim;
IMeshArena arena;
IMesh m_in = meshes_to_imesh(meshes, transforms, material_remaps, target_transform, arena, &mim);
meshintersect::IMeshArena arena;
meshintersect::IMesh m_in = meshes_to_imesh(
meshes, transforms, material_remaps, target_transform, arena, &mim);
std::function<int(int)> shape_fn = [&mim](int f) {
for (int mi = 0; mi < mim.mesh_face_offset.size() - 1; ++mi) {
if (f < mim.mesh_face_offset[mi + 1]) {
@ -831,14 +850,8 @@ Mesh *direct_mesh_boolean(Span<const Mesh *> meshes,
}
return int(mim.mesh_face_offset.size()) - 1;
};
IMesh m_out = boolean_mesh(m_in,
static_cast<BoolOpType>(boolean_mode),
meshes.size(),
shape_fn,
use_self,
hole_tolerant,
nullptr,
&arena);
meshintersect::IMesh m_out = boolean_mesh(
m_in, boolean_mode, meshes.size(), shape_fn, use_self, hole_tolerant, nullptr, &arena);
if (dbg_level > 0) {
std::cout << m_out;
write_obj_mesh(m_out, "m_out");
@ -851,7 +864,7 @@ Mesh *direct_mesh_boolean(Span<const Mesh *> meshes,
const OffsetIndices faces = result->faces();
const Span<int> corner_edges = result->corner_edges();
for (int fi : m_out.face_index_range()) {
const Face &face = *m_out.face(fi);
const meshintersect::Face &face = *m_out.face(fi);
const IndexRange mesh_face = faces[fi];
for (int i : face.index_range()) {
if (face.is_intersect[i]) {
@ -863,17 +876,318 @@ Mesh *direct_mesh_boolean(Span<const Mesh *> meshes,
}
return result;
#else // WITH_GMP
UNUSED_VARS(meshes,
transforms,
material_remaps,
target_transform,
use_self,
hole_tolerant,
boolean_mode,
r_intersecting_edges);
return nullptr;
#endif // WITH_GMP
}
} // namespace blender::meshintersect
#endif // WITH_GMP
/** \} */
/* -------------------------------------------------------------------- */
/** \name Float Boolean
* \{ */
/* has no meaning for faces, do this so we can tell which face is which */
#define BM_FACE_TAG BM_ELEM_DRAW
/**
* Function use to say what operand a face is part of, based on the `BM_FACE_TAG`,`
* which is set in `bm_mesh_create`.
*/
static int face_boolean_operand(BMFace *f, void * /*user_data*/)
{
return BM_elem_flag_test(f, BM_FACE_TAG) ? 0 : 1;
}
/* Create a BMesh that is the concatenation of the given meshes.
* The corresponding mesh-to-world transformations are also given,
* as well as a target_tranform.
* A triangulation is also calculated and returned in the last two
* parameters.
* The faces of the first mesh are tagged with BM_FACE_TAG so that the
* face_boolean_operand() function can distinguish those faces from the
* rest.
* The caller is responsible for using `BM_mesh_free` on the returned
* BMesh, and calling `MEM_freeN` on the returned looptris.
*
* TODO: maybe figure out how to use the join_geometries() function
* to join all the meshes into one mesh first, and then convert
* that single mesh to BMesh. Issues with that include needing
* to apply the transforms and material remaps.
*/
static BMesh *mesh_bm_concat(Span<const Mesh *> meshes,
Span<float4x4> transforms,
const float4x4 &target_transform,
Span<Array<short>> material_remaps,
BMLoop *(**r_looptris)[3],
int *r_looptris_tot)
{
const int meshes_num = meshes.size();
BLI_assert(meshes_num >= 1);
bool ok;
float4x4 inv_target_mat = math::invert(target_transform, ok);
if (!ok) {
BLI_assert_unreachable();
inv_target_mat = float4x4::identity();
}
Array<float4x4> to_target(meshes_num);
Array<bool> is_negative_transform(meshes_num);
Array<bool> is_flip(meshes_num);
const int tsize = transforms.size();
for (const int i : IndexRange(meshes_num)) {
if (tsize > i) {
to_target[i] = inv_target_mat * transforms[i];
is_negative_transform[i] = math::is_negative(transforms[i]);
is_flip[i] = is_negative_transform[i] != is_negative_transform[0];
}
else {
to_target[i] = inv_target_mat;
is_negative_transform[i] = false;
is_flip[i] = false;
}
}
/* Make a BMesh that will be a concatenation of the elements of all the meshes */
BMAllocTemplate allocsize;
allocsize.totvert = 0;
allocsize.totedge = 0;
allocsize.totloop = 0;
allocsize.totface = 0;
for (const int i : meshes.index_range()) {
allocsize.totvert += meshes[i]->verts_num;
allocsize.totedge += meshes[i]->edges_num;
allocsize.totloop += meshes[i]->corners_num;
allocsize.totface += meshes[i]->faces_num;
}
BMeshCreateParams bmesh_create_params{};
BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params);
BM_mesh_copy_init_customdata_from_mesh_array(
bm, const_cast<const Mesh **>(meshes.begin()), meshes_num, &allocsize);
BMeshFromMeshParams bmesh_from_mesh_params{};
bmesh_from_mesh_params.calc_face_normal = true;
bmesh_from_mesh_params.calc_vert_normal = true;
Array<int> verts_end(meshes_num);
Array<int> faces_end(meshes_num);
verts_end[0] = meshes[0]->verts_num;
faces_end[0] = meshes[0]->faces_num;
for (const int i : meshes.index_range()) {
/* Append meshes[i] elements and data to bm. */
BM_mesh_bm_from_me(bm, meshes[i], &bmesh_from_mesh_params);
if (i > 0) {
verts_end[i] = verts_end[i - 1] + meshes[i]->verts_num;
faces_end[i] = faces_end[i - 1] + meshes[i]->faces_num;
if (is_flip[i]) {
/* Need to flip face normals to match that of mesh[0]. */
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
BM_mesh_elem_table_ensure(bm, BM_FACE);
for (int j = faces_end[i - 1]; j < faces_end[i]; j++) {
BMFace *efa = bm->ftable[j];
BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true);
}
}
}
}
/* Make a triangulation of all polys before transforming vertices
* so we can use the original normals. */
const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop);
BMLoop *(*looptris)[3] = (BMLoop * (*)[3])
MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__);
BM_mesh_calc_tessellation_beauty(bm, looptris);
*r_looptris = looptris;
*r_looptris_tot = looptris_tot;
/* Tranform the vertices that into the desired target_transform space. */
BMIter iter;
BMVert *eve;
int i = 0;
int mesh_index = 0;
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
copy_v3_v3(eve->co, math::transform_point(to_target[mesh_index], float3(eve->co)));
++i;
if (i == verts_end[mesh_index]) {
mesh_index++;
}
}
/* Transform face normals and tag the first-operand faces.
* Also, apply material remaps. */
BMFace *efa;
i = 0;
mesh_index = 0;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
copy_v3_v3(efa->no, math::transform_direction(to_target[mesh_index], float3(efa->no)));
if (is_negative_transform[mesh_index]) {
negate_v3(efa->no);
}
normalize_v3(efa->no);
/* Temp tag used in `face_boolean_operand()` to test for operand 0. */
if (i < faces_end[0]) {
BM_elem_flag_enable(efa, BM_FACE_TAG);
}
/* Remap material. */
int cur_mat = efa->mat_nr;
if (cur_mat < material_remaps[mesh_index].size()) {
int new_mat = material_remaps[mesh_index][cur_mat];
if (new_mat >= 0) {
efa->mat_nr = material_remaps[mesh_index][cur_mat];
}
}
++i;
if (i == faces_end[mesh_index]) {
mesh_index++;
}
}
return bm;
}
static int operation_to_float_mode(const Operation operation)
{
switch (operation) {
case Operation::Intersect:
return BMESH_ISECT_BOOLEAN_ISECT;
case Operation::Union:
return BMESH_ISECT_BOOLEAN_UNION;
case Operation::Difference:
return BMESH_ISECT_BOOLEAN_DIFFERENCE;
}
BLI_assert_unreachable();
return BMESH_ISECT_BOOLEAN_NONE;
}
static Mesh *mesh_boolean_float(Span<const Mesh *> meshes,
Span<float4x4> transforms,
const float4x4 &target_transform,
Span<Array<short>> material_remaps,
const int boolean_mode,
Vector<int> * /*r_intersecting_edges*/)
{
BLI_assert(meshes.size() == transforms.size() || transforms.size() == 0);
BLI_assert(material_remaps.size() == 0 || material_remaps.size() == meshes.size());
if (meshes.is_empty()) {
return nullptr;
}
if (meshes.size() == 1) {
/* The float solver doesn't do self union. Just return nullptr, which will
* cause geometry nodes to leave the input as is. */
return BKE_mesh_copy_for_eval(meshes[0]);
}
BMLoop *(*looptris)[3];
int looptris_tot;
if (meshes.size() == 2) {
BMesh *bm = mesh_bm_concat(
meshes, transforms, target_transform, material_remaps, &looptris, &looptris_tot);
BM_mesh_intersect(bm,
looptris,
looptris_tot,
face_boolean_operand,
nullptr,
false,
false,
true,
true,
false,
false,
boolean_mode,
1e-6f);
MEM_freeN(looptris);
Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, meshes[0]);
BM_mesh_free(bm);
return result;
}
/* Iteratively operate with each operand. */
Array<const Mesh *> two_meshes = {meshes[0], meshes[1]};
Array<float4x4> two_transforms = {transforms[0], transforms[1]};
Array<Array<short>> two_remaps = {material_remaps[0], material_remaps[1]};
Mesh *prev_result_mesh = nullptr;
for (const int i : meshes.index_range().drop_back(1)) {
BMesh *bm = mesh_bm_concat(
two_meshes, two_transforms, float4x4::identity(), two_remaps, &looptris, &looptris_tot);
BM_mesh_intersect(bm,
looptris,
looptris_tot,
face_boolean_operand,
nullptr,
false,
false,
true,
true,
false,
false,
boolean_mode,
1e-6f);
MEM_freeN(looptris);
Mesh *result_i_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, meshes[0]);
BM_mesh_free(bm);
if (prev_result_mesh != nullptr) {
/* Except in the first iteration, two_meshes[0] holds the intermediate
* mesh result from the previous iteraiton. */
BKE_mesh_eval_delete(prev_result_mesh);
}
if (i < meshes.size() - 2) {
two_meshes[0] = result_i_mesh;
two_meshes[1] = meshes[i + 2];
two_transforms[0] = float4x4::identity();
two_transforms[1] = transforms[i + 2];
two_remaps[0] = {};
two_remaps[1] = material_remaps[i + 2];
prev_result_mesh = result_i_mesh;
}
else {
return result_i_mesh;
}
}
BLI_assert_unreachable();
return nullptr;
}
/** \} */
Mesh *mesh_boolean(Span<const Mesh *> meshes,
Span<float4x4> transforms,
const float4x4 &target_transform,
Span<Array<short>> material_remaps,
BooleanOpParameters op_params,
Solver solver,
Vector<int> *r_intersecting_edges)
{
switch (solver) {
case Solver::Float:
return mesh_boolean_float(meshes,
transforms,
target_transform,
material_remaps,
operation_to_float_mode(op_params.boolean_mode),
r_intersecting_edges);
case Solver::MeshArr:
#ifdef WITH_GMP
return mesh_boolean_mesh_arr(meshes,
transforms,
target_transform,
material_remaps,
!op_params.no_self_intersections,
!op_params.watertight,
operation_to_mesh_arr_mode(op_params.boolean_mode),
r_intersecting_edges);
#else
return nullptr;
#endif
default:
BLI_assert_unreachable();
}
return nullptr;
}
} // namespace blender::geometry::boolean

View File

@ -366,6 +366,9 @@ struct SStruct {
#define imageStore(_tex, _coord, _value) _texture_write_internal(_tex, _coord, _value)
#define imageStoreFast(_tex, _coord, _value) _texture_write_internal_fast(_tex, _coord, _value)
/* Texture synchronization functions. */
#define imageFence(image) image.texture->fence()
/* Singular return values from texture functions of type DEPTH are often indexed with either .r or
* .x. This is a lightweight wrapper type for handling this syntax. */
union _msl_return_float {

View File

@ -83,6 +83,9 @@
#define isampler2DAtomic isampler2D
#define isampler3DAtomic isampler3D
/* Pass through functions. */
#define imageFence(image)
/* Backend Functions. */
#define select(A, B, mask) mix(A, B, mask)

View File

@ -648,6 +648,10 @@ ImBuf *IMB_dupImBuf(const ImBuf *ibuf1)
tbuf.display_buffer_flags = nullptr;
tbuf.colormanage_cache = nullptr;
/* GPU textures can not be easily copied, as it is not guaranteed that this function is called
* from within an active GPU context. */
tbuf.gpu.texture = nullptr;
*ibuf2 = tbuf;
return ibuf2;

View File

@ -448,7 +448,7 @@ static void colormanage_cache_handle_release(void *cache_handle)
/** \name Initialization / De-initialization
* \{ */
static void colormanage_role_color_space_name_get(OCIO_ConstConfigRcPtr *config,
static bool colormanage_role_color_space_name_get(OCIO_ConstConfigRcPtr *config,
char *colorspace_name,
const char *role,
const char *backup_role)
@ -457,56 +457,56 @@ static void colormanage_role_color_space_name_get(OCIO_ConstConfigRcPtr *config,
ociocs = OCIO_configGetColorSpace(config, role);
if (!ociocs && backup_role) {
if (ociocs == nullptr && backup_role) {
ociocs = OCIO_configGetColorSpace(config, backup_role);
}
if (ociocs) {
const char *name = OCIO_colorSpaceGetName(ociocs);
if (ociocs == nullptr) {
/* Overall fallback role. */
ociocs = OCIO_configGetColorSpace(config, "default");
}
/* assume function was called with buffer properly allocated to MAX_COLORSPACE_NAME chars */
BLI_strncpy(colorspace_name, name, MAX_COLORSPACE_NAME);
OCIO_colorSpaceRelease(ociocs);
}
else {
printf("Color management: Error could not find role %s role.\n", role);
if (ociocs == nullptr) {
printf("Color management: Error, could not find role \"%s\"\n", role);
return false;
}
const char *name = OCIO_colorSpaceGetName(ociocs);
/* assume function was called with buffer properly allocated to MAX_COLORSPACE_NAME chars */
BLI_strncpy(colorspace_name, name, MAX_COLORSPACE_NAME);
OCIO_colorSpaceRelease(ociocs);
return true;
}
static void colormanage_load_config(OCIO_ConstConfigRcPtr *config)
static bool colormanage_load_config(OCIO_ConstConfigRcPtr *config)
{
int tot_colorspace, tot_display, tot_display_view, tot_looks;
int index, viewindex, viewindex2;
const char *name;
bool ok = true;
/* get roles */
colormanage_role_color_space_name_get(config, global_role_data, OCIO_ROLE_DATA, nullptr);
colormanage_role_color_space_name_get(
ok &= colormanage_role_color_space_name_get(config, global_role_data, OCIO_ROLE_DATA, nullptr);
ok &= colormanage_role_color_space_name_get(
config, global_role_scene_linear, OCIO_ROLE_SCENE_LINEAR, nullptr);
colormanage_role_color_space_name_get(
ok &= colormanage_role_color_space_name_get(
config, global_role_color_picking, OCIO_ROLE_COLOR_PICKING, nullptr);
colormanage_role_color_space_name_get(
ok &= colormanage_role_color_space_name_get(
config, global_role_texture_painting, OCIO_ROLE_TEXTURE_PAINT, nullptr);
colormanage_role_color_space_name_get(
ok &= colormanage_role_color_space_name_get(
config, global_role_default_sequencer, OCIO_ROLE_DEFAULT_SEQUENCER, OCIO_ROLE_SCENE_LINEAR);
colormanage_role_color_space_name_get(
ok &= colormanage_role_color_space_name_get(
config, global_role_default_byte, OCIO_ROLE_DEFAULT_BYTE, OCIO_ROLE_TEXTURE_PAINT);
colormanage_role_color_space_name_get(
ok &= colormanage_role_color_space_name_get(
config, global_role_default_float, OCIO_ROLE_DEFAULT_FLOAT, OCIO_ROLE_SCENE_LINEAR);
/* load colorspaces */
tot_colorspace = OCIO_configGetNumColorSpaces(config);
for (index = 0; index < tot_colorspace; index++) {
OCIO_ConstColorSpaceRcPtr *ocio_colorspace;
const char *description;
bool is_invertible, is_data;
const int tot_colorspace = OCIO_configGetNumColorSpaces(config);
for (int index = 0; index < tot_colorspace; index++) {
const char *name = OCIO_configGetColorSpaceNameByIndex(config, index);
name = OCIO_configGetColorSpaceNameByIndex(config, index);
ocio_colorspace = OCIO_configGetColorSpace(config, name);
description = OCIO_colorSpaceGetDescription(ocio_colorspace);
is_invertible = OCIO_colorSpaceIsInvertible(ocio_colorspace);
is_data = OCIO_colorSpaceIsData(ocio_colorspace);
OCIO_ConstColorSpaceRcPtr *ocio_colorspace = OCIO_configGetColorSpace(config, name);
const char *description = OCIO_colorSpaceGetDescription(ocio_colorspace);
const bool is_invertible = OCIO_colorSpaceIsInvertible(ocio_colorspace);
const bool is_data = OCIO_colorSpaceIsData(ocio_colorspace);
ColorSpace *colorspace = colormanage_colorspace_add(name, description, is_invertible, is_data);
@ -525,51 +525,49 @@ static void colormanage_load_config(OCIO_ConstConfigRcPtr *config)
}
/* load displays */
viewindex2 = 0;
tot_display = OCIO_configGetNumDisplays(config);
const int tot_display = OCIO_configGetNumDisplays(config);
int viewindex2 = 0;
for (index = 0; index < tot_display; index++) {
const char *displayname;
ColorManagedDisplay *display;
for (int index = 0; index < tot_display; index++) {
const char *displayname = OCIO_configGetDisplay(config, index);
displayname = OCIO_configGetDisplay(config, index);
display = colormanage_display_add(displayname);
ColorManagedDisplay *display = colormanage_display_add(displayname);
/* load views */
tot_display_view = OCIO_configGetNumViews(config, displayname);
for (viewindex = 0; viewindex < tot_display_view; viewindex++, viewindex2++) {
const char *viewname;
ColorManagedView *view;
LinkData *display_view;
viewname = OCIO_configGetView(config, displayname, viewindex);
const int tot_display_view = OCIO_configGetNumViews(config, displayname);
for (int viewindex = 0; viewindex < tot_display_view; viewindex++, viewindex2++) {
const char *viewname = OCIO_configGetView(config, displayname, viewindex);
/* first check if view transform with given name was already loaded */
view = colormanage_view_get_named(viewname);
ColorManagedView *view = colormanage_view_get_named(viewname);
if (!view) {
view = colormanage_view_add(viewname);
}
display_view = BLI_genericNodeN(view);
LinkData *display_view = BLI_genericNodeN(view);
BLI_addtail(&display->views, display_view);
}
}
global_tot_display = tot_display;
if (global_tot_display == 0) {
printf("Color management: Error, could not find any displays\n");
ok = false;
}
else if (global_tot_view == 0) {
printf("Color management: Error, could not find any views\n");
ok = false;
}
/* load looks */
tot_looks = OCIO_configGetNumLooks(config);
const int tot_looks = OCIO_configGetNumLooks(config);
colormanage_look_add("None", "", true);
for (index = 0; index < tot_looks; index++) {
OCIO_ConstLookRcPtr *ocio_look;
const char *process_space;
name = OCIO_configGetLookNameByIndex(config, index);
ocio_look = OCIO_configGetLook(config, name);
process_space = OCIO_lookGetProcessSpace(ocio_look);
for (int index = 0; index < tot_looks; index++) {
const char *name = OCIO_configGetLookNameByIndex(config, index);
OCIO_ConstLookRcPtr *ocio_look = OCIO_configGetLook(config, name);
const char *process_space = OCIO_lookGetProcessSpace(ocio_look);
OCIO_lookRelease(ocio_look);
colormanage_look_add(name, process_space, false);
@ -587,6 +585,8 @@ static void colormanage_load_config(OCIO_ConstConfigRcPtr *config)
mul_m3_m3m3(imbuf_aces_to_scene_linear, imbuf_xyz_to_scene_linear, OCIO_ACES_TO_XYZ);
invert_m3_m3(imbuf_scene_linear_to_aces, imbuf_aces_to_scene_linear);
return ok;
}
static void colormanage_free_config()
@ -645,60 +645,61 @@ static void colormanage_free_config()
/* free looks */
BLI_freelistN(&global_looks);
global_tot_looks = 0;
OCIO_exit();
}
void colormanagement_init()
{
const char *ocio_env;
char configfile[FILE_MAX];
OCIO_ConstConfigRcPtr *config = nullptr;
OCIO_init();
ocio_env = BLI_getenv("OCIO");
/* First try config from environment variable. */
const char *ocio_env = BLI_getenv("OCIO");
if (ocio_env && ocio_env[0] != '\0') {
config = OCIO_configCreateFromEnv();
if (config != nullptr) {
printf("Color management: Using %s as a configuration file\n", ocio_env);
OCIO_setCurrentConfig(config);
const bool ok = colormanage_load_config(config);
OCIO_configRelease(config);
if (!ok) {
printf("Color management: Failed to load config from environment\n");
colormanage_free_config();
config = nullptr;
}
}
}
/* Then try bunded config file. */
if (config == nullptr) {
const std::optional<std::string> configdir = BKE_appdir_folder_id(BLENDER_DATAFILES,
"colormanagement");
if (configdir.has_value()) {
char configfile[FILE_MAX];
BLI_path_join(configfile, sizeof(configfile), configdir->c_str(), BCM_CONFIG_FILE);
config = OCIO_configCreateFromFile(configfile);
if (config != nullptr) {
OCIO_setCurrentConfig(config);
const bool ok = colormanage_load_config(config);
OCIO_configRelease(config);
if (!ok) {
printf("Color management: Failed to load bundled config\n");
colormanage_free_config();
config = nullptr;
}
}
}
}
/* Then use fallback. */
if (config == nullptr) {
printf("Color management: using fallback mode for management\n");
config = OCIO_configCreateFallback();
}
if (config) {
OCIO_setCurrentConfig(config);
colormanage_load_config(config);
OCIO_configRelease(config);
}
/* If there are no valid display/views, use fallback mode. */
if (global_tot_display == 0 || global_tot_view == 0) {
printf("Color management: no displays/views in the config, using fallback mode instead\n");
/* Free old config. */
colormanage_free_config();
/* Initialize fallback config. */
printf("Color management: Using fallback mode for management\n");
config = OCIO_configCreateFallback();
colormanage_load_config(config);
}
@ -730,6 +731,7 @@ void colormanagement_exit()
memset(&global_color_picking_state, 0, sizeof(global_color_picking_state));
colormanage_free_config();
OCIO_exit();
}
/** \} */

View File

@ -240,6 +240,7 @@ typedef enum GreasePencilLayerTreeNodeFlag {
GP_LAYER_TREE_NODE_USE_LIGHTS = (1 << 4),
GP_LAYER_TREE_NODE_USE_ONION_SKINNING = (1 << 5),
GP_LAYER_TREE_NODE_EXPANDED = (1 << 6),
GP_LAYER_TREE_NODE_HIDE_MASKS = (1 << 7),
} GreasePencilLayerTreeNodeFlag;
struct GreasePencilLayerTreeGroup;
@ -292,6 +293,8 @@ typedef struct GreasePencilLayer {
* List of `GreasePencilLayerMask`.
*/
ListBase masks;
int active_mask_index;
char _pad2[4];
/**
* Layer parent object. Can be an armature in which case the `parsubstr` is the bone name.
*/
@ -302,7 +305,7 @@ typedef struct GreasePencilLayer {
* Use the functions is the `bke::greasepencil::Layer` class instead.
*/
float translation[3], rotation[3], scale[3];
char _pad2[4];
char _pad3[4];
/** Name of the view layer used to filter render output. */
char *viewlayername;
/**

View File

@ -63,7 +63,7 @@
.collection = NULL, \
.double_threshold = 1e-6f, \
.operation = eBooleanModifierOp_Difference, \
.solver = eBooleanModifierSolver_Exact, \
.solver = eBooleanModifierSolver_Mesh_Arr, \
.flag = eBooleanModifierFlag_Object, \
.bm_flag = 0, \
}

View File

@ -983,8 +983,8 @@ typedef enum {
/** #BooleanModifierData.solver */
typedef enum {
eBooleanModifierSolver_Fast = 0,
eBooleanModifierSolver_Exact = 1,
eBooleanModifierSolver_Float = 0,
eBooleanModifierSolver_Mesh_Arr = 1,
} BooleanModifierSolver;
/** #BooleanModifierData.flag */

View File

@ -2655,12 +2655,6 @@ typedef enum GeometryNodeProximityTargetType {
GEO_NODE_PROX_TARGET_FACES = 2,
} GeometryNodeProximityTargetType;
typedef enum GeometryNodeBooleanOperation {
GEO_NODE_BOOLEAN_INTERSECT = 0,
GEO_NODE_BOOLEAN_UNION = 1,
GEO_NODE_BOOLEAN_DIFFERENCE = 2,
} GeometryNodeBooleanOperation;
typedef enum GeometryNodeCurvePrimitiveCircleMode {
GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS = 0,
GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS = 1

View File

@ -64,6 +64,60 @@ static void rna_grease_pencil_dependency_update(Main *bmain, Scene * /*scene*/,
WM_main_add_notifier(NC_GPENCIL | NA_EDITED, rna_grease_pencil(ptr));
}
static void rna_grease_pencil_layer_mask_name_get(PointerRNA *ptr, char *dst)
{
using namespace blender;
GreasePencilLayerMask *mask = static_cast<GreasePencilLayerMask *>(ptr->data);
if (mask->layer_name != nullptr) {
strcpy(dst, mask->layer_name);
}
else {
dst[0] = '\0';
}
}
static int rna_grease_pencil_layer_mask_name_length(PointerRNA *ptr)
{
using namespace blender;
GreasePencilLayerMask *mask = static_cast<GreasePencilLayerMask *>(ptr->data);
if (mask->layer_name != nullptr) {
return strlen(mask->layer_name);
}
return 0;
}
static void rna_grease_pencil_layer_mask_name_set(PointerRNA *ptr, const char *value)
{
using namespace blender;
GreasePencil *grease_pencil = rna_grease_pencil(ptr);
GreasePencilLayerMask *mask = static_cast<GreasePencilLayerMask *>(ptr->data);
const std::string oldname(mask->layer_name);
if (bke::greasepencil::TreeNode *node = grease_pencil->find_node_by_name(oldname)) {
grease_pencil->rename_node(*node, value);
}
}
static int rna_grease_pencil_active_mask_index_get(PointerRNA *ptr)
{
GreasePencilLayer *layer = static_cast<GreasePencilLayer *>(ptr->data);
return layer->active_mask_index;
}
static void rna_grease_pencil_active_mask_index_set(PointerRNA *ptr, int value)
{
GreasePencilLayer *layer = static_cast<GreasePencilLayer *>(ptr->data);
layer->active_mask_index = value;
}
static void rna_grease_pencil_active_mask_index_range(
PointerRNA *ptr, int *min, int *max, int * /*softmin*/, int * /*softmax*/)
{
GreasePencilLayer *layer = static_cast<GreasePencilLayer *>(ptr->data);
*min = 0;
*max = max_ii(0, BLI_listbase_count(&layer->masks) - 1);
}
static void rna_iterator_grease_pencil_layers_begin(CollectionPropertyIterator *iter,
PointerRNA *ptr)
{
@ -229,6 +283,60 @@ static int rna_iterator_grease_pencil_layer_groups_length(PointerRNA *ptr)
#else
static void rna_def_grease_pencil_layers_mask_api(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
PropertyRNA *prop;
RNA_def_property_srna(cprop, "GreasePencilLayerMasks");
srna = RNA_def_struct(brna, "GreasePencilLayerMasks", nullptr);
RNA_def_struct_sdna(srna, "GreasePencilLayer");
RNA_def_struct_ui_text(
srna, "Grease Pencil Mask Layers", "Collection of grease pencil masking layers");
prop = RNA_def_property(srna, "active_mask_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_int_funcs(prop,
"rna_grease_pencil_active_mask_index_get",
"rna_grease_pencil_active_mask_index_set",
"rna_grease_pencil_active_mask_index_range");
RNA_def_property_ui_text(prop, "Active Layer Mask Index", "Active index in layer mask array");
}
static void rna_def_grease_pencil_layer_mask(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "GreasePencilLayerMask", nullptr);
RNA_def_struct_sdna(srna, "GreasePencilLayerMask");
RNA_def_struct_ui_text(srna, "Grease Pencil Masking Layers", "List of Mask Layers");
// RNA_def_struct_path_func(srna, "rna_GreasePencilLayerMask_path");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(prop, "Layer", "Mask layer name");
RNA_def_property_string_sdna(prop, nullptr, "layer_name");
RNA_def_property_string_funcs(prop,
"rna_grease_pencil_layer_mask_name_get",
"rna_grease_pencil_layer_mask_name_length",
"rna_grease_pencil_layer_mask_name_set");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA | NA_RENAME, nullptr);
prop = RNA_def_property(srna, "hide", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", GP_LAYER_MASK_HIDE);
RNA_def_property_ui_icon(prop, ICON_HIDE_OFF, -1);
RNA_def_property_ui_text(prop, "Hide", "Set mask Visibility");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
prop = RNA_def_property(srna, "invert", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", GP_LAYER_MASK_INVERT);
RNA_def_property_ui_icon(prop, ICON_SELECT_INTERSECT, 1);
RNA_def_property_ui_text(prop, "Invert", "Invert mask");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
}
static void rna_def_grease_pencil_layer(BlenderRNA *brna)
{
StructRNA *srna;
@ -236,6 +344,15 @@ static void rna_def_grease_pencil_layer(BlenderRNA *brna)
static const float scale_defaults[3] = {1.0f, 1.0f, 1.0f};
static const EnumPropertyItem rna_enum_layer_blend_modes_items[] = {
{GP_LAYER_BLEND_NONE, "REGULAR", 0, "Regular", ""},
{GP_LAYER_BLEND_HARDLIGHT, "HARDLIGHT", 0, "Hard Light", ""},
{GP_LAYER_BLEND_ADD, "ADD", 0, "Add", ""},
{GP_LAYER_BLEND_SUBTRACT, "SUBTRACT", 0, "Subtract", ""},
{GP_LAYER_BLEND_MULTIPLY, "MULTIPLY", 0, "Multiply", ""},
{GP_LAYER_BLEND_DIVIDE, "DIVIDE", 0, "Divide", ""},
{0, nullptr, 0, nullptr, nullptr}};
srna = RNA_def_struct(brna, "GreasePencilLayer", nullptr);
RNA_def_struct_sdna(srna, "GreasePencilLayer");
RNA_def_struct_ui_text(srna, "Grease Pencil Layer", "Collection of related drawings");
@ -251,6 +368,13 @@ static void rna_def_grease_pencil_layer(BlenderRNA *brna)
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA | NA_RENAME, "rna_grease_pencil_update");
/* Mask Layers */
prop = RNA_def_property(srna, "mask_layers", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "masks", nullptr);
RNA_def_property_struct_type(prop, "GreasePencilLayerMask");
RNA_def_property_ui_text(prop, "Masks", "List of Masking Layers");
rna_def_grease_pencil_layers_mask_api(brna, prop);
/* Visibility */
prop = RNA_def_property(srna, "hide", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
@ -282,6 +406,16 @@ static void rna_def_grease_pencil_layer(BlenderRNA *brna)
prop, "Onion Skinning", "Display onion skins before and after the current frame");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
/* Use Masks. */
prop = RNA_def_property(srna, "use_masks", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(
prop, "GreasePencilLayerTreeNode", "flag", GP_LAYER_TREE_NODE_HIDE_MASKS);
RNA_def_property_ui_text(
prop,
"Use Masks",
"The visibility of drawings on this layer is affected by the layers in its masks list");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
/* pass index for compositing and modifiers */
prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_UNSIGNED);
RNA_def_property_ui_text(prop, "Pass Index", "Index number for the \"Layer Index\" pass");
@ -332,6 +466,12 @@ static void rna_def_grease_pencil_layer(BlenderRNA *brna)
prop,
"ViewLayer",
"Only include Layer in this View Layer render output (leave blank to include always)");
prop = RNA_def_property(srna, "blend_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "blend_mode");
RNA_def_property_enum_items(prop, rna_enum_layer_blend_modes_items);
RNA_def_property_ui_text(prop, "Blend Mode", "Blend mode");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
}
static void rna_def_grease_pencil_layers_api(BlenderRNA *brna, PropertyRNA *cprop)
@ -392,6 +532,16 @@ static void rna_def_grease_pencil_layer_group(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Locked", "Protect group from further editing and/or frame changes");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
/* Use Masks. */
prop = RNA_def_property(srna, "use_masks", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(
prop, "GreasePencilLayerTreeNode", "flag", GP_LAYER_TREE_NODE_HIDE_MASKS);
RNA_def_property_ui_text(prop,
"Use Masks",
"The visibility of drawings in the layers in this group is affected by "
"the layers in the masks lists");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
}
static void rna_def_grease_pencil_data(BlenderRNA *brna)
@ -399,6 +549,16 @@ static void rna_def_grease_pencil_data(BlenderRNA *brna)
StructRNA *srna;
PropertyRNA *prop;
static EnumPropertyItem prop_stroke_depth_order_items[] = {
{0, "2D", 0, "2D Layers", "Display strokes using grease pencil layers to define order"},
{GREASE_PENCIL_STROKE_ORDER_3D,
"3D",
0,
"3D Location",
"Display strokes using real 3D position in 3D space"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "GreasePencilv3", "ID");
RNA_def_struct_sdna(srna, "GreasePencil");
RNA_def_struct_ui_text(srna, "Grease Pencil", "Grease Pencil data-block");
@ -462,12 +622,23 @@ static void rna_def_grease_pencil_data(BlenderRNA *brna)
"Auto-Lock Layers",
"Automatically lock all layers except the active one to avoid accidental changes");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_autolock");
/* Uses a single flag, because the depth order can only be 2D or 3D. */
prop = RNA_def_property(srna, "stroke_depth_order", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, nullptr, "flag");
RNA_def_property_enum_items(prop, prop_stroke_depth_order_items);
RNA_def_property_ui_text(
prop,
"Stroke Depth Order",
"Defines how the strokes are ordered in 3D space (for objects not displayed 'In Front')");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, "rna_grease_pencil_update");
}
void RNA_def_grease_pencil(BlenderRNA *brna)
{
rna_def_grease_pencil_data(brna);
rna_def_grease_pencil_layer(brna);
rna_def_grease_pencil_layer_mask(brna);
rna_def_grease_pencil_layer_group(brna);
}

View File

@ -3291,12 +3291,16 @@ static void rna_def_modifier_boolean(BlenderRNA *brna)
};
static const EnumPropertyItem prop_solver_items[] = {
{eBooleanModifierSolver_Fast,
{eBooleanModifierSolver_Float,
"FAST",
0,
"Fast",
"Simple solver for the best performance, without support for overlapping geometry"},
{eBooleanModifierSolver_Exact, "EXACT", 0, "Exact", "Advanced solver for the best result"},
{eBooleanModifierSolver_Mesh_Arr,
"EXACT",
0,
"Exact",
"Advanced solver for the best result"},
{0, nullptr, 0, nullptr, nullptr},
};
@ -3363,7 +3367,7 @@ static void rna_def_modifier_boolean(BlenderRNA *brna)
prop = RNA_def_property(srna, "solver", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_solver_items);
RNA_def_property_enum_default(prop, eBooleanModifierSolver_Exact);
RNA_def_property_enum_default(prop, eBooleanModifierSolver_Mesh_Arr);
RNA_def_property_ui_text(prop, "Solver", "Method for calculating booleans");
RNA_def_property_update(prop, 0, "rna_Modifier_update");

View File

@ -952,6 +952,7 @@ void RNA_api_wm(StructRNA *srna)
parm = RNA_def_property(func, "icon", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(parm, rna_operator_popup_icon_items);
RNA_def_property_enum_default(parm, ALERT_ICON_NONE);
RNA_def_property_ui_text(parm, "Icon", "Optional icon displayed in the dialog");
api_ui_item_common_translation(func);

View File

@ -84,7 +84,7 @@ static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_re
}
if (bmd->flag & eBooleanModifierFlag_Collection) {
/* The Exact solver tolerates an empty collection. */
return !col && bmd->solver != eBooleanModifierSolver_Exact;
return !col && bmd->solver != eBooleanModifierSolver_Mesh_Arr;
}
return false;
}
@ -176,7 +176,7 @@ static bool BMD_error_messages(const Object *ob, ModifierData *md)
bool error_returns_result = false;
const bool operand_collection = (bmd->flag & eBooleanModifierFlag_Collection) != 0;
const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact;
const bool use_exact = bmd->solver == eBooleanModifierSolver_Mesh_Arr;
const bool operation_intersect = bmd->operation == eBooleanModifierOp_Intersect;
#ifndef WITH_GMP
@ -478,14 +478,19 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd,
const bool use_self = (bmd->flag & eBooleanModifierFlag_Self) != 0;
const bool hole_tolerant = (bmd->flag & eBooleanModifierFlag_HoleTolerant) != 0;
Mesh *result = blender::meshintersect::direct_mesh_boolean(meshes,
obmats,
ctx->object->object_to_world(),
material_remaps,
use_self,
hole_tolerant,
bmd->operation,
nullptr);
blender::geometry::boolean::BooleanOpParameters op_params;
op_params.boolean_mode = blender::geometry::boolean::Operation(bmd->operation);
op_params.no_self_intersections = !use_self;
op_params.watertight = !hole_tolerant;
op_params.no_nested_components = false;
Mesh *result = blender::geometry::boolean::mesh_boolean(
meshes,
obmats,
ctx->object->object_to_world(),
material_remaps,
op_params,
blender::geometry::boolean::Solver::MeshArr,
nullptr);
if (material_mode == eBooleanModifierMaterialMode_Transfer) {
MEM_SAFE_FREE(result->mat);
@ -513,7 +518,7 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
}
#ifdef WITH_GMP
if (bmd->solver == eBooleanModifierSolver_Exact) {
if (bmd->solver == eBooleanModifierSolver_Mesh_Arr) {
return exact_boolean_mesh(bmd, ctx, mesh);
}
#endif
@ -631,7 +636,7 @@ static void solver_options_panel_draw(const bContext * /*C*/, Panel *panel)
uiLayout *layout = panel->layout;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
const bool use_exact = RNA_enum_get(ptr, "solver") == eBooleanModifierSolver_Exact;
const bool use_exact = RNA_enum_get(ptr, "solver") == eBooleanModifierSolver_Mesh_Arr;
uiLayoutSetPropSep(layout, true);

View File

@ -77,7 +77,7 @@ class AntiAliasingOperation : public NodeOperation {
* algorithm expects it in the [0, 10] range. */
float get_local_contrast_adaptation_factor()
{
return node_storage(bnode()).threshold * 10.0f;
return node_storage(bnode()).contrast_limit * 10.0f;
}
/* Blender encodes the corner rounding factor in the float [0, 1] range, while the SMAA algorithm

View File

@ -30,12 +30,15 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Bool>("Self Intersection");
b.add_input<decl::Bool>("Hole Tolerant");
b.add_output<decl::Geometry>("Mesh").propagate_all();
b.add_output<decl::Bool>("Intersecting Edges").field_on_all();
b.add_output<decl::Bool>("Intersecting Edges").field_on_all().make_available([](bNode &node) {
node.custom2 = int16_t(geometry::boolean::Solver::MeshArr);
});
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
uiItemR(layout, ptr, "solver", UI_ITEM_NONE, "", ICON_NONE);
}
struct AttributeOutputs {
@ -44,27 +47,34 @@ struct AttributeOutputs {
static void node_update(bNodeTree *ntree, bNode *node)
{
GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)node->custom1;
const geometry::boolean::Operation operation = geometry::boolean::Operation(node->custom1);
const geometry::boolean::Solver solver = geometry::boolean::Solver(node->custom2);
bNodeSocket *geometry_1_socket = static_cast<bNodeSocket *>(node->inputs.first);
bNodeSocket *geometry_2_socket = geometry_1_socket->next;
bNodeSocket *intersecting_edges_socket = static_cast<bNodeSocket *>(node->outputs.last);
switch (operation) {
case GEO_NODE_BOOLEAN_INTERSECT:
case GEO_NODE_BOOLEAN_UNION:
case geometry::boolean::Operation::Intersect:
case geometry::boolean::Operation::Union:
bke::nodeSetSocketAvailability(ntree, geometry_1_socket, false);
node_sock_label(geometry_2_socket, "Mesh");
break;
case GEO_NODE_BOOLEAN_DIFFERENCE:
case geometry::boolean::Operation::Difference:
bke::nodeSetSocketAvailability(ntree, geometry_1_socket, true);
node_sock_label(geometry_2_socket, "Mesh 2");
break;
}
bke::nodeSetSocketAvailability(
ntree, intersecting_edges_socket, solver == geometry::boolean::Solver::MeshArr);
}
static void node_init(bNodeTree * /*tree*/, bNode *node)
{
node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE;
node->custom1 = int16_t(geometry::boolean::Operation::Difference);
node->custom2 = int16_t(geometry::boolean::Solver::Float);
}
#ifdef WITH_GMP
@ -82,7 +92,8 @@ static Array<short> calc_mesh_material_map(const Mesh &mesh, VectorSet<Material
static void node_geo_exec(GeoNodeExecParams params)
{
#ifdef WITH_GMP
GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1;
geometry::boolean::Operation operation = geometry::boolean::Operation(params.node().custom1);
geometry::boolean::Solver solver = geometry::boolean::Solver(params.node().custom2);
const bool use_self = params.get_input<bool>("Self Intersection");
const bool hole_tolerant = params.get_input<bool>("Hole Tolerant");
@ -92,7 +103,7 @@ static void node_geo_exec(GeoNodeExecParams params)
Vector<Array<short>> material_remaps;
GeometrySet set_a;
if (operation == GEO_NODE_BOOLEAN_DIFFERENCE) {
if (operation == geometry::boolean::Operation::Difference) {
set_a = params.extract_input<GeometrySet>("Mesh 1");
/* Note that it technically wouldn't be necessary to realize the instances for the first
* geometry input, but the boolean code expects the first shape for the difference operation
@ -153,18 +164,24 @@ static void node_geo_exec(GeoNodeExecParams params)
}
AttributeOutputs attribute_outputs;
attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed(
"Intersecting Edges");
if (solver == geometry::boolean::Solver::MeshArr) {
attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed(
"Intersecting Edges");
}
Vector<int> intersecting_edges;
Mesh *result = blender::meshintersect::direct_mesh_boolean(
geometry::boolean::BooleanOpParameters op_params;
op_params.boolean_mode = operation;
op_params.no_self_intersections = !use_self;
op_params.watertight = !hole_tolerant;
op_params.no_nested_components = true; /* TODO: make this configurable. */
Mesh *result = geometry::boolean::mesh_boolean(
meshes,
transforms,
float4x4::identity(),
material_remaps,
use_self,
hole_tolerant,
operation,
op_params,
solver,
attribute_outputs.intersecting_edges_id ? &intersecting_edges : nullptr);
if (!result) {
params.set_default_remaining_outputs();
@ -203,19 +220,36 @@ static void node_geo_exec(GeoNodeExecParams params)
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem rna_node_geometry_boolean_method_items[] = {
{GEO_NODE_BOOLEAN_INTERSECT,
{int(geometry::boolean::Operation::Intersect),
"INTERSECT",
0,
"Intersect",
"Keep the part of the mesh that is common between all operands"},
{GEO_NODE_BOOLEAN_UNION, "UNION", 0, "Union", "Combine meshes in an additive way"},
{GEO_NODE_BOOLEAN_DIFFERENCE,
{int(geometry::boolean::Operation::Union),
"UNION",
0,
"Union",
"Combine meshes in an additive way"},
{int(geometry::boolean::Operation::Difference),
"DIFFERENCE",
0,
"Difference",
"Combine meshes in a subtractive way"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem rna_geometry_boolean_solver_items[] = {
{int(geometry::boolean::Solver::MeshArr),
"EXACT",
0,
"Exact",
"Exact solver for the best results"},
{int(geometry::boolean::Solver::Float),
"FLOAT",
0,
"Float",
"Simple solver for the best performance, without support for overlapping geometry"},
{0, nullptr, 0, nullptr, nullptr},
};
RNA_def_node_enum(srna,
"operation",
@ -223,7 +257,15 @@ static void node_rna(StructRNA *srna)
"",
rna_node_geometry_boolean_method_items,
NOD_inline_enum_accessors(custom1),
GEO_NODE_BOOLEAN_INTERSECT);
int(geometry::boolean::Operation::Intersect));
RNA_def_node_enum(srna,
"solver",
"Solver",
"",
rna_geometry_boolean_solver_items,
NOD_inline_enum_accessors(custom2),
int(geometry::boolean::Solver::Float));
}
static void node_register()

View File

@ -195,7 +195,7 @@ PyDoc_STRVAR(
" :arg id: The command identifier (must pass an ``str.isidentifier`` check).\n"
"\n"
" If the ``id`` is already registered, a warning is printed and "
"the command is inaccessible to prevent accidents invoking the wrong command."
"the command is inaccessible to prevent accidents invoking the wrong command.\n"
" :type id: str\n"
" :arg execute: Callback, taking a single list of strings and returns an int.\n"
" The arguments are built from all command-line arguments following the command id.\n"

View File

@ -133,7 +133,8 @@ static float seq_cache_timeline_frame_to_frame_index(Scene *scene,
/* With raw images, map timeline_frame to strip input media frame range. This means that static
* images or extended frame range of movies will only generate one cache entry. No special
* treatment in converting frame index to timeline_frame is needed. */
if (ELEM(type, SEQ_CACHE_STORE_RAW, SEQ_CACHE_STORE_THUMBNAIL)) {
bool is_effect = seq->type & SEQ_TYPE_EFFECT;
if (!is_effect && ELEM(type, SEQ_CACHE_STORE_RAW, SEQ_CACHE_STORE_THUMBNAIL)) {
return SEQ_give_frame_index(scene, seq, timeline_frame);
}

View File

@ -474,6 +474,11 @@ void WM_OT_circle_gesture(wmOperatorType *ot)
/* -------------------------------------------------------------------- */
/** \name Lasso Gesture
* There are two types of lasso gesture:
* 1. #WM_GESTURE_LASSO: A lasso that follows the mouse cursor with the enclosed area shaded.
* 2. #WM_GESTURE_LINES: A lasso that follows the mouse cursor without the enclosed area shaded.
*
* The operator stores data in the "path" property as a series of screen space positions.
* \{ */
int WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event)

View File

@ -153,6 +153,7 @@ static ImBuf *wm_block_splash_image(int width, int *r_height)
}
if (ibuf) {
ibuf->planes = 32; /* The image might not have an alpha channel. */
height = (width * ibuf->y) / ibuf->x;
if (width != ibuf->x || height != ibuf->y) {
IMB_scaleImBuf(ibuf, width, height);

View File

@ -272,7 +272,7 @@ def words_from_text(text: str, check_type: str) -> List[Tuple[str, int]]:
w_prev = w_lower
w_prev_start = w_start
else:
assert False
assert False, "unreachable"
return words
@ -551,7 +551,7 @@ def spell_check_file(
# print(filepath + ":" + str(slineno + 1) + ":" + str(scol), w, "(duplicates)")
yield (w, slineno, scol)
else:
assert False
assert False, "unreachable"
def spell_check_file_recursive(

View File

@ -772,7 +772,7 @@ class BlendFileHeader:
elif pointer_size_id == b'_':
self.pointer_size = 4
else:
assert 0
assert False, "unreachable"
endian_id = values[2]
if endian_id == b'v':
self.is_little_endian = True
@ -783,7 +783,7 @@ class BlendFileHeader:
self.endian_index = 1
self.endian_str = b'>'
else:
assert 0
assert False, "unreachable"
version_id = values[3]
self.version = int(version_id)