modes
This commit is contained in:
parent
97d294e559
commit
f3036dcc29
98
__init__.py
98
__init__.py
@ -1,18 +1,3 @@
|
||||
"""
|
||||
Armature Mesher - Blender Addon
|
||||
Converts a selected armature into an envelope-style mesh using each bone's
|
||||
head/tail radius values (exactly like Envelope display mode).
|
||||
|
||||
Supports:
|
||||
- Object mode → rest pose (arm.bones)
|
||||
- Pose mode → current pose (obj.pose.bones)
|
||||
- Edit mode → current edit-bone positions
|
||||
|
||||
Geometry:
|
||||
- UV-sphere at head and tail (head_radius / tail_radius)
|
||||
- Capped frustum cylinder connecting the two spheres
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import math
|
||||
@ -37,22 +22,36 @@ def _rotation_matrix_to_axis(target_axis: Vector) -> Matrix:
|
||||
return Matrix.Rotation(angle, 4, cross.normalized())
|
||||
|
||||
|
||||
def add_sphere(bm: bmesh.types.BMesh, center: Vector, radius: float, segments: int):
|
||||
"""Add a closed UV-sphere to bm."""
|
||||
def add_sphere(bm: bmesh.types.BMesh, center: Vector, radius: float, segments: int, axis: Vector = None):
|
||||
"""Add a closed UV-sphere to bm. If axis is provided, orient the sphere so
|
||||
its polar axis (sphere Z) aligns with `axis` — this makes the equator sit
|
||||
correctly where frustum rings expect it.
|
||||
"""
|
||||
rings = max(segments // 2, 2)
|
||||
verts = []
|
||||
|
||||
rot = None
|
||||
if axis is not None:
|
||||
# If axis is near-zero length, ignore orientation.
|
||||
if axis.length >= 1e-6:
|
||||
rot = _rotation_matrix_to_axis(axis.normalized())
|
||||
|
||||
for r in range(rings + 1):
|
||||
phi = math.pi * r / rings
|
||||
sin_phi = math.sin(phi)
|
||||
cos_phi = math.cos(phi)
|
||||
for s in range(segments):
|
||||
theta = 2.0 * math.pi * s / segments
|
||||
v = bm.verts.new(center + Vector((
|
||||
local = Vector((
|
||||
radius * sin_phi * math.cos(theta),
|
||||
radius * sin_phi * math.sin(theta),
|
||||
radius * cos_phi,
|
||||
)))
|
||||
))
|
||||
if rot is not None:
|
||||
offset = (rot @ local.to_4d()).to_3d()
|
||||
else:
|
||||
offset = local
|
||||
v = bm.verts.new(center + offset)
|
||||
verts.append(v)
|
||||
|
||||
for r in range(rings):
|
||||
@ -89,8 +88,8 @@ def add_capped_frustum(bm: bmesh.types.BMesh,
|
||||
rot = _rotation_matrix_to_axis(ax)
|
||||
|
||||
# Place rings at the sphere equators along the bone axis
|
||||
head_ring_center = head_center + ax * head_radius
|
||||
tail_ring_center = tail_center - ax * tail_radius
|
||||
head_ring_center = head_center # + ax * head_radius
|
||||
tail_ring_center = tail_center # - ax * tail_radius
|
||||
|
||||
# Skip if rings would overlap (bone too short relative to radii)
|
||||
if (tail_ring_center - head_ring_center).dot(ax) < 1e-6:
|
||||
@ -176,35 +175,37 @@ class ARMATURE_OT_build_envelope_mesh(bpy.types.Operator):
|
||||
def parts(mat, rad):
|
||||
return (world_mat @ mat, max(rad, 0.001))
|
||||
|
||||
def meshify(head_matrix, head_radius, tail_matrix, tail_radius):
|
||||
def meshify(head_matrix, head_radius, tail_matrix, tail_radius, draw_head=True):
|
||||
|
||||
head_matrix_final, head_radius_final = parts(head_matrix, head_radius)
|
||||
tail_matrix_final, tail_radius_final = parts(tail_matrix, tail_radius)
|
||||
|
||||
add_sphere(bm, head_matrix_final, head_radius_final, segs)
|
||||
add_sphere(bm, tail_matrix_final, tail_radius_final, segs)
|
||||
# Orient spheres so their equators align with the bone axis
|
||||
axis_vec = tail_matrix_final - head_matrix_final
|
||||
if draw_head:
|
||||
add_sphere(bm, head_matrix_final, head_radius_final, segs, axis=axis_vec)
|
||||
add_sphere(bm, tail_matrix_final, tail_radius_final, segs, axis=axis_vec)
|
||||
add_capped_frustum(bm,
|
||||
head_matrix_final, head_radius_final,
|
||||
tail_matrix_final, tail_radius_final,
|
||||
segs)
|
||||
|
||||
if mode == 'EDIT':
|
||||
# edit_bones is accessible while in edit mode
|
||||
for bone in arm_obj.data.edit_bones:
|
||||
meshify(bone.head, bone.head_radius, bone.tail, bone.tail_radius)
|
||||
|
||||
else: # OBJECT / everything else → rest pose
|
||||
for bone in arm_obj.data.bones:
|
||||
meshify(bone.head_local, bone.head_radius, bone.tail_local, bone.tail_radius)
|
||||
# pose mode or object mode:
|
||||
if mode == "EDIT":
|
||||
bone_list = arm_obj.data.edit_bones
|
||||
for bone in bone_list:
|
||||
meshify(bone.head, bone.head_radius, bone.tail, bone.tail_radius, bone.parent is None)
|
||||
else:
|
||||
bone_list = arm_obj.pose.bones
|
||||
for bone in bone_list:
|
||||
meshify(bone.head, bone.bone.head_radius, bone.tail, bone.bone.tail_radius, bone.parent is None)
|
||||
|
||||
|
||||
# Must be in OBJECT mode to create and link new mesh objects.
|
||||
if original_mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# Merge verts from shared joints (parent/child bone connections)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
mesh = bpy.data.meshes.new(arm_obj.name + "_envelope_mesh")
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
@ -213,18 +214,6 @@ class ARMATURE_OT_build_envelope_mesh(bpy.types.Operator):
|
||||
result_obj = bpy.data.objects.new(arm_obj.name + "_envelope", mesh)
|
||||
context.collection.objects.link(result_obj)
|
||||
|
||||
# Re-enter edit mode on the armature if that's where we came from,
|
||||
# then make the new mesh the active/selected object.
|
||||
if original_mode == 'EDIT':
|
||||
arm_obj.select_set(True)
|
||||
context.view_layer.objects.active = arm_obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
arm_obj.select_set(False)
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
result_obj.select_set(True)
|
||||
context.view_layer.objects.active = result_obj
|
||||
|
||||
source_label = {
|
||||
'OBJECT': "rest pose",
|
||||
'POSE': "current pose",
|
||||
@ -246,22 +235,13 @@ class VIEW3D_PT_armature_mesher(bpy.types.Panel):
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Armature"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj is not None and obj.type == 'ARMATURE'
|
||||
# @classmethod
|
||||
# def poll(cls, context):
|
||||
# obj = context.active_object
|
||||
# return obj is not None and obj.type == 'ARMATURE'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
obj = context.active_object
|
||||
mode_labels = {
|
||||
'OBJECT': "Object → rest pose",
|
||||
'POSE': "Pose → current pose",
|
||||
'EDIT': "Edit → edit bones",
|
||||
}
|
||||
layout.label(text=obj.name)
|
||||
layout.label(text=mode_labels.get(obj.mode, obj.mode), icon='INFO')
|
||||
layout.separator()
|
||||
op = layout.operator(
|
||||
ARMATURE_OT_build_envelope_mesh.bl_idname,
|
||||
text="Build Envelope Mesh",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user