0
votes

I've written a very basic script in Blender that builds a circular set of vertices then rotates and translates these to a rotated face in 3D space (see code below). This works well for any Z rotation however for X or Y rotations it's all over the place.

I know I'm missing a step somewhere but can't see it at the moment. So any help is much appreciated.

To run the code, there simply needs to be a cube in the scene.

The idea is that I build the circle of vertices at (0,0,0) then rotate these to the required face normal (I've chosen face 2 of the cube at random for now), then I translate the vertices to the face location. The main rotation function is at the bottom of the code (rotate_verts_towards_face(..))

NB. This is part of a larger project, so I've written this as a separate script as a demonstration only, so it's rough code.

(results of current code)

Face rotation at 0 degrees Z axis

Face rotation at 45 degrees Z axis

Face rotated at 45 degrees X axis

import bpy
import bmesh
import math
from mathutils import Vector


verts_norm = Vector((0, 0, 1))
verts_center = Vector((0, 0, 0))

# FACE BUILDING
def build_face_verts():
    theta = 2 * math.pi / 12
    dx = 0
    dy = 0
    dz = 0
    verts = []
    for i in range(12):
        dx = 1 * math.sin(theta * i)
        dy = 1 * math.cos(theta * i)
        verts.append(Vector((dx, dy, dz)))
    return verts


def update_bm_verts(bm):
    bm.verts.index_update()
    bm.verts.ensure_lookup_table()

def setup_new_obj(name, context):
    obj_name = name
    mesh = bpy.data.meshes.new("mesh")
    obj = bpy.data.objects.new(obj_name, mesh)
    context.scene.collection.objects.link(obj)
    context.view_layer.objects.active = obj
    obj.select_set(True)
    mesh = context.object.data
    bm = bmesh.new()
    return obj, mesh, bm


def build_face(verts, context):
    obj, mesh, bm = setup_new_obj("NEW_FACE", context)

    for v in verts:
        bm.verts.new(v)
    update_bm_verts(bm)

    bm.to_mesh(mesh)
    bm.free()

# ROTATE AND TRANSLATE
def translate(pt, verts):
    for v in verts:
        v[0] += pt[0]
        v[1] += pt[1]
        v[2] += pt[2]


def rotate_verts_towards_face(verts, target, target_norm, target_loc):
    mat_world = target.matrix_world
    trans_verts = []
    
    # transform face verts to target object space
    trans_verts = [mat_world @ v for v in verts] 
    
    # build rotation matrix
    mat = (
        verts_norm.rotation_difference(target_norm).to_matrix().to_4x4()
        
    )

    return [mat @ v for v in trans_verts]


target_obj = bpy.data.objects["Cube"]
target_face_norm = target_obj.data.polygons[2].normal
mat_world = target_obj.matrix_world
target_face_norm = mat_world @ target_face_norm
target_location = target_obj.data.polygons[2].center
 
# rotate verts
r_verts = rotate_verts_towards_face(build_face_verts(), target_obj, target_face_norm, target_location)
# translate verts to face position
translate(target_face_norm, r_verts)
build_face(r_verts, bpy.context)
1

1 Answers

1
votes

Sorry to answer my own question but I figured this out. It's a simple fix that just involves translating the verts_normal by the target object's matrix world. The rotation code becomes:

    norm = mat_world @ verts_norm
    
    # build rotation matrix
    mat = (
        norm.rotation_difference(target_norm).to_matrix().to_4x4()
        
    )