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
- Sweep(扫掠):
- 定义: 沿着路径的方向创建一个截面的过程。可以想象成在一个轨迹上滑动一个截面,形成一个扫掠体。
- 操作: 选择一个截面(形状),然后定义一个路径,软件会沿着路径创建一个与截面相切的三维形体。
- Extrude(挤压):
- 定义: 沿着指定方向拉伸或压缩一个平面形状,以创建一个具有一定深度的三维对象。
- 操作: 选择一个平面形状,然后指定拉伸或压缩的方向和距离,软件将生成一个沿该方向伸展的三维体。
- Loft(放样):
- 定义: 使用两个或多个截面之间的过渡形成一个平滑的曲面或体积。
- 操作: 选择两个或多个截面,软件会在它们之间创建一个平滑的过渡形状,可以是曲面或实体。
:::
Selectors 选择器
选择器允许您选择一个或多个特征,以便定义新特征。例如,你可以挤出一个盒子,然后选择顶面作为新特征的位置。或者,你可以挤出一个盒子,然后选择所有的垂直边缘,这样就可以对它们应用圆角。
您可以使用选择器选择 Vertices
顶点、Edges
边、Faces
面、Solids
实体和 Wires
线。
如果您使用传统 CAD 系统构建一个对象,选择器就相当于您的手和鼠标。
了解更多信息,请参阅 Selectors
Construction Geometry 构造几何
构造几何体是不属于对象的一部分的特征,其定义只是为了帮助构建对象。一个常见的例子可能是定义一个矩形,然后用矩形的角来定义一组孔的位置。
大多数 CadQuery 构造方法都提供一个forConstruction
关键字,该关键字创建的特征仅用于定位其他特征。
The Stack 堆栈
当您使用 CadQuery 时,每个操作的结果都会返回一个新的 Workplane 对象。每个工作平面对象都有一个对象列表以及对其父对象的引用。
您可以通过从堆栈中移除当前对象,返回到以前的操作。例如
scss
cq.Workplane(someObject).faces(">Z").first().vertices()
返回一个 CadQuery 对象,其中包含 someObject 对象最高面上的所有顶点。但您始终可以在堆栈中向后移动以获取面:
scss
cq.Workplane(someObject).faces(">Z").first().vertices().end()
您可以在此处浏览堆栈访问方法:Stack and Selector Methods
Chaining 链式
所有 Workplane 方法都会返回另一个 Workplane 对象,以便您可以将这些方法流畅地链接在一起。使用核心工作平面方法来获取创建的对象。
在这些链式调用过程中,每次生成一个新的 Workplane 对象时,它都有一个 parent
属性指向创建它的 Workplane 对象。多个 CadQuery 方法搜索此父链,例如在搜索上下文实体时。您还可以给Workplane 对象一个标签,并且在调用链的下游,您可以使用标签引用该特定对象。
ini
# 定义一个工作平面并设置标签
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 会观察你的操作,并将创建的第一个实体对象定义为 "上下文实体"。之后,您创建的任何特征都会自动与该实体相结合(除非您另行指定)。即使实体是在堆栈的较远处创建的,也会发生这种情况。例如:
scss
cq.Workplane("XY").box(1, 2, 3).faces(">Z").circle(0.25).extrude(1)
将创建一个 1x2x3 的盒子,顶面延伸出一个圆柱形凸台。无需手动组合通过长方体挤压出来的圆,因为挤出的默认行为是将结果与上下文实体结合起来。hole() 方法的工作原理类似 - CadQuery 假定您要从上下文实体中减去孔。
如果您想避免这种情况,可以指定combine=False
,CadQuery 将单独创建实体。
scss
cq.Workplane("XY").box(1, 2, 3).faces(">Z").circle(0.25).extrude(1,combine = True)
Iteration 迭代
CAD 模型经常有重复的几何图形,采用 for 循环来构建特征确实很烦人。许多 CadQuery 方法会自动对堆栈中的每个元素进行操作,因此您不必编写循环。例如:
scss
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 个应用程序接口是在彼此之上实现的。
- The Fluent API
- The Direct API
- The OCCT API
The Fluent API
我们所说的 Fluent API 就是您第一次开始使用 CadQuery 时所使用的,该类Workplane
及其所有方法定义了 Fluent API。这是您在大多数情况下都会使用和看到的 API,它相当容易使用,并且为您简化了很多事情。一个经典的例子是:
scss
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 编写的代码通常如下所示:
scss
part = cq.Workplane("XY").box(1, 2, 3).faces(">Z").vertices().circle(0.5).cutThruAll()
或者像这样:
ini
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 个拓扑类别是:
每个类都有自己的方法来创建和/或编辑各自类型的形状。正如 概念 中已经解释的那样,拓扑类中也存在某种分层结构。一个线由多个边组成,这些边本身又由多个顶点组成。这意味着您可以自下而上创建几何图形并对其进行大量控制。
例如我们可以像这样创建一个圆形面
scss
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
是最抽象的拓扑类。拓扑类继承Mixin3D
或Mixin1D
,这两个类提供了额外的方法,这些方法可以在继承他们的类之间共享。 ::: 顾名思义,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++ 库有深入的了解才能实现您想要的目标。要获得这些知识,最有效的方法是:
- 阅读 Direct API 源代码,因为它是基于 OCCT API 构建的,有很多用法示例。
- 浏览 C++ 文档
::: primary Note 导入 OCCT API 的特定类的一般方法是
javascript
from OCP.thePackageName import theClassName
例如,如果您想使用 BRepPrimAPI_MakeBox 类。您将通过以下方式
javascript
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
任何类的包名称都写在文档页面的顶部。通常它作为前缀写在类名本身中。 :::
在 API 之间来回切换
虽然 3 个 API 提供了 3 个不同的复杂性和功能层,您可以随意混合这 3 层。下面介绍了与不同 API 层交互的不同方式。
Fluent API <=> Direct API
以下是您从 Direct API 获取对象(即拓扑对象)的所有可能性。
您可以结束 Fluent API 调用链并使用 Workplane.val()
获取堆栈上的最后一个对象,或者您也可以使用 Workplane.vals()
获取所有对象
python
>>> 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()
:
scss
>>> 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
对象。
ini
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()
添加一个拓扑对象作为一个新的操作或步骤:
ini
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 对象:
python
>>> 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 对象,只需将其作为目标类的参数传递即可:
python
>>> 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
,Plane
和CQContext
类定义更详细的字符串表示形式,并对其进行修补:
python
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
现在,我们可以制作一个简单的部件,并检查每个步骤中的 Workplane
和 CQContext
对象。最终的部件看起来像这样:
less
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 模式调试,逐步查看对象
:::
我们的调用链的开始是:
scss
part = cq.Workplane()
print(part)
产生输出:
yaml
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)
行:
ini
part = part.box(1, 1, 1)
产生输出:
yaml
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
:
ini
part = part.tag("base")
现在的字符串表示形式part
是:
yaml
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()
方法给出的字符串名称 base 与 Workplane
相关联。workplaneFromTagged()
方法 以及 选择器方法中的edges()
可以操作被标记的 Workplane
。 请注意,与part = part.box(1, 1, 1)
步骤不同,该步骤我们从!!#ff0000 Workplane object at 0x2760
到 Workplane object at 0xaa90
创建了新的对象,tag()
方法返回了相同的对象 0xaa90
,并未创建新的对象。这对于 Workplane
方法来说是一个特殊的情况,和下文中的toPending
一样。
下一步是:
ini
part = part.faces(">>Z")
输出是:
yaml
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>}
我们的选择器方法从上一个 Workplane
的 objects
列表中取出Solid
,找到中心在 Z 方向最远的面,并将该面放入 objects
属性中。 当前Solid
代表z方向最远的面,我们建模的盒子已经消失了,因为当一个Workplane
方法需要访问实体时,它会在父链中搜索最近的实体。该动作也可以由用户通过该 findSolid()
方法来完成。
现在我们想要选择这个 Face
(a Wire
) 的边界,我们可以这样:
ini
part = part.wires()
现在的输出是:
yaml
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()
命令,我们需要将这条线推送到建模上下文:
ini
part = part.toPending()
现在我们有:
yaml
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
中的线:
ini
part = part.translate((0.1, 0.1, 1.0))
现在的 part
字符串表示形式如下所示:
yaml
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
对象是不同。为了将此线放入待处理线列表中,我们再次使用:
ini
part = part.toPending()
结果:
yaml
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所需要的两条线,我们只需调用:
ini
part = part.loft()
::: primary
Loft 阁楼
在3D建模领域,LOFT是一种建模技术,它可以通过将一个或多个几何体的曲面组合在一起来创建新的曲面。LOFT命令通常用于创建复杂的几何形状,例如汽车、飞机和建筑等。 LOFT命令的工作原理是将一个或多个几何体的表面作为基底曲面,然后通过对这些曲面进行剪切、旋转、缩放等操作,将它们组合成一个新的曲面。这个新的曲面可以是任何形状,包括曲线、螺旋、球体等等。 LOFT命令在3D建模中非常常见,特别是在建筑和工业设计领域中。它可以帮助设计师快速创建复杂的几何形状,同时还可以提高建模效率和精度。
:::
loft 操作后,我们的 Workplane 看起来完全不同:
yaml
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()
列表,在本例中将是单个对象。例如:Solid
Compound
Solid
:::
ini
>>> a_compound = part.findSolid()
>>> a_list_of_solids = a_compound.Solids()
>>> len(a_list_of_solids)
1
现在,我们将在原始盒子的一个面上创建一个突出的小圆柱体。我们需要设置一个工作平面,在上面画一个圆,因此首先我们将选择正确的面:
ini
part = part.faces(">>X", tag="base")
结果是:
yaml
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()
方法创建新平面:
ini
part = part.workplane()
现在:
yaml
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 方向上偏移。
在这个平面上,我们可以画出一个圆:
ini
part = part.circle(0.2)
现在:
yaml
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
方法)。
下一步是挤压这个圆并创建一个圆柱形突起:
ini
part = part.extrude(1, clean=False)
现在:
yaml
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 装配
简单的模型可以组合成复杂的、可能嵌套的组件。
一个简单的例子如下:
ini
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)
结果是:
请注意,子部件的位置是相对于其父部件定义的 - 在上面的示例中,子部件的位置 part3
将位于全局坐标系中的 (-5,-5,20)。可以通过这种方式创建具有不同颜色的组件,并将其导出为 STEP 或本机 OCCT xml 格式。
您可以在此处浏览与装配相关的方法:Assemblies。
Assemblies with constraints 带约束的装配体
::: primary Note
下边的代码会调用 .solve()
, 在 下载的 CQ-editor.exe 中执行,会报错 # Plugin 'ipopt' is not found
,暂时没找到原因,不过下载官方的编辑器的源代码 ,基于源代码生成的 CQ-editor 是没有这个问题的
:::
有时,不需要明确定义元件位置,而是使用约束来获得完全参数化的装配。这可以通过以下方式实现:
less
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)
该代码生成的对象与上一节中的对象完全相同。这样做的另一个好处是,随着参数 w
、d
、h
的变化,最终位置将自动计算出来。不可否认它很密集,可以使用标签使其更加清晰。构造约束时可以直接引用标签:
less
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。