2
$\begingroup$

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.

before cutting the mesh, the area was smooth, now it is visible that there are cuts in the mesh, the mesh cannot be exported like this to a game engine.

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.

enter image description here

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.

$\endgroup$
  • 1
    $\begingroup$ Related even if the subject is different. This answer shows a way to recalculate the normals blender.stackexchange.com/questions/75704/… $\endgroup$ – lemon Mar 17 '17 at 8:27
  • 1
    $\begingroup$ @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. $\endgroup$ – CSPassionant Mar 17 '17 at 22:40
  • $\begingroup$ As explained in the answer, this is approximated mainly because of the double vertices created by the cuts. $\endgroup$ – lemon Mar 19 '17 at 8:14
1
$\begingroup$

enter image description here

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()
    scene.objects.link( newObj )
    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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.