面向对象程序设计的核心思想是数据抽象,继承和动态绑定。
通过使用数据抽象,我们可以将类的接口和实现分离;
使用继承,可以定义相似的类型并对其相似关系建模;
使用动态绑定,可以在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象。
—— c++ primer
chatGPT,我的basic全学科导师。
class Dog:
num_dogs = 0 # 公有类变量
def __init__(self, name, age):
self.__name = name # 私有实例变量
self.age = age # 公有实例变量
Dog.num_dogs += 1
def bark(self):
print(f"{self.__name} says woof!")
def get_name(self):
return self.__name
@classmethod
def display_num_dogs(cls):
print(f"Total number of dogs: {cls.num_dogs}")
@staticmethod
def sleep():
print("All dogs are sleeping.")
def __str__(self):
return f"{self.__name} is {self.age} years old."
# 创建狗的实例
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
# 访问公有变量
print(dog1.age) # 输出:3
# 访问私有变量
# print(dog1.__name) # 会报错,因为私有变量无法直接访问
# 使用公有方法访问私有变量
print(dog1.get_name()) # 输出:Buddy
__init__
下面是关于
__init__
方法的一些详细说明:
- 名称和特殊性:
__init__
方法是一个特殊的方法,由两个下划线开头和结尾。这种命名约定表示这是一个特殊的方法,用于在对象创建时进行初始化。- 参数:
__init__
方法的第一个参数通常是self
,它代表当前对象本身。在方法内部,可以使用self
来访问对象的属性和方法。除了self
参数之外,__init__
方法可以接受其他参数,用于在创建对象时传递初始化数据。- 对象初始化:
__init__
方法主要用于初始化对象的状态。你可以在这个方法中设置对象的属性,为对象提供必要的初始值。通常,这些属性将在类的其他方法中使用,或者用于对象的其他操作。- 自动调用:当你创建一个新的对象时,Python 会自动调用该类的
__init__
方法,而你无需手动调用。这确保了对象在创建后立即进行初始化,以便在创建后立即可用。- 初始化过程:在创建对象时,你可以向
__init__
方法传递参数,这些参数将用于初始化对象的属性。通过传递不同的参数,可以为每个对象提供不同的初始状态。
__str__
__str__
方法是 Python 中的一个特殊方法(也称为魔术方法),用于定义对象的字符串表示形式。当你使用print()
函数或str()
函数来打印对象时,解释器会自动调用该方法来获取对象的字符串表示。下面是
__str__
方法的基本结构:
def __str__(self): # 返回对象的字符串表示形式 # 可以包括对象的属性信息等 return "string_representation"
# 调用公有方法
dog1.bark() # 输出:Buddy says woof!
# 调用类方法
Dog.display_num_dogs() # 输出:Total number of dogs: 2
# 调用静态方法
Dog.sleep() # 输出:All dogs are sleeping.
# 调用魔术方法
print(dog2) # 输出:Max is 5 years old.
- 普通方法 (
bark
):这是一个普通方法,它接受self
参数,表示对象本身。它用于打印狗的名字和 "woof!"。这个方法是对象特定的,每个对象都可以调用它。- 类方法 (
display_num_dogs
):这是一个类方法,它使用@classmethod
装饰器进行装饰,并接受cls
参数,表示类本身。该方法用于访问和操作类变量num_dogs
,并打印出当前创建的狗的数量。这个方法是针对整个类的,可以通过类名调用它,也可以通过对象调用。- 静态方法 (
sleep
):这是一个静态方法,它使用@staticmethod
装饰器进行装饰,不接受类或对象作为参数。它是与类相关联的功能性函数,与类的实例无关。这个方法可以通过类名或对象调用。- 魔术方法 (
__str__
):这是一个魔术方法,它是在对象被打印为字符串时由解释器自动调用的。在这个例子中,它返回一个描述狗的字符串,包括狗的名字和年龄
__del__
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} says woof!")
def __del__(self):
print(f"{self.name} is being destroyed.")
# 创建对象
dog1 = Dog("Buddy")
dog2 = Dog("Max")
# 调用方法
dog1.bark() # 输出:Buddy says woof!
# 删除对象
del dog1
del dog2
感觉类变量还挺独特的,有什么案例讲解吗?
类变量在整个类的实例中是公用的,定义在类中且在函数体之外。这些类变量可以通过类名或类的实例访问。
当谈到类变量时,一个经典的案例是创建一个计数器类,用于跟踪创建的对象数量。
在这个例子中,我们扩展了
Dog
类,添加了一个类变量all_dogs
,用于记录所有创建的狗的实例。在__init__()
方法中,我们将每个创建的狗的实例添加到all_dogs
列表中,并记录了创建时间。通过类方法display_all_dogs()
,我们可以打印出所有狗的信息,包括它们的名称、年龄和创建时间。这个示例展示了类变量如何用于跟踪和记录对象的信息,从而实现更复杂的功能。
import datetime
class Dog:
num_dogs = 0 # 类变量,用于跟踪创建的狗的数量
all_dogs = [] # 类变量,用于记录所有创建的狗的实例
def __init__(self, name, age):
self.name = name
self.age = age
self.created_at = datetime.datetime.now() # 记录对象创建时间
Dog.num_dogs += 1
Dog.all_dogs.append(self) # 将创建的狗的实例添加到类变量 all_dogs 中
def bark(self):
print(f"{self.name} says woof!")
@classmethod
def display_num_dogs(cls):
print(f"Total number of dogs: {cls.num_dogs}")
@classmethod
def display_all_dogs(cls):
print("All dogs:")
for dog in cls.all_dogs:
print(f"- {dog.name}, {dog.age} years old, created at {dog.created_at}")
# 创建狗的实例
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
# 调用类方法显示狗的数量
Dog.display_num_dogs() # 输出:Total number of dogs: 2
# 调用类方法显示所有狗的信息
Dog.display_all_dogs()
不建议使用类变量作为实例变量
在
Dog
类中,我们已经定义了一个类变量num_dogs
用于跟踪创建的狗的数量。现在,让我们看看如果尝试将类变量直接作为实例变量使用会发生什么:
class Dog: num_dogs = 0 # 类变量,用于跟踪创建的狗的数量 def __init__(self, name): self.name = name self.num_dogs = 100 # 将类变量直接作为实例变量使用 Dog.num_dogs += 1 # 创建狗的实例 dog1 = Dog("Buddy") dog2 = Dog("Max") # 打印实例变量 print(dog1.num_dogs) # 输出:100 print(dog2.num_dogs) # 输出:100 # 打印类变量 print(Dog.num_dogs) # 输出:2
在这个例子中,我们将
num_dogs
类变量直接赋值给了实例变量self.num_dogs
。然而,这并不会影响类变量Dog.num_dogs
,因为类变量和实例变量是不同的。所以,虽然实例变量num_dogs
被赋值为100,但是Dog.num_dogs
仍然被正确地增加了。
对于 Python 3.11,动态添加方法时需要考虑到函数绑定的问题。在 Python 3.11 中,你需要使用
types.MethodType
来将函数绑定到实例上。
import types def run(self): print(f"{self.name} is running!") class Dog: def __init__(self, name): self.name = name # 创建狗的实例 dog1 = Dog("Buddy") dog2 = Dog("Max") # 动态添加新方法 dog1.run = types.MethodType(run, dog1) # 调用新方法 dog1.run() # 输出:Buddy is running! dog2.run() # 报错:AttributeError: 'Dog' object has no attribute 'run'
通过
types.MethodType(run, dog1)
将run
函数绑定到了dog1
实例上,使其成为了dog1
实例的方法。这样就可以成功调用dog1.run()
来运行run
函数了。
generated by chatGPT
主题 | 描述 |
---|---|
成员变量 | 成员变量是属于类的属性,在类中定义,可以在类的任何方法中使用。成员变量可以通过实例对象访问和修改。 |
私有变量 | 私有变量通过在变量名前加双下划线 __ 来声明,表示只能在类内部访问。私有变量不能直接通过类的实例对象访问。 |
成员方法 | 成员方法是在类中定义的函数,用于定义类的行为。成员方法的第一个参数通常是 self ,代表类的实例对象。 |
类的设计 | 类的设计包括类的结构、属性和方法的设计。良好的类设计应该具备高内聚、低耦合的特点,便于扩展和维护。 |
构造方法 | 构造方法 __init__ 是在创建对象时自动调用的方法,用于初始化对象的属性。 |
析构方法 | 析构方法 __del__ 是在对象被删除时自动调用的方法,用于清理资源。 |
为了更好地理解这些概念,可以通过以下示例代码来演示:
class MyClass:
# 成员变量
class_var = "I am a class variable"
def __init__(self, value):
# 实例变量
self.instance_var = value
# 私有变量
self.__private_var = "I am private"
# 成员方法
def display_vars(self):
print(f"Class Variable: {MyClass.class_var}")
print(f"Instance Variable: {self.instance_var}")
print(f"Private Variable: {self.__private_var}")
# 私有方法
def __private_method(self):
print("This is a private method")
# 析构方法
def __del__(self):
print(f"Object with value {self.instance_var} is being destroyed")
# 创建对象
obj = MyClass("Hello")
obj.display_vars()
# 尝试访问私有变量(会报错)
# print(obj.__private_var)
# 正确访问私有变量的方式
print(obj._MyClass__private_var)
# 销毁对象
del obj
这个表格和示例代码展示了成员变量、私有变量、成员方法、构造方法和析构方法的使用方式,以及类设计中的一些原则。通过实际操作和运行这些代码,可以加深对这些概念的理解。
作为资深用户,blender会有多个版本,辛亏安装都比较讲究,放在了一个地方:D:\libregd\Blender Foundation\
Blender 3.1\
blender-3.3.12-windows-x64\
blender-3.5.0-windows-x64\
blender-3.6.4-windows-x64\
blender-4.1.0-alpha+main.7e8557e6c64e-windows.amd64-release\
我打算基于4.1 来玩,所以我进入了最后一个文件夹,在这个版本的插件文件夹(插件所在地)D:\libregd\Blender Foundation\blender-4.1.0-alpha+main.7e8557e6c64e-windows.amd64-release\4.1\scripts\addons
materials_library_vx/
__pycache__/ materials_utils/
add_camera_rigs/ measureit/
add_curve_extra_objects/ mesh_auto_mirror.py
add_curve_ivygen.py mesh_bsurfaces.py
add_curve_sapling/ mesh_f2.py
add_mesh_BoltFactory/ mesh_inset/
add_mesh_discombobulator/ mesh_looptools.py
add_mesh_extra_objects/ mesh_snap_utilities_line/
add_mesh_geodesic_domes/ mesh_tiny_cad/
amaranth/ mesh_tissue/
animation_add_corrective_shape_key.py mesh_tools/
animation_animall/ node_arrange.py
ant_landscape/ node_presets.py
archimesh/ node_wrangler/
blender_id/ object_boolean_tools.py
bone_selection_sets.py object_carver/
btrace/ object_collection_manager/
camera_turnaround.py object_color_rules.py
copy_global_transform.py object_edit_linked.py
curve_assign_shapekey.py object_fracture_cell/
curve_simplify.py object_print3d_utils/
curve_tools/ object_scatter/
cycles/ object_skinify.py
depsgraph_debug.py paint_palette.py
development_edit_operator.py pose_library/
development_icon_get.py power_sequencer/
development_iskeyfree.py precision_drawing_tools/
greasepencil_tools/ presets/
hydra_storm/ real_snow.py
io_anim_bvh/ render_copy_settings/
io_anim_camera.py render_freestyle_svg.py
io_anim_nuke_chan/ render_povray/
io_coat3D/ render_ui_animation_render.py
io_curve_svg/ rigify/
io_export_dxf/ space_clip_editor_refine_solution.py
io_export_paper_model.py space_view3d_3d_navigation.py
io_export_pc2.py space_view3d_align_tools.py
io_import_BrushSet.py space_view3d_brush_menus/
io_import_dxf/ space_view3d_copy_attributes.py
io_import_images_as_planes.py space_view3d_math_vis/
io_import_palette/ space_view3d_modifier_tools.py
io_mesh_atomic/ space_view3d_pie_menus/
io_mesh_stl/ space_view3d_spacebar_menu/
io_mesh_uv_layout/ space_view3d_stored_views/
io_scene_3ds/ storypencil/
io_scene_fbx/ sun_position/
io_scene_gltf2/ system_blend_info.py
io_scene_x3d/ system_demo_mode/
io_shape_mdd/ system_property_chart.py
lighting_dynamic_sky.py ui_translate/
lighting_tri_lights.py vdm_brush_baker/
magic_uv/ viewport_vr_preview/
新建了一个Test
文件夹,内有1个文件:
__init__.py
bl_info = {
"name": "My Blender Plugin-1",
"author": "libregd",
"description": "A brief description of your plugin",
"blender": (4, 1, 0),
"location": "View3D > Tools",
"category": "meo",
"version": (1, 0)
}
# Your plugin's operators, panels, etc. can be defined here
def register():
# Register your operators, panels, etc. here
print("插件已注册")
def unregister():
# Unregister your operators, panels, etc. here
print("插件已卸载")
if __name__ == "__main__":
register()
这就是基本上是一个插件的最基础的框架,通过在blender的插件列表中刷新,就可以获取自己插件的最新版本。
我:帮我做一下作业,这是blender开发课程中python 的作业。可能和blender API相关。
# 作业1
# 尝试定义一个类 这个类用于描述网格物体
# 包含 物体名称 顶点数量 边数量,材质名称、网格位置
# 包含 更改材质名、更改网格位置、打印网格所有信息 等操作
#
# 并尝试使用这个类 定义不同特征的网格物体
chatGPT:
import bpy class MeshObject: def __init__(self, name, vertices, edges, material_name, location): self.name = name self.vertices = vertices self.edges = edges self.material_name = material_name self.location = location def change_material(self, new_material_name): self.material_name = new_material_name def change_location(self, new_location): self.location = new_location def print_info(self): print("Object Name:", self.name) print("Vertex Count:", len(self.vertices)) print("Edge Count:", len(self.edges)) print("Material Name:", self.material_name) print("Location:", self.location) # Example usage: mesh1 = MeshObject("Cube", [(0,0,0), (1,0,0), (1,1,0), (0,1,0)], [(0,1), (1,2), (2,3), (3,0)], "Red", (0,0,0)) mesh1.print_info() mesh1.change_material("Blue") mesh1.change_location((1,1,1)) mesh1.print_info()
这些代码在vscode中正确运行了,(但bpy显然没有用上,不然也不可能能在vs中运行)
我:看起来还不错,但我觉得扩展性有点差了,能不能更方便的更改所有的信息呢?
chatGPT:
import bpy class MeshObject: def __init__(self, name, vertices, edges, material_name, location): self.name = name self.vertices = vertices self.edges = edges self.material_name = material_name self.location = location def update_properties(self, name=None, vertices=None, edges=None, material_name=None, location=None): if name is not None: self.name = name if vertices is not None: self.vertices = vertices if edges is not None: self.edges = edges if material_name is not None: self.material_name = material_name if location is not None: self.location = location def print_info(self): print("Object Name:", self.name) print("Vertex Count:", len(self.vertices)) print("Edge Count:", len(self.edges)) print("Material Name:", self.material_name) print("Location:", self.location) # Example usage: mesh1 = MeshObject("Cube", [(0,0,0), (1,0,0), (1,1,0), (0,1,0)], [(0,1), (1,2), (2,3), (3,0)], "Red", (0,0,0)) mesh1.print_info() # Update all properties at once mesh1.update_properties(name="NewCube", vertices=[(0,0,0), (1,0,0), (1,1,1), (0,1,1)], edges=[(0,1), (1,2), (2,3), (3,0)], material_name="Blue", location=(1,1,1)) mesh1.print_info()
这个也在vscode中正确运行了,更加符合作业要求。
我:在blender中,mesh object 有很多种特性的物体对吧?比如圆柱体,正方形,球体,等等。如果我都想通过这个class meshObject 定义出来,好像这个代码弹性依旧不够?
chatGPT:
你说得对。如果你想使用
MeshObject
类来定义各种不同特性的网格对象,你可能需要更灵活的方法来实现这一点。一种方法是使用子类化(继承)的方式来扩展MeshObject
类,以便针对不同类型的网格对象添加特定的属性和方法。让我们来看看如何实现这一点: 在这个例子中,我们创建了两个子类CubeObject
和SphereObject
分别用于表示立方体和球体。每个子类通过调用父类MeshObject
的__init__
方法来初始化共同的属性,并针对特定类型的网格对象添加了额外的属性和方法。这样一来,你就可以根据需要轻松地定义不同类型的网格对象。
import bpy class MeshObject: def __init__(self, name, vertices, edges, material_name, location): self.name = name self.vertices = vertices self.edges = edges self.material_name = material_name self.location = location def update_properties(self, name=None, vertices=None, edges=None, material_name=None, location=None): if name is not None: self.name = name if vertices is not None: self.vertices = vertices if edges is not None: self.edges = edges if material_name is not None: self.material_name = material_name if location is not None: self.location = location def print_info(self): print("-------------------------") print("Object Name:", self.name) print("Vertex Count:", len(self.vertices)) print("Edge Count:", len(self.edges)) print("Material Name:", self.material_name) print("Location:", self.location) class CubeObject(MeshObject): def __init__(self, name, size, material_name, location): bpy.ops.mesh.primitive_cube_add(size=size, location=location) obj = bpy.context.object vertices = [(v.co.x, v.co.y, v.co.z) for v in obj.data.vertices] edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges] super().__init__(name, vertices, edges, material_name, location) class SphereObject(MeshObject): def __init__(self, name, radius, material_name, location): bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=location) obj = bpy.context.object vertices = [(v.co.x, v.co.y, v.co.z) for v in obj.data.vertices] edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges] super().__init__(name, vertices, edges, material_name, location) # Example usage: cube = CubeObject("Cube", size=2, material_name="Red", location=(0, 0, 0)) cube.print_info() sphere = SphereObject("Sphere", radius=1, material_name="Blue", location=(3, 0, 0)) sphere.print_info()
这个是能够在blender中运行的,运行方式:
打开blender 4.1. 将窗口分割为4部分:
3D viewport
(3D视图窗口,也就是默认窗口),info
(整个blender的日志窗口)python console
( 虽然排不上用场,但我喜欢开着)text editor
(编辑器,可以理解成内置运行Python的IDE)把上面的代码贴到
text editor
中,点击Run Script,info
窗口会记录操作信息,3D viewport
会出现1个cube 和 1个sphere。顶部菜单中
Window -> Toggle System Console
可以打开一个类似CMD的悬浮窗口,这是默认隐藏的,它的作用是查看上面代码中print()
打印的内容,方便调试。有很多看不太懂,已经需要学习了。
我:谢谢,你现在是一位python专家和blender API专家,我想就上面的代码中涉及具体实现的部分向你请教,希望你一步步的推理告诉我,请确保内容真实无误:
chatGPT:
对于
CubeObject
类中的__init__
方法:
bpy.ops.mesh.primitive_cube_add(size=size, location=location)
:这一句调用了 Blender Python API 中的原语函数bpy.ops.mesh.primitive_cube_add
来创建一个指定尺寸和位置的立方体网格对象。参数size
指定了立方体的尺寸,而location
指定了立方体的位置。为什么采用这个方法?因为
bpy.ops.mesh.primitive_cube_add
是 Blender 提供的一个方便创建基本几何体的函数,使用它可以快速创建出一个具有标准尺寸和位置的立方体网格对象。
obj = bpy.context.object
:这一句获取了刚刚创建的立方体对象,并将其赋值给变量obj
。为什么采用这个方法?因为在调用
bpy.ops.mesh.primitive_cube_add
函数后,Blender 会自动选中新创建的立方体对象,所以我们可以通过bpy.context.object
来获取到当前选中的对象。
vertices = [(v.co.x, v.co.y, v.co.z) for v in obj.data.vertices]
:这一句遍历了立方体对象的顶点,并将每个顶点的坐标(x, y, z)
添加到vertices
列表中。为什么采用这个方法?因为立方体对象的顶点坐标保存在其数据块(
obj.data
)的vertices
属性中,我们可以通过遍历这些顶点来获取它们的坐标。
edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges]
:这一句遍历了立方体对象的边,并将每条边的顶点索引(v1, v2)
添加到edges
列表中。为什么采用这个方法?因为立方体对象的边由两个顶点索引组成,保存在其数据块的
edges
属性中,我们可以通过遍历这些边来获取它们的顶点索引。
super().__init__(name, vertices, edges, material_name, location)
:这一句调用了父类MeshObject
的__init__
方法来初始化网格对象的属性,包括名称、顶点、边、材质名称和位置。为什么采用这个方法?因为我们希望在子类的
__init__
方法中初始化父类的属性,以便继承父类的功能并保持代码的一致性。使用super().__init__
调用父类的构造函数可以确保父类的初始化逻辑被正确执行。
我对上面内容进行了一些验证:
obj = bpy.context.object
print(f"The type of bpy.context.object is {type(obj)}")
vertices = [(v.co.x, v.co.y, v.co.z) for v in obj.data.vertices]
print(f"The type of vertices is {type(vertices)}")
print(f"vertices is : {vertices}")
edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges]
print(f"The type of edges is {type(edges)}")
print(f"edges is : {edges}")
得到的结果:
The type of bpy.context.object is <class 'bpy_types.Object'>
The type of vertices is <class 'list'>
vertices is : [(-1.0, -1.0, -1.0), (-1.0, -1.0, 1.0), (-1.0, 1.0, -1.0), (-1.0, 1.0, 1.0), (1.0, -1.0, -1.0), (1.0, -1.0, 1.0), (1.0, 1.0, -1.0), (1.0, 1.0, 1.0)]
The type of edges is <class 'list'>
edges is : [(2, 0), (0, 1), (1, 3), (3, 2), (6, 2), (3, 7), (7, 6), (4, 6), (7, 5), (5, 4), (0, 4), (5, 1)]
-------------------------
Object Name: Cube
Vertex Count: 8
Edge Count: 12
Material Name: Red
Location: (0, 0, 0)
vertices (顶点)和edges(边) 都是 list 里面套tuple
edges 没有出现8,是因为0-7 指代的8个vertices,(2,0)
的意思是,第2个顶点指向第0个顶点。即:顶点(-1.0, 1.0, -1.0) 指向→顶点(-1.0, -1.0, -1.0)。这是一种基于拓扑的索引表示法,不需要直接存储顶点的坐标。这样做的好处是节省了内存空间,并且更容易处理网格对象的几何结构。
chatGPT:在Blender 中,网格对象的边数据就是以这种形式存储的,通过顶点索引,可以唯一确定一条边连接的两个顶点。
在 Blender 中,边的顺序通常是根据网格对象的拓扑结构自动确定的。当你创建一个网格对象时,Blender 会根据顶点的连接关系自动构建边的顺序。这种自动构建的顺序通常是按照顶点的创建顺序来排列的,但也可能受到其他因素的影响,比如顶点的连接顺序或者边的法线方向等。
针对class SphereObject
我做了同样的验证:
class SphereObject(MeshObject):
def __init__(self, name, radius, material_name, location):
bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=location)
obj = bpy.context.object
vertices = [(v.co.x, v.co.y, v.co.z) for v in obj.data.vertices]
print(f"The type of vertices is {type(vertices)}")
print(f"vertices is : {vertices}")
edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges]
print(f"The type of edges is {type(edges)}")
print(f"edges is : {edges}")
super().__init__(name, vertices, edges, material_name, location)
得到的结果:
The type of bpy.context.object is <class 'bpy_types.Object'>
The type of vertices is <class 'list'>
vertices is : too much
The type of edges is <class 'list'>
edges is : too much
-------------------------
Object Name: Sphere
Vertex Count: 482
Edge Count: 992
Material Name: Blue
Location: (3, 0, 0)
在Blender中,
bpy.ops.mesh.primitive_uv_sphere_add
函数创建的球体并不是以简单的顶点和边的形式表示的,而是以更复杂的方式进行表示,这就解释了为什么你在打印出的顶点和边的数量会有这么多。这个函数创建的球体实际上是由许多个三角形面构成的,而不是简单的顶点和边。每个面由三个顶点组成,而每个顶点都在球体的表面上。所以打印出的顶点数量会非常多,因为这些顶点是用于构成球体表面的。
另外,因为球体是一个连续的曲面,而不是由直线边界分割的平面,所以它看起来并不像立方体那样清晰可见.
在 Blender 的编辑模式中,你可能只看到了一个简单的球体,因为 Blender 在编辑模式下会以一种更简单的方式来显示网格,以便于编辑。在编辑模式下,Blender 会显示一个低多边形的近似网格,以提高性能和交互性。
然而,在内部数据中,球体实际上是由更多的顶点和面构成的。当你通过
bpy.ops.mesh.primitive_uv_sphere_add
创建球体时,Blender 默认会使用相对较高的分辨率来表示球体的表面,以确保球体的外观足够圆滑。这就解释了为什么打印出的顶点和边的数量会很多。虽然在编辑模式下只显示了少量的面,但是这些面实际上是球体表面的一部分,并且是由更多的顶点和边构成的。因此,尽管在编辑模式下看起来简单,但球体的内部数据实际上是复杂的。
如果你想创建更简单的球体,可以使用
bpy.ops.mesh.primitive_uv_sphere_add
函数的一些参数来控制球体的分辨率,以减少顶点和边的数量。这些参数包括segments
(分段数)和ring
(环数),可以通过调整它们来控制球体的复杂度。例如,你可以将这些参数设置得更低一些,以创建一个具有较少顶点和边的球体。
bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=location, segments=8, ring_count=8)
得到的结果:
------------------------- Object Name: Sphere Vertex Count: 58 Edge Count: 120 Material Name: Blue Location: (3, 0, 0)
bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=location, segments=8, ring_count=8)
不写后面的segments 和ring_count,默认会给到32,和16.所以一开始的数据特别大。但这一条生成的结果不像个uv_sphere了,所以默认的还是不错的。
我:这一部分好像有些难懂,我希望给出更有可读性,即便代码变得更多了,但是结构上对新手更友好的代码形式。
vertices = [(v.co.x, v.co.y, v.co.z) for v in obj.data.vertices]
edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges]
chatGPT:
vertices = []
for v in obj.data.vertices:
vertex_coords = (v.co.x, v.co.y, v.co.z) # v.co 表示当前顶点的世界坐标。
vertices.append(vertex_coords) # 将坐标打包成一个元组,添加到vertics列表
edges = []
for e in obj.data.edges:
edge_indices = (e.vertices[0], e.vertices[1]) # 取得了当前边对象的第一个顶点的索引,第二个顶点的索引
edges.append(edge_indices)
所以版本4 只是对可读性进行一些优化,甚至给了另一个可读性变差,但更python味儿的代码:
vertices = [v.co[:] for v in obj.data.vertices]
edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges]
我:我的新问题是,这个代码可以进一步简化吗?甚至MeshObject部分采用更多bpy的功能来实现?
chatGPT:
当然可以进一步简化代码,特别是在创建 CubeObject 和 SphereObject 时可以利用更多 Blender 提供的功能来减少代码量。下面是进一步简化后的代码:
在这个简化后的版本中,我们在
CubeObject
和SphereObject
中去除了直接处理网格数据的代码,而是将get_vertices
和get_edges
方法添加到这两个类中,这些方法使用 Blender 提供的函数来获取相应的顶点和边。这样可以使代码更加清晰和易于维护。
import bpy class MeshObject: def __init__(self, name, vertices, edges, material_name, location): self.name = name self.vertices = vertices self.edges = edges self.material_name = material_name self.location = location def update_properties(self, name=None, vertices=None, edges=None, material_name=None, location=None): if name is not None: self.name = name if vertices is not None: self.vertices = vertices if edges is not None: self.edges = edges if material_name is not None: self.material_name = material_name if location is not None: self.location = location def print_info(self): print("Object Name:", self.name) print("Vertex Count:", len(self.vertices)) print("Edge Count:", len(self.edges)) print("Material Name:", self.material_name) print("Location:", self.location) class CubeObject(MeshObject): def __init__(self, name, size, material_name, location): bpy.ops.mesh.primitive_cube_add(size=size, location=location) super().__init__(name, self.get_vertices(), self.get_edges(), material_name, location) def get_vertices(self): return [(v.co.x, v.co.y, v.co.z) for v in bpy.context.object.data.vertices] def get_edges(self): return [(e.vertices[0], e.vertices[1]) for e in bpy.context.object.data.edges] class SphereObject(MeshObject): def __init__(self, name, radius, material_name, location): bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=location) super().__init__(name, self.get_vertices(), self.get_edges(), material_name, location) def get_vertices(self): return [(v.co.x, v.co.y, v.co.z) for v in bpy.context.object.data.vertices] def get_edges(self): return [(e.vertices[0], e.vertices[1]) for e in bpy.context.object.data.edges] # Example usage: cube = CubeObject("Cube", size=2, material_name="Red", location=(0, 0, 0)) cube.print_info() sphere = SphereObject("Sphere", radius=1, material_name="Blue", location=(3, 0, 0)) sphere.print_info()
我:看起来好像没区别?但是写法又有一些改变,这样做真的有好处吗?能否举例说明
chatGPT:
你是对的,这两种写法的结果确实是相同的。但是,在实际开发中,尤其是对于更复杂的场景和更多的功能,将获取数据的逻辑封装到类的方法中有几个好处:
虽然在当前的例子中,这两种写法的结果相同,并且可能没有明显的区别,但是采用第二种写法可以使代码更具有扩展性和可维护性,尤其是在实际开发中面对更复杂的场景和更多的功能时。