Skip to content

Commit

Permalink
Bone Circle operator and support for 3.2+
Browse files Browse the repository at this point in the history
  • Loading branch information
arocull committed Aug 27, 2022
1 parent de41378 commit 1404aee
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 12 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"python.autoComplete.extraPaths": [
"blender_autocomplete/3.0"
"blender_autocomplete/3.2"
],
"python.linting.pylintArgs": [
"--init-hook",
"import sys; sys.path.append('blender_autocomplete/3.0')"
"import sys; sys.path.append('blender_autocomplete/3.2')"
],
"python.analysis.extraPaths": [
"blender_autocomplete/3.0"
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
# BoneJuice
Armature utility plugin for Blender, for niche cases I encounter where it would be a lot nicer to have something do the work for me!

Version 0.0.7 which supports Blender 3.0+
Version 0.0.8 which supports Blender 3.2+

Feature List (click on the links to see how-to/examples):
- Object Mode
- **[Clean and Combine](docs/examples/clean_and_combine.md)** - Combine multiple meshes, with modifiers, into one export-ready object. Armatures are preserved.
- **[Merge Vertex Groups](docs/examples/merge_vertex_groups.md)** - Combine two vertex groups on selected meshes using a given operation. Also available in Weight Paint mode.
- **[Merge/Operate Vertex Groups](docs/examples/merge_vertex_groups.md)** - Run a math operation on one or two vertex groups. Also available in Weight Paint mode.
- Armature Edit Mode
- **[Add Bone Circle](docs/examples/add_bone_circle.md)** - Creates a circle of bones around an active bone, with rolls adjusted to face it
- **[Add Leaf Bones](docs/examples/add_leaf_bones.md)** - Add leaf bones to an armature for external purposes
- **[Select End Bones](docs/examples/select_end_bones.md)** - Select bones at the bottom of the hiearchy
- **[Surface Bone Placer](docs/examples/surface_bone_placer.md)** - Place bones on geometric surfaces with a single click
- **[Mark Bone Side](docs/examples/mark_bone_side.md)** - Mark bones as left or right in an armature. Alternative to built-in method which relies on specific rules.
- Armature Pose Mode
- **[Set Rotation Mode](docs/examples/set_bone_rotation_mode.md)** - Select multiple pose bones and set their rotation mode (Quaternion, XYZ Euler, Axis Angle, etc) with just the click of a button. Alternative to built-in method which can be hit-or-miss (although this will not convert keyframed transforms).
- **[Curl Bones](docs/examples/curl_bones.md)** - Offsets euler rotations of all selected pose bones by the given rotation. Only works in Euler rotation mode.
- **[Curl Bones](docs/examples/curl_bones.md)** - Offsets euler rotations of all selected pose bones by the given rotation. Currently only works in Euler rotation mode.
- Rendering
- **(Work In Progress) Batch Render NLA Tracks** - Individually render out each animated NLA track inside of all selected objects. Renders from all selected cameras. Great for game animation previews, or spritesheets.
- **(Work In Progress) Batch Render NLA Tracks** - Individually render out each animated NLA track inside of all selected objects. Renders from all selected cameras. Great for game animation previews or spritesheets.

# Installation
Download the zip file from the releases area on GitHub, and then go to `Edit > Preferences > Add-ons` and then click `Install` in the top right, and select the zip file. Make sure the plugin it points to is enabled.
Expand Down
39 changes: 39 additions & 0 deletions docs/examples/add_bone_circle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Add Leaf Bones
Creates a circle of bones around the active bone, with bone rolls adjusted to face said active bone.
Useful for radial symmetry.

Additional roll offset can be added with the built-in `Set Roll` command (which takes a roll in radians).

## Accessing
While editing an Armature, go to `Add > Add Bone Circle`.

## Use
Make the bone active you want to place a circle around, and run the operator.

## Example - Jetpack Thruster
Place and make active a bone for the center of your thruster.

![](../images/exmp_bonecircle1.png)

Then run `Add > Add Bone Circle`--this will create a circle around your active bone. Adjust settings as needed.

![](../images/exmp_bonecircle2.png)

You can see that the bone rolls were adjusted so each bone has the same axis facing the center of the circle.

Also, my bones are spaced evenly, but my fins aren't lining up. That's an issue with how my geometry is placed, not the bones themselves.

![](../images/exmp_bonecircle3.png)

If necessary, you can use Blender's built-in `Set Roll` command to adjust the rolls. Note that this takes a value in radians.
Use pi to offset rolls by 180 degrees.

![](../images/exmp_bonecircle4.png)

Now our Z axis is facing outward instead of inward!

![](../images/exmp_bonecircle5.png)

I also selected the ends of the bones, then scaled them in to create a funneling effect.

![](../images/exmp_bonecircle6.png)
6 changes: 4 additions & 2 deletions docs/examples/merge_vertex_groups.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Merge Vertex Groups
# Merge/Operate Vertex Groups
Run a math operation on one or two vertex groups. Useful for things like merging two vertex groups.

## Accessing
From Object mode, select the objects you want to edit, and go to `Object > Apply > Merge Vertex Groups`.
The operation should apply to all possible objects it can, which is useful for multi-mesh character rigs.
The operation should apply to all selected objects it can, which is useful for multi-mesh character rigs.

Or, if you're in Weight Paint Mode, simply go to `Weights > Merge Vertex Groups`.
This will allow real-time view of the operation if you make changes after applying it.
Expand Down
Binary file added docs/images/exmp_bonecircle1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/exmp_bonecircle2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/exmp_bonecircle3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/exmp_bonecircle4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/exmp_bonecircle5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/exmp_bonecircle6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name" : "BoneJuice",
"author" : "Alan O'Cull",
"description" : "Armature Utility Plugin for Blender",
"blender" : (3, 0, 0),
"blender" : (3, 3, 0),
"version" : (0, 0, 8),
"location" : "Edit Armature, Pose Mode, Object Mode",
"warning" : "",
Expand Down Expand Up @@ -64,6 +64,7 @@ def register():

registerClass(BoneJuice_SurfacePlacer, [bpy.types.TOPBAR_MT_edit_armature_add])
registerClass(BoneJuice_AddLeafBones, [bpy.types.TOPBAR_MT_edit_armature_add])
registerClass(BoneJuice_AddBoneCircle, [bpy.types.TOPBAR_MT_edit_armature_add])
registerClass(BoneJuice_SelectBoneChainEnds, [bpy.types.VIEW3D_MT_select_edit_armature])
registerClass(BoneJuice_MarkSide, [bpy.types.VIEW3D_MT_edit_armature_names, bpy.types.VIEW3D_MT_pose_names], [BoneJuice_MarkSide.button_edit, BoneJuice_MarkSide.button_pose])
registerClass(BoneJuice_CleanAndCombine, [bpy.types.VIEW3D_MT_object_cleanup])
Expand All @@ -78,6 +79,7 @@ def register():
def unregister():
unregisterClass(BoneJuice_SurfacePlacer, [bpy.types.TOPBAR_MT_edit_armature_add])
unregisterClass(BoneJuice_AddLeafBones, [bpy.types.TOPBAR_MT_edit_armature_add])
unregisterClass(BoneJuice_AddBoneCircle, [bpy.types.TOPBAR_MT_edit_armature_add])
unregisterClass(BoneJuice_SelectBoneChainEnds, [bpy.types.VIEW3D_MT_select_edit_armature])
unregisterClass(BoneJuice_MarkSide, [bpy.types.VIEW3D_MT_edit_armature_names, bpy.types.VIEW3D_MT_pose_names], [BoneJuice_MarkSide.button_edit, BoneJuice_MarkSide.button_pose])
unregisterClass(BoneJuice_CleanAndCombine, [bpy.types.VIEW3D_MT_object_cleanup])
Expand Down
141 changes: 138 additions & 3 deletions src/armature/edit_add.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import string
import bpy
from typing import List
from bpy.ops import armature
from bpy.props import StringProperty, BoolProperty, FloatProperty
from bpy.props import IntProperty, StringProperty, BoolProperty, FloatProperty
from bpy.types import Armature, EditBone, Operator
from mathutils import Vector
from ..utility import dist_threshold, raycast, get_active
from ..utility import dist_threshold, lerpVector, raycast, get_active
from math import pi

class BoneJuice_SurfacePlacer(Operator):
bl_idname = "armature.bj_bone_placer_surf"
Expand Down Expand Up @@ -197,3 +197,138 @@ def execute(self, context: bpy.types.Context):

self.report({'INFO'}, "Geneated leaf bones.")
return {'FINISHED'}

class BoneJuice_AddBoneCircle(Operator):
"""Creates a circle of bones around the active one."""
bl_idname = "object.bj_add_bone_circle"
bl_label = "Add Bone Circle"
bl_description = "Creates a circle of bones around the active one"
bl_options = {'REGISTER', 'UNDO'}

## MAPPING
def button(self, context):
self.layout.operator(
BoneJuice_AddBoneCircle.bl_idname,
text="Add Bone Circle",
icon='BONE_DATA')

def manual_map():
url_manual_prefix = "https://docs.blender.org/manual/en/latest/"
url_manual_mapping = (
("bpy.ops.object.bj_add_bone_circle", "scene_layout/object/types.html"),
)
return url_manual_prefix, url_manual_mapping

## PROPERTIES
count: IntProperty (
name = "Count",
description = "Number of bones to place around the circle",
default = 5,
min = 1,
soft_max = 12,
)
rotationOffset: FloatProperty(
name = "Rotation Offset",
description = "Offset to start rotation with, in degrees",
soft_min = 0,
soft_max = 2 * pi,
subtype='ANGLE',
unit='ROTATION',
)
circleRadius: FloatProperty(
name = "Radius",
description = "Radius to place the bones in around the current one",
default = 1.0,
soft_min = 0.05,
soft_max = 2.0,
unit = 'LENGTH',
)
boneLength: FloatProperty(
name = "Bone Length",
description = "Length of the bones to add",
default = 0.2,
min = 0,
soft_min = 0.1,
soft_max = 1,
unit = 'LENGTH',
)
toTail: FloatProperty(
name = "Head/Tail",
description= "Proportion for bone placement, near either the head (0%) or tail (100%) of the active bone",
default = 0.0,
soft_min = 0.0,
soft_max = 1.0,
precision = 3,
subtype = 'FACTOR',
)
boneName: StringProperty(
name = "Name",
description = "Name of the created bones. # will be replaced with the index",
default = "circle#",
)
useDeform: BoolProperty(
name = "Use Deform",
description = "If true, bones are marked to deform. False otherwise",
default = False,
)
preserveSelection: BoolProperty(
name = "Preserve Selection",
description = "If true, does not alter selection to select the newly created bones",
default = False,
)

## ACTUAL EXECUTION
def execute(self, context: bpy.context):
bones: List[EditBone] = bpy.context.selected_editable_bones
if len(bones) == 0:
self.report({'WARNING'}, "No bones selected")
return {'FINISHED'}

armatureObj = bpy.context.active_object
editArmature: Armature
if type(armatureObj.data) is Armature:
editArmature = armatureObj.data
else:
self.report({'WARNING'}, "Active object is not an armature!")
return {'FINISHED'}

if context.active_bone is None:
self.report({'WARNING'}, "No active edit bone!")
return {'FINISHED'}
bone: EditBone = context.active_bone
origRoll: float = bone.roll

newBones: List[EditBone] = []
idx: int = 0
while idx < self.count:
idx = idx + 1
theta: float = self.rotationOffset + (((2 * pi) / float(self.count)) * float(idx))
boneName: str = str(self.boneName).replace('#', str(idx))

circleBone: EditBone = editArmature.edit_bones.new(boneName)
circleBone.parent = bone
circleBone.use_deform = self.useDeform
circleBone.use_connect = False

headPos = Vector((bone.head[0], bone.head[1], bone.head[2]))
tailPos = Vector((bone.tail[0], bone.tail[1], bone.tail[2]))
basePose: Vector = lerpVector(headPos, tailPos, self.toTail)
tailDir: Vector = (tailPos - headPos).normalized()
circleTail: Vector = tailDir * self.boneLength + basePose

bone.roll = origRoll + theta

circleBone.head = basePose + bone.z_axis * self.circleRadius
circleBone.tail = circleTail + bone.z_axis * self.circleRadius
circleBone.align_roll(basePose - circleBone.head)
newBones.append(circleBone)

bone.roll = origRoll

if not self.preserveSelection:
bpy.ops.armature.select_all(action='DESELECT')
for bone in newBones:
bone.select = True

self.report({'INFO'}, "Geneated circle bones.")
return {'FINISHED'}
6 changes: 6 additions & 0 deletions src/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,16 @@ def isCollection(self, obj):
def clampf(num: float, min: float, max: float) -> float:
return min if num < min else max if num > max else num

def lerpVector(a: Vector, b: Vector, alpha: float) -> Vector:
return (a * (1 - alpha)) + (b * alpha)

def getPreferences(context: bpy.types.Context) -> AddonPreferences:
print(context.preferences.addons)
return context.preferences.addons["BoneJuice"].preferences

def vectorToFloatList(inp: Vector) -> List[float]:
return [inp.x, inp.y, inp.z]

def floatListToVector(inp: List[float]) -> Vector:
return Vector((inp[0], inp[1], inp[2]))

Expand Down

0 comments on commit 1404aee

Please sign in to comment.