0
$\begingroup$

I'm using Blender 2.82 and I'm trying to take body measurements from a 3d mesh of a human body. Right now I'm concentrating on measuring the perimeter of a certain body part, like waist, bust, hips, etc. To do that, I'm calculating the intersection between the mesh and a plane using a boolean modifier. But I'm not being successful.

This is my process so far:

  1. I created the plane by selecting 3 points of the mesh and fill them to form a face. Plane with a single face created from 3 selected vertices of the mesh to be measured.

  2. Scaled the plane in order to be bigger than the area that I'm trying to intersect (I scale to 20x bigger). Scaled plane to reach all are that I want to intersect

  3. Created a boolean modifier to intersect the created plane with the mesh.boolean modifier

The problem is that when I change to object view and view the result of the modifier, it doesn't look like an intersection at all on the top half of the mesh, but it works completely fine for the bottom half: triangular plane intersection result

I thought that this was strange, so I tested the very same process but creating a plane using the function bpy.ops.mesh.primitive_plane_add() with the default location and rotation. The result of the intersection with this method is exactly what I want! plane intersection result

This brings me to my question number 1: Why doesn't my triangular plane intersect the mesh the same way as this last plane does? If it did, my problem was solved because I'm already able to correctly measure the perimeter of the intersection.

Since my 3 point plane was not working, I decided to extract the location/rotation of the triangular plane that I created and apply it to the new plane, but the problem is that at the end the rotation is not equal to both meshes.

Here is my code:

from mathutils import Vector,Matrix,Euler
from math import atan2, degrees, radians
import numpy as np
import bmesh
import bpy

def get_face_euler_angle(obj):
    normal = obj.data.polygons[0].normal
    normal_vec = obj.matrix_world @ normal
    euler = obj.matrix_world.to_euler()
    return Euler(normal_vec)

def setOriginToGeometry(object):
    bpy.context.view_layer.objects.active = object
    bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')

def intersection():
    bpy.ops.object.mode_set(mode='OBJECT')
    mesh = bpy.context.object
    
    # default vertex indices - for testing
    i0 = 5873
    i1 = 6702
    i2 = 9576
    mesh.data.vertices[i0].select = True
    mesh.data.vertices[i1].select = True
    mesh.data.vertices[i2].select = True
    
    # Global coordinates of the variables
    p0 = list(mesh.matrix_world @ mesh.data.vertices[i0].co)
    p1 = list(mesh.matrix_world @ mesh.data.vertices[i1].co)
    p2 = list(mesh.matrix_world @ mesh.data.vertices[i2].co)
    p_avg = np.mean([p0, p1, p2], axis=0) 
    
    # Create a triangular face defined by the 3 selected vertices
    triangular_plane = bpy.data.meshes.new("Plane")
    triangular_plane.from_pydata([p0, p1, p2], [[0,1], [0,2], [1,2]], [[0,1,2]])
    triangular_plane.update()
    triangular_plane_obj = bpy.data.objects.new("Plane", triangular_plane)
    bpy.data.collections[0].objects.link(triangular_plane_obj)
    
    # Select plane to scale
    bpy.ops.object.mode_set(mode='OBJECT')
    mesh.data.vertices[i0].select = False
    mesh.data.vertices[i1].select = False
    mesh.data.vertices[i2].select = False
    setOriginToGeometry(triangular_plane_obj)
    
    # Change origin to the center of the plane to scale
    setOriginToGeometry(triangular_plane_obj)
    bpy.ops.object.mode_set(mode='EDIT')
    
    # Scale the plane
    # https://blender.stackexchange.com/questions/58397/scaling-works-differently-in-gui-and-from-script
    #
    # It's a context issue and you need to Override the Context.
    # The pivot point is a member of the 3D_VIEW space. In code below would be area.spaces.active.pivot_point 
    # Example of overriding the context which "fools" the operator into thinking it is being run from the 3D 
    # view, not the text editor.
    override = bpy.context.copy()
    for area in bpy.context.screen.areas:
        if area.type == 'VIEW_3D':
            override["area"] = area
            override["space_data"] = area.spaces.active
            override["region"] = area.regions[-1]
            break
    
    bpy.ops.transform.resize(override,
                             value=(20.0,20.0,20.0), 
                             orient_type='GLOBAL', 
                             orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), 
                             orient_matrix_type='GLOBAL', 
                             mirror=True, 
                             use_proportional_edit=False, 
                             proportional_edit_falloff='SMOOTH', 
                             proportional_size=1, 
                             use_proportional_connected=False, 
                             use_proportional_projected=False,
                             snap_target='MEDIAN')
                             
    # Boolean modifier on triangular plane (intersect)
    modifier = triangular_plane_obj.modifiers.new(name='modifier_intersect', type='BOOLEAN')
    modifier.object = mesh
    modifier.operation = 'INTERSECT'
    
    # Now it's possible to see the effect that the boolean modifier has on the mesh

    
    # Since the modifier doesn' work, I want to add a new plane to the scene with the same location and 
    # rotation as triangular_plane_obj
    triangular_plane_euler = get_face_euler_angle(triangular_plane_obj)
    print('triangular_plane_obj Euler', triangular_plane_euler)
    
    # Create Plane.001
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.mesh.primitive_plane_add(enter_editmode=False, location=p_avg, rotation=triangular_plane_euler)
    
    plane = bpy.data.objects['Plane.001']
    plane_euler = get_face_euler_angle(plane)
    print('plane_obj Euler', plane_euler)

    # Code to measure the perimeter
    

The output is

triangular_plane_obj Euler   <Euler (x=0.2274, y=0.0125, z=-0.9737), order='XYZ'>
plane_obj Euler              <Euler (x=-0.1665, y=-0.0987, z=1.6984), order='XYZ'>

The Euler angles are not the same, here is an image of both planes after applying the rotation. results after rotation

What am I doing wrong?

$\endgroup$
2
  • 1
    $\begingroup$ To find the perimeter of bisection with plane could instead use bmesh bisect plane operator, and sum the resultant edge lengths. One assumes angle error generates from within whatever get_face_euler_angle(triangular_plane_obj) function code is $\endgroup$ – batFINGER Jun 8 at 19:55
  • 1
    $\begingroup$ If global Z axis is (0, 0, 1) matches face normal what is the expectation of using Euler((0, 0, 1)) $\endgroup$ – batFINGER Jun 8 at 20:28
0
$\begingroup$

Apparently, my problem was that my triangular plane didn't have any thickness. So, the solution to my triangular plane being messed up when I apply the intersection is to apply a solidify modifier before. It's very important to apply the solidify before the boolean modifier otherwise, it won't work.

If you replace this code with the one where I apply the boolean modifier, by this one it will work like a charm.

# Solidify modifier on Plane (intersect)
solidify = triangular_plane_obj.modifiers.new(name='solidify', type='SOLIDIFY')
solidify.thickness = 0.0001
                         
# Boolean modifier on Plane (intersect)
boolean = triangular_plane_obj.modifiers.new(name='modifier_intersect', type='BOOLEAN')
boolean.object = mesh
boolean.operation = 'INTERSECT'

# Apply modifiers (apply solidify first)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.modifier_apply(
    {"object": triangular_plane_obj},
    apply_as='DATA',
    modifier='solidify')
bpy.ops.object.modifier_apply(
    {"object": triangular_plane_obj},
    apply_as='DATA',
    modifier='modifier_intersect')

However, with this solution the perimeter that I was calculating resulted in more than double given the increased amount of vertices. I resolved that by merge the vertices with less than 0.001cm of proximity.

bpy.context.scene.objects["SPRING0001"].select_set(False)
bpy.context.scene.objects["Plane"].select_set(True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles(threshold=0.001)
$\endgroup$
1
  • $\begingroup$ Booltool has a split operation that can use a plane with no thickness, I believe. Might be worth looking into $\endgroup$ – Allen Simpson Jun 11 at 15:48

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.