Files
2026-09-01 09:52:12 +08:00

438 lines
14 KiB
Python

bl_info = {
"name": "Mabulig Rigger Plugin",
"author": "Fritz Villahermosa",
"version": (1, 3),
"blender": (5, 2, 0),
"location": "View3D > Sidebar > Mabulig Rigger",
"description": "Frog rigging made simple!",
"category": "3D View",
}
import bpy
import os
from bpy.props import FloatProperty, PointerProperty, BoolProperty
from . import rigger_functionality
from . import controlrig_generate
# =========================================================================
# GLOBAL VARIABLES
# =========================================================================
#
# current_dir = os.path.dirname(bpy.data.filepath)
# filepath = os.path.join(current_dir, source_blend_name)
# =========================================================================
# PROPERTIES
# =========================================================================
# SVL Properties
class SpecimenProperties(bpy.types.PropertyGroup):
svl: FloatProperty(
name="Specimen SVL",
description="Snout-Vent Length of the specimen in meters",
default=0.1, # 10 cm default
min=0.001,
soft_max=10.0,
unit='LENGTH'
) # pyright: ignore[reportInvalidTypeForm]
# Model Picker
target_model: PointerProperty(
type=bpy.types.Object,
name="Target Model",
description="Points to the specimen's 3d model"
) # pyright: ignore[reportInvalidTypeForm]
class PanelProperties(bpy.types.PropertyGroup):
panel1_g2: BoolProperty(
name="Panel 1, Group 2",
description="Toggle panel/group visibility",
default=True
) # pyright: ignore[reportInvalidTypeForm]
panel2_g1: BoolProperty(
name="Panel 2, Group 1",
description="Toggle panel/group visibility",
default=True
) # pyright: ignore[reportInvalidTypeForm]
panel3_g1: BoolProperty(
name="Panel 3, Group 1",
description="Toggle panel/group visibility",
default=False
) # pyright: ignore[reportInvalidTypeForm]
# =========================================================================
# Functions
# =========================================================================
def hide_all_coll():
mabulig_rig_coll = bpy.data.collections['Mabulig-Rig'].children
for collection in mabulig_rig_coll:
collection.hide_viewport = True
mabulig_rig_objects = bpy.data.collections['Mabulig-Rig'].objects
for obj in mabulig_rig_objects:
obj.hide_viewport = True
def show_all_helpers():
mabulig_rig_coll = bpy.data.collections['Mabulig-Rig'].children
for collection in mabulig_rig_coll:
collection.hide_viewport = False
mabulig_rig_objects = bpy.data.collections['Mabulig-Rig'].objects
for obj in mabulig_rig_objects:
obj.hide_viewport = False
def inspect_scene():
coll_name = "Mabulig-Rig"
if bpy.data.collections.get(coll_name) is not None:
bpy.context.scene.panel_propert.panel2_g1 = False
bpy.context.scene.panel_propert.panel3_g1 = True
else:
bpy.context.scene.panel_propert.panel3_g1 = False
bpy.context.scene.panel_propert.panel2_g1 = True
# =========================================================================
# OPERATORS
# =========================================================================
# SETUP SCENE BUTTON
class SceneSetup(bpy.types.Operator):
bl_idname = "scene_setup.operator"
bl_label = "Setup Scene"
bl_description = "Imports all the necessary rigging setup components"
def execute(self, context):
source_blend_name = "rig_setup.blend"
collection_name = "Mabulig-Rig"
script_dir = os.path.dirname(os.path.abspath(__file__))
filepath = os.path.join(script_dir, source_blend_name)
with bpy.data.libraries.load(filepath) as (data_from, data_to):
if collection_name in data_from.collections:
data_to.collections.append(collection_name)
else:
print(f"Collection '{collection_name}' not found in {source_blend_name}")
appended_collection = bpy.data.collections.get(collection_name)
if appended_collection:
# Link it to the scene's master collection
bpy.context.scene.collection.children.link(appended_collection)
print(f"Successfully appended and linked collection: '{collection_name}'")
# initially hide all collections
hide_all_coll();
#make certain layers unselectable
bpy.data.collections['gular'].hide_select = True
bpy.data.collections['belly_l'].hide_select = True
bpy.data.collections['belly_r'].hide_select = True
bpy.data.collections['nostril_l'].hide_select = True
bpy.data.collections['nostril_r'].hide_select = True
#Disable self panel group
bpy.context.scene.panel_propert.panel2_g1 = False
bpy.context.scene.panel_propert.panel3_g1 = True
return {'FINISHED'}
# SETUP SKELETAL BONES
class SkelSetup(bpy.types.Operator):
bl_idname = "skel_setup.operator"
bl_label = "Skeleton"
bl_description = "Enables the empties for easier skeletal bone placement"
def execute(self, context):
hide_all_coll();
bpy.data.collections['helpers'].hide_viewport = False
return {'FINISHED'}
# SETUP GULAR BONES
class GularSetup(bpy.types.Operator):
bl_idname = "gular_setup.operator"
bl_label = "Gular"
bl_description = "Enables the gular mesh"
def execute(self, context):
hide_all_coll();
bpy.data.objects['gular_mesh'].hide_viewport = False
# bpy.data.collections['gular'].hide_viewport = False
return {'FINISHED'}
# SETUP BELLY BONES
class BellySetup(bpy.types.Operator):
bl_idname = "belly_setup.operator"
bl_label = "Belly"
bl_description = "Enables the belly mesh"
def execute(self, context):
hide_all_coll();
bpy.data.objects['belly_mesh'].hide_viewport = False
# bpy.data.collections['belly_l'].hide_viewport = False
# bpy.data.collections['belly_r'].hide_viewport = False
return {'FINISHED'}
# SETUP NOSTRIL BONES
class NostrilsSetup(bpy.types.Operator):
bl_idname = "nostrils_setup.operator"
bl_label = "Nostrils"
bl_description = "Enables the nostrils mesh"
def execute(self, context):
hide_all_coll();
bpy.data.objects['nostril_mesh'].hide_viewport = False
# bpy.data.collections['nostril_l'].hide_viewport = False
# bpy.data.collections['nostril_r'].hide_viewport = False
return {'FINISHED'}
# SETUP FINGER BONES
class FingersSetup(bpy.types.Operator):
bl_idname = "fingers_setup.operator"
bl_label = "Fingers"
bl_description = "Enables the empties for finger bones placement"
def execute(self, context):
hide_all_coll();
bpy.data.collections['fingers'].hide_viewport = False
return {'FINISHED'}
# SETUP TOES BONES
class ToesSetup(bpy.types.Operator):
bl_idname = "toes_setup.operator"
bl_label = "Toes"
bl_description = "Enables the empties for toe bones placement"
def execute(self, context):
hide_all_coll();
bpy.data.collections['toes'].hide_viewport = False
return {'FINISHED'}
# SPAWN RULER PLANE
class RulerPlane(bpy.types.Operator):
bl_idname = "ruler_plane.operator"
bl_label = "Spawn Ruler Plane"
bl_description = "Spawns a plane with the SVL length"
def execute(self, context):
props = context.scene.specimen_props
svl_val = props.svl
# Directly reset the scene cursor location and rotation
context.scene.cursor.location = (0.0, 0.0, 0.0)
context.scene.cursor.rotation_euler = (0.0, 0.0, 0.0)
# Spawn plane mesh, set size to SVL, diplaymode to wireframe, then show in front
bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', scale=(1, 1, 1))
rulerplane_obj = context.active_object
rulerplane_obj.scale = (svl_val, svl_val, 1.0)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
rulerplane_obj.name = f"Specimen_Plane_{svl_val:.2f}m"
rulerplane_obj.display_type = 'WIRE'
rulerplane_obj.show_in_front = True
# Enable Panel 2, Group 2
bpy.context.scene.panel_propert.panel1_g2 = True
return {'FINISHED'}
# APPLY SCALE
class ApplyScale(bpy.types.Operator):
bl_idname = "apply_scale.operator"
bl_label = "Apply Scale and Adjust"
bl_description = "Applies the scale and adjusts the mesh for the 3d viewer scale"
def execute(self, context):
props = context.scene.specimen_props
bpy.ops.object.select_all(action='DESELECT')
target_obj = props.target_model
if target_obj:
target_obj.select_set(True)
target_obj.scale = (10, 10, 10)
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
# Enable Panel 2, Group 2
bpy.context.scene.panel_propert.panel2_g1 = True
else:
print("No model selected")
return {'FINISHED'}
# SHOW ALL
class ShowAllComponents(bpy.types.Operator):
bl_idname = "show_all_components.operator"
bl_label = "Show All"
bl_description = "Show all rigging components"
def execute(self, context):
show_all_helpers()
bpy.data.objects['OG_Armature'].hide_viewport = True
bpy.data.collections['rig_shapes'].hide_viewport = True
bpy.data.collections['gular'].hide_viewport = True
bpy.data.collections['belly_l'].hide_viewport = True
bpy.data.collections['belly_r'].hide_viewport = True
bpy.data.collections['nostril_l'].hide_viewport = True
bpy.data.collections['nostril_r'].hide_viewport = True
return {'FINISHED'}
# GENERATE CONTROL RIG
class GenerateControlRig(bpy.types.Operator):
bl_idname = "generate_control_rig.operator"
bl_label = "Generate Control Rig"
bl_description = "Builds the control rig according to helper placement"
def execute(self, context):
controlrig_generate.generatecontrolrig();
return {'FINISHED'}
# REFRESH BUTTON
class RefreshScene(bpy.types.Operator):
bl_idname = "refresh_inspect.operator"
bl_label = "Refresh"
bl_description = "Forces re-check to see if the Mabulig-Rig setup exists"
def execute(self, context):
inspect_scene()
return {'FINISHED'}
# ADJUST ARMATURE
class AdjustArmature(bpy.types.Operator):
bl_idname = "adjust_armature.operator"
bl_label = "Adjust Armature"
bl_description = "Auto setup armature to follow helpers"
def execute(self, context):
rigger_functionality.startAutoRig()
return {'FINISHED'}
# =========================================================================
# PANELS
# =========================================================================
class ModelPrepPanel(bpy.types.Panel):
bl_label = "1. Model Preparation"
bl_idname = "model_prep.panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Mabulig Rigger"
def draw(self, context):
layout = self.layout
props = context.scene.specimen_props
mod_prep_col1 = layout.column()
mod_prep_col1.prop(props, "svl")
mod_prep_col1.operator("ruler_plane.operator", icon='MESH_PLANE')
# layout.separator()
# mod_prep_col2 = layout.column()
# mod_prep_col2.prop(props, "target_model")
# mod_prep_col2.operator("apply_scale.operator", icon='FIXED_SIZE')
# mod_prep_col2.enabled = bpy.context.scene.panel_propert.panel1_g2
class MabuligRiggerSceneSetupPanel(bpy.types.Panel):
bl_label = "2. Scene Setup"
bl_idname = "scene_setup.panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Mabulig Rigger"
def draw_header_preset(self, context):
layout = self.layout
layout.operator("refresh_inspect.operator", icon='FILE_REFRESH')
def draw(self, context):
layout = self.layout
#Add Scene Setup button
scene_prep_col1 = layout.column()
scene_prep_col1.operator("scene_setup.operator", icon='OUTLINER_COLLECTION')
scene_prep_col1.enabled = bpy.context.scene.panel_propert.panel2_g1
class RiggingProcessPanel(bpy.types.Panel):
bl_label = "3. Rigging Process"
bl_idname = "rigging_process.panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Mabulig Rigger"
def draw(self, context):
layout = self.layout
rig_proc_row = layout.row()
rig_proc_row.alignment = 'CENTER'
rig_proc_row.label(text="Setup Helpers")
rig_proc_grid = layout.grid_flow(columns=3, align=True)
#Add Buttons
rig_proc_grid.operator("skel_setup.operator")
rig_proc_grid.operator("gular_setup.operator")
rig_proc_grid.operator("fingers_setup.operator")
rig_proc_grid.operator("belly_setup.operator")
rig_proc_grid.operator("toes_setup.operator")
rig_proc_grid.operator("nostrils_setup.operator")
rig_proc_row2 = layout.column()
rig_proc_row2.alignment = 'CENTER'
rig_proc_row2.operator("show_all_components.operator")
layout.separator()
rig_proc_row3 = layout.column()
rig_proc_row3.alignment = 'CENTER'
rig_proc_row3.operator("adjust_armature.operator")
rig_proc_row3.operator("generate_control_rig.operator")
rig_proc_grid.enabled = bpy.context.scene.panel_propert.panel3_g1
rig_proc_row2.enabled = bpy.context.scene.panel_propert.panel3_g1
rig_proc_row3.enabled = bpy.context.scene.panel_propert.panel3_g1
classes = (SceneSetup, SkelSetup, GularSetup, BellySetup, NostrilsSetup, FingersSetup, ToesSetup, RulerPlane, ApplyScale, ModelPrepPanel, MabuligRiggerSceneSetupPanel, RiggingProcessPanel, SpecimenProperties, PanelProperties, ShowAllComponents, GenerateControlRig, RefreshScene, AdjustArmature)
def register():
for i in classes:
bpy.utils.register_class(i)
bpy.types.Scene.specimen_props = bpy.props.PointerProperty(type=SpecimenProperties)
bpy.types.Scene.panel_propert = bpy.props.PointerProperty(type=PanelProperties)
def unregister():
for i in classes:
bpy.utils.unregister_class(i)
del bpy.types.Scene.specimen_props
del bpy.types.Scene.panel_propert
if __name__ == "__main__":
register()
inspect_scene()