Skip to content

Latest commit

 

History

History
582 lines (407 loc) · 13.5 KB

File metadata and controls

582 lines (407 loc) · 13.5 KB
Error in user YAML: (<unknown>): found a tab character that violate indentation while scanning a plain scalar at line 3 column 3
---
- oeasy Python 0742
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。
- 本教程同步发布在: 
	- 个人网站: `https://oeasy.org` 
	- 蓝桥云课: `https://www.lanqiao.cn/courses/3584` 
	- GitHub: `https://github.com/overmind1980/oeasy-python-tutorial` 
	- Gitee: `https://gitee.com/overmind1980/oeasypython` 
---

父子关系_骨骼基础

父子关系_骨骼基础

  • 先添加一个立方体
    • 按下shift先后点选两个立方体
    • 添加父子关系
      • Object - Parent - Object

图片描述

  • 在两个立方体之间建立父子关系之后
    • 两者之间会有一条连线
    • 旋转父级对象
      • 子级对象 会跟随
  • 父亲 带着 儿子
    • 移动、旋转、缩放

图片描述

  • 代码应该如何完成

代码完成

import bpy

# 清空当前场景(先删旧对象)
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# 创建两个立方体
bpy.ops.mesh.primitive_cube_add(size=1.0)
father = bpy.context.active_object
father.name = 'father'

bpy.ops.mesh.primitive_cube_add(size=1.0)
child = bpy.context.active_object
child.name = 'child'
child.location = (2, 2, 0)

# 先后选中 并设置父子关系
father.select_set(True)
armature.select_set(True)
bpy.ops.object.parent_set()

清除父子关系

  • 选中子对象
    • Object - Parent - Clear Parent
    • 清除父级关系

图片描述

  • 然后两者之间
    • 连线消失
    • 父子关系消失

图片描述

一父两子

  • 子只能有一个父亲
    • 父亲 可以有多个子

图片描述

  • 选中对象
    • 右键
    • 一拖二
      • 控制旋转

图片描述

  • 甚至可以 一拖三

代码创建

图片描述

实际代码

import bpy

# 清除场景中已有的物体
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# 创建父立方体
bpy.ops.mesh.primitive_cube_add(size=1.5, enter_editmode=False, location=(0, 1, 1))
parent_cube = bpy.context.active_object

# 创建第一个子立方体
bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, location=(2, 2, 0))
child_cube1 = bpy.context.active_object
child_cube1.parent = parent_cube

# 创建第二个子立方体
bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, location=(-2, 2, 0))
child_cube2 = bpy.context.active_object
child_cube2.parent = parent_cube

# 创建第三个子立方体
bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, location=(0, 2, 0))
child_cube3 = bpy.context.active_object
child_cube3.parent = parent_cube
  • 通过 对象之间的 父子关系
    • 实现了层级控制

图片描述

  • 可以利用骨骼来控制这些对象吗?

建立骨骼

import bpy

# 清除场景中已有的物体
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
  • 清场景
    • 建立骨架 Armature

图片描述

  • 场景中 出现了一个Bone

图片描述

  • 骨架 里面 只有一根 骨骼
    • 骨骼看起来像 飞镖

图片描述

切换四视图

  • 切换到layout布局

图片描述

  • 切换到四视图

图片描述

挤出子骨骼

  • 选中骨骼
    • 设置 编辑模式
    • 选中挤出工具e
    • 挤出子骨骼

图片描述

  • 修改骨骼名称
    • 父级 为 upper_arm 大臂
    • 子级 为 forearm 小臂

图片描述

  • 可以用代码生成骨架吗?

代码生成

图片描述

具体代码

import bpy

# 删除所有对象
for obj in bpy.data.objects:
    bpy.data.objects.remove(obj)
for armature in bpy.data.armatures:
    bpy.data.armatures.remove(armature)

# 创建一个新的Armature数据块
arm_data = bpy.data.armatures.new("arm_data")
# 创建一个新的对象并关联到上述Armature数据块
arm_obj = bpy.data.objects.new("arm_obj", arm_data)
# 确保 Armature 对象被链接到指定的集合
collection = bpy.data.collections.get("Collection")
collection.objects.link(arm_obj)

# 进入编辑模式
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT')

# 获取编辑模式下的骨骼数据
edit_bones = arm_data.edit_bones

# 创建骨骼
upper_arm = edit_bones.new("upper_arm")
upper_arm.head = (0, 0, -1)
upper_arm.tail = (0, 0, 0)

# 创建子骨骼
forearm = edit_bones.new("forearm")
forearm.head = upper_arm.tail
forearm.tail = (0, 0, 1)

# 设置子骨骼的父骨骼为parent_bone
forearm.parent = upper_arm

# 退出编辑模式
bpy.ops.object.mode_set(mode='OBJECT')
  • 效果

图片描述

  • 如何才能选中骨骼呢?

摆姿势(pose)

  • 进入姿势(pose)模式
    • 选中大臂(upper_arm)
    • 旋转

图片描述

  • 观察到 大臂
    • 在 骨骼属性中 旋转值 修改
    • 并 带动 小臂

小臂的旋转

  • 在大臂旋转的基础上
    • 旋转小臂
    • 修改 骨骼旋转值

图片描述

初始化骨骼

图片描述

import bpy

# 删除所有对象
for obj in bpy.data.objects:
    bpy.data.objects.remove(obj)
for armature in bpy.data.armatures:
    bpy.data.armatures.remove(armature)

# 创建一个新的Armature数据块
arm_data = bpy.data.armatures.new("arm_data")
# 创建一个新的对象并关联到上述Armature数据块
arm_obj = bpy.data.objects.new("arm_obj", arm_data)
# 确保 Armature 对象被链接到指定的集合
collection = bpy.data.collections.get("Collection")
collection.objects.link(arm_obj)

# 进入编辑模式
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT')

# 获取编辑模式下的骨骼数据
edit_bones = arm_data.edit_bones

# 创建骨骼
upper_arm = edit_bones.new("upper_arm")
upper_arm.head = (0, 0, -1)
upper_arm.tail = (0, 0, 0)

# 创建子骨骼
forearm = edit_bones.new("forearm")
forearm.head = upper_arm.tail
forearm.tail = (0, 0, 1)

# 设置子骨骼的父骨骼为parent_bone
forearm.parent = upper_arm

# 退出编辑模式
bpy.ops.object.mode_set(mode='OBJECT')

生成网格(mesh)对象

  • 新建 圆柱体
    • 并设置属性

图片描述

  • 修改名字 为 upper_arm_obj

制作小臂

  • 复制 此圆柱对象
    • 向下移动位置

图片描述

  • 改名为
    • 小臂对象
    • forearm_obj

图片描述

  • 目前看不到骨架

骨架可视化

  • 选中骨架
    • 视窗 可视化
      • 名字
      • 在最前

图片描述

  • 观察视窗变化

用代码实现

import bpy

# 删除所有对象
for obj in bpy.data.objects:
    bpy.data.objects.remove(obj)
for armature in bpy.data.armatures:
    bpy.data.armatures.remove(armature)

# 创建一个新的Armature数据块
arm_data = bpy.data.armatures.new("arm_data")
# 创建一个新的对象并关联到上述Armature数据块
arm_obj = bpy.data.objects.new("arm_obj", arm_data)
# 确保 Armature 对象被链接到指定的集合
collection = bpy.data.collections.get("Collection")
collection.objects.link(arm_obj)

# 进入编辑模式
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT')

# 获取编辑模式下的骨骼数据
edit_bones = arm_data.edit_bones

# 创建骨骼
upper_arm = edit_bones.new("upper_arm")
upper_arm.head = (0, 0, -1)
upper_arm.tail = (0, 0, 0)

# 创建子骨骼
forearm = edit_bones.new("forearm")
forearm.head = upper_arm.tail
forearm.tail = (0, 0, 1)

# 设置子骨骼的父骨骼为parent_bone
forearm.parent = upper_arm

# 退出编辑模式
bpy.ops.object.mode_set(mode='OBJECT')

# 创建小臂圆柱体
bpy.ops.mesh.primitive_cylinder_add(
    radius=0.3,  # 圆柱体半径
    depth=1,  # 圆柱体高度
)
forearm_obj = bpy.context.active_object
forearm_obj.name = "forearm_obj"
forearm_obj.location = (0, 0, 0.5)


# 创建大臂圆柱体
bpy.ops.mesh.primitive_cylinder_add(
    radius=0.3,  # 圆柱体半径
    depth=1,  # 圆柱体高度
)
upper_arm_obj = bpy.context.active_object
upper_arm_obj.name = "upper_arm_obj"
# 将大臂圆柱体的位置和旋转设置为默认值
upper_arm_obj.location = (0, 0, -0.5)

将mesh放入骨架

  • 选中 骨架 进入姿态模式

图片描述

  • 先选中 骨架中的小臂骨骼 准备作为 父亲
    • ctrl选中 Mesh中的 小臂 准备作为 儿子

图片描述

设置结果

  • 小臂mesh 进入 骨架(armature) 对象

图片描述

  • 选择 姿势(Pose)模式
    • 旋转 小臂骨骼
    • 会影响到 小臂网格(Mesh)对象

图片描述

  • 将 小臂骨骼 回正

绑定大臂

  • 姿势(Pose)模式下
    • 先选中 大臂骨骼
    • 然后 选择 大臂 网格(Mesh) 对象

图片描述

  • 对象模式下
    • 设置 对象 - 父级 - 骨骼

图片描述

  • 将 大臂 放入骨骼

修改姿势

  • 姿势 模式下
    • 旋转 大臂 骨骼
      • 大臂 网格 对象 跟着旋转
    • 小臂 骨骼 跟着 大臂 旋转
      • 小臂 网格 跟着 小臂 骨骼 旋转

图片描述

  • 旋转 小臂 骨骼
    • 小臂 网格 跟着 小臂 骨骼旋转

图片描述

  • 可以把这一切代码化吗?

优化代码

  • 在原代码基础上 更进一步

图片描述

最终代码

  • 可以实现需求

# 删除所有对象
for obj in bpy.data.objects:
    bpy.data.objects.remove(obj)
for armature in bpy.data.armatures:
    bpy.data.armatures.remove(armature)

# 创建一个新的Armature数据块
arm_data = bpy.data.armatures.new("arm_data")
# 创建一个新的对象并关联到上述Armature数据块
arm_obj = bpy.data.objects.new("arm_obj", arm_data)
# 确保 Armature 对象被链接到指定的集合
collection = bpy.data.collections.get("Collection")
collection.objects.link(arm_obj)

# 设置骨骼显示在最前方,方便观察和操作
arm_obj.show_in_front = True

# 进入编辑模式
bpy.context.view_layer.objects.active = arm_obj
bpy.ops.object.mode_set(mode='EDIT')

# 获取编辑模式下的骨骼数据
edit_bones = arm_data.edit_bones

# 创建骨骼
upper_arm = edit_bones.new("upper_arm")
upper_arm.head = (0, 0, -1)
upper_arm.tail = (0, 0, 0)

# 创建子骨骼
forearm = edit_bones.new("forearm")
forearm.head = upper_arm.tail
forearm.tail = (0, 0, 1)

# 设置子骨骼的父骨骼为parent_bone
forearm.parent = upper_arm

# 退出编辑模式
bpy.ops.object.mode_set(mode='OBJECT')

# 创建小臂圆柱体
bpy.ops.mesh.primitive_cylinder_add(
    radius=0.3,  # 圆柱体半径
    depth=1,  # 圆柱体高度
)
forearm_obj = bpy.context.active_object
forearm_obj.name = "forearm_obj"
forearm_obj.location = (0, 0, 0.5)


# 创建大臂圆柱体
bpy.ops.mesh.primitive_cylinder_add(
    radius=0.3,  # 圆柱体半径
    depth=1,  # 圆柱体高度
)
upper_arm_obj = bpy.context.active_object
upper_arm_obj.name = "upper_arm_obj"
# 将大臂圆柱体的位置和旋转设置为默认值
upper_arm_obj.location = (0, 0, -0.5)

# 绑定函数:使用 parent_set operator 确保 keep_transform 生效
def parent_object_to_bone(obj, armature, bone_name):
    # 确保在 Object Mode 下进行选择
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action='DESELECT')
    
    # 选中子物体和骨架
    obj.select_set(True)
    armature.select_set(True)
    
    # 激活骨架
    bpy.context.view_layer.objects.active = armature
    
    # 进入 Pose Mode
    bpy.ops.object.mode_set(mode='POSE')
    
    # 使用 operator 取消所有骨骼的选择 (兼容性更好)
    bpy.ops.pose.select_all(action='DESELECT')
    
    # 激活目标骨骼
    armature.data.bones.active = armature.data.bones[bone_name]
    
    # 选中目标骨骼 (兼容不同版本 API)
    # Blender 4.0+ 使用 PoseBone.select,旧版本使用 PoseBone.bone.select
    pbone = armature.pose.bones[bone_name]
    try:
        pbone.bone.select = True
    except AttributeError:
        pbone.select = True
    
    # 执行绑定,保持变换
    bpy.ops.object.parent_set(type='BONE', keep_transform=True)
    
    # 回到 Object Mode
    bpy.ops.object.mode_set(mode='OBJECT')

# 绑定大臂
parent_object_to_bone(upper_arm_obj, arm_obj, "upper_arm")

# 绑定小臂
parent_object_to_bone(forearm_obj, arm_obj, "forearm")

总结

  • 这次 通过骨骼 控制对象了

    • 骨骼 就是 父子关系
    • 把对象挂接到骨头上 就可以控制对象了
  • 但是 这是 将对象作为一个整体 控制呢?

    • 如何控制具体的点呢?

  • 本文来自 oeasy Python 系统教程。
  • 想完整、扎实学 Python,
  • 搜索 oeasy 即可。