0

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?

Felix
  • 1
  • 3
  • 1
    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 – batFINGER Jun 08 '21 at 19:55
  • 1
    If global Z axis is (0, 0, 1) matches face normal what is the expectation of using Euler((0, 0, 1)) – batFINGER Jun 08 '21 at 20:28

1 Answers1

0

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)
Felix
  • 1
  • 3