BL插件开发学习笔记-01

这一篇主要是关于面向对象,类,的基础理解,以及BL插件的step1

对象-类

面向对象程序设计的核心思想是数据抽象,继承和动态绑定。

通过使用数据抽象,我们可以将类的接口和实现分离;

使用继承,可以定义相似的类型并对其相似关系建模;

使用动态绑定,可以在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象。

—— 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__ 方法的一些详细说明:

  1. 名称和特殊性__init__ 方法是一个特殊的方法,由两个下划线开头和结尾。这种命名约定表示这是一个特殊的方法,用于在对象创建时进行初始化。
  2. 参数__init__ 方法的第一个参数通常是 self,它代表当前对象本身。在方法内部,可以使用 self 来访问对象的属性和方法。除了 self 参数之外,__init__ 方法可以接受其他参数,用于在创建对象时传递初始化数据。
  3. 对象初始化__init__ 方法主要用于初始化对象的状态。你可以在这个方法中设置对象的属性,为对象提供必要的初始值。通常,这些属性将在类的其他方法中使用,或者用于对象的其他操作。
  4. 自动调用:当你创建一个新的对象时,Python 会自动调用该类的 __init__ 方法,而你无需手动调用。这确保了对象在创建后立即进行初始化,以便在创建后立即可用。
  5. 初始化过程:在创建对象时,你可以向 __init__ 方法传递参数,这些参数将用于初始化对象的属性。通过传递不同的参数,可以为每个对象提供不同的初始状态。

__str__

__str__ 方法是 Python 中的一个特殊方法(也称为魔术方法),用于定义对象的字符串表示形式。当你使用 print() 函数或 str() 函数来打印对象时,解释器会自动调用该方法来获取对象的字符串表示。

下面是 __str__ 方法的基本结构:

def __str__(self):
    # 返回对象的字符串表示形式
    # 可以包括对象的属性信息等
    return "string_representation"

类的方法分类

4种def

# 调用公有方法
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.
  1. 普通方法 (bark):这是一个普通方法,它接受 self 参数,表示对象本身。它用于打印狗的名字和 "woof!"。这个方法是对象特定的,每个对象都可以调用它。
  2. 类方法 (display_num_dogs):这是一个类方法,它使用 @classmethod 装饰器进行装饰,并接受 cls 参数,表示类本身。该方法用于访问和操作类变量 num_dogs,并打印出当前创建的狗的数量。这个方法是针对整个类的,可以通过类名调用它,也可以通过对象调用。
  3. 静态方法 (sleep):这是一个静态方法,它使用 @staticmethod 装饰器进行装饰,不接受类或对象作为参数。它是与类相关联的功能性函数,与类的实例无关。这个方法可以通过类名或对象调用。
  4. 魔术方法 (__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

这个表格和示例代码展示了成员变量、私有变量、成员方法、构造方法和析构方法的使用方式,以及类设计中的一些原则。通过实际操作和运行这些代码,可以加深对这些概念的理解。

插件的step1

找到插件存放的位置

作为资深用户,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

编写init

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的插件列表中刷新,就可以获取自己插件的最新版本。

作业

ver 1

:帮我做一下作业,这是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中运行)

ver 2

:看起来还不错,但我觉得扩展性有点差了,能不能更方便的更改所有的信息呢?

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中正确运行了,更加符合作业要求。

ver 3

:在blender中,mesh object 有很多种特性的物体对吧?比如圆柱体,正方形,球体,等等。如果我都想通过这个class meshObject 定义出来,好像这个代码弹性依旧不够?

chatGPT:

你说得对。如果你想使用 MeshObject 类来定义各种不同特性的网格对象,你可能需要更灵活的方法来实现这一点。一种方法是使用子类化(继承)的方式来扩展 MeshObject 类,以便针对不同类型的网格对象添加特定的属性和方法。让我们来看看如何实现这一点: 在这个例子中,我们创建了两个子类 CubeObjectSphereObject 分别用于表示立方体和球体。每个子类通过调用父类 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中运行的,运行方式:

  1. 打开blender 4.1. 将窗口分割为4部分:

    1. 3D viewport(3D视图窗口,也就是默认窗口),
    2. info(整个blender的日志窗口)
    3. python console( 虽然排不上用场,但我喜欢开着)
    4. text editor (编辑器,可以理解成内置运行Python的IDE)
  2. 把上面的代码贴到text editor中,点击Run Scriptinfo 窗口会记录操作信息,3D viewport 会出现1个cube 和 1个sphere。

  3. 顶部菜单中Window -> Toggle System Console 可以打开一个类似CMD的悬浮窗口,这是默认隐藏的,它的作用是查看上面代码中print()打印的内容,方便调试。

有很多看不太懂,已经需要学习了。

详解 ver 3

:谢谢,你现在是一位python专家和blender API专家,我想就上面的代码中涉及具体实现的部分向你请教,希望你一步步的推理告诉我,请确保内容真实无误:

  1. 每一句起到的作用是什么?
  2. 为什么采用这个方法?

chatGPT:

对于 CubeObject 类中的 __init__ 方法:

  1. 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 提供的一个方便创建基本几何体的函数,使用它可以快速创建出一个具有标准尺寸和位置的立方体网格对象。

  2. obj = bpy.context.object:这一句获取了刚刚创建的立方体对象,并将其赋值给变量 obj

    为什么采用这个方法?因为在调用 bpy.ops.mesh.primitive_cube_add 函数后,Blender 会自动选中新创建的立方体对象,所以我们可以通过 bpy.context.object 来获取到当前选中的对象。

  3. vertices = [(v.co.x, v.co.y, v.co.z) for v in obj.data.vertices]:这一句遍历了立方体对象的顶点,并将每个顶点的坐标 (x, y, z) 添加到 vertices 列表中。

    为什么采用这个方法?因为立方体对象的顶点坐标保存在其数据块(obj.data)的 vertices 属性中,我们可以通过遍历这些顶点来获取它们的坐标。

  4. edges = [(e.vertices[0], e.vertices[1]) for e in obj.data.edges]:这一句遍历了立方体对象的边,并将每条边的顶点索引 (v1, v2) 添加到 edges 列表中。

    为什么采用这个方法?因为立方体对象的边由两个顶点索引组成,保存在其数据块的 edges 属性中,我们可以通过遍历这些边来获取它们的顶点索引。

  5. 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)
  1. vertices (顶点)和edges(边) 都是 list 里面套tuple

  2. edges 没有出现8,是因为0-7 指代的8个vertices,(2,0) 的意思是,第2个顶点指向第0个顶点。即:顶点(-1.0, 1.0, -1.0) 指向→顶点(-1.0, -1.0, -1.0)。这是一种基于拓扑的索引表示法,不需要直接存储顶点的坐标。这样做的好处是节省了内存空间,并且更容易处理网格对象的几何结构。

    chatGPT:在Blender 中,网格对象的边数据就是以这种形式存储的,通过顶点索引,可以唯一确定一条边连接的两个顶点。

  3. 在 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)
  1. 在Blender中,bpy.ops.mesh.primitive_uv_sphere_add 函数创建的球体并不是以简单的顶点和边的形式表示的,而是以更复杂的方式进行表示,这就解释了为什么你在打印出的顶点和边的数量会有这么多。

    这个函数创建的球体实际上是由许多个三角形面构成的,而不是简单的顶点和边。每个面由三个顶点组成,而每个顶点都在球体的表面上。所以打印出的顶点数量会非常多,因为这些顶点是用于构成球体表面的。

    另外,因为球体是一个连续的曲面,而不是由直线边界分割的平面,所以它看起来并不像立方体那样清晰可见.

  2. 在 Blender 的编辑模式中,你可能只看到了一个简单的球体,因为 Blender 在编辑模式下会以一种更简单的方式来显示网格,以便于编辑。在编辑模式下,Blender 会显示一个低多边形的近似网格,以提高性能和交互性。

    然而,在内部数据中,球体实际上是由更多的顶点和面构成的。当你通过 bpy.ops.mesh.primitive_uv_sphere_add 创建球体时,Blender 默认会使用相对较高的分辨率来表示球体的表面,以确保球体的外观足够圆滑。这就解释了为什么打印出的顶点和边的数量会很多。

    虽然在编辑模式下只显示了少量的面,但是这些面实际上是球体表面的一部分,并且是由更多的顶点和边构成的。因此,尽管在编辑模式下看起来简单,但球体的内部数据实际上是复杂的。

  3. 如果你想创建更简单的球体,可以使用 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了,所以默认的还是不错的。

ver4

:这一部分好像有些难懂,我希望给出更有可读性,即便代码变得更多了,但是结构上对新手更友好的代码形式。

	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]

ver 5

:我的新问题是,这个代码可以进一步简化吗?甚至MeshObject部分采用更多bpy的功能来实现?

chatGPT:

当然可以进一步简化代码,特别是在创建 CubeObject 和 SphereObject 时可以利用更多 Blender 提供的功能来减少代码量。下面是进一步简化后的代码:

在这个简化后的版本中,我们在 CubeObjectSphereObject 中去除了直接处理网格数据的代码,而是将 get_verticesget_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:

你是对的,这两种写法的结果确实是相同的。但是,在实际开发中,尤其是对于更复杂的场景和更多的功能,将获取数据的逻辑封装到类的方法中有几个好处:

  1. 封装性和抽象性: 通过将获取数据的逻辑封装到类的方法中,可以提高代码的封装性和抽象性。这样,其他开发者在阅读和理解代码时,可以更清晰地知道这些方法的作用和用途,而不需要深入了解具体的实现细节。
  2. 可维护性和可扩展性: 如果后续需要对获取数据的逻辑进行修改或者添加新的功能,只需要修改对应类的方法即可,而不需要修改每个调用该方法的地方。这样可以提高代码的可维护性和可扩展性,减少了代码修改的风险和成本。
  3. 代码重用性: 如果在其他地方也需要获取相同类型的数据,可以直接调用类的方法,而不需要重复编写相同的代码。这样可以提高代码的重用性,减少了代码冗余和重复劳动。

虽然在当前的例子中,这两种写法的结果相同,并且可能没有明显的区别,但是采用第二种写法可以使代码更具有扩展性和可维护性,尤其是在实际开发中面对更复杂的场景和更多的功能时。

作业总结

  1. 获得一些关于blender中点和边的存在逻辑的理解。
  2. 复习了一些python味儿的代码,找到一点熟悉感。
  3. 对类的使用和存在有一个初步的理解
  4. blender中运行python脚本的基础用法走通。