likes
comments
collection
share

06.概念 - CadQuery 中文文档

作者站长头像
站长
· 阅读数 4

3D BREP 拓扑概念

在讨论 CadQuery 之前,有必要先讨论一下 3D CAD 拓扑。CadQuery 基于 OpenCascade 内核,该内核对对象使用边界表示 (BREP)。这就意味着,对象是由其封闭表面定义的。

::: primary Note

Boundary Representations(边界表示)是一种用于描述物体表面的数据格式,它通常用于计算机辅助设计(CAD)和计算机辅助制造(CAM)领域。边界表示通常用于表示三维物体的几何形状、尺寸和属性等信息,这些信息可以被其他应用程序读取和使用。边界表示格式通常包括点、线、面、曲线和曲面等基本元素,以及这些元素之间的关系和约束。边界表示格式的主要优点是它能够支持复杂的几何形状,并且可以在不同的CAD软件之间进行高效的数据交换。常见的边界表示格式包括STL、IGES、STEP和JT等。

:::

在 BREP 系统中工作时,存在这些基本结构来定义形状(沿食物链向上工作):

顶点空间中的一个点
边缘沿特定路径(称为曲线)的连接两个或多个顶点
线连接在一起的边的集合
一组边或线封闭而成
外壳沿边缘连接在一起的面的集合
实心体具有封闭内部的外壳
复合体一组实心体的集合

使用 CadQuery 时,所有这些对象都会创建,希望能尽量减少工作量。在实际的 CAD 内核中,还涉及另一套几何结构。例如,弧形边会保存一条全圆曲线的底层参考信息,而每条线性边的下方都会保存一条直线的等式。CadQuery 可使您免受这些结构的影响。

Workplane Class 工作平面类

Workplane 类包含当前选定的对象(形状列表、在objects属性中的向量或位置)、建模上下文( 在ctx属性中)以及 CadQuery 的流畅 api 方法。它是用户将实例化的主类。

了解更多信息,请参阅 API 参考

Workplanes 工作平面

大多数 CAD 程序都使用工作平面的概念。如果您有使用其他 CAD 程序的经验,您可能会对 CadQuery 的工作平面感到得心应手,但如果你没有经验,那么它们就是你必须了解的基本概念。

工作平面代表空间中的一个平面,从这个平面可以定位其他特征。工作平面有一个中心点和一个本地坐标系。大多数创建对象的方法都是相对于当前工作平面创建对象的。

通常创建的第一个工作平面是 "XY "平面,也称为 "front"平面。定义好实体后,创建工作平面最常用的方法是在实体上选择一个要修改的面,然后相对于该面创建一个新的工作平面。您也可以在世界坐标系的任何位置创建新的工作平面,或使用偏移或旋转来创建相对于其他平面的工作平面。

工作平面最强大的功能是允许你在工作平面坐标系的二维空间中工作,然后 CadQuery 会将这些点从工作平面坐标系转换到世界坐标系,这样你的三维特征就会位于你想要的位置。这使得脚本的创建和维护变得更加容易。

了解更多信息,请参阅 cadquery.Workplane

2D构造

创建工作平面后,您就可以在 2D 环境中工作,然后使用创建的特征来制作 3D 物体。你会发现所有你期望的二维结构--圆、线、弧、镜像、点等。

了解更多信息请参阅 2D Operations

3D构造

您可以直接构建3D原型,如方框、楔形、圆柱和球体。您还可以扫掠、挤压和展平 2D 几何图形以形成 3D 特征。当然基本的原始操作也是可用的。

了解更多信息,请参阅 3D Operations

::: primary Note

  1. Sweep(扫掠):
    • 定义: 沿着路径的方向创建一个截面的过程。可以想象成在一个轨迹上滑动一个截面,形成一个扫掠体。
    • 操作: 选择一个截面(形状),然后定义一个路径,软件会沿着路径创建一个与截面相切的三维形体。
  2. Extrude(挤压):
    • 定义: 沿着指定方向拉伸或压缩一个平面形状,以创建一个具有一定深度的三维对象。
    • 操作: 选择一个平面形状,然后指定拉伸或压缩的方向和距离,软件将生成一个沿该方向伸展的三维体。
  3. Loft(放样):
    • 定义: 使用两个或多个截面之间的过渡形成一个平滑的曲面或体积。
    • 操作: 选择两个或多个截面,软件会在它们之间创建一个平滑的过渡形状,可以是曲面或实体。

:::

Selectors 选择器

选择器允许您选择一个或多个特征,以便定义新特征。例如,你可以挤出一个盒子,然后选择顶面作为新特征的位置。或者,你可以挤出一个盒子,然后选择所有的垂直边缘,这样就可以对它们应用圆角。

您可以使用选择器选择 Vertices顶点、Edges边、Faces面、Solids实体和 Wires线。

如果您使用传统 CAD 系统构建一个对象,选择器就相当于您的手和鼠标。

了解更多信息,请参阅 Selectors

Construction Geometry 构造几何

构造几何体是不属于对象的一部分的特征,其定义只是为了帮助构建对象。一个常见的例子可能是定义一个矩形,然后用矩形的角来定义一组孔的位置。

大多数 CadQuery 构造方法都提供一个forConstruction关键字,该关键字创建的特征仅用于定位其他特征。

The Stack 堆栈

当您使用 CadQuery 时,每个操作的结果都会返回一个新的 Workplane 对象。每个工作平面对象都有一个对象列表以及对其父对象的引用。

您可以通过从堆栈中移除当前对象,返回到以前的操作。例如

cq.Workplane(someObject).faces(">Z").first().vertices()

返回一个 CadQuery 对象,其中包含 someObject 对象最高面上的所有顶点。但您始终可以在堆栈中向后移动以获取面:

cq.Workplane(someObject).faces(">Z").first().vertices().end()

您可以在此处浏览堆栈访问方法:Stack and Selector Methods

Chaining 链式

所有 Workplane 方法都会返回另一个 Workplane 对象,以便您可以将这些方法流畅地链接在一起。使用核心工作平面方法来获取创建的对象。

在这些链式调用过程中,每次生成一个新的 Workplane 对象时,它都有一个 parent 属性指向创建它的 Workplane 对象。多个 CadQuery 方法搜索此父链,例如在搜索上下文实体时。您还可以给Workplane 对象一个标签,并且在调用链的下游,您可以使用标签引用该特定对象。

# 定义一个工作平面并设置标签
result = cq.Workplane("XY").tag("my_workplane")
# 在工作平面上创建一个长方体
result = result.box(1, 1, 1)

# 定义另一个工作平面并设置标签
result = result.faces(">Z").workplane(offset=2).tag("another_workplane")
# 在另一个工作平面上创建一个圆柱体
result = result.circle(0.5).extrude(1)

# 切换回之前定义的工作平面并在其上创建一个球体
result = result.workplaneFromTagged("my_workplane").sphere(0.75)

# 显示结果
show_object(result)

workplaneFromTagged切换回之前定义的工作平面

The Context Solid 上下文实体

大多数情况下,您都是在创建一个单一对象,并为该单一对象添加特征。CadQuery 会观察你的操作,并将创建的第一个实体对象定义为 "上下文实体"。之后,您创建的任何特征都会自动与该实体相结合(除非您另行指定)。即使实体是在堆栈的较远处创建的,也会发生这种情况。例如:

cq.Workplane("XY").box(1, 2, 3).faces(">Z").circle(0.25).extrude(1)

将创建一个 1x2x3 的盒子,顶面延伸出一个圆柱形凸台。无需手动组合通过长方体挤压出来的圆,因为挤出的默认行为是将结果与上下文实体结合起来。hole() 方法的工作原理类似 - CadQuery 假定您要从上下文实体中减去孔。

如果您想避免这种情况,可以指定combine=False,CadQuery 将单独创建实体。

cq.Workplane("XY").box(1, 2, 3).faces(">Z").circle(0.25).extrude(1,combine = True)

Iteration 迭代

CAD 模型经常有重复的几何图形,采用 for 循环来构建特征确实很烦人。许多 CadQuery 方法会自动对堆栈中的每个元素进行操作,因此您不必编写循环。例如:

cq.Workplane("XY").box(1, 2, 3).faces(">Z").vertices().circle(0.5)

实际上会创建 4 个圆,因为vertices()选择了矩形面的 4 个顶点,circle()方法会遍历堆栈中的每个成员。

当您编写自己的插件时,记住这一点非常有用。cadquery.Workplane.each() 可用于遍历对象。

CadQuery API layers

一旦你开始深入研究CadQuery,你可能会发现自己在CadQuery API所能返回的不同类型对象之间有点无所适从。本章旨在对这一主题进行解释,并提供底层实现和内核层的背景知识,以便你可以利用更多的CadQuery功能。

CadQuery 由 3 个不同的应用程序接口(API)组成,这 3 个应用程序接口是在彼此之上实现的。

  1. The Fluent API
  2. The Direct API
  3. The OCCT API

The Fluent API

我们所说的 Fluent API 就是您第一次开始使用 CadQuery 时所使用的,该类Workplane及其所有方法定义了 Fluent API。这是您在大多数情况下都会使用和看到的 API,它相当容易使用,并且为您简化了很多事情。一个经典的例子是:

part = cq.Workplane("XY").box(1, 2, 3).faces(">Z").vertices().circle(0.5).cutThruAll()

在这里,我们创建一个Workplane对象,随后在该对象上调用多个方法来创建我们的零件。一般 Fluent API 将Workplane视为部件对象,并将其所有方法视为影响部件的操作。通常您会从一个空的 Workplane 开始,然后通过调用 Workplane 的方法添加更多特征。

在 CadQuery 代码中使用的传统代码风格,可以很好地看出修改零件操作的这种分层结构。使用 CadQuery Fluent API 编写的代码通常如下所示:

part = cq.Workplane("XY").box(1, 2, 3).faces(">Z").vertices().circle(0.5).cutThruAll()

或者像这样:

part = Workplane("XY")
part = part.box(1, 2, 3)
part = part.faces(">Z")
part = part.vertices()
part = part.circle(0.5)
part = part.cutThruAll()

::: primary Note

虽然第一种代码风格是人们默认的,但重要的是要注意,当您像这样编写代码时,它相当于将其编写在一行上。这样一来,调试就会变得更加困难,因为您无法逐步可视化每个操作,而这是由 CQ-Editor 调试器等提供的功能。

:::

The Direct API

虽然 Fluent API 提供了很多功能,但您可能会发现一些场景需要额的外灵活性或需要使用较低级别对象。

Direct API 是被 Fluent API 调用的底层API。9 个拓扑类及其方法组成了 Direct API。这些类实际上包装了 Open CASCADE Technology (OCCT) 类。9 个拓扑类别是:

  1. Shape
  2. Compound
  3. CompSolid
  4. Solid
  5. Shell
  6. Face
  7. Wire
  8. Edge
  9. Vertex

每个类都有自己的方法来创建和/或编辑各自类型的形状。正如 概念 中已经解释的那样,拓扑类中也存在某种分层结构。一个线由多个边组成,这些边本身又由多个顶点组成。这意味着您可以自下而上创建几何图形并对其进行大量控制。

例如我们可以像这样创建一个圆形面

circle_wire = cq.Wire.makeCircle(10, cq.Vector(0, 0, 0), cq.Vector(0, 0, 1))
circular_face = cq.Face.makeFromWires(circle_wire, [])
show_object(circular_face)

::: primary Note 在CadQuery(和OCCT)中所有的拓扑类都是形状,类Shape是最抽象的拓扑类。拓扑类继承Mixin3DMixin1D,这两个类提供了额外的方法,这些方法可以在继承他们的类之间共享。 ::: 顾名思义,Direct API 不提供父/子数据结构,而是每个方法调用直接返回指定拓扑类型的对象。它比 Fluent API 更冗长,使用起来也更繁琐,但由于它提供了更大的灵活性(您可以使用面,这是在 Fluent API 中无法做到的),因此有时比 Fluent API 更方便。

The OCCT API

最后我们讨论 OCCT API。OCCT API 是 CadQuery 的最低层。Direct API 构建于 OCCT API 之上,其中 CadQuery 中的 OCCT API 通过 OCP 提供。OCP 是 CadQuery 使用的 OCCT C++ 库的 Python 绑定。这意味着您可以访问 Python 和 CadQuery 中的(几乎)所有 OCCT C++ 库。使用 OCCT API 将为您提供更大的灵活性和控制力,但它非常繁琐且难以使用。您需要对不同的 C++ 库有深入的了解才能实现您想要的目标。要获得这些知识,最有效的方法是:

  1. 阅读 Direct API 源代码,因为它是基于 OCCT API 构建的,有很多用法示例。
  2. 浏览 C++ 文档

::: primary Note 导入 OCCT API 的特定类的一般方法是

from OCP.thePackageName import theClassName

例如,如果您想使用 BRepPrimAPI_MakeBox 类。您将通过以下方式

from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox

任何类的包名称都写在文档页面的顶部。通常它作为前缀写在类名本身中。 :::

在 API 之间来回切换

虽然 3 个 API 提供了 3 个不同的复杂性和功能层,您可以随意混合这 3 层。下面介绍了与不同 API 层交互的不同方式。

Fluent API <=> Direct API

以下是您从 Direct API 获取对象(即拓扑对象)的所有可能性。

您可以结束 Fluent API 调用链并使用 Workplane.val() 获取堆栈上的最后一个对象,或者您也可以使用 Workplane.vals() 获取所有对象

>>> box = cq.Workplane().box(10, 5, 5)
>>> print(type(box))
<class cadquery.cq.Workplane>

>>> box = cq.Workplane().box(10, 5, 5).val()
>>> print(type(box))
<class cadquery.occ_impl.shapes.Solid>

如果您只想获取 Workplane 的上下文实体,您可以使用 Workplane.findSolid()

>>> part = cq.Workplane().box(10,5,5).circle(3).val()
>>> print(type(part))
<class cadquery.cq.Wire>

>>> part = cq.Workplane().box(10,5,5).circle(3).findSolid()
>>> print(type(part))
<class cadquery.occ_impl.shapes.Compound>
# findSolid 的返回类型是实体或复合对象

如果您想反其道而行之,即在 Fluent API 中使用拓扑 API 中的对象,您可以选择:

您可以将拓扑对象作为基础对象传递给该Workplane对象。

solid_box = cq.Solid.makeBox(10, 10, 10)
part = cq.Workplane(obj=solid_box)
# 您可以继续使用 Fluent API 建模 
part = part.faces(">Z").circle(1).extrude(10)

您可以在 Fluent API 调用链中,用 Workplane.newObject() 添加一个拓扑对象作为一个新的操作或步骤:

circle_wire = cq.Wire.makeCircle(1, cq.Vector(0, 0, 0), cq.Vector(0, 0, 1))
box = cq.Workplane().box(10, 10, 10).newObject([circle_wire])
# 继续构建模型
box = (
    box.toPending().cutThruAll()
)  
# 注意,如果要在后续操作中使用它,需要调用 `toPending`

Direct API <=> OCCT API

Direct API 的每个对象,都在 wrapped 属性中存储其对应的 OCCT 对象:

>>> box = cq.Solid.makeBox(10,5,5)
>>> print(type(box))
<class cadquery.occ_impl.shapes.Solid>

>>> box = cq.Solid.makeBox(10,5,5).wrapped
>>> print(type(box))
<class OCP.TopoDS.TopoDS_Solid>

如果您想将 OCCT 对象转换为 Direct API 对象,只需将其作为目标类的参数传递即可:

>>> from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
>>> occt_box = BRepPrimAPI_MakeBox(5,5,5).Solid()
>>> print(type(occt_box))
<class OCP.TopoDS.TopoDS_Solid>

>>> direct_api_box = cq.Solid(occt_box)
>>> print(type(direct_api_box))
<class cadquery.occ_impl.shapes.Solid>

::: primary Note

您可以在 Direct API 中使用以下类型 here

:::

Multimethods 多方法

CadQuery 使用 Multimethod 允许根据参数类型调用方法。一个例子是 arc()a_sketch.arc((1,2),(2,3)) 将被分派给一个方法, a_sketch.arc((1, 2), (2, 3), (3, 4)) 将被分派给不同的方法。 为使多方法正常工作,不应使用关键字参数来指定位置参数。例如,您不应该这样写a_sketch.arc(p1=(1, 2), p2=(2, 3), p3=(3, 4)) ,而应该像前面的示例那样。 注意 如果发生调度错误,CadQuery 会尝试退回到第一个注册的多方法,但在 CadQuery 中最佳实践仍然是不使用关键字指定参数位置。

一个自省的例子

::: primary Note

如果你刚刚开始使用 CadQuery,那么你可以稍后再看这个示例。如果你有一些创建 CadQuery 模型的经验,现在又想阅读 CadQuery 源代码以更好地理解代码的作用,那么建议你先阅读本例。

:::

为了演示上述概念,我们可以为 Workplane,PlaneCQContext类定义更详细的字符串表示形式,并对其进行修补:

import cadquery as cq

def tidy_repr(obj):
    """Shortens a default repr string"""
    return repr(obj).split(".")[-1].rstrip(">")

def _ctx_str(self):
    return (
        tidy_repr(self)
        + ":\n"
        + f"    pendingWires: {self.pendingWires}\n"
        + f"    pendingEdges: {self.pendingEdges}\n"
        + f"    tags: {self.tags}"
    )

cq.cq.CQContext.__str__ = _ctx_str

def _plane_str(self):
    return (
        tidy_repr(self)
        + ":\n"
        + f"    origin: {self.origin.toTuple()}\n"
        + f"    z direction: {self.zDir.toTuple()}"
    )

cq.occ_impl.geom.Plane.__str__ = _plane_str

def _wp_str(self):
    out = tidy_repr(self) + ":\n"
    out += f"  parent: {tidy_repr(self.parent)}\n" if self.parent else "  no parent\n"
    out += f"  plane: {self.plane}\n"
    out += f"  objects: {self.objects}\n"
    out += f"  modelling context: {self.ctx}"
    return out

cq.Workplane.__str__ = _wp_str

现在,我们可以制作一个简单的部件,并检查每个步骤中的 WorkplaneCQContext 对象。最终的部件看起来像这样:

part = (
    cq.Workplane()
    .box(1, 1, 1)
    .tag("base")
    .wires(">Z")
    .toPending()
    .translate((0.1, 0.1, 1.0))
    .toPending()
    .loft()
    .faces(">>X", tag="base")
    .workplane(centerOption="CenterOfMass")
    .circle(0.2)
    .extrude(1)
)

::: primary Note

这部分的一些建模过程有些刻意,不是流畅的 CadQuery 技术的典范。

使用 debug 模式调试,逐步查看对象

:::

我们的调用链的开始是:

part = cq.Workplane()
print(part)

产生输出:

Workplane object at 0x2760:
  no parent
  plane: Plane object at 0x2850:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: []
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {}

这只是一个空 Workplane。作为链中的第一个 Workplane ,它没有父级。该plane属性包含一个 Plane描述 XY 平面的对象。

现在我们创建一个简单的盒子。为了简单起见,其余代码将不会再显示print(part)行:

part = part.box(1, 1, 1)

产生输出:

Workplane object at 0xaa90:
  parent: Workplane object at 0x2760
  plane: Plane object at 0x3850:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Solid object at 0xbbe0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {}

首先要注意的是,对比前一个对象,这是一个不同的 Workplane,这个 Workplane 的parent 属性是我们的 前一个 Workplane。返回新的Workplane实例是大多数Workplane方法的正常行为(有一些例外,如下所示),这就是 链式 概念的实现方式。

其次,建模上下文对象与前一个 Workplane中的是相同的,并且该建模上下文 0x2730 将在该链中的每个对象之间共享Workplane。如果我们用 part2 = cq.Workplane() 实例化一个新的 Workplane ,那么将产生一个不同上下文实例对象 CQContext

第三,在我们的对象列表objects中有一个Solid对象,这是我们刚刚创建的盒子。

通常,在创建模型时,您会发现自己想要返回特定的 Workplane对象,也许是因为要选择在之前状态下的特征,或者因为您想要重用之前的平面。标签Tag提供了一种引用前一个 Workplane的方式. 我们可以标记包含这个 box 的 Workplane

part = part.tag("base")

现在的字符串表示形式part是:

Workplane object at 0xaa90:
  parent: Workplane object at 0x2760
  plane: Plane object at 0x3850:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Solid object at 0xbbe0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

建模上下文的属性tags是一个简单的字典,它将 tag() 方法给出的字符串名称 baseWorkplane相关联。workplaneFromTagged()方法 以及 选择器方法中的edges()可以操作被标记的 Workplane。 请注意,与part = part.box(1, 1, 1) 步骤不同,该步骤我们从!!#ff0000 Workplane object at 0x2760Workplane object at 0xaa90创建了新的对象,tag() 方法返回了相同的对象 0xaa90,并未创建新的对象。这对于 Workplane方法来说是一个特殊的情况,和下文中的toPending一样。

下一步是:

part = part.faces(">>Z")

输出是:

Workplane object at 0x8c40:
  parent: Workplane object at 0xaa90
  plane: Plane object at 0xac40:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Face object at 0x3c10>]
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

我们的选择器方法从上一个 Workplaneobjects列表中取出Solid,找到中心在 Z 方向最远的面,并将该面放入 objects 属性中。 当前Solid 代表z方向最远的面,我们建模的盒子已经消失了,因为当一个Workplane方法需要访问实体时,它会在父链中搜索最近的实体。该动作也可以由用户通过该 findSolid() 方法来完成。

现在我们想要选择这个 Face(a Wire) 的边界,我们可以这样:

part = part.wires()

现在的输出是:

Workplane object at 0x6880:
  parent: Workplane object at 0x8c40
  plane: Plane object at 0x38b0:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Wire object at 0xaca0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

建模操作从建模上下文的待处理列表中获取它们的线和边。为了在链中进一步使用 loft()命令,我们需要将这条线推送到建模上下文:

part = part.toPending()

现在我们有:

Workplane object at 0x6880:
  parent: Workplane object at 0x8c40
  plane: Plane object at 0x38b0:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Wire object at 0xaca0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: [<cadquery.occ_impl.shapes.Wire object at 0xaca0>]
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

Wire之前仅存在于objects属性中,现在也存在于建模上下文的pendingWires中。 toPending()方法也是另一种特殊的方法,它返回相同的Workplane对象而不是新的对象。

要在下一步的链中设置 loft() 命令的另一侧,我们通过调用translate平移objects中的线:

part = part.translate((0.1, 0.1, 1.0))

现在的 part 字符串表示形式如下所示:

Workplane object at 0x3a00:
  parent: Workplane object at 0x6880
  plane: Plane object at 0xac70:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Wire object at 0x35e0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: [<cadquery.occ_impl.shapes.Wire object at 0xaca0>]
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

它可能看起来与上一步相似,但objects中的Wire对象是不同。为了将此线放入待处理线列表中,我们再次使用:

part = part.toPending()

结果:

Workplane object at 0x3a00:
  parent: Workplane object at 0x6880
  plane: Plane object at 0xac70:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Wire object at 0x35e0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: [<cadquery.occ_impl.shapes.Wire object at 0xaca0>, <cadquery.occ_impl.shapes.Wire object at 0x7f5c7f5c35e0>]
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

建模上下文的pendingWires属性现在包含了要执行loft所需要的两条线,我们只需调用:

part = part.loft()

::: primary

Loft 阁楼

在3D建模领域,LOFT是一种建模技术,它可以通过将一个或多个几何体的曲面组合在一起来创建新的曲面。LOFT命令通常用于创建复杂的几何形状,例如汽车、飞机和建筑等。 LOFT命令的工作原理是将一个或多个几何体的表面作为基底曲面,然后通过对这些曲面进行剪切、旋转、缩放等操作,将它们组合成一个新的曲面。这个新的曲面可以是任何形状,包括曲线、螺旋、球体等等。 LOFT命令在3D建模中非常常见,特别是在建筑和工业设计领域中。它可以帮助设计师快速创建复杂的几何形状,同时还可以提高建模效率和精度。

:::

loft 操作后,我们的 Workplane 看起来完全不同:

Workplane object at 0x32b0:
  parent: Workplane object at 0x3a00
  plane: Plane object at 0x3d60:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Compound object at 0xad30>]
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

cq.Workplane.objects属性中,我们现在有一个Compound复合对象,并且建模上下文pendingWires已被 loft() 清除。

::: primary Note

要进一步检查Compound对象,您可以使用 val()findSolid()获取该 Compound对象,然后使用返回中包含的对象cadquery.Shape.Solids()列表,在本例中将是单个对象。例如:SolidCompoundSolid

:::

>>> a_compound = part.findSolid()
>>> a_list_of_solids = a_compound.Solids()
>>> len(a_list_of_solids)
1

现在,我们将在原始盒子的一个面上创建一个突出的小圆柱体。我们需要设置一个工作平面,在上面画一个圆,因此首先我们将选择正确的面:

part = part.faces(">>X", tag="base")

结果是:

Workplane object at 0x3f10:
  parent: Workplane object at 0x32b0
  plane: Plane object at 0xefa0:
    origin: (0.0, 0.0, 0.0)
    z direction: (0.0, 0.0, 1.0)
  objects: [<cadquery.occ_impl.shapes.Face object at 0x3af0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

我们在objects属性中找到需要的面 Face,但plane尚未更改。我们使用 Workplane.workplane()方法创建新平面:

part = part.workplane()

现在:

Workplane object at 0xe700:
  parent: Workplane object at 0x3f10
  plane: Plane object at 0xe730:
    origin: (0.5, 0.0, 0.0)
    z direction: (1.0, 0.0, 0.0)
  objects: []
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

列表objects列表已被清除,并且Plane 对象在全局 X 方向上具有局部 Z 方向。由于平面的底面是盒子的侧面,因此原点在 X 方向上偏移。

在这个平面上,我们可以画出一个圆:

part = part.circle(0.2)

现在:

Workplane object at 0xe790:
  parent: Workplane object at 0xe700
  plane: Plane object at 0xaf40:
    origin: (0.5, 0.0, 0.0)
    z direction: (1.0, 0.0, 0.0)
  objects: [<cadquery.occ_impl.shapes.Wire object at 0xe610>]
  modelling context: CQContext object at 0x2730:
    pendingWires: [<cadquery.occ_impl.shapes.Wire object at 0xe610>]
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

circle()方法 - 与所有 2D 绘图方法一样 - 已将圆放入属性objects(在下一个建模步骤中将被清除)和建模上下文的pendingWires中(它将持续存在,直到使用被另一个Workplane方法)。

下一步是挤压这个圆并创建一个圆柱形突起:

part = part.extrude(1, clean=False)

现在:

Workplane object at 0xafd0:
  parent: Workplane object at 0xe790
  plane: Plane object at 0x3e80:
    origin: (0.5, 0.0, 0.0)
    z direction: (1.0, 0.0, 0.0)
  objects: [<cadquery.occ_impl.shapes.Compound object at 0xaaf0>]
  modelling context: CQContext object at 0x2730:
    pendingWires: []
    pendingEdges: []
    tags: {'base': <cadquery.cq.Workplane object at 0xaa90>}

extrude()方法已清除所有pendingWires pendingEdges中待处理的线和边。objects属性中包含在3D 视图中显示的最终的复合对象Compound

::: primary Note

extrude()有一个参数 clean ,默认为 True 。这会挤出待处理的线(创建一个新Workplane对象),然后运行该clean()方法来优化结果,并创建另一个 Workplane。如果您要使用默认值 clean=True 运行该示例,那么您将在 parent 中看到一个中间 Workplane对象,而不是上一步步骤中的对象。

:::

Assemblies 装配

简单的模型可以组合成复杂的、可能嵌套的组件。 06.概念 - CadQuery 中文文档

一个简单的例子如下:

from cadquery import *

w = 10
d = 10
h = 10

part1 = Workplane().box(2 * w, 2 * d, h)
part2 = Workplane().box(w, d, 2 * h)
part3 = Workplane().box(w, d, 3 * h)

assy = (
    Assembly(part1, loc=Location(Vector(-w, 0, h / 2)))
    .add(
        part2, loc=Location(Vector(1.5 * w, -0.5 * d, h / 2)), color=Color(0, 0, 1, 0.5)
    )
    .add(part3, loc=Location(Vector(-0.5 * w, -0.5 * d, 2 * h)), color=Color("red"))
)
show_object(assy)

结果是: 06.概念 - CadQuery 中文文档

请注意,子部件的位置是相对于其父部件定义的 - 在上面的示例中,子部件的位置 part3将位于全局坐标系中的 (-5,-5,20)。可以通过这种方式创建具有不同颜色的组件,并将其导出为 STEP 或本机 OCCT xml 格式。

您可以在此处浏览与装配相关的方法:Assemblies

Assemblies with constraints 带约束的装配体

::: primary Note

下边的代码会调用 .solve() , 在 下载的 CQ-editor.exe 中执行,会报错 # Plugin 'ipopt' is not found,暂时没找到原因,不过下载官方的编辑器的 源代码 ,基于源代码生成的 CQ-editor 是没有这个问题的

:::

有时,不需要明确定义元件位置,而是使用约束来获得完全参数化的装配。这可以通过以下方式实现:

from cadquery import *

w = 10
d = 10
h = 10

part1 = Workplane().box(2 * w, 2 * d, h)
part2 = Workplane().box(w, d, 2 * h)
part3 = Workplane().box(w, d, 3 * h)

assy = (
    Assembly(part1, name="part1", loc=Location(Vector(-w, 0, h / 2)))
    .add(part2, name="part2", color=Color(0, 0, 1, 0.5))
    .add(part3, name="part3", color=Color("red"))
    .constrain("part1@faces@>Z", "part3@faces@<Z", "Axis")
    .constrain("part1@faces@>Z", "part2@faces@<Z", "Axis")
    .constrain("part1@faces@>Y", "part3@faces@<Y", "Axis")
    .constrain("part1@faces@>Y", "part2@faces@<Y", "Axis")
    .constrain("part1@vertices@>(-1,-1,1)", "part3@vertices@>(-1,-1,-1)", "Point")
    .constrain("part1@vertices@>(1,-1,-1)", "part2@vertices@>(-1,-1,-1)", "Point")
    .solve()
)

show_object(assy)

该代码生成的对象与上一节中的对象完全相同。这样做的另一个好处是,随着参数 wdh 的变化,最终位置将自动计算出来。不可否认它很密集,可以使用标签使其更加清晰。构造约束时可以直接引用标签:

from cadquery import *

w = 10
d = 10
h = 10

part1 = Workplane().box(2 * w, 2 * d, h)
part2 = Workplane().box(w, d, 2 * h)
part3 = Workplane().box(w, d, 3 * h)

part1.faces(">Z").edges("<X").vertices("<Y").tag("pt1")
part1.faces(">X").edges("<Z").vertices("<Y").tag("pt2")
part3.faces("<Z").edges("<X").vertices("<Y").tag("pt1")
part2.faces("<X").edges("<Z").vertices("<Y").tag("pt2")

assy1 = (
    Assembly(part1, name="part1", loc=Location(Vector(-w, 0, h / 2)))
    .add(part2, name="part2", color=Color(0, 0, 1, 0.5))
    .add(part3, name="part3", color=Color("red"))
    .constrain("part1@faces@>Z", "part3@faces@<Z", "Axis")
    .constrain("part1@faces@>Z", "part2@faces@<Z", "Axis")
    .constrain("part1@faces@>Y", "part3@faces@<Y", "Axis")
    .constrain("part1@faces@>Y", "part2@faces@<Y", "Axis")
    .constrain("part1?pt1", "part3?pt1", "Point")
    .constrain("part1?pt2", "part2?pt2", "Point")
    .solve()
)

show_object(assy1)

目前实施的制约因素如下:

  • Axis:两个法向量不重合或它们之间的夹角(弧度)等于指定值。可为所有具有一致法向量的实体(平面、线和边)定义。
  • Point:两点重合或相距一定距离。可为所有实体定义,质量中心用于线、面、实体,顶点位置用于顶点。
  • Plane:Axis 和 Point 约束的组合。

有关更详细的装配示例,请参阅 Assemblies

文章来源:CadQuery 中文文档