Mesh: Update addons for auto smooth removal #104609

Merged
Hans Goudey merged 10 commits from HooglyBoogly/blender-addons:refactor-mesh-corner-normals-lazy into main 2023-10-20 16:53:48 +02:00
20 changed files with 24 additions and 119 deletions

View File

@ -418,7 +418,6 @@ class add_mesh_bolt(Operator, AddObjectHelper):
(context.active_object.data is not None) and ('Bolt' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
use_auto_smooth = bool(obj.data.use_auto_smooth) # Copy value, do not take a reference
use_smooth = bool(obj.data.polygons[0].use_smooth) # Copy value, do not take a reference
mesh = createMesh.Create_New_Mesh(self, context)
@ -430,7 +429,6 @@ class add_mesh_bolt(Operator, AddObjectHelper):
bm.free()
# Preserve flat/smooth choice. New mesh is flat by default
obj.data.use_auto_smooth = use_auto_smooth
if use_smooth:
bpy.ops.object.shade_smooth()
else:

View File

@ -150,9 +150,6 @@ def createMeshObject(context, verts, edges, faces, name):
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Set mesh to use auto smoothing:
mesh.use_auto_smooth = True
# Update mesh geometry after adding stuff.
mesh.update()

View File

@ -17,11 +17,12 @@ def create_and_link_mesh(name, faces, face_nors, points, global_matrix):
mesh.from_pydata(points, [], faces)
if face_nors:
# Note: we store 'temp' normals in loops, since validate() may alter final mesh,
# we can only set custom lnors *after* calling it.
mesh.create_normals_split()
# Write imported normals to a temporary attribute so they are interpolated by #mesh.validate().
# It's important to validate before calling #mesh.normals_split_custom_set() which expects a
# valid mesh.
lnors = tuple(chain(*chain(*zip(face_nors, face_nors, face_nors))))
mesh.loops.foreach_set("normal", lnors)
mesh.attributes.new("temp_custom_normals", 'FLOAT_VECTOR', 'CORNER')
mesh.attributes["temp_custom_normals"].data.foreach_set("vector", lnors)
mesh.transform(global_matrix)
@ -30,13 +31,12 @@ def create_and_link_mesh(name, faces, face_nors, points, global_matrix):
if face_nors:
clnors = array.array('f', [0.0] * (len(mesh.loops) * 3))
mesh.loops.foreach_get("normal", clnors)
mesh.attributes["temp_custom_normals"].data.foreach_get("vector", clnors)
mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons))
mesh.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3)))
mesh.use_auto_smooth = True
mesh.free_normals_split()
mesh.attributes.remove(mesh.attributes["temp_custom_normals"])
mesh.update()

View File

@ -1298,12 +1298,9 @@ def make_object_node(ob, translation, rotation, scale, name_id):
obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name)))
obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0x0040))
"""Flags2 defines 0x01 for display path, 0x02 use autosmooth, 0x04 object frozen,
"""Flags2 defines 0x01 for display path, 0x04 object frozen,
0x10 for motion blur, 0x20 for material morph and bit 0x40 for mesh morph."""
if ob.type == 'MESH' and ob.data.use_auto_smooth:
obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0x02))
else:
obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
obj_node_header_chunk.add_variable("parent", _3ds_ushort(ROOT_OBJECT))
'''
@ -1343,12 +1340,6 @@ def make_object_node(ob, translation, rotation, scale, name_id):
obj_boundbox.add_variable("max", _3ds_point_3d(ob.bound_box[6]))
obj_node.add_subchunk(obj_boundbox)
# Add smooth angle if autosmooth is used
if ob.type == 'MESH' and ob.data.use_auto_smooth:
obj_morph_smooth = _3ds_chunk(OBJECT_MORPH_SMOOTH)
obj_morph_smooth.add_variable("angle", _3ds_float(round(ob.data.auto_smooth_angle, 6)))
obj_node.add_subchunk(obj_morph_smooth)
# Add track chunks for position, rotation, size
ob_scale = scale[name] # and collect masterscale
if parent is None or (parent.name not in name_id):

View File

@ -1331,8 +1331,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, CONSTRAI
elif new_chunk.ID == MORPH_SMOOTH and tracking == 'OBJECT': # Smooth angle
smooth_angle = read_float(new_chunk)
if child.data is not None: # Check if child is a dummy
child.data.use_auto_smooth = True
child.data.auto_smooth_angle = smooth_angle
child.data.set_sharp_from_angle(smooth_angle)
elif KEYFRAME and new_chunk.ID == COL_TRACK_TAG and tracking == 'AMBIENT': # Ambient
keyframe_data = {}

View File

@ -1158,7 +1158,6 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
# NOTE: this is not supported by importer currently.
# XXX Official docs says normals should use IndexToDirect,
# but this does not seem well supported by apps currently...
me.calc_normals_split()
ln_bl_dtype = np.single
ln_fbx_dtype = np.float64
@ -1258,8 +1257,6 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
# del t_lnw
me.free_tangents()
me.free_normals_split()
# Write VertexColor Layers.
colors_type = scene_data.settings.colors_type
vcolnumber = 0 if colors_type == 'NONE' else len(me.color_attributes)

View File

@ -1653,8 +1653,6 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh):
1, fbx_item_size, layer_id,
xform=np.logical_not, # in FBX, 0 (False) is sharp, but in Blender True is sharp.
)
# We only set sharp edges here, not face smoothing itself...
mesh.use_auto_smooth = True
return False
elif fbx_layer_mapping == b'ByPolygon':
blen_data = MESH_ATTRIBUTE_SHARP_FACE.ensure(mesh.attributes).data
@ -1737,23 +1735,23 @@ def blen_read_geom_layer_normal(fbx_obj, mesh, xform=None):
bl_norm_dtype = np.single
item_size = 3
# try loops, then polygons, then vertices.
tries = ((mesh.loops, "Loops", False, blen_read_geom_array_mapped_polyloop),
tries = ((mesh.attributes["temp_custom_normals"].data, "Loops", False, blen_read_geom_array_mapped_polyloop),
(mesh.polygons, "Polygons", True, blen_read_geom_array_mapped_polygon),
(mesh.vertices, "Vertices", True, blen_read_geom_array_mapped_vert))
for blen_data, blen_data_type, is_fake, func in tries:
bdata = np.zeros((len(blen_data), item_size), dtype=bl_norm_dtype) if is_fake else blen_data
if func(mesh, bdata, "normal", bl_norm_dtype,
if func(mesh, bdata, "vector", bl_norm_dtype,
fbx_layer_data, fbx_layer_index, fbx_layer_mapping, fbx_layer_ref, 3, item_size, layer_id, xform, True):
if blen_data_type == "Polygons":
# To expand to per-loop normals, repeat each per-polygon normal by the number of loops of each polygon.
poly_loop_totals = np.empty(len(mesh.polygons), dtype=np.uintc)
mesh.polygons.foreach_get("loop_total", poly_loop_totals)
loop_normals = np.repeat(bdata, poly_loop_totals, axis=0)
mesh.loops.foreach_set("normal", loop_normals.ravel())
mesh.attributes["temp_custom_normals"].data.foreach_set("normal", loop_normals.ravel())
elif blen_data_type == "Vertices":
# We have to copy vnors to lnors! Far from elegant, but simple.
loop_vertex_indices = MESH_ATTRIBUTE_CORNER_VERT.to_ndarray(mesh.attributes)
mesh.loops.foreach_set("normal", bdata[loop_vertex_indices].ravel())
mesh.attributes["temp_custom_normals"].data.foreach_set("normal", bdata[loop_vertex_indices].ravel())
return True
blen_read_geom_array_error_mapping("normal", fbx_layer_mapping)
@ -1877,7 +1875,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
if settings.use_custom_normals:
# Note: we store 'temp' normals in loops, since validate() may alter final mesh,
# we can only set custom lnors *after* calling it.
mesh.create_normals_split()
mesh.attributes.new("temp_custom_normals", 'FLOAT_VECTOR', 'CORNER')
if geom_mat_no is None:
ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh)
else:
@ -1889,7 +1887,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
if ok_normals:
bl_nors_dtype = np.single
clnors = np.empty(len(mesh.loops) * 3, dtype=bl_nors_dtype)
mesh.loops.foreach_get("normal", clnors)
mesh.attributes["temp_custom_normals"].data.foreach_get("vector", clnors)
if not ok_smooth:
sharp_face = MESH_ATTRIBUTE_SHARP_FACE.get(attributes)
@ -1900,10 +1898,8 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings):
# Iterating clnors into a nested tuple first is faster than passing clnors.reshape(-1, 3) directly into
# normals_split_custom_set. We use clnors.data since it is a memoryview, which is faster to iterate than clnors.
mesh.normals_split_custom_set(tuple(zip(*(iter(clnors.data),) * 3)))
mesh.use_auto_smooth = True
if settings.use_custom_normals:
mesh.free_normals_split()
mesh.attributes.remove(mesh.attributes["temp_custom_normals"])
if not ok_smooth:
sharp_face = MESH_ATTRIBUTE_SHARP_FACE.get(attributes)

View File

@ -64,8 +64,6 @@ class PrimitiveCreator:
self.blender_object = self.export_settings['vtree'].nodes[self.uuid_for_skined_data].blender_object
self.use_normals = self.export_settings['gltf_normals']
if self.use_normals:
self.blender_mesh.calc_normals_split()
self.use_tangents = False
if self.use_normals and self.export_settings['gltf_tangents']:
@ -776,7 +774,6 @@ class PrimitiveCreator:
self.normals = np.array(self.normals, dtype=np.float32)
else:
self.normals = np.empty(len(self.blender_mesh.loops) * 3, dtype=np.float32)
self.blender_mesh.calc_normals_split()
self.blender_mesh.loops.foreach_get('normal', self.normals)
self.normals = self.normals.reshape(len(self.blender_mesh.loops), 3)

View File

@ -61,10 +61,9 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
# Use a class here, to be able to pass data by reference to hook (to be able to change them inside hook)
class IMPORT_mesh_options:
def __init__(self, skinning: bool = True, skin_into_bind_pose: bool = True, use_auto_smooth: bool = True):
def __init__(self, skinning: bool = True, skin_into_bind_pose: bool = True):
self.skinning = skinning
self.skin_into_bind_pose = skin_into_bind_pose
self.use_auto_smooth = use_auto_smooth
mesh_options = IMPORT_mesh_options()
import_user_extensions('gather_import_mesh_options', gltf, mesh_options, pymesh, skin_idx)
@ -479,9 +478,7 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob):
mesh.update(calc_edges_loose=has_loose_edges)
if has_normals:
mesh.create_normals_split()
mesh.normals_split_custom_set_from_vertices(vert_normals)
mesh.use_auto_smooth = mesh_options.use_auto_smooth
def points_edges_tris(mode, indices):

View File

@ -819,10 +819,6 @@ def export(file,
# --- Write IndexedFaceSet Attributes (same as IndexedTriangleSet)
fw('solid="%s"\n' % bool_as_str(material and material.use_backface_culling))
if is_smooth:
# use Auto-Smooth angle, if enabled. Otherwise make
# the mesh perfectly smooth by creaseAngle > pi.
fw(ident_step + 'creaseAngle="%.4f"\n' % (mesh.auto_smooth_angle if mesh.use_auto_smooth else 4.0))
if use_normals:
# currently not optional, could be made so:

View File

@ -3013,8 +3013,7 @@ def importShape_ProcessObject(
# solid=false, we don't support it yet.
creaseAngle = geom.getFieldAsFloat('creaseAngle', None, ancestry)
if creaseAngle is not None:
bpydata.auto_smooth_angle = creaseAngle
bpydata.use_auto_smooth = True
bpydata.set_sharp_from_angle(creaseAngle)
else:
bpydata.polygons.foreach_set("use_smooth", [False] * len(bpydata.polygons))

View File

@ -677,8 +677,7 @@ def mu_set_auto_smooth(self, angle, affect, set_smooth_shading):
#bpy.ops.object.shade_smooth()
object.data.use_auto_smooth = 1
object.data.auto_smooth_angle = angle # 35 degrees as radians
object.data.set_sharp_from_angle(angle) # 35 degrees as radians
objects_affected += 1

View File

@ -171,13 +171,6 @@ class VIEW3D_MT_materialutilities_specials(bpy.types.Menu):
text = "Join by material",
icon = "OBJECT_DATAMODE")
layout.separator()
op = layout.operator(MATERIAL_OT_materialutilities_auto_smooth_angle.bl_idname,
text = "Set Auto Smooth",
icon = "SHADING_SOLID")
op.affect = mu_prefs.set_smooth_affect
op.angle = mu_prefs.auto_smooth_angle
class VIEW3D_MT_materialutilities_main(bpy.types.Menu):
"""Main menu for Material Utilities"""

View File

@ -68,20 +68,6 @@ class VIEW3D_MT_materialutilities_preferences(AddonPreferences):
default = 0
)
set_smooth_affect: EnumProperty(
name = "Set Auto Smooth Affect",
description = "Which objects to affect",
items = mu_affect_enums,
default = 'SELECTED'
)
auto_smooth_angle: FloatProperty(
name = "Auto Smooth Angle",
description = "Maximum angle between face normals that will be considered as smooth",
subtype = 'ANGLE',
min = 0,
max = radians(180),
default = radians(35)
)
def draw(self, context):
layout = self.layout
@ -105,11 +91,6 @@ class VIEW3D_MT_materialutilities_preferences(AddonPreferences):
c.row().prop(self, "link_to", expand = False)
c.row().prop(self, "link_to_affect", expand = False)
d = box.box()
d.label(text = "Set Auto Smooth")
d.row().prop(self, "auto_smooth_angle", expand = False)
d.row().prop(self, "set_smooth_affect", expand = False)
box = layout.box()
box.label(text = "Miscellaneous")

View File

@ -802,7 +802,6 @@ def tessellate_patch(props):
n2 = n2[masked_faces][:,None,:]
else:
if normals_mode == 'CUSTOM':
me0.calc_normals_split()
normals_split = [0]*len(me0.loops)*3
vertex_indexes = [0]*len(me0.loops)
me0.loops.foreach_get('normal', normals_split)

View File

@ -338,8 +338,7 @@ def CreateBevel(context, CurrentObject):
bpy.ops.object.shade_smooth()
context.object.data.use_auto_smooth = True
context.object.data.auto_smooth_angle = 1.0471975
context.object.data.set_sharp_from_angle(1.0471975)
# Restore the active object
context.view_layer.objects.active = SavActive

View File

@ -108,12 +108,6 @@ class PovDataButtonsPanel(properties_data_mesh.MeshButtonsPanel):
# We cannot inherit from RNA classes (like e.g. properties_data_mesh.DATA_PT_vertex_groups).
# Complex py/bpy/rna interactions (with metaclass and all) simply do not allow it to work.
# So we simply have to explicitly copy here the interesting bits. ;)
class DATA_PT_POV_normals(PovDataButtonsPanel, Panel):
bl_label = properties_data_mesh.DATA_PT_normals.bl_label
draw = properties_data_mesh.DATA_PT_normals.draw
class DATA_PT_POV_texture_space(PovDataButtonsPanel, Panel):
bl_label = properties_data_mesh.DATA_PT_texture_space.bl_label
bl_options = properties_data_mesh.DATA_PT_texture_space.bl_options
@ -1066,7 +1060,6 @@ class VIEW_WT_POV_blobcube_add(WorkSpaceTool):
classes = (
# ObjectButtonsPanel,
# PovDataButtonsPanel,
DATA_PT_POV_normals,
DATA_PT_POV_texture_space,
DATA_PT_POV_vertex_groups,
DATA_PT_POV_shape_keys,

View File

@ -180,7 +180,6 @@ def pov_cylinder_define(context, op, ob, radius, loc, loc_cap):
ob.name = ob.data.name = "PovCylinder"
ob.pov.cylinder_radius = radius
ob.pov.cylinder_location_cap = vec
ob.data.use_auto_smooth = True
ob.pov.object_as = "CYLINDER"
ob.update_tag() # as prop set via python not updated in depsgraph
@ -326,7 +325,6 @@ def pov_sphere_define(context, op, ob, loc):
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.hide(unselected=False)
bpy.ops.object.mode_set(mode="OBJECT")
ob.data.use_auto_smooth = True
bpy.ops.object.shade_smooth()
ob.pov.object_as = "SPHERE"
ob.update_tag() # as prop set via python not updated in depsgraph
@ -471,7 +469,6 @@ def pov_cone_define(context, op, ob):
ob.pov.cone_height = height
ob.pov.cone_base_z = zb
ob.pov.cone_cap_z = zc
ob.data.use_auto_smooth = True
bpy.ops.object.shade_smooth()
ob.pov.object_as = "CONE"
ob.update_tag() # as prop set via python not updated in depsgraph
@ -659,9 +656,7 @@ def pov_torus_define(context, op, ob):
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.hide(unselected=False)
bpy.ops.object.mode_set(mode="OBJECT")
ob.data.use_auto_smooth = True
ob.data.auto_smooth_angle = 0.6
bpy.ops.object.shade_smooth()
ob.data.set_sharp_from_angle(0.6)
ob.pov.object_as = "TORUS"
ob.update_tag() # as prop set via python not updated in depsgraph

View File

@ -171,8 +171,7 @@ def pov_superellipsoid_define(context, op, ob):
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.hide(unselected=False)
bpy.ops.object.mode_set(mode="OBJECT")
ob.data.auto_smooth_angle = 1.3
bpy.ops.object.shade_smooth()
ob.data.set_sharp_from_angle(1.3)
ob.pov.object_as = "SUPERELLIPSOID"
ob.update_tag() # as prop set via python not updated in depsgraph
@ -1051,8 +1050,7 @@ def pov_parametric_define(context, op, ob):
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.hide(unselected=False)
bpy.ops.object.mode_set(mode="OBJECT")
ob.data.auto_smooth_angle = 0.6
bpy.ops.object.shade_smooth()
ob.data.set_sharp_from_angle(0.6)
ob.pov.object_as = "PARAMETRIC"
ob.update_tag() # as prop set via python not updated in depsgraph
return{'FINISHED'}
@ -1180,8 +1178,6 @@ class POV_OT_polygon_to_circle_add(Operator):
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.hide(unselected=False)
bpy.ops.object.mode_set(mode="OBJECT")
#ob.data.auto_smooth_angle = 0.1
#bpy.ops.object.shade_smooth()
ob.pov.object_as = "POLYCIRCLE"
ob.update_tag() # as prop set via python not updated in depsgraph
return {"FINISHED"}

View File

@ -180,21 +180,6 @@ class VIEW3D_OT_selecteditVertsEdgesFaces(Operator):
return {'FINISHED'}
# ********** Normals / Auto Smooth Menu **********
# Thanks to marvin.k.breuer for the Autosmooth part of the menu
def menu_func(self, context):
layout = self.layout
obj = context.object
obj_data = context.active_object.data
layout.separator()
layout.prop(obj_data, "use_auto_smooth", text="Normals: Auto Smooth")
# Auto Smooth Angle - two tab spaces to align it with the rest of the menu
layout.prop(obj_data, "auto_smooth_angle",
text=" Auto Smooth Angle")
# List The Classes #
classes = (
@ -215,7 +200,6 @@ def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.VIEW3D_MT_edit_mesh_normals.append(menu_func)
# Unregister Classes & Hotkeys #
def unregister():
@ -223,7 +207,6 @@ def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
bpy.types.VIEW3D_MT_edit_mesh_normals.remove(menu_func)
if __name__ == "__main__":
register()