StackExchange 原问题
翻译
有没有一个简单的方法来创建一个圆,其中包含一个洞/内半径。理想情况 下,我可以在添加时动态设置内半径(以同样的方式,我还可以在添加圆形网格时设置外半径)。
虽然我可以手动创建它,添加一个没有填充的圆,克隆、缩小,然后创建面(见下图),但这是 乏味 和 容易出错 的。我也可以通过创建一个三角形扇形(triangle fan)的圆,向下复制出边缘,删除中间,但同样,这有点 乏味,很难得到准确的内半径。

有没有办法在 Blender 中原生地做到这一点,或者通过一个插件?
解答
方法 1 —— 添加新的对象网格插件模板

添加以下 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_fill为True时,才会执行后续的操作。获取边列表
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) 的情况下,对网格进行特定的编辑操作:
- 选择一条边:从当前网格的边列表中选择一条边,并将其保留下来。
- 分割这条边:沿着选定的边分割网格,以确保该边不会被后续操作影响。
- 溶解其他边:溶解剩下的所有边,简化网格结构,可能将复杂的多边形转换为更简单的形状(如三角形)。
这种操作通常用于优化网格结构,减少不必要的几何复杂度,或者为了更好地支持某些渲染或物理模拟功能。通过这种方式,可以有效地管理网格拓扑,确保生成的网格既简洁又符合预期的几何特性。
方法 2 —— 添加圆锥图元

该功能是非常类似于添加没有填充和高度的圆锥图元(可以完全在 z 上缩放到 0 并应用)
其他方法
其他还有通过 “旋绕(Spin)” “螺旋(Screw)修改器” “圆柱体” 等实现类似的效果,但不如以上方法来得便捷,但是可以当作一种技术思路,详细可以参考下文