# Wrong shading as a result of “bisect_plane” in bmesh

I am using a test script, I took it from here How to cut a mesh into smaller pieces with python?. The problem is that, the sliced meshes at the cut are creating unpleasant shading, I think the problem is in the recalculation of vertex normals.

I did triangulate the mesh then run "bisect_plane", I got the same problem.

Is there anyway that we can make them smooth during/after "bisect_plane", with a script.

I would like to thank @Lemon for his suggestion to use "DataTransfer" modifier How to create a seamless low poly terrain with displace and multiple planes?, from programming/mathematical point of view, this solution is not optimal. It is an approximation, in addition it is a work around and recalculating normals that is supposed to be done already. I did try the "DataTransfer" modifier, for sure it gives much better result, but the problem of wrong shading at the cut does exist still.

I have made a picture to demonstrate that. Please note with "DataTransfer" I tried all parameters with different values, such as "Mix Factor", "Mix Mode", etc.

I am a programmer and from programming point of view, the moment you make "bisect_plane" you can have the proper normals at the cut. There must be some boolean value in the function prototype "bisect_plane" indicating how the normals should look like at the cut and the normals do not change after leaving edit mode.

I can write code that has nothing to do with blender to make the cut and have the mathematically correct normals, but it would be great to have it within blender to see the result directly.

I think this can be a feature request to blender community.

• Related even if the subject is different. This answer shows a way to recalculate the normals blender.stackexchange.com/questions/75704/… – lemon Mar 17 '17 at 8:27
• @lemon thank you for you comment, the solution you propose is an approximation and not optimal, please have a look on the image I have added. – CSPassionant Mar 17 '17 at 22:40
• As explained in the answer, this is approximated mainly because of the double vertices created by the cuts. – lemon Mar 19 '17 at 8:14

Following the comments in the question and the edit of it, here is a script that automates the cuts and smooth normals to the original.

The principle is the following:

• Copy the original mesh
• Set a data transfer modifier on the copy targeting the original
• Cut the copy
• Remove double vertices (because the cuts may create doubles and these doubles explain what is shown in the edited question)
• Apply the modifier

The code is commented below. But if the comments are not clear enough, please tell.

import bpy, bmesh
from math import pi
from mathutils import Vector

#Utility: test if obj exists and is a mesh
def MeshObject( obj ):
return obj is not None and obj.type == 'MESH'

#Utility: go in the specified mode, call the lambda and returns to previous mode
def InMode( obj, mode, call ):
prevMode = obj.mode
result = None
try:
bpy.ops.object.mode_set( mode = mode, toggle = False )
result = call()
finally:
bpy.ops.object.mode_set( mode = prevMode, toggle = False )
return result

#Cut the mesh and gets the normals for the vertices created during the cut
def CutMesh( obj, cuts ): #Original code from CoDEmanX

bm = bmesh.from_edit_mesh( obj.data )

for (plane_co, plane_no) in cuts:
#Adds a bisectrice following the cut
result = bmesh.ops.bisect_plane( bm, geom = bm.verts[:] + bm.edges[:] + bm.faces[:], plane_co = plane_co, plane_no = plane_no )
#Gets the created bm edges from the result
edges = [e for e in result['geom_cut'] if isinstance( e, bmesh.types.BMEdge )]
#Splits the mesh according to these edges
bmesh.ops.split_edges( bm, edges = edges )

bmesh.update_edit_mesh( obj.data )

#Select all
def SelectAllVertices( obj ):
for v in obj.data.vertices: v.select = True

#Get selected objects
def GetSelectedObjects():
return bpy.context.selected_objects[:]

#Removes double vertices at the normals positions
def RemoveDoubles( obj ):
bm = bmesh.from_edit_mesh( obj.data )
bmesh.ops.remove_doubles( bm, verts = bm.verts[:] )
bmesh.update_edit_mesh( obj.data )

#Duplicate the object
def DuplicateObject( obj, scene ):
newObj = obj.copy()
newObj.data = obj.data.copy()
newObj.animation_data_clear()
return newObj

#Duplicate the mesh and add a data transfert modifier to the copy
def DuplicateObjectAndDataTransfert( obj ):
copy = DuplicateObject( obj, bpy.context.scene )

#Auto smooth is needed as if not, the normals will be lost when the modifier will be applied
copy.data.use_auto_smooth = True

#Create the modifier, keeping mainly the default options
mod = copy.modifiers.new( "Data transfer", 'DATA_TRANSFER' )
mod.object = obj
mod.use_loop_data = True
mod.data_types_loops = {'CUSTOM_NORMAL'}

#The copy is now the selection
obj.select = False
bpy.context.scene.objects.active = copy

#Returns both copy and its modifier
return copy, mod

#Creates cut objects from the selected object and normalize their normal to the original
def CutAndNormalize( cuts ):

obj = bpy.context.object

if MeshObject( obj ) and obj.mode == 'OBJECT':

#Creates a base copy of the original object
copy, mod = InMode( obj, 'OBJECT', lambda: DuplicateObjectAndDataTransfert( obj ) )

#Cuts the copy according to the given cuts
InMode( copy, 'EDIT', lambda: CutMesh( copy, cuts ) )
InMode( copy, 'OBJECT', lambda: SelectAllVertices( copy ) )
#Separates loose parts from the copy
InMode( copy, 'EDIT', lambda: bpy.ops.mesh.separate( type = 'LOOSE' ) )

#Get the objects created from the previous separation
createdObjects = InMode( copy, 'OBJECT', lambda: GetSelectedObjects() )

#And for each of them
for createdObject in createdObjects:
#Activate it
bpy.context.scene.objects.active = createdObject
#Select all...
InMode( createdObject, 'OBJECT', lambda: SelectAllVertices( createdObject ) )
#... in order to remove double vertices created by the cuts
InMode( createdObject, 'EDIT', lambda: RemoveDoubles( createdObject ) )
#And finally apply the data transfer modifier
InMode( createdObject, 'OBJECT', lambda: bpy.ops.object.modifier_apply( modifier = mod.name ) )

print( '---------------' )

scale = 0.4
cuts = []
cuts.extend( [((i * scale, 0, 0), (1, 0, 0)) for i in range( -5, 5, 1 )] )
cuts.extend( [((0, i * scale, 0), (0, 1, 0)) for i in range( -5, 5, 1 )] )

CutAndNormalize( cuts )


Note1:

The result is not totally perfect where the cuts have created very small non quad polygons.

Note2:

I've tried to set the custom normals "manually" by affecting them directly. But I failed to do it. What happened is some per-face normals were wrong. I don't know why... if someone knows, please comment.