如何使用 Blender 创建带有内半径的圆环

1,472 阅读5分钟

StackExchange 原问题

Create Circle with Inner Radius

翻译

有没有一个简单的方法来创建一个圆,其中包含一个洞/内半径。理想情况 下,我可以在添加时动态设置内半径(以同样的方式,我还可以在添加圆形网格时设置外半径)。

虽然我可以手动创建它,添加一个没有填充的圆,克隆、缩小,然后创建面(见下图),但这是 乏味容易出错 的。我也可以通过创建一个三角形扇形(triangle fan)的圆,向下复制出边缘,删除中间,但同样,这有点 乏味很难得到准确的内半径

image.png

有没有办法在 Blender 中原生地做到这一点,或者通过一个插件?

解答

方法 1 —— 添加新的对象网格插件模板

SRQ56.gif

添加以下 Python 脚本代码(我已注释关键代码),该代码功能如下

  • 添加两个圆,桥接循环边(edge loops)。
  • 如果选择 Ngon Fill ,则仅分割一个填充边缘(fill edge),溶解其余的填充边缘。
bl_info = {
    "name": "New 2D Ring",
    "author": "batFINGER",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "View3D > Add > Mesh > New 2D Ring",
    "description": "Adds a 2D ring (circle with hole)",
    "warning": "",
    "wiki_url": "",
    "category": "Add Mesh",
}

import bpy
import bmesh
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from bpy.props import (
        IntProperty,
        BoolProperty,
        FloatProperty,
)

class MESH_OT_primitive_ring_add(AddObjectHelper, bpy.types.Operator):
    """Add a 2D filled ring mesh"""
    bl_idname = "mesh.primitive_ring_add"
    bl_label = "Add 2D Filled Ring"
    bl_options = {'REGISTER', 'UNDO'}

    inner_radius: FloatProperty(
        name="Inner Radius",
        description="Inner Radius",
        min=0,
        default=1.0,
    )
    outer_radius: FloatProperty(
        name="Outer Radius",
        description="Outer Radius",
        min=0,
        default=2.0,
    )
    segments: IntProperty(
        name="Segments",
        description="Number of Segments",
        min=3,
        default=8,
    )
    ngon_fill: BoolProperty(
        name="Ngon Fill",
        description="Fill with ngon",
    )

    def draw(self, context):
        '''Generic Draw'''
        layout = self.layout
        # annnotated on this class
        for prop in self.__class__.__annotations__.keys():
            layout.prop(self, prop)
        # annotated on AddObjectHelper
        for prop in AddObjectHelper.__annotations__.keys():
            layout.prop(self, prop)

    def execute(self, context):

        me = bpy.data.meshes.new("CircleHole")

        bm = bmesh.new()
        bmesh.ops.create_circle(bm, radius=self.inner_radius, segments=self.segments)
        bmesh.ops.create_circle(bm, radius=self.outer_radius, segments=self.segments)
        
        # 桥接带有面的循环边,返回新的面和新的边
        ret = bmesh.ops.bridge_loops(bm, edges=bm.edges)
        if self.ngon_fill:
            edges = ret['edges']
            e = edges.pop() # 取出队尾的一条边
            bmesh.ops.split_edges(bm, edges=[e]) # 沿着指定输入边断开面
            bmesh.ops.dissolve_edges(bm,
                                     edges=edges) # 溶解所有边
        bm.to_mesh(me) # BMesh -> Mesh
        # 使用视图上下文和首选项添加一个对象来初始化位置、旋转和层。
        object_data_add(context, me, operator=self)

        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(MESH_OT_primitive_ring_add.bl_idname, icon='MESH_CUBE')

def register():
    bpy.utils.register_class(MESH_OT_primitive_ring_add)
    bpy.types.VIEW3D_MT_mesh_add.append(menu_func)

def unregister():
    bpy.utils.unregister_class(MESH_OT_primitive_ring_add)
    bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)

if __name__ == "__main__":
    register()

    # test call
    #bpy.ops.mesh.primitive_ring_add()

[!NOTE] 这段代码是在使用 bmesh 模块进行网格操作,特别是在处理多边形(ngons)填充的情况下。它通过一系列步骤来修改和简化网格结构。下面是对代码的详细解析:

代码解析

if self.ngon_fill:
    edges = ret['edges']
    e = edges.pop() # 取出队尾的一条边
    bmesh.ops.split_edges(bm, edges=[e]) # 沿着指定输入边断开面
    bmesh.ops.dissolve_edges(bm, edges=edges) # 溶解所有边
条件判断 if self.ngon_fill:
  • 条件self.ngon_fill 是一个布尔值属性或变量,用于控制是否执行多边形填充逻辑。
  • 作用:只有当 self.ngon_fillTrue 时,才会执行后续的操作。
获取边列表 edges = ret['edges']
  • ret:假设 ret 是一个包含网格操作结果的字典,其中 'edges' 键对应的值是一个包含多个边的列表。
  • edges:将 ret['edges'] 的值赋给 edges,即获取当前网格中的边列表。
取出并移除一条边 e = edges.pop()
  • pop() 方法从列表中取出最后一个元素并将其从列表中移除
  • e:保存被取出的边对象。
  • 作用选择一条边作为特殊处理的对象,通常是为了保留这条边不被溶解
分割边 bmesh.ops.split_edges(bm, edges=[e])
  • bmesh.ops.split_edges:这是一个 bmesh 操作,用于沿着指定的边分割网格。
  • 参数
    • bm:表示当前的 bmesh 对象。
    • edges=[e]:指定要分割的边列表,这里只包含刚刚取出的那一条边 e
  • 作用:沿着边 e 断开面,这会在原边的位置创建新的顶点和边,从而将原来的多边形分割成更小的多边形。
溶解边 bmesh.ops.dissolve_edges(bm, edges=edges)
  • bmesh.ops.dissolve_edges:这是另一个 bmesh 操作,用于溶解指定的边。
  • 参数
    • bm:表示当前的 bmesh 对象。
    • edges=edges:指定要溶解的边列表,这里是除了 e 之外的所有边
  • 作用:溶解这些边会移除它们,并合并相邻的面,从而简化网格结构。如果这些边是多边形的边界,则溶解后可能会导致多边形变成三角形或其他简单形状。

小结

这段代码的主要目的是在启用多边形填充 (self.ngon_fill) 的情况下,对网格进行特定的编辑操作:

  1. 选择一条边:从当前网格的边列表中选择一条边,并将其保留下来。
  2. 分割这条边:沿着选定的边分割网格,以确保该边不会被后续操作影响。
  3. 溶解其他边:溶解剩下的所有边,简化网格结构,可能将复杂的多边形转换为更简单的形状(如三角形)。

这种操作通常用于优化网格结构,减少不必要的几何复杂度,或者为了更好地支持某些渲染或物理模拟功能。通过这种方式,可以有效地管理网格拓扑,确保生成的网格既简洁又符合预期的几何特性。

方法 2 —— 添加圆锥图元

1SPGH.gif

该功能是非常类似于添加没有填充和高度的圆锥图元(可以完全在 z 上缩放到 0 并应用)

其他方法

其他还有通过 “旋绕(Spin)” “螺旋(Screw)修改器” “圆柱体” 等实现类似的效果,但不如以上方法来得便捷,但是可以当作一种技术思路,详细可以参考下文

Blender 制作刀光特效所用模型