OCCT 中的可视化模块建立在下面的概念分离上:
表示对象由 Presentation 模块来管理,而选择对象则由 Selection 模块来管理;
Applicaiton Interactive Service (AIS) 正在提供应用 GUI 和这些组建之间的交互,使得这个过程能够更加直观和透明。AIS 模块使用 Interactive Object,即可交互对象来表示一个可显示,可选择实体。除非你需要进行深度定制,否则你一般不需要触及底层的表示与选择组件的对应对象;
如果你真的要进行深度定制, 你需要知道可表示对象和可选择对象的实现原理,具体而言,你需要知道应该实现那些虚函数。首先你需要了解一些更为基础的概念,包括 Sensitive Primitive 以及 Presentable Object。
以下这些包都参与了三维物体的渲染:
以上这些模块也适用于二维物体的渲染。
下面这张图显示了 Visualization 模块的一些重要的概念和组建之间的关系。“几何与拓扑”数据只是 AIS 可以处理的应用数据的一个例子,你可以根据实际应用的需要基于其他形式的数据来支持可视化。
表示模块的一个基础概念是,表示数据与他背后的几何拓扑数据是解耦的,这使得你可以在不改变一个图形的呈现形式的前提下修改图形数据。
要讲一个对象展示到屏幕上需要三类不同的对象的参与:
AIS_InteractiveObject
AIS_InteractiveContext
接下来我们一一阐述。
可表示对象的主要目标是一 Graphic3d_Structure
的形式来提供一个对象的图形化表示数据。在初次展示请求时,可表示对象会调用合适的算法来创建这些结构数据。
StdPrs
和 Prs3d
报提供了标注的可视化算法。你也可以创建自己的算法实现,来创建对应的表示结构数据(来自 Graphic3d 包)。你也可以为单个可表示对象创建多个表示结构数据,每个对应一种显示模式。
每个需要被展示的对象都要是 presenable 的,或者是与一个可展示对象关联。
Viewer 允许以交互式的方式来操作对象。当你缩放、平移或者旋转视角时 Viewer 会操作可表示对象创建的 Graphic 结构数据,而不是直接操作应用的数据层对象。创建 Graphic3d 包中的定义的结构数据对象可以使得你能够直接应用 Viewer 提供的变换机制。
可交互上下文为整个渲染过程提供了一些通用的高层 API。 当应用需要显示某个对象时,上下文会请求这一对象的表示数据结构,并发往 Viewer 进行渲染。
Presentation 至少涉及到 AIS, PrsMgr, StdPrs 以及 V3d 等子包。如果你需要实现自己的渲染算法,你可能还需要涉及到 Prs3d 和 Graphic3d 这两个包;
Prs3d_Drawer
来来控制表示对象的属性,包括颜色,线型,厚度等;PrsMgr_Presentation
,PrsMgr_PresentableObject
以及 concrete 类 PrsMgr_PresentationManager
Graphic3d_GraphicDriver
,用于同底层图形 API(如 OpenGL)的沟通;1 | Handle(V3d_Viewer) theViewer; |
我们通过 BRepPrimAPI_MakeWedge
工具类来创建了一个几何形状,然后调用 Display
进行展示,在 SDK 的内部,交互上下文会调用可表示对象(即 AIS_Shape
) 的 Compute
方法来计算出渲染数据,并将数据转交给 Viewer。这个流程如下:
标准的 OCCT 选择算法由两个部分组成:静态和动态。动态选择会导致数据在经过对应的对象上方时被高亮,而静态的选择会让对象可以被选中,从而参与后续的过程。
选择分为三种类型:
在 OCCT 的选择算法中,所有的可选择对象都由一些列的敏感区域(Sensitive Zones)表示,也被称为 Sensitive entities。当鼠标移动时,这些敏感区域会被分析用作碰撞检测。
这个部分介绍一些在后续的算法描述中涉及的基本的术语和标记。
敏感实体与实体所有者一样,是对象与选择机制之间的链接。
实体的目的是特别定义对象的哪些部分将可被选择。因此,任何意图可被选择的对象都必须被拆分成敏感实体(一个或多个)。例如,要对一个对象应用面选择,就必须将其拆分成面,并使用这些面来创建一个敏感实体集。
根据用户的不同需要,敏感实体可以是原子化的(点或者线条),也可以是复杂的。敏感实体被用作选择算法的内部单元,并且其不包含任何拓扑信息,他们连接到上层接口来获取这些拓扑信息。
每个敏感实体 Select3D_SensitiveEntity
存储了一个指向其所有者 SelectMgr_EntityOwner
的引用,这个类将一个实体和对应的可选择对象(SelectMgr_SelectableObject
)关联起来。另外,拥有者可以存储一些额外的信息。例如,敏感实体的图谱形状,高亮颜色,选中状态等。
为了简化不同的选择模式的处理,敏感实体被组成集合,即 SelectMgr_Selection
,每个选择集合包含了某个特定的选择模式下的敏感实体内容以及对应的设置与状态。
可选择对象(SelectMgr_SelectableObject
, AIS_InteractiveObject
)存储所有创建出来的选择集合和敏感实体的信息。
所有可选择对象的子类都需要实现预先定义的接口以将表示数据根据选择模式拆解成敏感实体。这些计算出来的敏感实体被组成成一个选择集合,并加入到此可选择对象的选择集合列表中。仅有可选择对象被永久销毁时,这些选择集合相关的数据才会被删除。
对于标准的 OCCT 可交互对象,选择模式 0 表示选中整个对象(这些行为可以在子类实现中被修改)。例如 AIS_Shape
对象定义下面的选择模式(见 AIS_Shape::SelectionMode()
:
AIS_Shape
)TopAbs_VERTEX
)TopAbs_EDGE
)TopAbs_WIRE
)TopAbs_FACE
)TopAbs_SHELL
)TopAbs_SOLID
)
对于每个 OCCT Viewer 都有一个 Viewer 选择器类 SelectMgr_ViewerSelector3d
,它提供了整个选择算法模块的一些高阶 AI,并为每次鼠标点击封装了对象以及敏感实体的处理过程。选择器维护了选择模式的激活状态,启动算法,并且探测将要被拾取的对象,存储结果,并实现了接口来保证选择结构的及时更新。
选择管理器 SelectMgr_SelectionManager
是一个操作所有展示对象的选择集合的高阶 API 提供者。它负责处理所有 Viewer 的选择器,负责对象的选择模式的激活,负责维护每个对象的选择集合的计算。最终奥的任务是保证选择数据的及时更新。
OCCT 的所有三种选择类型都作为一个单一概念实现,基于通过三级 BVH 树遍历搜索视锥与敏感实体之间的重叠。
选择算法每次运行的第一步都是根据当前的选择类型来构建选择视锥。
对于点选或矩形选取,视锥的底面是一个矩形,根据像素容差或用户定义区域的尺寸分别构建。对于多段线选择,由构建线条定义的多边形被三角剖分,每个三角形作为其自己的视锥的底面。因此,这种类型的选择使用一组三角形视锥来进行重叠检测。
视锥的长度由近视和远视视觉体积平面限制,并且每个平面都平行于相应的视觉体积平面建造。
为了在 Viewer 的层级维持选择机制,我们构建了一个 3 层 BVH 树。
第一级树由每个可选择对象的轴对齐边界框构成。因此,这棵树的根包含了所有可选择边界的组合,即使它们当前没有激活的选择。对象在显示AIS_InteractiveObject时被添加,并且只有当对象被销毁时才会从这棵树中移除。第 1 级 BVH 树是在选择算法首次运行时按需构建的。
第二级 BVH 树包含一个可选择对象的所有敏感实体。当默认模式被激活时,第 2 级树会自动构建,并且每当首次为新的选择模式计算时,就会重新构建。
第三级 BVH 树用于包含许多元素的复杂敏感实体,例如:三角剖分、带有许多段的线、点集等。它是按需为拥有超过 800K 子元素的敏感实体构建的(由StdSelect_BRepSelectionTool::PreBuildBVH()
定义)。
选择算法包含预处理阶段和三个主要阶段
包含计算选择视锥及其主要特征数据;
在成功构建选择视锥之后,算法开始遍历对象级 BVH 树。包含轴对齐边界框的节点按照分离轴定理(SAT)的条件测试与选择视锥的重叠。当遍历进行到叶节点时,意味着找到了一个可能包含重叠敏感实体的候选对象。如果没有检测到这样的对象,算法停止,假设没有对象需要被选择。否则,它进入下一个阶段处理找到的可选择对象的实体。
关于轴分离定理:
SAT 代表“分离轴定理”(Separating Axis Theorem),这是计算机图形学和物理引擎中用于检测两个凸多边形之间是否存在重叠(即碰撞检测)的一个重要方法。根据这个定理,如果能找到一个轴(分离轴),使得将两个多边形在这个轴上的投影完全分开,那么这两个多边形就不相交;反之,如果对于所有可能的分离轴,两个多边形的投影都重叠,则两个多边形相交。这个定理简化了复杂形状之间碰撞检测的计算,使得它成为实现高效碰撞检测算法的关键。
在这个阶段,需要确定在一个对象的所有敏感实体中是否有候选者。
首先,在这个阶段,算法会检查当前对象是否应用了任何变换。如果它有自己的位置,那么相应变换后的视锥将被用于进一步的计算。在下一步,访问给定对象的第二级 BVH 树的节点,以搜索重叠的叶子。如果没有找到这样的叶子,算法返回到上一个阶段。否则,它开始通过执行以下检查来处理找到的实体:
激活检查 - 实体此刻可能是不活跃的,因为它属于被停用的选择; 容差检查 - 当前的选择视锥对于进一步的检查可能太大,因为它总是以所有激活实体中的最大容差构建的;因此,在这一步,视锥可能会被缩放。
经过这些检查后,算法进入最后阶段。
如果实体是原子的,将执行一个简单的 SAT 测试。在复杂实体的情况下,将遍历第三级 BVH 树。分析匹配的敏感实体的定量特征(如深度、到几何中心的距离),并应用裁剪平面(如果已设置)。检测结果被存储,算法返回到第一主要阶段。
选择机制的实现涉及到多个包 - SelectBasics, Select3D, SelectMgr, StdSelect
这个包定义了一些非常基础的类型和接口,其中值得一提的是
SelectBasics_PickResult
:存储探测过程的量化结果,例如深度以及距离几何中心的距离;SelectBasics_SelectingVolumeManager
: 与当前选择视锥的接口和交互方式。每个自定义选择实体都必须至少继承 SelectBasics_SensitiveEntity
。
Select3D 包包括了一系列标准的选择实体的定义,例如
这些基础的选择实体都继承了 Select3D_SensitiveEntity
。这包还引入了两个辅助类 Select3D_SensitivePoly
以及Select3D_SensitiveSet
,其中后者是需要第三级 BVH 树的复杂选择实体的基类,前者则描述了任意点集并实现了基础的选择函数(注意,Select3D_SensitivePoly
类的内部并不会进行数据检查,因此,如果你需要自行派生加入自定义实现,你需要满足 SAT 理论以便使用标准的 OCCT 重叠检测方法)。
这个包含用来维护整个选择过程,基于这个目的,此包提供了以下服务:
一下是对主要类的简要描述:
SelectMgr_BaseFrustum
, SelectMgr_Frustum
, SelectMgr_RectangularFrustum
, SelectMgr_TriangularFrustum
and SelectMgr_TriangularFrustumSet
- 这些类和皆苦与选择视锥有关,他们定义了不同的 SAT 测试方法,同时也包含了计算定量特质数值的方法(例如计算深度和距离)SelectMgr_SensitiveEntity
, SelectMgr_Selection
and SelectMgr_SensitiveEntitySet
- 存储和处理敏感实体的数据,SelectMgr_SensitiveEntitySet
实现了第二级 BVH 树的元素集合;SelectMgr_SelectableObject
and SelectMgr_SelectableObjectSet
- 描述了可选择对象。它们也负责存储,计算和移除选择数据。SelectMgr_SelectableObjectSet
提供了第一级 BVH 树的实现;SelectMgr_SelectionManager
- 管理所有可选择对虾干的激活状态,选择数据计算以及 BVH 的更新。这个包包含了一些 SelectMgr
的类和工具来创建选择数据结构,例如
StdSelect_BRepOwner
- 定义了一个敏感实体的拥有者,它连接到拓扑形状以及高亮方法;StdSelect_BRepSelectionTool
- 包含了一些算法实现来将标准 AIS 形状分割成敏感组元;StdSelect_FaceFilter
, StdSelect_EdgeFilter
- 实现选择过滤器;第一个代码片段展示了在自定义交互对象中实现 SelectMgr_SelectableObject::ComputeSelection()
方法的例子。该方法用于计算用户定义的选择模式。假设需要使一个盒子在两种模式下可选择——整个形状(模式 0)和每个边缘(模式 1)。为了选择整个盒子,应用程序可以为交互对象的每个面创建一个敏感组元。在这种情况下,所有组元共享同一个所有者——盒子本身。为了选择盒子的边缘,应用程序必须为每个边缘创建一个敏感组元。这里所有敏感实体不能共享所有者,因为不同的几何组元必须作为选择程序的结果被高亮显示。
1 | void InteractiveBox::ComputeSelection (const Handle(SelectMgr_Selection)& theSel, |
创建选择结构的算法将敏感组元存储在SelectMgr_Selection
实例中。对象的选择列表中的每个SelectMgr_Selection
序列必须对应于特定的选择模式。为了描述对象分解为可选择组元的过程,Select3D
包中提供了一套现成的敏感实体。可以通过从Select3D_SensitiveEntity
继承来定义自定义敏感组元。为了使自定义交互对象可选择或自定义现有对象的选择模式,必须定义实体所有者。它们必须继承SelectMgr_EntityOwner
接口。
任何交互对象的选择结构都在SelectMgr_SelectableObject::ComputeSelection()
方法中创建。下面的示例展示了如何使用标准 OCCT 机制,实现在StdSelect_BRepSelectionTool
中,来计算拓扑形状的不同选择模式。
1 | void MyInteractiveObject::ComputeSelection (const Handle(SelectMgr_Selection)& theSelection, |
StdSelect_BRepSelectionTool
类为计算给定类型(例如,面、顶点、边、线等)的敏感实体提供了一个高级 API,使用来自给定TopoDS_Shape
的拓扑数据。
Open CASCADE Technology 采用的传统方式是每个实体所有者自行高亮显示自己。这种方法有两个缺点:
Graphic3d_Structure
对象,这会导致相当大的内存开销;因此,为了克服这些限制,OCCT 提供了一种替代方式来实现选定表示的高亮显示。采用这种方法,交互对象本身将负责高亮显示,而不是实体所有者。
基于SelectMgr_EntityOwner::IsAutoHilight()
返回值,AIS_InteractiveContext
对象要么使用传统的高亮显示方式(如果IsAutoHilight()
返回 TRUE),要么根据它们的可选择对象将这些所有者分组,最终调用SelectMgr_SelectableObject::HilightSelected()
或SelectMgr_SelectableObject::ClearSelected()
,并传递所有者组作为参数。
因此,应用程序可以派生自己的交互对象并重新定义来自SelectMgr_SelectableObject
的虚拟方法HilightSelected()
、ClearSelected()
和HilightOwnerWithColor()
。SelectMgr_SelectableObject::GetHilightPresentation
和SelectMgr_SelectableObject::GetSelectPresentation
方法可以用来根据用户的需求优化填充选择和高亮表示。
在通过重定义SelectMgr_SelectableObject::ComputeSelection()
方法计算并打包了所有必要的敏感实体以及相应的所有者到SelectMgr_Selection
实例后,需要通过以下步骤在SelectMgr_SelectionManager
中注册准备好的选择数据:
AIS_InteractiveContext
,创建一个交互上下文并在其中显示可选择对象;AIS_InteractiveContext::Load()
方法将可选择对象加载到交互上下文的选择管理器中。如果传递给此方法的选择模式不等于-1,则将调用此选择模式的ComputeSelection()
;AIS_InteractiveContext::Activate()
或AIS_InteractiveContext::Deactivate()
方法激活或停用定义的选择模式。完成这些步骤后,创建的交互上下文的选择管理器将包含给定的对象及其选择实体,它们将参与检测程序。
下面的代码片段展示了上面的步骤,其中还包含了开启选择过程并解析选择结果的流程:
1 | // Suppose there is an instance of class InteractiveBox from the previous sample. |
另外需要提及的一个重点是,OCCT 的矩形选择支持两种探测模式:
默认情况下 OCCT 会使用第一种,要修改这个默认行为,可以使用下面的方法:
1 | // Assume there is a created interactive context |
应用交互服务允许以简单和透明的方式管理查看器中的表示和动态选择。管理可视化和选择的核心实体是交互上下文(AIS_InteractiveContext)。它连接到主查看器(V3d_Viewer
)。
交互上下文默认在中立点启动,每个可选择对象作为一个整体被选中,但用户可以为特定对象激活局部选择,以使对象的部分可选择。局部/全局选择由为每个显示对象激活的选择模式列表管理,其中 0(默认选择模式)通常意味着全局(整个对象)选择。
交互对象(AIS_InteractiveObject
)是被可视化和选择的实体。你可以使用标准交互对象的类,所有必要的功能已经被编程,或者你可以通过遵循下面描述的一定数量的规则和约定,实现你自己的交互对象类。
交互对象是一个“虚拟”实体,可以被呈现和选择。交互对象可以具有一定数量的特定图形属性,如可视化模式、颜色和材料。当交互对象被可视化时,所需的图形属性从它自己的绘制器(Prs3d_Drawer
)中获取,如果它具有所需的自定义属性,否则从上下文绘制器中获取。
可能需要过滤要选择的实体。因此,有过滤器实体(SelectMgr_Filter
),它们允许精细化动态检测上下文。这些过滤器中的一些只能在中立点使用,其他的只能在局部选择中使用。可以编程自定义过滤器并将它们加载到交互上下文中。
在 AIS 查看器中被可视化和选择的实体是对象(AIS_InteractiveObject
)。它们将模型的底层参考几何连接到其在 AIS 中的图形表示。你可以使用预定义的 OCCT 标准交互对象类,所有必要的功能已经被编程,或者,如果你是高级用户,你可以实现你自己的交互对象类。
一个交互对象可以有任意多个表示。三维表示数据由表示管理器(PrsMgr_PresentationManager
)来管理;
表示数据由一个索引(显示模式)和对表示管理器的引用来表示。默认情况下,交互对象的默认表示索引是 0.
交互对象的表示数据计算由继承自 PrsMgr_PresentableObject::Compute
函数来进行。这个函数由表示管理器在显示或者更新的时候调用。
如果你要创建自己的交互对象,你需要以下面的方式来实现 Compute
函数。
1 | void PackageName_ClassName::Compute (const Handle(PrsMgr_PresentationManager)& thePresentationManager, |
1 | void PackageName_ClassName::Compute (const Handle(Prs3d_Projector)& theProjector, |
视图可以有两种状态:正常模式或计算模式(隐藏线删除模式)。当后者被激活时,视图会查找在正常模式下显示的所有表示,这些表示已被标记为接受 HLR 模式。一个内部机制允许调用交互对象自己的 Compute,即投影器(projector)函数。
按照惯例,交互对象接受或拒绝 HLR 模式的表示。可以通过以下两种方式之一进行此声明:
PrsMgr_TypeOfPresentation3d
的值之一:PrsMgr_TOP_AllView
,PrsMgr_TOP_ProjectorDependent
。PrsMgr_PresentableObject::SetTypeOfPresentation
。AIS_Shape
类是支持 HLR 表示的交互对象的一个例子。HLR 算法的类型存储在形状的Prs3d_Drawer
中。它是Prs3d_TypeOfHLR
枚举的一个值,可以设置为:
Prs3d_TOH_PolyAlgo
,用于基于形状三角化的多边形算法;Prs3d_TOH_Algo
,用于与形状的真实几何工作的精确算法;Prs3d_TOH_NotSet
,如果没有为给定的交互对象实例设置算法类型。用于AIS_Shape
的 HLR 算法类型可以通过调用AIS_Shape::SetTypeOfHLR()
方法来更改。当前HLR算法类型可以使用AIS_Shape::TypeOfHLR()
方法获得。
这些方法从AIS_Shape
的绘制器中获取值。如果在Prs3d_Drawer
中设置的HLR算法类型为Prs3d_TOH_NotSet
,则Prs3d_Drawer
会从AIS_InteractiveContext
的默认绘制器中获取值。因此,可以更改所有新显示的交互对象所使用的默认HLR算法。存储在上下文绘制器中的HLR算法类型的值可以是Prs3d_TOH_Algo
或Prs3d_TOH_PolyAlgo
。多边形算法是默认算法。
AIS 中的交互对象有四种类型:
在每个类别内部还可以使用一个签名(索引形式)来提供更多的特征信息。默认情况下,交互对象具有 NONE
类型,和 0 值签名,如果你需要更改这些属性,你需要重新盯一下下面两个虚函数:
AIS_InteractiveObject::Type
AIS_InteractiveObject::Signature
注意:某些签名值已经被 AIS 内部的标准交互类使用。
交互上下文可以为一组交互对象设定一个默认的表示模式。这个模式可能不被某个给定的对象类接受。因此,要获取关于这个类的信息,需要使用虚拟函数AIS_InteractiveObject::AcceptDisplayMode
。
函数AIS_InteractiveContext::SetDisplayMode
和AIS_InteractiveContext::UnsetDisplayMode
允许为对象设置一个自定义的显示模式,这可以与交互上下文提议的模式不同。
在动态检测中,交互上下文回显的表示默认是已经在屏幕上的表示。
函数AIS_InteractiveObject::SetHilightMode
和AIS_InteractiveObject::UnsetHilightMode
允许指定用于高亮显示的显示模式(所谓的高亮模式),这个模式与对象的活动表示独立有效。无论这个选择是临时的还是最终的都没有关系。
注意,用于高亮检测到的对象和用于高亮选中的对象的是相同的表示(因此是相同的高亮模式),后者用特殊的选择颜色绘制(参考与交互上下文服务相关的部分)。
例如,你想要系统地高亮显示一个形状的线框表示——不管它是在线框表示中可视化还是以阴影方式。因此,你在交互对象的构造器中将高亮模式设置为 0。不要忘记在 Compute 函数中实现这种表示模式。
如果你不希望一个对象受到全视图(FitAll)的影响,你必须声明它为无限的;你可以使用AIS_InteractiveObject::SetInfiniteState
和AIS_InteractiveObject::IsInfinite
函数取消其“无限”的状态。
以一个代表交互对象的类 IShape
为例:
1 | myPk_IShape::myPk_IShape (const TopoDS_Shape& theShape, PrsMgr_TypeOfPresentation theType) |
一个交互对象可以有无限数量的选择模式,每个模式代表一种敏感原语的“分解”。每个原语都有一个所有者(SelectMgr_EntityOwner),它允许识别已检测到的确切交互对象或形状(见选择章节)。
对应于给定模式的敏感原语集合存储在一个选择(SelectMgr_Selection)中。
每种选择模式由一个索引标识。按照惯例,允许我们完整把握交互对象的默认选择模式是模式0。然而,可以在自定义交互对象中使用方法SelectMgr_SelectableObject::setGlobalSelMode()
进行修改。
选择原语(或敏感实体)的计算在一个虚拟函数ComputeSelection
中完成。对于假定具有不同选择模式的每种类型的交互对象,都应实现该函数AIS_InteractiveObject::ComputeSelection
。在选择章节中已给出了关于此机制和实现此函数方式的详细说明。
有一些最广泛使用的交互对象在 OCCT 中的选择模式计算示例——AIS_Shape
(按顶点、按边缘等选择)。要创建具有与AIS_Shape
相同选择行为的新交互对象类——如顶点和边缘——你必须重新定义虚拟函数AIS_InteractiveObject::AcceptShapeDecomposition
。
图形属性管理器,或者说 Prs3d_Drawer
,存储特定交互对象的图形属性以及由交互上下文控制的交互对象的图形属性。
在初始条件下,所有 Drawer 属性都填充了预定义的值,这些值将定义默认的 3D 对象外观。当交互对象被可视化时,所需的图形属性首先从其自有的 Drawer 中取得(如果存在的话),或者如果没有该类型对象的特定 Drawer,则从上下文 Drawer 中取得。
关于图形属性,需记住以下几点:
AIS_InteractiveObject
抽象类中,包括颜色、线条粗细、材质和透明度在内的标准属性被特别优先考虑。因此,有一定数量的虚拟函数,允许对这些属性进行操作。每个新的交互对象类可以重新定义这些函数并改变类的行为。以下虚拟函数提供了颜色、宽度、材料和透明度的设置:
AIS_InteractiveObject::UnsetColor
AIS_InteractiveObject::SetWidth
AIS_InteractiveObject::UnsetWidth
AIS_InteractiveObject::SetMaterial
AIS_InteractiveObject::UnsetMaterial
AIS_InteractiveObject::SetTransparency
AIS_InteractiveObject::UnsetTransparency
这些方法可以作为一种通用方式分配属性的快捷方式,但结果可能不可用。一些交互对象可能根本不实现这些方法,或者只实现它们的一个子集。直接修改由AIS_InteractiveObject::Attributes
返回的Prs3d_Drawer
属性,可以用于更精确和可预测的配置。
重要的是要知道哪些函数可能意味着对象的表示需要重新计算。如果要更新交互对象的表示模式,PrsMgr_PresentableObject
指示这一点的一个标志。可以使用AIS_InteractiveContext
中的Display
和Redisplay
函数更新模式。
注意以下问题
下面的这些函数允许移动一个表示对象的位置而不需要重新计算表示数据或者修原始的形状数据:
AIS_InteractiveContext::SetLocation
AIS_InteractiveContext::ResetLocation
AIS_InteractiveContext::HasLocation
AIS_InteractiveContext::Location
每个交互对象都允许通过 GetOwner
方法来获取 Transient
形式的数据;
AIS_InteractiveObject::SetOwner
AIS_InteractiveObject::HasOwner
AIS_InteractiveObject::GetOwner
这种数据关联不会影响交互对象本身的行为。
由于三维图形坐标的精确度具有有限的分辨率,拓扑对象的元素可以重合,产生“弹出”某些元素覆盖另一些元素的效果。
对于两个或多个交互对象的元素重合的问题,你可以应用多边形偏移。这是一种图形计算偏移,或深度缓冲区偏移,允许你通过修改它们的深度值来排列元素,而不改变它们的坐标。接受这种偏移的图形元素是实心多边形或作为边界线和点显示的多边形。通过设置适当的内部样式,可以将多边形显示为线条或点。
方法AIS_InteractiveObject::SetPolygonOffsets
和AIS_InteractiveContext::SetPolygonOffsets
允许设置多边形偏移。
每个PrsMgr_PresentableObject
都有一个称为 myChildren
的对象列表。对PrsMgr_PresentableObject
的任何变换也会应用到其子对象上。这种层次结构不会传播到Graphic3d级别及以下。
PrsMgr_PresentableObject
将其结合的(根据层次结构)变换下传到Graphic3d_Structure
。结构的材质不受层次结构的影响。
对象层次结构可以通过以下 API 调用来控制:
PrsMgr_PresentableObject::AddChild
;PrsMgr_PresentableObject::RemoveChild
。实例化的概念按如下方式操作对象层次结构:
AIS_ConnectedInteractive
和 AIS_MultipleConnectedInteractive
用于实现这一概念。AIS_ConnectedInteractive
是一个对象实例,它重用连接对象的几何体,但拥有自己的变换和可见性标志。这种连接传递到 OpenGl 级别,即到 OpenGl_Structure
。OpenGl_Structure
只能连接到单个其他结构。
AIS_ConnectedInteractive
通常可以引用任何 AIS_InteractiveObject
。当它引用另一个 AIS_ConnectedInteractive
时,它只是复制引用。
AIS_MultipleConnectedInteractive
表示一个不具有自己的表示的装配体。装配体能够参与对象层次结构,并旨在处理一组分组的实例化对象。就选择而言,它表现为单一对象。因为它位于层次结构中的较高位置,它对所有子元素应用高级变换。
所有 AIS_MultipleConnectedInteractive
都能拥有子装配体。如果一个装配体附加到另一个装配体上,则执行对象实例树的深度复制。
注意,AIS_ConnectedInteractive
不能引用 AIS_MultipleConnectedInteractive
。AIS_ConnectedInteractive
复制原始对象的敏感实体以进行选择,不同于AIS_MultipleConnectedInteractive
重用原始对象的实体。
实例可以通过以下DRAW命令控制:
vconnect
:从输入对象和位置创建并显示AIS_MultipleConnectedInteractive对象。vconnectto
:使用给定位置制作对象的一个实例。vdisconnect
:断开装配体中的所有对象的连接,或通过名称或编号断开对象的连接。vaddconnected
:将对象添加到装配体中。vlistconnected
:列出装配体中的对象。我们来看下面的例子:
1 | pload MODELING VISUALIZATION |
下面是 OpenGL_Structure
表示这些数据结构的方式:
被引用的对象即便没有显示的情况下,实例对象也可以正常工作。实例对象同时还会处理好选择数据的变换。
交互上下文以透明的方式管理一个或多个查看器中交互对象的图形和可选择行为。在前一章中介绍的允许修改交互对象属性的大多数功能,在这里将再次审视。
有一个基本规则需要遵循:对已经被上下文认识的交互对象的修改,必须使用上下文函数来完成。只有当交互对象尚未加载到交互上下文中时,才可以直接调用可用于交互对象的函数。
1 | Handle(AIS_Shape) aShapePrs = new AIS_Shape (theShape); |
也可以这么写
1 | Handle(AIS_Shape) aShapePrs = new AIS_Shape (theShape); |
中立点和局部选择构成了交互上下文的两种操作模式或状态,交互上下文是控制可视化和选择的中心实体。中立点,作为默认模式,允许轻松地可视化和选择已加载到上下文中的交互对象。为特定对象激活局部选择允许选择它们的子部分。
交互对象可以具有一定数量的特定图形属性,如可视化模式、颜色和材料。相应地,交互上下文具有一组图形属性,即Drawer,这是默认对其控制的对象有效的。当交互对象被可视化时,所需的图形属性首先从对象自己的Drawer中获取(如果存在),否则从上下文Drawer中获取。
以下可调整设置允许个性化呈现和选择的行为:
Quantity_NOC_CYAN1
;Quantity_NOC_GREEN
;Quantity_NOC_GRAY80
;所有这些设置都可以通过AIS_InteractiveContext
的适当函数修改。当你改变与上下文有关的图形属性(例如,可视化模式)时,所有没有相应适当属性的交互对象都会被更新。
看下面的例子:
1 | theCtx->Display (theObj1, false); |
PrsMgr_PresentationManager
和SelectMgr_ViewerSelector3d
,管理当前交互对象的展示和选择,与主查看器相关联。
警告!不要在真实代码中使用整数值(如上例所示)- 而应使用相应的枚举代替!每个可展示对象都有支持的显示和选择模式的独立列表;例如,AIS_DisplayMode
枚举只适用于AIS_Shape
展示。
局部选择由索引(选择模式)定义。特定交互对象实现的选择模式及其含义应在该类的文档中检查。例如,参见MeshVS_SelectionModeFlags
,用于MeshVS_Mesh
对象。
AIS_Shape
是最常用的交互对象。它提供了管理对形状构成元素的选择操作的API(选择顶点、边缘、面等)。特定形状类型(TopAbs_ShapeEnum
)的选择模式由方法AIS_Shape::SelectionMode()
返回。
方法AIS_InteractiveContext::Display()
没有选择模式参数激活对象的默认选择模式。方法AIS_InteractiveContext::Activate()
和AIS_InteractiveContext::Deactivate()
激活和停用特定的选择模式。
可以同时激活多个选择模式(但默认的 0 模式用于选择整个对象是排他的 - 它不能与其他模式组合)。可以使用函数AIS_InteractiveContext::ActivatedModes
检索活动模式的列表。
为了定义动态检测的环境,你可以使用标准过滤器类或创建你自己的过滤器类。过滤器查询敏感原语的所有者,以确定它是否具有所需的特质。如果答案是肯定的,它就被保留;如果不是,则被拒绝。
对象的根类是SelectMgr_Filter
。其背后的原理很简单:过滤器测试鼠标位置由选择器检测到的所有者(SelectMgr_EntityOwner
)是否回答OK。如果是,它就被保留,否则就被拒绝。你可以通过实现延迟函数SelectMgr_Filter::IsOk()
来创建一个自定义的过滤器对象类。
在SelectMgr
中,还有组合过滤器(AND过滤器,OR过滤器),允许组合多个过滤器。在交互上下文中,你添加的所有过滤器都存储在一个OR过滤器中(如果至少有一个过滤器回答OK,则回答OK)。
有一些标准过滤器,已经在几个包中实现:
StdSelect_EdgeFilter
– 用于边缘,如线条和圆;StdSelect_FaceFilter
– 用于面,如平面、圆柱和球体;StdSelect_ShapeTypeFilter
– 用于形状类型,如复合体、实体、壳体和线条;AIS_TypeFilter
– 用于交互对象的类型;AIS_SignatureFilter
– 用于交互对象的类型和签名;AIS_AttributeFilter
– 用于交互对象的属性,如颜色和宽度。有几个函数用于操作过滤器:
AIS_InteractiveContext::AddFilter
添加一个作为参数传递的过滤器。AIS_InteractiveContext::RemoveFilter
移除一个作为参数传递的过滤器。AIS_InteractiveContext::RemoveFilters
移除所有现有的过滤器。AIS_InteractiveContext::Filters
获取上下文中活动的过滤器列表。1 | // shading visualization mode, no specific mode, authorization for decomposition into sub-shapes |
动态检测和选择以一种直接的方式实施。只有一些约定和函数需要熟悉:
AIS_InteractiveContext::MoveTo
– 将鼠标位置传递给交互上下文选择器。AIS_InteractiveContext::SelectDetected
– 存储在最后一次MoveTo
时检测到的内容。改变之前选中的对象。根据选择方案,选择被丰富、替换或其他。AIS_InteractiveContext::SelectPoint
/SelectRectangle
/SelectPolygon
– 对点、矩形或周围区域应用选择。改变之前选中的对象。根据选择方案,选择被丰富、替换或其他。检测到的和选中的实体的高亮显示由交互上下文自动管理。高亮颜色是上面处理过的颜色。如果你想自己管理这一部分,你可以断开这种自动模式:
1 | AIS_InteractiveContext::SetAutomaticHilight |
你可以通过移动鼠标来查询交互上下文。以下函数可以使用:
AIS_InteractiveContext::HasDetected
– 检查是否有检测到的实体;AIS_InteractiveContext::DetectedOwner
– 返回(当前高亮显示的)检测到的实体。使用 Select 函数后,你可以探索选择列表。以下函数可以使用:
AIS_InteractiveContext::InitSelected
– 初始化迭代器;AIS_InteractiveContext::MoreSelected
– 检查迭代器是否有效;AIS_InteractiveContext::NextSelected
– 将迭代器移动到下一个位置;AIS_InteractiveContext::SelectedOwner
– 返回当前迭代器位置的实体。所有者对象SelectMgr_EntityOwner
是一个关键对象,用于识别查看器中的可选择实体(由方法AIS_InteractiveContext::DetectedOwner
和AIS_InteractiveContext::SelectedOwner
返回)。交互对象本身可以通过方法SelectMgr_EntityOwner::Selectable
检索,而识别子部分则取决于交互对象的类型。在AIS_Shape
的情况下,(子)形状通过方法StdSelect_BRepOwner::Shape
返回。
1 | for (myAISCtx->InitSelected(); myAISCtx->MoreSelected(); myAISCtx->NextSelected()) |
AIS_InteractiveContext
的 Select*
方法接受一个额外参数指定选择机制,下面的表格阐述了这些机制的含义:
交互对象是可选择和可视的对象,连接图形表示和底层参考几何体。
它们被分为四种类型:
在这些类别内部,还可以通过签名进一步进行特征化。签名提供了进一步特征化的索引。默认情况下,交互对象具有None类型和0的签名(等同于None)。如果你想给你的交互对象指定特定的类型和签名,你必须重新定义两个虚拟方法:Type
和Signature
。
基准元素将构造元素如线、圆、点、三面体、平面三面体、平面和轴线等聚集在一起。
AIS_Point
、AIS_Axis
、AIS_Line
、AIS_Circle
、AIS_Plane
和AIS_Trihedron
具有四种选择模式:
AIS_TrihedronSelectionMode_EntireObject
:选择一个三面体;AIS_TrihedronSelectionMode_Origin
:选择三面体的原点;AIS_TrihedronSelectionMode_Axes
:选择轴线;AIS_TrihedronSelectionMode_MainPlanes
:选择XOY、YOZ、XOZ平面。当你激活其中一种模式时,你可以选择的AIS对象类型包括:
AIS_Point
;AIS_Axis
(及轴线的类型信息);AIS_Plane
(及平面的类型信息)。AIS_PlaneTrihedron
提供三种选择模式:
对于平面和三面体的表示,默认的长度单位是毫米,轴线表示的默认值是10。要修改这些尺寸,你必须暂时恢复对象Drawer。从中取出DatumAspect()
并改变值FirstAxisLength
。最后,重新计算表示。
对象类型包括拓扑形状及形状之间的连接。
AIS_Shape
有两种可视化模式:
AIS_WireFrame
:线框(默认模式)AIS_Shaded
:着色(取决于形状的类型)AIS_ConnectedInteractive
是一个连接到另一个交互对象引用的交互对象,并且位于查看器的其他地方,使得不需要计算表示和选择,而是可以从你的对象引用中推断它们。AIS_MultipleConnectedInteractive
是一个连接到一系列交互对象(也可以是连接的对象;它不需要占用大量内存的表示计算)的对象。
MeshVS_Mesh
是一个表示网格的交互对象,它有一个提供几何信息(节点,元素)的数据源,并且可以使用自定义的表示构建器从源数据构建。
类AIS_ColoredShape
允许对TopoDS_Shape
对象及其子形状使用自定义颜色和线宽。
1 | AIS_ColoredShape aColoredShape = new AIS_ColoredShape (theShape); |
表示类AIS_PointCloud
可用于高效绘制大量任意集合的彩色点。它使用Graphic3d_ArrayOfPoints
将点数据传递到OpenGl图形驱动,以将一组点作为"点精灵"的数组绘制。点数据被打包进顶点缓冲对象以提高性能。
用于绘制点的点标记类型可以作为表示方面被指定。该表示提供了通过可视化点集的边界框进行选择的支持。它支持两种显示/高亮模式:点或边界框。
下面是一个例子:
1 | Handle(Graphic3d_ArrayOfPoints) aPoints = new Graphic3d_ArrayOfPoints (2000, Standard_True); |
关系由一个或多个交互形状和相应的参考几何体上的约束组成。例如,你可能想要将两条边约束在平行关系中。这个约束被视为一个独立的对象,并显示为一个敏感原语。它采取的图形形式是一个带有||符号的垂直箭头,位于两条边之间。
PrsDim
提供以下关系:
PrsDim_ConcentricRelation
PrsDim_FixRelation
PrsDim_IdenticRelation
PrsDim_ParallelRelation
PrsDim_PerpendicularRelation
PrsDim_Relation
PrsDim_SymmetricRelation
PrsDim_TangentRelation
关系列表不是详尽无遗的。
PrsDim
提供了这个包,这表明 Relation 的作用主要是在标注领域。
PrsDim_AngleDimension
PrsDim_Chamf3dDimension
PrsDim_DiameterDimension
PrsDim_DimensionOwner
PrsDim_LengthDimension
PrsDim_OffsetDimension
PrsDim_RadiusDimension
MeshVS_Mesh
是一个表示网格的交互对象。这个对象与AIS_Shape
不同,因为它的几何数据由描述对象节点和元素的数据源MeshVS_DataSource
支持。结果是,你可以提供你自己的数据源。
然而,DataSource不提供任何关于属性的信息,例如节点颜色,但你可以以一种特殊的方式应用它们——通过选择适当的表示构建器。
MeshVS_Mesh
的表示是通过表示构建器MeshVS_PrsBuilder
构建的。你可以在构建器之间选择,以不同的方式表示对象。此外,你可以重新定义基础构建器类并提供你自己的表示构建器。
你可以使用以下方法添加/移除构建器:
1 | MeshVS_Mesh::AddBuilder (const Handle(MeshVS_PrsBuilder)& theBuilder, Standard_Boolean theToTreatAsHilighter); |
MeshVS_Mesh
有一组保留的显示和高亮模式标志。模式值是一个位数,允许选择额外的显示参数并组合以下模式标志,这些模式标志允许以线框、着色和收缩模式显示网格:
1 | MeshVS_DMF_WireFrame |
也可以以下方式来以线框、着色和收缩模式展示拆解后的 Mesh:
1 | MeshVS_DMF_DeformedPrsWireFrame |
下面这些方法代表了不同类型的数据:
1 | MeshVS_DMF_VectorDataPrs |
下面这些方法提供选择和高亮
1 | MeshVS_DMF_SelectionPrs |
MeshVS_DMF_User
则是用户自定义的模式;
下面这些值为表示构造器使用,它们也是选择模式的控制符:
1 | MeshVS_SMF_0D |
例如下面的对象,可以并用来显示 STL 文件格式的对象:
1 | // read the data and create a data source |
MeshVS_NodalColorPrsBuilder
允许表示一个网格,并在其上映射一个颜色缩放纹理。为此,你应该为颜色刻度定义一个颜色映射,将这个映射传递给表示构建器,并为每个节点定义一个范围在0.0到1.0之间的适当值。以下示例展示了你如何做到这一点(检查视图是否已设置为显示纹理):
1 | // assign nodal builder to the mesh |
动态选择通过敏感原语的分解来表示你想要选择的拓扑形状——将被检测和高亮显示的形状的子部分。这些原语的集合由强大的三级 BVH 树选择算法处理。
有关算法的更多细节和使用示例,请参考选择章节。
PHIGS代表“程序化图形标准”(Programmer's Hierarchical Interactive Graphics System)。它是一个三维计算机图形标准,用于描述和控制三维对象和场景的构造和渲染。PHIGS被设计为支持复杂的图形应用程序,提供了一种结构化的、层次化的方法来创建和管理图形数据。
PHIGS提供了一套丰富的图形编程接口,允许开发者定义图形对象(如线、面、体)、组织这些对象成结构,并通过变换(如旋转、缩放和平移)操作这些结构。这些图形结构可以被组织成一个场景图,支持复杂的三维场景表示。
视图映射(View mapping)在PHIGS中指的是将这些三维图形数据从模型空间转换到视口(即屏幕空间或投影空间)的过程,这包括投影变换(如透视投影或正交投影)、视图变换(定义观察者的位置和方向)等步骤。
总的来说,PHIGS方案提供了一个高度结构化和层次化的方法来处理复杂的三维图形数据和场景,虽然它已经被更现代的图形API(如OpenGL和DirectX)所超越,但它在历史上对三维图形标准的发展起到了重要作用。
Graphic3d
包用于在3D查看器中创建3D图形对象。这些被称为结构的对象由原语(如线段、三角形、文本和标记)的组合以及属性(如颜色、透明度、反射、线型、线宽和文本字体)组成。群组是结构中最小的可编辑元素。可以对结构应用变换。结构可以连接起来形成由变换组成的结构树。结构由查看器全局操作。
图形结构可以被:
有类用于:
根是结构层次或结构网络的顶部。父结构的属性会传递给其后代。后代结构的属性不会影响父结构。不支持递归结构网络。
不同类型的原语可以通过以下原语数组来表示:
Graphic3d_ArrayOfPoints
,Graphic3d_ArrayOfPolylines
,Graphic3d_ArrayOfSegments
,Graphic3d_ArrayOfTriangleFans
,Graphic3d_ArrayOfTriangles
,Graphic3d_ArrayOfTriangleStrips
.Graphic3d_ArrayOfPrimitives
是这些原语数组的基类。方法Graphic3d_ArrayOfPrimitives::AddVertex
允许向原语数组添加带有其属性(颜色、法线、纹理坐标)的顶点。你也可以通过顶点索引修改分配给顶点的值或查询这些值。
以下示例展示了如何定义一个点的数组:
1 | // create an array |
如果原语共享相同的顶点(多边形、三角形等),那么你可以将它们定义为顶点数组的索引。方法Graphic3d_ArrayOfPrimitives::AddEdge
允许通过索引定义原语。这个方法在数组的范围[1, VertexNumber()]中添加一个“边”。也可以使用方法Graphic3d_ArrayOfPrimitives::Edge
查询由边定义的顶点。
以下示例展示了如何定义一个三角形数组:
1 | // create an array |
TKOpenGl
工具包使用纹理字体渲染文本标签。Graphic3d
文本原语具有以下特性:
群组的文本属性可以通过Graphic3d_AspectText3d
属性群组来定义。要将任何文本添加到图形结构中,你可以使用以下方法:
1 | void Graphic3d_Group::AddText (const Handle(Graphic3d_Text)& theTextParams, |
如果你不希望文本位置影响Graphic3d
结构的边界,可以传递FALSE
作为theToEvalMinMax
参数。
注意:文本的朝向角度可以通过 Graphic3d_AspectText3d
的属性来设置;
看下面的例子:
1 | // get the group |
Graphic3d_MaterialAspect
定义了以下常见的材料属性:
确定三种反射颜色需要以下项目:
常见材料属性在 Phong 着色模型中使用(Graphic3d_TypeOfShadingModel_Phong
、Graphic3d_TypeOfShadingModel_PhongFacet
和Graphic3d_TypeOfShadingModel_Gouraud
)。在PBR 着色模型(Graphic3d_TypeOfShadingModel_Pbr
和Graphic3d_TypeOfShadingModel_PbrFacet
)中,材料属性由以下Graphic3d_PBRMaterial
属性定义(Graphic3d_MaterialAspect::PBRMaterial()
):
纹理由一个名字来定义。以下三类纹理受到支持:
OCCT可视化核心支持GLSL着色器。自定义着色器可以通过其绘制器属性(Graphic3d方面)分配给通用表示。要在你的应用程序中为特定的AIS_Shape
启用自定义着色器,可以使用以下 API 函数:
1 | // Create shader program |
Aspect
包提供了查看器中图形元素的类:
V3d
包提供了定义 3D 查看器及其附属视图(正交视图、透视视图)的资源。这个包提供了操作屏幕上视图中可视化的任何3D对象的图形场景的命令。
一组高级命令允许分别操作参数和投影结果(旋转、缩放、平移等)以及任何特定视图中的可视化属性(模式、照明、裁剪等)。
V3d
包基本上是一套由查看器前端指令驱动的工具集。这套工具包含了创建和编辑查看器类的方法,例如:
1 | // create a default display connection |
OCCT的V3d_View视图投影和方向由摄像机控制。摄像机计算并提供投影和视图方向矩阵供OpenGL渲染。这允许用户控制所有的投影参数。摄像机由以下属性定义:
最常见的视图操作(平移、缩放、旋转)作为V3d_View类的便利方法或通过AIS_ViewController工具实现。然而,Graphic3d_Camera类也可以直接由应用程序开发者使用。
]]>我们可以通过 find
+ ln
来批量创建硬链接。要结合使用 find
和 ln
命令来为给定目录中的每个文件在另一个目录中创建硬链接,可以通过管道(pipe)将 find
命令的输出传递给 xargs
命令,然后使用 ln
创建硬链接。这种方法可以高效地处理大量文件和复杂的文件名。
以下是具体的步骤和命令示例:
使用 find
查找文件:首先,使用 find
命令来查找您想要链接的所有文件。确保只选取文件,不包括目录。
将 find
的输出传递给 ln
命令:通过 xargs
将 find
命令的输出作为 ln
命令的输入。
这是一个具体的命令示例:
1 | find /path/to/source-directory -type f -exec ln {} /path/to/destination-directory/ \; |
这个命令解释如下:
find /path/to/source-directory
:在 /path/to/source-directory
目录下查找文件。-type f
:确保只选取文件(file),不包括目录。-exec ln {} /path/to/destination-directory/ \;
:对于 find
命令找到的每个文件,执行 ln
命令创建硬链接。其中 {}
是一个占位符,代表 find
命令找到的每个文件的路径。这个命令会在 /path/to/destination-directory/
目录下为源目录中的每个文件创建一个硬链接。请确保目标目录已经存在,因为 ln
命令不会自动创建目录。同时,由于硬链接的限制,源文件和目标目录必须位于同一文件系统内。
问题出在 shfs 这个进程上。在 Unraid 中,这个进程负责将分散在各个 Disk 上的文件夹内容聚合成一个统一的目录库,这意味着如果我们要访问任何文件都需要通过 shfs 来查找文件,这也使得 shfs 成文高吞吐率场景下的一个性能瓶颈。之前我经常可以看到 shfs 的 CPU 占用飙升到 100% 甚至 200%。在新版的 Unraid 中提供了 exclusive access 的特性。 Exclusive 的是排他性,独占的意思。Exclusive Access 是指固定将一个目录放在一个 Disk 中,这样我们在访问这个目录下的文件时,就不需要通过 shfs 来检索目录,而是可以直接访问磁盘上的目录系统,从而降低开销。
另一方面,Exclusive 也意味着这个目录无法有效的利用磁盘阵列提供的存储池,而只能利用单磁盘空间。因此,Nextcloud 的数据文件夹是不能使用 Exclusive Access 的。不过好在数据文件的访问其实并不是主要瓶颈,造成 Nextcluod 卡顿的主要原因在对容器挂载的配置文件(也就是 appdata)里面的众多小文件的高频读写。
另一个需要指出的是,在 Unraid 中,尽管缓存有多个 SSD,但是这些 SSD 会预先通过 Raid 机制聚合成一个统一的缓存磁盘,对于 Shfs 来说这个缓存池就是一个单一的磁盘,因此对缓存池的访问可以启用 Exclusive Access
要是用 Exclusive Access,首先需要再 Settings --> Global Share Settings 下打开(如下图,将 Permit exclusive shares 设置为 yes)。
但是在共享目录使用时,Exclusive Access 的含义是比较模糊的。因为 Share 的管理界面并不会提供一个可以由用户可以直接控制的选项来为具体的 Share 开启 Exclusive Access。事实上,Exclusive Access 是否会起作用是系统根据目录的状态来自动确定的。如前文所述,Exclusive Access 作用的前提是数据只位于一个磁盘中。因此,在 Permit exclusive shares 启用的前提下,如果某个 Share 的文件只可能位于一个磁盘中,那么 Exclusive Access 将自动启用。
要实现 Share 的文件只属于一个磁盘中,需要满足一下条件:
在本文的场景中,我们需要在 appdata 这个共享目录上启用 Exclusive Access,就可能需要进行必要的 Mover 操作。如果你看到 appdata 的 Share settings 页面中显示了如下 Exclusive access: no 的文字,进行如下操作:
上面的这套操作,能够极大改善 Nextcloud 在 Unraid 上的表现。其他的应用也会有很大的改观(例如 Jellyfin 的打开和搜索速度会有质的提升)。
]]>首先通过 ps -aux | grep nginx
找到 Nginx 主进程的 PID,然后通过 kill -9 <PID>
干掉进程。要再次启动 Nginx,可以运行
1 | cd /etc/rc.d/ |
注意通过 ./rc.nginx restart
在正常情况下可以重启 Nginx 服务,但是在 Nginx 处于无法响应的状态时,这个脚本无法杀死 Nginx 进程,必须要手动终止。
确保用户未登录: 在删除用户之前,最好确保该用户未登录。你可以使用 who
命令来检查哪些用户当前已登录。
1 | who |
删除用户: 使用 userdel
命令删除用户。如果你还希望删除用户的主目录和邮件池,可以使用 -r
选项。
1 | sudo userdel -r username |
其中,username
是你想删除的用户的用户名。
注意:-r
选项会删除用户的主目录(通常是 /home/username
)。确保你已经备份了所有重要的数据!
检查文件系统: 即使删除了用户和其主目录,可能仍然在文件系统上遗留一些属于该用户的文件。你可以使用 find
命令来搜索这些文件:
1 | sudo find / -user username |
这将列出所有属于 username
的文件。根据你的需要,你可以手动删除这些文件或更改它们的所有权。
删除用户的 cron 作业: 如果用户配置了任何 cron 作业,你还需要手动删除它们。检查 /var/spool/cron/crontabs/username
是否存在,如果存在,删除它。
1 | sudo rm /var/spool/cron/crontabs/username |
其他服务或配置: 如果该用户有其他特定的配置,例如在 /etc/sudoers
中的条目或在其他服务中的特殊访问权限,你需要手动检查并删除它们。
请在进行任何删除操作之前确保备份所有重要数据。确保你明确知道正在执行的操作,避免意外删除重要文件或配置。
]]>为了达成这个任务,作者建立了一个极为庞大的数据集,包含了 1100 万个样本上的 10 亿个 Mask。这个数据集的名字叫做 SA-1B。作者还引入了一个 promptable segmentation 的概念,即我们可以通过输入文本来控制被 Segment 标记出来的像素集合的类别,如上图 (a) 所示。
参考大语言模型中在近期取得突飞猛进的进展,如果在图像领域也构造出足够强大的通用模型成为一个新的热点。本文的目标就是为图像分割任务训练一个基础的通用模型。另一个重点是将图像分割筒 Prompt 工程集合起来。
]]>中国2023年一季度GDP284997亿元,同比增长4.5%,超出市场预期的4%;一季度GDP比上年四季度环比增长2.2%。1
中国驻法国大使卢沙野4月21日在访谈中有关“前苏联国家不具独立国家的主权地位”等言论引起欧美国家广泛挞伐。中国外交部发言人4月24日被问及是否认可其言论时称,“中方尊重苏联解体后各加盟共和国主权国家地位”。2
苏丹冲突持续超过一周,各国加紧撤侨行动。6
最近中文网络流传一种说法,称俄罗斯开始实施电子征兵令,当一个俄罗斯青年征兵码变红时,就需要前往征兵处报道。8 但澎湃新闻考证此事可能为假9。
北京长峰医院住院部东楼发生火情,21 人死亡。10
时隔很久还是恢复 Weekly 的习惯!
美国机密文件泄露事件嫌疑人被捕,为一名 21 岁的空军国民警卫队成员 杰克·道格拉斯·特谢拉,他涉嫌泄露含有一系列国家安全机密的数十份高度机密文件,其中包括美国能够对俄罗斯进行的监视范围。3
德国外长贝尔伯克访问中国。4月13日,德国外交部长贝尔伯克抵达中国港口城市天津,开始她为期三天的中国之行。5
传言美国海军“里根”号航空母舰发生严重核泄漏事故,目前“里根”号。6
声音事件检测(AED或SED)的目标是预测给定音频段中目标事件的时间位置。这项任务在安全监测、声学预警和其他场景中起着重要作用。然而,数据不足和声学事件来源的多样性使AED任务成为一个艰巨的问题,特别是对于普遍采用数据驱动方法的情况。本文从分析声学事件的时频域特性入手,表明不同的声学事件具有不同的时频尺度特征。受到这个分析的启发,我们提出了一种自适应多尺度检测(AdaMD)方法。通过利用沙漏神经网络 (hourglass neural network) 和门控循环单元(gated recurrent unit, GRU)模块,我们的AdaMD在不同的时间和频率分辨率下产生多个预测。随后采用自适应训练算法,将多尺度预测相结合以增强整体能力。在2017年声学场景和事件的检测和分类(DCASE 2017)任务2、DCASE 2016任务3和DCASE 2017任务3上的实验结果表明,AdaMD在事件错误率(ER)和F1分数的指标上优于已发表的最先进竞争对手。我们收集的工厂机械数据集上的验证实验也证明了AdaMD的抗噪能力,提供了实际应用的证明。
AED 问题的主要挑战:
AdaMD 的网络架构如上图所示。此网络由一个 CNN 网络和一个 RNN 网络组成。其中,CNN 部分的网络架构被称为 Hourglass,这种网络架构在计算机视觉领域被广泛应用于关键点检测,其优势在于其可以在多种时频分辨率的条件下进行特征提取。在 RNN 部分,作者采用了 Gate Recurrent Unit (GRU) 模块来处理 CNN 模型输出的每个通道,从而处理时域信息。GRU 的输出会经过一个上采样流程的处理,使得各个通道的输出具有相同的尺寸。
这篇论文介绍了一个名为CADTransformer的新框架,用于自动化CAD图纸中的全景符号识别任务。该任务需要识别和解析可数对象实例(如窗户、门、桌子等)和不可数的物品(如墙壁、栏杆等),并在CAD图纸中进行标记。该任务的主要难点在于图纸中符号的高度不规则的排序和方向。现有方法基于卷积神经网络(CNNs)和/或图神经网络(GNNs)来回归实例边界框并将预测结果转换为符号。相比之下,CADTransformer直接从CAD图形原始集合中进行标记,通过一对预测头同时优化线条级别的语义和实例符号识别。此外,该框架还通过几个可插拔的修改增强了主干网络,包括邻域感知自注意力、分层特征聚合和图形实体位置编码等。此外,该论文还提出了一种新的数据增强方法,称为随机层(Random Layer),通过CAD图纸的分层分离和重组来进行数据增强。最终,CADTransformer在最新发布的FloorPlanCAD数据集上,将先前的最先进水平从0.595提高到0.685,展示了该模型可以识别具有不规则形状和任意方向的符号。
在 CAD 文件中对图元要素分类的任务在本文中被称为 Symbol Spotting。这类任务在建筑行业和其他工业领域都要广泛的应用前景。Symbol Spotting 任务分类的目标是具有几何特征或更多异质特征的图元要素,不同的是,传统的基于计算机视觉,尤其是基于 CNN 网络的图像分割任务,其分割的目标元素是同质化的像素。由于存在遮挡、非均匀聚集、制图风格差异等原因,Symbol Spotting 任务具有较大的难度。
传统的 Symbol Spotting 采用的是一种名为 Query-by-example,望文生义地来看,这些方法需要提前指定特定的符号构成,然后以匹配的方式寻找图面中与之类似的元素。这种方法显然无法适应现实中复杂多变的画图方式。近年来也涌现了一些基于机器学习的方法,他们提出使用 CNN 方法来解决 Symbol Spotting 问题。但是将 CAD 矢量数据本身转化成位图天然就引入了精度损失,这使得这些方法存在先天性不足。
以建筑行业为例,建筑 CAD 图纸具有“高对比度”(或者说“高灵敏度:)的特点,在一个 100 米乘 100 米的平面空间内,在围观层面上要求达到 1 毫米的精度。为了保持这个精度,我们必须将其转化成 10 万像素乘 10 万像素的位图,这个尺寸的图像处理起来代价过于高昂。而如果减少像素数量,则无法保持 1 毫米的精度。
这篇论文的团队在之前的发表的论文中提出了 FloorPlanCAD 数据集。基于这个数据集可以训练能够进行 Panoptic Segmentation Task 的模型。这类任务是综合了实例分割和语义分割的复合任务。在 CAD Symbol Spotting 场景,这意味着需要同时得到图元级别的类型归属和具有语音含义的每个建筑构件的识别。
本文的作者通过引入图卷积神经网络 (GCN) 来解决 Symbol Spotting 问题。GCN 网络可以给出每个图元的语义信息,同时还有一个 CNN 网络来给出“可数”构件的实例分割结果。
基于 Transformer,可以在不预先定义图连接结构的情况下,基于注意力机制推断到 tokens 之间的全局关系 (Transformers reason global relationships across tokens without pre-defined graph connectivity, by instead learning with self-attention)。这使得 Transformer 可以在 panoptic symbol spotting 任务中替代 GCN 的作用。但是标准的 Transformer 在这类任务上的使用仍然存在一些挑战:
作者提出的 CADTransformer 旨在成为一个通用的框架,可以被轻易地和现有的 ViT 骨干网络整合起来。
CADTransformer 是作为一个完整的 ViT 处理 Pipe 的一部分被插入到图像处理流程中。
一般意义上的 Panoptic symbol spotting 任务可以表述为 ,其中 表示一个基础图元。 和 分别是此图元的语义标签与实例索引。对于输入的 CAD 矢量图纸,我们首先将其拆解成基础图元要素(弧线、多段线等),并将图纸转换成栅格化后的位图。图元的嵌入的获取方式是将每个图元投影到一个预训练的 CNN 网络输出的二维特征图上。基于这个这样获取的嵌入,使用一个标注你的 Vision Transformer 以及作者提出的可插拔的附加网络,模型可学习到不同的 Token 之间的关系。Transformer 层后面跟着的是 Two-Branch heads 结构,我们可以通过这个结构来优化图元级预测的精度。本文提出的模型的整体结构如下图所示
具体而言,图元 Embeding 获取的方式是将图纸的栅格化图像输入 HRNetV2-W48 这个预训练网络。此网络是一个多级多分辨率的网络,我们取不同分辨率的分支,拼成一个 的张量。对于每个几何图元,我们将其中心点投影到这个张量的第二、三个维度张成的平面上,去第一个维度的特征值构成特征向量。这样最终得到所有 token 的 Embedding 就是 ,其中 是图元的数量, 是 Embedding 的维度。
如前文所述,Two-Branch heads 提出的目的是优化预测精度。TBH 层以 Transformer 骨干网络输出的几何 Embedding 为输入。
对于聚合后的 Transformer 特征输出 ,施加一个 MLP 层,可以生成一个语义分数 。损失函数使用交叉熵 。
CAD 图纸场景中的实例分割结果无法简单地用 BoundingBox 来表示。作者提出了一个新的方法,不是在像素图像中预测 2D BoundingBox,而是针对每个图形实体预测一个偏移向量,以便将实例实体聚集在一个公共的实例中心周围。为了更好地将实例相关的图元聚类在一起,作者使用一系列的 MLP 来编码图元的 Embedding,进而产生 个偏移向量 。针对这个偏移量的训练使用 损失函数。
其中 是一个指示变量组成的向量,只有可数构件的图元参与这个计算。在经过这个 Offset 量处理之后,可以用聚类算法对偏移后的图元中心点进行聚类。
ViT 骨干网络以上述编码层的输出,即 Tokenization Module 的输出作为输入,执行自注意力机制,并生成送往 Two-Branch Head 网络的特征表示。
典型的 ViT 的架构由多个 Transformer 层组成,每层包含一个 self-attention 模块和一个前馈网络(Feed-Forward Network) 。自注意力机制的计算方式为:
其中 分别是 query, key 和 value 矩阵, 和 表示样本数量和隐藏层维度。
然而,图纸中图元的数量非常多,因此全局注意力复杂度太高。为了解决这个问题,作者提出以下改进方法。
让注意力机制的计算只在图元最邻近的 k 个邻居之间进行计算,这样可以限制复杂度随着图纸规模增长的速度。这里的临近关系通过计算起始点和终止点的距离来实现。
其中 和 表示图元 的起始点和终止点, 是目标图元, 表示其他图元。增加注意力机制计算中考虑的临近的图元数量可以增加感知的范围。作者提出的 Transformer 主干网络划分成了若干个阶段,每经过一个阶段会提提高 的值。
我们将不同阶段得到的特征输出整合起来,令
由于图元数据天然来自于二维欧式空间,因此可以的比较容易的定义位置编码:
本章节介绍的是一种数据增强策略。这个策略很简单,就是利用 CAD 图纸的图层结构信息,将图层划分为是哪个类别,分别是 Thing layer, Stuff layer 和 Background Layer。我们让这些层内随机挑选出的若干层组成新的图纸。
]]>如果你在网络上搜索这类问题你会发现各种文章给出的方案一般都是为 OpenVPN 的配置文件添加 route-nopull
选项来阻止 OpenVPN 设置客户端路由,但是这会导致客户端的对外访问无法通过 VPN 进行。事实上,从 IP 路由的角度来看,如果我的服务器是 A,而某个访问 Web 服务的 IP 是 B,同时 B 也可能是 A 试图访问的外部的目标服务提供者。对传输层路由而言,A 给 B 的 Web 服务响应和 A 主动发往 B 的请求是无法区分的。从这个角度来看,要达成 inbound 和 outbound 流量分别路由似乎是不可能的。
但是其实我们有一个突破点:那就是 Web 服务总是在本机使用固定的端口和请求者通信,我们可以通过端口来区分 inbound 和 outbound 流量。首先我们输入下面的命令:
1 | iptables -t mangle -A OUTPUT -p tcp -m multiport --sports 80,443 -j MARK --set-mark 1 |
在这个命令中,我们会让路由表观察从本地的 80 和 443 端口发出的包,并给这些包打上一个特定的标签(--set-mark 1
的作用)。然后输入下面的命令:
1 | ip route add default via [gateway addr of eth0] dev eth0 table 100 |
这个命令会创建一个新的路由表 table 100
,然后在这个表中加入一个默认路由,将流量送往原有的网关。然后再运行下面的命令:
1 | ip rule add fwmark 1 table 100 |
这个命令会让带有标记 1
的包使用 table 100
路由表进行路由。运行完上面的配置后,web 服务就可以正常访问呢了。
以上方法是在 ChatGPT 的协助下摸索出来的,我的提问是:
如何通过 iptables 或者 route 命令,让访问 80 与 443 端口的包使用 tun0 接口,而访问其他端口的包使用 eth0 接口?
GPT 的回答是
可以使用iptables和route命令结合起来实现此目的。具体操作如下:
首先需要添加一条规则,将访问80和443端口的包标记为一个特定的标记值(比如"1"),使用tun0接口发送出去。iptables规则如下:
1 | iptables -t mangle -A OUTPUT -p tcp -m multiport --dports 80,443 -j MARK --set-mark 1 |
接着需要添加一条规则,将标记为"1"的包使用tun0接口发送出去。route命令如下:
1 | ip route add default dev tun0 table 100 |
最后需要添加一条规则,将所有未被标记的包使用eth0接口发送出去。route命令如下:
1 | ip route add default dev eth0 |
以上三个步骤分别实现了:将访问80和443端口的包标记,并使用tun0接口发送出去;将标记为"1"的包使用tun0接口发送出去;将所有未被标记的包使用eth0接口发送出去。这样就实现了访问80和443端口的包使用tun0接口,访问其他端口的包使用eth0接口的效果。
新的方案设计一个新的工具:spiped(github)。spiped是一种安全的、轻量级的加密通信工具,可用于在不安全的网络中传输敏感数据。它的设计目标是提供简单、易于配置和使用的加密通信,而不需要复杂的密钥管理或公钥基础设施。spiped使用对称密钥加密算法,可以在客户端和服务器之间建立加密通道。它可以用于保护各种类型的数据流,包括SSH、Telnet、HTTP和SMTP等协议。spiped的另一个优点是它可以在不同的平台上使用,包括Linux、FreeBSD、OpenBSD和Windows等操作系统。
spiped
的使用非常简单。在 Ubuntu 上可以通过 apt
来安装。也可以自行编译。在完成安装以后,我们首先进入服务端,运行下面的命令生成一个秘钥:
1 | dd if=/dev/urandom bs=32 count=1 of=keyfile |
其中 keyfile
是生成的秘钥文件,注意将其保管到合适的位置。然后用下面的服务启动 spiped 服务端服务:
1 | spiped -d -s '[0.0.0.0]:8025' -t '[127.0.0.1]:25' -k /path/to/keyfile |
注意,不同于 ssh
使用同一个 TCP 链接来走所有的隧道流量,spiped
会为每个流创建一个独立的 TCP 链接。因此在做代理中介使用时,spiped 可能会需要创建数量较多的 TCP 链接。spiped
的 -n
配置可以指定最大链接数量,这个数值默认是 100,对于代理应用来说这是不够的。你可以将其设置为 10000。
其中 -s
后的地址是服务端的监听地址,-t
的转发的目标地址,-d
表示将加密的流量从 -s
后的地址解密后发往 -t
后面的地址。这里我们可以填写 Shadowsocks 服务监听的地址。注意 Host 可以保持成 127.0.0.1
的形式。这样,我们可以在防火墙中把 Shadowssocks 的公网监听端口关闭,仅允许从加密隧道访问,这样可以提升安全性。
上面的命令在输入后会自动进入 Daemon 模式运行。
然后我们来到客户端(一般是跳板机),将服务端生成的秘钥文件下载到客户端,运行
1 | spiped -e -s '[127.0.0.1]:25' -t $SERVERNAME:8025 -k /path/to/keyfile |
其中 $SERVERNAME
是服务端的 IP 地址。注意用 []
包裹的形式来填写。如果这里说的客户端也是 Shadowsocks 的机器,那么这里的源地址(-s
后的地址)可以写 127.0.0.1
,但是如果是跳板机,那么应该写成 0.0.0.0
。
在上面的设置中,方括号内可以写入 IPv6 地址,但是注意 IPv6 的全网段监听地址就不能写 [0.0.0.0]
了,而是要写 [::]
。
客户端的程序也是会自动进入 Daemon 模式运行。
完成设置之后,访问客户端(跳板机)的 25 端口就相当于直接访问服务器的 25 端口。
请注意,spiped:
dd if=/dev/urandom bs=32 count=1
是你的好朋友。)/dev/urandom
的强熵。(确保您的内核的随机数生成器在引导时被种子化!)上一篇文章我提到了可以试用 Proxifier 来解决通过 Shadowsocks 代理访问呢 ChatGPT 频繁出现 Something Went Wrong 的问题,在本文中我们来展开讲一下具体的做法。
Proxifier 的官方网站是 https://www.proxifier.com/,其正版价格较贵,约 40 美元。不过,较早的 2.x 版本存在破解版。读者可以根据自己的经济状况来选择。
如同开头提到,Proxifer 本身不是一个代理程序,读者应该自己提前准备好代理。这些代理中,无论是 Shadowsocks、V2Ray 还是其他常见的代理程序,都会在客户端本地重建一个 Socks5 代理,这一般都可以在代理客户端程序的配置或者服务器设置中看到。下图给出的是 Shadowsocks-NG 客户端在 macOS 中的系统配置,可以看到程序在本地 (localhost
) 的 1086 端口创建了一个 Socks5 代理。
这个代理会被进一步转成 http 代理,从而被浏览器使用,这个转换过程是导致 ChatGPT 链接不稳定的元凶。接下来我们要使用 Proxifier 让 ChatGPT 直接使用 Socks5 代理。
我们以知乎上这篇文章为范本简要讲一下设置过程。首先我们需要将现有代理软件提供的 Socks5 服务告知 Proxifier,这通过添加代理服务器实现。
注意一般各种代理软件在本地创建的代理都是无需验证的,我们可以取消验证启用。输入设置之后,可以点击检查确认设置是否正确。
为了让 ChatGPT 的访问能够通过 Proxifier 访问代理,我们需要配置 Proxifier 的代理规则。
添加代理规则的页面如下:
我们有两种方法引导 ChatGPT 的网络访问:
chat.openai.com; challenges.cloudflare.com; *.openai.com;
即可。事实上,因为 Proxifier 能够全局性地处理所有网络访问,因此,第二种方法对于使用 ChatGPT Desktop 的用户来说同样有效。下图是完整代理规则。
至此我们就完成了设置,你可能需要重启浏览器才能能让 Proxifier 成功地引导 ChatGPT 的流量。
]]>update at 2023.4.27:
Github 上有人做了一个开源的油猴脚本 KeepChatGPT 可以解决这个问题。在浏览器上这个脚本可以正常使用,但是注入到客户端时(尽管内部仍然是一个浏览器)会出现错误。
update at 2023.4.12:
在 Windows 上这套方法存在一定的问题。可能的原因是 Proxifier 的代理权限有时候会被 Shadowsocks 客户端,也就是 Privoxy 争抢。
由于国情原因我们使用使用 ChatGPT 需要使用各种形式的代理,这时我们肯能会发现在与 ChatGPT 对话的过程中可能会出现下面的错误:
这种错误一般在我们再让网页空闲一段时间(通常是一分钟)后再次提问时出现。当然,去 OpenAI 的帮助中心是不会搜到什么有用的信息的。如果你去检索这个问题,你能得到的最好答案是在出现这个页面之后刷新页面,然后回到对话窗口继续对话即可。但是刷新后如果再次出现空闲窗口的情况,这个问题会反复出现,因此你在对话中就不得不反复地刷新页面,再加上每次页面重载你都需要去勾选 Cloudflare 的机器人验证框,这就会极大程度上拖慢我们的速度,影响产品体验。
这个问题是否可能是由于我翻墙访问导致的呢?我使用的翻墙软件是 Shadowsocks,经过一番搜索,一个 Github Issue 的讨论给了我灵感。
尽管讨论的题目和 ChatGPT 没有直接关系,但是这个回复提到,Shadowsocks 在构建代理管道时,会在客户端使用 Privoxy 来讲 Socks5 代理转换成 http 代理,进而被浏览器使用。Privoxy 的配置中包含了 Socket 连接时长方面的控制。于是我查看了我本地的 Shadowsocks 使用的 Privoxy 的配置文件,这个文件在 MacOS 中位于 /Users/lena/Library/Application Support/ShadowsocksX-NG/privoxy.config
文件。
1 | toggle 1 |
可以看到 keep-alive-timeout
和 socket-timeout
这两个选项设定的超时时间都比较低。我做了下面两个尝试:
至此,我也不想和 Privoxy 纠缠下去,既然问题出在 Socks5 代理转 Http 代理的环节,我们可以使用 Proxifier 这个软件直接使用 Shadowsocks 提供的 Socks5 代理,就可以很好地解决这个问题。
Proxifier 的教程网上非常多,我就不在这篇文章里赘述了。
Proxifier 的使用参加我的文章:如何使用 Proxifier 来进行流量代理控制
]]>作者在这篇文章里面提出 SceneHGH 网络来解决三维室内场景生成的问题。这个模型统筹考虑了房间的空间立体关系与每个待放置物体的形态细节。因此这个模型可以以细致的结合细节生成房间布局。为了让学习过程更加可控,作者引入了功能区 (Function Regions) 这一中间概念来串联起房间与待放置实体。为了确保模型的合理性(Plausibility),作者使用的基于图的表示形式同时包含了链接不同层级的父子节点的垂直边与连接同级别子节点的水平边。
作者使用的生成网络是一个基于可变自动编码器 (variational autoencoder, VAE) 条件式递归神经网络 (conditional recursive neural network, RvNN)。在给定放假你的边界条件的情况下,此网络可以生成具有良好几何表达形式的房间排布。
在此章节中的行业背景我们暂且不表,主要关注其场景构建与假设条件。
现有的针对三维室内场景的排布工作往往是部分的,例如大部分工作着眼于生成家具的排布,但是并未包含家具的几何结构细节,或者只能从有限的现有家具几何形状中有选择性地布置。但是家具布局和家具的几何形态二者在排布问题上是存在关联的,将这二者拆解成独立的两个步骤可能导致形成一些次优结构。本文提出的架构是一个端到端的架构,可以将布局与几何结构统筹起来考虑。
当然,这种统筹方法会存在很多挑战。一个房间内的各个要素构成了一个天然的垂直层级结构:房间内包含多个家具对象,每个家具对象也可以由多个灵活子组件构成。房间内的对象数量可能很多(3D FRONT 数据集中的一个房间最多可以包含 188 个对象),这使得学习会比较困难。考虑到在大型房间中,部分对象之间内部会存在一定的关联(例如若干个椅子围绕着一个桌子摆放),作者引入了 Function Regions 的概念,如用餐区,沙发区等。功能区概念作为一个中间层在房间与具体的对象之间,作为一个衔接桥梁。
在生成一个室内场景时,房间的形状会预先给定,作者将房间形状拆解为单元格,这个单元格的编码被视为是房间生成过程的一个输入条件。为了确保模型的合理性,需要利用对象之间的丰富关系信息。这些关系包括垂直关系:功能区必须位于房间边界内,对象必须要在功能区内部;以及水平关系:对象的对称性,邻接关系,对象内的组件之间的对称性等。因此,作者提出的分层图网络是一个基于 VAE 的 RvNN 网络,此网络具有 4 层,分别是房间、功能区、对象以及对象组件。在图中,作者精心设计了节点之间的边来体现约束关系。训练如此大的网络也是富有挑战性的,作者提出了一种多阶段训练策略来保证续联的稳定性和有效性。
作者结合了多个开源数据集,包括 3D-FRONT,PartNet 等。
暂略
作者提出将三维室内场景表示为一个分层树结构,分为房间、功能区、对象以及对象组件四个层次。
室内场景中的三维对象由多个组件组成,作者将对象组织为其组件的 n-ary 分层树结构,此结构中包含了各个组件的几何表示及其连接关系。每个对象的编码会包含其组件的结构信息,及其布局信息。
在整个场景结构树中,作者同时编码了不同层级之间的对象的丰富关系。除了天然了父子垂直关系之外,同一层的节点之间也有水平关系(可以是多对多关系,这种关系表示为图中的 hyper-edge constraints)。
作者提出的网络架构包括一个房屋布局编码器(room layout encoder),一个场景层次结构编码器(scene hierachy encoder)以及一个场景层次结构解码器(scene heirachy decoder)。其中房间布局编码器以房间边界的分解梯度表示(deformation gradients of the floor boundary)作为输入条件,并提取处一个向量。此向量被用于解码器的输入条件。场景分层编码器完成了从房间级别逐级向下到功能区级别,对象级别并最终到对象组件几何级别,逐层地、递归地将房间多层体系映射到的通用隐藏空间(类似神经网络的隐藏层?)中(这句话很难翻译,原文是 The scene hierarchy encoder maps the indoor scene hierarchies from the room level to the functional region level, the object level, and finally down to the object part geometry level into a common space hierarchically and recursively),解码器的作用正好相反。
在编码过程中,家具级别的对象首先用一个预训练的 DSG-Net 编码、然后结合对象的特征及其在区域中的位置信息,功能域级别的特征被提取出来。最终,房间级别的特征政和路所有区域级别的信息。解码操作的过程为编码的逆操作。在编解码器的训练过程中,若干图消息传递操作被用来提取对象之间的关联与约束关系。下图展示了在场景生成过程中不同要对象之间的二元关系以及多对象之间的关系(hyper-edge)。
考虑一个三维室内场景,作者提出如下的分层结构,共分为四层,分别是房间层级、功能区域层级、家具层级以及对象组件几何层级。其中对象层级与对象组件几何层级在 PartNet 中有已经引入,而功能区层级为本文的创新点。每个层级的节点定义如下:
区域:根据家具对象的功能作用,功能区将整个室内场景的对象划分成若干个组,这样可以提高一致性,并更有利于学习过程。不同类型的房间通常有多重不同类型的功能区域。显式地构建这些区域的语义概念不仅为场景生产结果提供了许多有意义的语义标签,同时有利于更好地组织每个房间中的大量对象。另外,由于 RvNNs 模型的能力限制(capacity limit),作者也无法将同一个房间内的大量节点直接作为单一房间对象的子节点。
在实现过程中,作者使用 DBSCAN 空间聚类方法将每个场景划分成若干个更小的功能区域。DBSCAN 算法是一种基于密度的无参数聚类算法,分蔟的数量是自适应的,不需要预先指定。通过 DBSCAN 算法啊,所有的对象可以根据其空间临近性质进行分组,这种空间临近的性质,通常与其功能性相关。每个功能区的功能定义由其中面积最大的对象决定。对于一些的显然不合理的划分,作者会提出算法进行人为修正。
总结来看,对一个三维场景 ,有 ,其中 表示第 个分蔟(功能区域), 意味着功能区域之间的水平连接。
对象:在我们将室内空间划分为多个功能区域之后,每个功能区域内会包含多个家具对象。每个对象的形状 由一个 Mesh 几何对象描述,描述信息还包括其语义类别,以及空间位置信息。我们给出正式定义: ,其中 表示功能区中的第 个对象, 表示对象之间的水平关系。
对象组件:作者是会用 PartNet 形状分层结构来讲每个三维几何形状划分成具有与含义的组件树,将其组织为一个 n-ary 分层树结构,改结构的不同层表示了不同精细度的拆解结构。具体而言,每个对象 被拆解为若干个部分 ,这些部分由分层结构 组织起来,不同的部分之间存在二元或者 n-ary 关系 。每个组成部分 具有一个预定义的语义标签和一个 Mesh 形式的几何表示 。组件之间的关系集合遵循了 StructNet 中的做法。
在一个场景分层结构中,我们考虑两类节点关系,分别是:
相比于之前的工作,本文在二元关系的基础上引入多元关系。这种超关系(hyper edge)可以让我们生成的室内场景更加真实合理。
一个非常类似的工作是 GRAINS。在这个工作中,作者用三个关系来描述对象和墙体之间的关系,分别是 supporting, surrounding 以及 co-occurence。不同于 GEAINS,我们在这里采用了更加精确的关系描述,包括二元关系: adjancency (),translational symmetry (), reflective symmetry (), rotational symmetry (), 以及多元关系 parallel collinearity . n-fold rotational symmetry ()。
垂直关系描述了父子节点知己的关系,包括:
此外,我们还额外引入了两个垂直关系:直接将房间跟节点和对象层连接起来:
这种跨层垂直边可以提高生成结果的合理性。
水平关系发生在同级别的对象之间,用来描述具有同一个父节点的兄弟节点之间丰富关系与约束。基于形状生成领域的已有工作启发,我们定义了四种二元关系:adjancency (),translational symmetry (), reflective symmetry (), rotational symmetry ()。对于邻接关系,我们定义两个组件邻接,如果他们的最小间距低于 ,其中 为两个组件的外接圆的平均半径。对于对称关系,作者遵循一个已有工作来定义 。这些二元边于训练输入时在对象层级自动完成。注意两个节点之间可能存在多个二元关系。、
二元关系还不足以充分描述复杂对象布局。例如在下图右侧,除了考虑的对象之间两两关系,最好能够考虑四把椅子环绕桌子布置的的多元关系。又如下图中右图,两个床头柜和床的方向线之间应该保持平行。
本文中我们引入两种多元关系:
多元关系发生一个功能区内的对象层层级。如果我们在任意两个对象之间构建二元关系(用以替代多元关系描述),这会形成一个致密网络,这对训练过程不利。
N 重旋转对称;考虑 个对象 ,如果满足下面的关系则称他们构成 N 重旋转对称关系:
其中 表示两个对象之间的 Chamfer 距离。 表示旋转矩阵。 为旋转中心点, 为旋转角度。 为一个常量阈值。
平行共线性 考虑 个对象 ,如果他们满足下面的套件则称他们满足平行共线关系:
其中 为任意向量,而 为任意非负实数, 为 的倾转 bbox 的中心点 为以常量阈值。
如前文所属,本文中采用的网络是基于 VAE 的 RvNN 网络。SceneHGN 网络采用场景分层树 (从房间层级到对象组件层级) 作为输入,房间的边界 作为额外的输入条件。 需要通过一个编码器 映射到特征向量的 。 也经过一个编码器 映射到一个隐藏向量 。然后这两个向量被拼接起来,送入解码器 来重建 。为了训练 VAE 生成模型,我们使用 KL 散度增加了一个正则量,将所有场景的隐藏层空间映射到一个标注你的高斯分布。
由于我们是从 DSG-Net 中获取对象组件层级,因此编码器 $Enc_{PG} 和解码器 遵循了 DSG-Net。 利用组件的几何信息 ,其中 为对象 的每个组件的 deformation gradients 表示形式, 为其中心点,以及结构信息 ,将其映射到一个隐藏的特征空间 。解码器的作用阈值相反,它将特征向量映射回来 。
FloorNet 的结构如下图所示。任意一个闭合的房间轮廓可以使用一个 deformed 2D unit square boundary 来表示。在本文中,一个单元方形边界由 596 个顶点和 596 个边组成。
为了高效地、精确地编码房间边界,尤其是变现处管教,我们使用了 2D non-rigid registration 技术,而非顶点坐标体系来表示一个 2D mesh。然后我们在这个 registered 2D mesh 上计算 ACAP deformation graidents。对于每个定点,我们最终得到 6 维特征 以及三维特征 (分别表示了缩放、剪切以及旋转)。
这段话有点东西啊,得找相关文献看看。
然后 矩阵被送入 FloorNet 编码器。FloorNet 的关键组件是一个图卷积网络。我们将整个 registered floor boundary 视为一个环装图,并在此基础上进行卷积操作来提取 2D mesh 的特征。在两轮卷积操作后,特征会通过一个 MLP 并输出一个隐藏层向量。我们执行两轮迭代,来将信息传递的给临近顶点,从而学习到邻接边的夹角(前面提到的着重学习拐角信息)。解码器的结构与编码器的相反。
这个编码器包含两个部分,分别是一个递归编码器 ,实现从组件层到对象层的编码,以及 实现从对象层到整个房间层的编码。作者完全遵循了 DSG-Net 的结构来实现 ,本章节剩下的部分主要着介绍 这个编码器。
每个对象节点存储了三类信息,分别是:其结构和几何信息 ,这个信息通过 编码器提取;放置参数 $ 以及语义分类标签 。放置参数 包括了中心位置 ,缩放系数 ,以方向参数 (以 z 轴为旋转轴。对象特征编码器 将上述信息编码为特征向量
其中 是一个全连接网络,而 表示串联操作符。
对于非对象的节点 ,使用一个递归编码器 来提取其所有子节点的特征,并沿着节点之间的关系边来传递消息。对于表示多元关系的边,我们首先只将关系的类型的集成到对应节点的特征中,这个过程使用了一个 MLP 。这个 MLP 由两个全连接层和一个 Leaky ReLu 激活函数组成。然后我们在子图中执行两个消息传递操作来讲所有子节点的特征集中起来,这个过程通过一个以 Leaky ReLu 为激活函数的全连接层实现。最终得到的特征向量
其中 表示 是 的子节点。 为对象的语义标签, 为多元关系边的语义标签。
整个 和 不断重复直到得到根节点的特征。最终根节点的特征同一个全连接层映射到特征向量所处的隐藏空间。我们用 KL 散度加入一个正则量,使得的特征向量符合高斯分布。
解码器以编码器输出的特征向量为输入,以空间边界特征为条件(condition),以递归的方式还原整个场景层级结构,解码器同样包含两个部分,分别是的递归式解码器 ,负责从对象特征描述中还原组件的几何结构,以及递归式解码器 ,负责接受根节点特征 以及条件输入 。对于对象解码, 解码器遵循了 DSG-Net 的设计,本章节主要介绍 .
如前文描述, 以房间节点未输入,并推断出其子节点,即功能区的特征,并递归式地解析处对象层级的特征信息。对于每一个解码步骤,我们假设每个父节点最多只有 10 个子节点,并且通过一个二分类网络(通过一个以 Sigmoid 为激活函数的 MLP 来实现)来预测节点存在的概率。同时我们也会预测出子节点的语义类型。对于对象节点,我们训练一个 MLP 来预测放置参数、分类语义标签以及对象特征,特征数据会被输入 来获取组件形态。放置参数估计网络由两个全连接网络,一个 Leaky ReLu 激活函数和一个 skip-link 组成。网络通过三个不同的全连接层来估计 , 和 。接下来我们通过节点的几何数据与特征来预测已有节点之间的关系。这些关系类型会与真值比较从而在训练阶段指导模型的超参数优化。
对于二元关系边的预测,我们对于任意两两节点组合,结合其特征判断每种边的存在概率。对于多元关系,我们通过注意力机制来预测一个 mask, 来表示哪些节点共享了一个多元边属性。利用节点之间的边连接关系,我们执行两轮消息传递过程来更新节点特征。最终得到的节点特征为:,其中 表示每个父节点 的子节点。综上,我们有
其中 ,其中 是子图中节点的数量, 是 hyper-edge 类型的数量。对于每一行,它预测了不同类型的 hyper-edge 出现的概率,最终推断的多元关系类型以 argmax 的操作给出。根据我们的定义,每个对象应该最多只有一个 hyper-edge。
根据之前的描述可以看出我么内购我们构架你的模型是一个由多个独立网络组成复杂体系,这个体系要想一次性训练到位是比较困难的。因此我们将训练分成两个阶段。首先我们训练从对象到组件级别的递归网络,然后训练整个网络。对于空间边界 VAE,我们在骨干网络之外单独对其进行训练。
总损失函数如下:
其中 是整个数据集中的场景分布。重建损失 包括 eaf loss,sematic loss, edges/node existence loss, geometry loss, placement loss 以及部分 edge losses. 这里除了 placement loss, room-object edges 以及本文提出的 hyper-edge loss 之外,其他的损失定义都参照了 StructureNet。对于结构性损失,StructNet 中使用这一损失来确保生成的结构对象的合理性与真实性。不过在本文中,我们只对对象内的层级结构施加这一损失,而非整个场景层级。正则项 只在使得隐藏空间更加平滑,并有利于下游网络(生成与插值网络)的运作。在本文中我们设置常量 ,这是一个经验数值。
下面我们给出 Placement losses 和 edge losses:
Placement Parameter Reconstrucion Loss: 对于中心点和缩放系数我们采用 L2 Loss 来计算其损失。对于朝向角度来说,我么你发现在真实室内场景中,大多数情况下家具的朝向角度来自有限的几个值,一般是 ,因此我们选择用离散的值来预测家具的大致朝向,并生成一个残差偏移量来得拟合真值。因此 ,其中 为是一个估计向量,每个元素表示家具朝向指定方向的概率, 为预测出来的残差偏移量,。 为真值。
Room-object Bindary Edge Loss:这个损失的主要目的是将预测出来的对象与房间的边界墙体之间对齐。在输入的室内场景数据中,房间边界和墙体基本对齐的。因此我们也鼓励对象的倾转 bbox 同水平面上的两个轴对齐。这一限制值对具有相关属性(即有 room-objects 边)的对象起作用。我们增加一个损失梁来估计对象的 box 和放假你的 box 沿着房间边界方向的距离,,其中 是所有预测出来的对象的集合。
为 Chamfer 距离。 为一个单元立方体的六个面的单位长度法向量, 为一个变换矩阵,将法向量旋转到的与对象的朝向 对齐。
Hyper-edge Loss:这个损失项的加入鼓励对象之间保留多元关系。本文引入了两种多元你关系。
对于满足 n 重旋转对称关系的 对象,我们可以定义损失函数:
其中 为所有对象的中心, 表示以竖直方向为轴以 为中心点,以 为角度的选转矩阵。注意由于在解码器作用之后对象之间不再保持旋转顺序,因此我们这里要尝试所有的顺序组合得到 Chamfer 距离的最小值。
对于共线平行关系(collinearly parallel hyper-edges),我们定义两个损失函数,第一个是:
这是对方向一致性的评估。第二个损失函数是:
其中 是 的倾转 bbox 的中点, 是所有相关对象的中点组成的点集的中点, 所有相关对象的平均相对位置。 表示将向量长度单元化。在计算 时,我们将对象中点的在水平面上的横纵坐标按照升序排序,从而避免相反向量被抵消。最终的损失函数是:
前文训练的是一个面向室内场景的通用编解码系统,其隐藏层的特征向量,可以用于大量后续应用。三维场景生成就是一个典型的应用。这里提出的网络架构可以用于自由场景生成,但是在显示中,大部分的生成任务都有额外的限制条件输入(例如房间轮廓)。
]]>论文的后续内容是结果对比和展示,不再整理。
一个建筑信息模型的包含的的建筑规划与维护所需要的所有相关信息。三维形态的几何苗猪相比于传统的二维图纸具有巨大的有事:
一般而已,衡量一个 BIM 建模工具的几何能力的核心要素是其使用的几何建模型核心的质量。目前使用最为广泛的几何工具包括了 ACIS 和 ParaSolid。
建模三维实心几何体的方法有两种不同的方向:
这两种方法在 BIM 建模工具中都会使用到,且都是 IFC 标准的一部分。
Boundary Representation 是最为广泛使用的三维几何描述模型。其核心思想是构造一系列边界元素的树状结构。通常这种树状结构包含了点 (Vertex)、线 (Edge)、面 (Face)、体 (Body) 等。每个元素都是由更底层的子元素来描述。这个树状结构代表了几何体的拓扑结构,如下图所示:
整个拓扑结构我们可以用一个图数据结构来表示,这个图数据结构被称为 vertex-edge-face graph,简称为 vef 图。
在拓扑结构的基础上,还需要增加几何维度信息来才能充分表示几何体。如果偶几何体只包含了直线和平面,则只需要顶点的几何坐标信息就可以充分表示几何体。但是如果几何体中包含了曲线和曲面,则需要更多的几何描述信息。
用来描述拓扑信息的数据结构通是多个变长列表。体用围合它的面来表示,面则用围合它的边来表示,每个边由其起点和终点来表示。不过这种简单的数据结构只适用于简单的,没有孔洞和开口的几何体。为了表示更加复杂的几何体,我们必须要扩展这一数据结构。
上图给出了 ACIS 使用的几何数据结构设计,ACIS 在多种 CAD 和 BIM 软件中得到应用。在这个数据结构中,Body
有若干个彼此不连通的 Lump
组成、Lump
则由若干个 Shell
来描述(因此可以描述孔洞和开口)。每个 Shell
由若干个 Faces
来表示。每个 Face
由一个或者多个 Loop
来表示。由于每个 Face
可以有多个 Loop
,因此可以表示含孔的多边形。
上述模型的一个重要特点是 Loop
本身并不直接指向边,而是通过 CoEdge
这个中间媒介。图中的下半部分显示了几何数据可以如何同拓扑数据整合起来。
这种方法是对 BRep 方法的简化。通过牺牲一定程度的精确度,可以将曲面表示为一系列的三角形。当然,三角形越小表示就越精确。三角面片表达方式通常用来做可视化。除了精度问题,三角面片表示方法也有更多的存储空间需求;
三角面片表达方式的底层数据结构的组织形式为 Indexed Face Set,其中每个顶点的坐标被存储为有序列表,每个三角面片由其在列表中的索引下表来定义。这种方法可以避免存储冗余的点坐标信息。同时避免因计算误差导致几何错误(三角面片之间出现错位)。
如前面论述过的,Implicit 建模方法的核心思量是记录几何体被狗砸偶读过程,因此 Implicit Modeling 也被称为 procedural methods。BIM 和 CAD 软件在实践过程中会对生成的几何体提取快照,从而降低每次加载时的计算时间。
CSG 是一个经典的面向构造过程的几何模型。CSG 模型在表示几何体时会选择一些基础元素(被称为 primitves),如立方体,圆柱体,棱锥结合几何布尔运算(交、并、差)等来创建更为复杂的几何体。
表达能力的相对受限使得 CSG 模型在 BIM 场景中基本不会单独使用。
很多 CAD 或者 BIM 工具提供了通过挤出和旋转操作来代构造三维几何体的操作方法,其操作过程通常是将一个二维图形(通常是一个闭合曲线)沿着三维曲线移动。如果移动的路径是直线,则这个操作被称为 Extrustion,如果是曲线。则被称为 Sweep。作为出发点的二维图形在移动的过程中其朝向可以根据行进方向改变,亦可以保持不变。
旋转操作的定义非常直接,就是将一个二维梯形沿着一个三维上的轴进行旋转。
Lofting 是一种特殊的变种操作。在这个操作中用户需要定义两个面,程序会基于差值方法建立二者之间的过程,并在这一过程中构造出立方体(见上图)。
略
]]>本文参考了:https://publications.cms.bgu.tum.de/books/bim_2018/06_IFC_07.pdf
构建建筑信息模型(BIM)的思想来源于我们试图将所有的建筑模型数据的数据交互操作中使用一致的建筑模型。这样可以避免手动添加模型中的已有数据(例如交圈的时候),并降低操作过程中的误差。除了方便模型构建过程中的数据交互,统一的 BIM 模型使得建筑模型交付过程变得简单而可靠。
目前市面上已经存在了很多不同的建筑设计软件,这些软件有的着眼于建筑物的几何设计,有的着眼于建筑分析和模拟,有的着眼于建筑操作维护,等等。然而,问题在于这些软件之间缺乏统一的数据交换协议,因此设计师在使用不同的软件时,需要讲相同的数据信息重复地手动输入到这些软件,这增加了工作量,并且提高了出错的概率。
为了解决这一问题,我们需要设计出满足这种需要的统一数据格式。此数据格式需要提供统一的、准确的几何描述;提供完备的建筑语义表达(包括建筑元素的分类树系统,以及这些元素之间的关系)。设计这样的数据格式存在诸多挑战,而且,在建筑领域,相比于其他工业领域,还存在下面这些挑战:
简而言之,建筑行业的特征是由众多独立参与者共同参与的高度碎片化的过程,这意味着在这一过程中中会涉及多种不同的软件工具,同时也意味着统一标准较难施行。同时,权力机关在指定标准时也比较难以某个特定的软件作为统一标准,即若要推行统一标准,此标准应该是 Vendor-free 的。
为了达成此目标,国籍组织 buildingSMART 投入了数十年的时间来开发 Industry Foundation CLasses (IFC)。IFC 是一个复杂的数据交互格式,其可以以面向对象的方式描述一个建筑物的几何属性和语义信息。IFC 将建筑物拆解为构件元素和空间,把那个描述他们之间的复杂关联关系。此数据结构可以贯穿建筑设计的整个生命周期。需要注意的是 IFC 是一个类型设计标准,而非一个具体的软件,因此 IFC 的落地施行还需奥各个软件厂商进行针对性的实现。然而,完整的 IFC 标准实现旷日费时,因此 buildingSMART 提出了 Model View Definitions (MVD) 的概念。MVD 定义了在特定任务场景下,哪些 IFC 数据模型应该被实现(而非整个 IFC 模型)。
略。
IFC 的研发采用了和 ISO 标准体系的相同技术手抖,即 EXPRESS 建模语言。EXPRESS 是一种声明式语言,可用于定义面向对象的数据模型结构。
EXPRESS 使用了 Entity(实体) 类型来对应面向对象理论中的类的概念。对于每个实体类型我们可以定义其具备的属性以及同其他实体的关系。EXPRESS 同样采用了面向对象的设计中的继承概念,这使得属性与关系可以应用到实体类型的子类型中。
两个类型 TypeA 和 TypeB 的对象之间的关系定义方式是赋予 TypeA 中定义类型为 TypeB 的属性。我们也可以以类似方式定义与之相对的反关系;
聚合数据类型 -- list, array, set 以及 bag 都是 EXPRESS 模型语言的一部分,这样方便我们定义一对多和多对多关系。EXPRESS 支持以 WHERE 关键字来定义保持数据一致性的算法条件。在 WHERE 语句块中包含了一个返回值为 Bool 类型的表达式,当此值为 True 时,其对应实例才会是有效的。
EXPRESS 中的 select 类型在继承树之外提供了一种额外的方法来讲多个实体类型赋予到一个高阶构型上。
取值范围局限于有限几个选择时,我们使用枚举类型来表示。例如 IfcBooleanOperator
可以取值为 UNION
, INTERSECTION
或者 DIFFERENCE
。
除了文本表示以外,EXPRESS 定义了一种图示方法来表示数据模型。这种图示方法被称为 EXPRESS-G。
为了改进了 IFC 模型的可维护性和可扩展性,该模型被构建成多个层。其基本思想是位于上层的元素可以索引其下方的元素,但是反之不可。这使得核心元素保持独立性。
核心层包含了数据模型的大多数基础类型。他们可以被上层的所有元素索引。这些类定义了基础的结构,管架拿到关系以及可以被复用的通用概念。Kernal schema 中定义了 IFC 模型的核心概念,其包括一些非常基本的类:IfcRoot
, IfcObject
, IfcActor
, IfcProcess
, IfcProduct
, IfcProject
, IfcRelationship
。在这些基础类型至善是是三个扩展包:Product Extension, Process Extension 和 Control Extension,这些也是核心层的一部分。
Product Extension 中包含了一个建筑物中实体性的和空间对象的定义及其关系。这些子类包括: IfcBuildng
, IfcBuildingStorey
, IfcSpace
, IfcElement
, IfcBuildingElement
, IfcOpenningElement
(都是 IfcProduct
的子类),以及 IfcRelAssociatesMaterial
, IfcRelFillsElement
and IfcRelVoidsElements
(都是 IfcRelationship
的子类)。
Process Extension 主要描述过程和操作,这个扩展包同时也提供了定义流程元素的依赖关系的手段。
Control Extension 定义了控制对象的基本类,如 IfcControl
和 IfcPerformanceHistory
,并可以将这些对象分配给物理和空间对象。
从共享层位位于核心层上方,起到核心层和应用领域之间的互操作(Interoperability)层的作用。这一层的类包括 IfcWall
, IfcColumn
, IfcBeam
, IfcPlate
, IfcWindow
。
Domain Layer 中的类是高度特异化的类,往往只用于特定的领域。他们构成了继承树的叶子节点。这一层的类不能被其他层引用,在同一层的不同领域之间也不能互相引用。
资源层是位于最底部的一层,可以在整个 IFC 模型中被使用。这一层中的类并非由 IfcRoot
导出,因此并不具备特定的 id 标识。不同于其他层的对象,这些类型并不能以独立的对象存在,而必须被其他 IfcRoot
的子类型对象引用。在这一层的对象中,最重要的资源类包括
IfcCsgSolid
, IfcFacetBrep
, IftSweptAreaSolid
;除了这些之外,资源层还具有一系列的 scheme 定义,例如 Cost, Measure, Datatime, Representation 等。
如同任何其他面向对象设计的数据模型一样,继承树在 IFC 中扮演了关键的角色。上图展示了 IFC 模型继承树的部分结构。在本章节我们关注其中的若干重要类。
IfcRoot
及其直接子类继承树的起点是 IfcRoot
类。除了资源层的类以外,其他所有层的对象都必须直接或者间接地继承自 IfcRoot
。这个类提供了基础的对象身份功能(GUID),该身份标识可以唯一地确定一个对象,并检索其在变更历史上的位置。除此之外,每个对象可以具备一个名称和一段描述。
直接从 IfcRoot
派生出来的类是 IfcObjectDefinition
, IfcPropertyDefinition
以及 IfcRelationship
。
IfcObjectDefinition
是所有代表物理对象、空间对象以及概念对象(conceptual elements)的类的祖先;这个类的是哪个子类是 IfcObject
, IfcTypeObject
和 IfcContext
(代表一般的项目信息)。
IfcRelationship
的子类代表了对象之间的关系。这个设计将关系本身的语义信息和对象自身的属性解耦开来。
IfcPropertyDefinition
定义了一个对象具有的,尚未定义在 IFC 数据模型中的属性。
IfcObject
及其直接子类如前所述,IfcObject
代表了一个建筑项目中的各种具有实际物理含义的对象。它是 IFC 模型中六个重要类的父类:
IfcProduct
: 代表一个物理对象或者空间类型;IfcProduct
对象可以被赋予一个几何形状表达,并被放置到项目坐标系中;IfcProcess
: 代表了在一个建筑项目中的一个处置流程(规划、建设、操作等)。这些流程具有时间维度;IfcControl
: 控制或者限制另一个对象的对象。这种控制形式可以是法律,指导、规范、边界条件或者其他限制条件;IfcResource
: 描述了作为一个流程的一部分的对象;IfcActor
: 描述建筑项目中人的参与;IfcGroup
: 对象的任意组合;IfcProduct
及其直接子类IfcProduct
能够表示所有和几何、空间上下文相关的对象。这类对象具有几何形态属性和空间位置属性。
IfcElemenet
是 IfcProduct
一个子类,它是一系列描述物理对象的重要类型的父类,如 IfcBuildingElement
(这个类是 IfcWall
, IfcColumn
, IfcWindow
等类的父类);
IfcSpatialElement
类,用来描述非物理形态的空间概念对象。其子类包括 IfcSite
, IfcBuilding
, IfcBuildingStorey
, IfcSpace
。
IfcProxy
这个子类被用作占位符,用于表示不具备语义类型的对象。这些对象有时候也可以具有几何形态和位置信息。其子类包括 IfcAnnotation
, IfcGrid
, IfcPort
。
对象关系是 IFC 数据模型的重要方面。典型的关系类型可以是整体/部分关系,链接关系或者定义关系。IFC 将关系描述本身对象化,即在业务含义上具备一定关联关系的对象直接并不直接持有指向对方的引用,而是通过一个媒介对象来建立间接的链接,此媒介对象代表了关系本身。关系对象本身描述了方向的指向,在命名规范上,分别用 related...Element
和 relating..Element
来代表关系的客体和主体。同时,为了检索反向关系,在主体和客体对象中会建立对应的关系属性。正向和反向关系相关属性指向的是同一个关系对象。
关系对象都是 IfcRelationship
的子类的实例。相关类型的继承树系统见下方的示意图。IFC 中定义了下面六种基本关系:
IfcRelAssociation
: 用于将一个外源信息(例如 classification, libraries or documents)关联到一个对象。IfcRelDecompose
: 用于表示对象的组分拆分的概念。这个关系描述了整体/部分的关系。其子类包括 IfcRelNests
(被嵌套的对象内有顺序), IfcRelAggregate
(聚合对象之间没有顺序), 以及 IfcVoidElement
(对应开口 openning 关系)。IfcRelDefines
: 将一个对象关联到一个属性集定义(Property Set Definition)或者一个类型定义(Type Definition)。IfcRelConnects
: 描述两个对象之间的连接关系;IfcRelDeclares
: 代表了一个对象,其定义属性与相应上下文的关系、IfcRelAssigns
: 代表两个对象之间的一般意义上的链接关系。IFC 模型对建筑的描述的一个重要底层概念是空间在不同层级上的聚合体系。所有具有空间语义含义的概念都派生自 IfcSpatialStructureElement
。IfcSite
用来表示建筑场地,IfcBuilding
用来表示建筑物,IfcBuildingStorey
表示特定的楼层,而 IfcSpace
用来表示独立的空间与走廊。IfcSpatialZone
表示一般意义上并不满足默认的建筑结构特征的空间区域,这些空间通过 IfcRelAggregates
关系组织起来。
上图展示了空间结构体系的一个示例。上图中需要注意的是在被聚合(related)的 IfcSpatialStructureElement
的属性 CompositionType
被用来进一步描述聚合关系所。PARTIAL
表示整体、部分的关系,而 ELEMENT
则代表了简单的嵌入元素。
注意数据模型本身并明确定义每种类型应该出现在聚合体系中的什么位置,其数据规则应该有程序的开发者来定义和保证。这些规则可能包括不同类型的上下层限制与嵌套关系不能构成环路等。
为了描述建筑元素在空间聚合体系中的层级,我们使用了 IfcRelContainedInSpatialStructure
这个关系类型(如下图)。在大多数情况下,建筑元素都会链接到层上但是注意,任何一个建筑元素只能建立一个 IfcRelContainedInSpatialStructure
关系。如果一个元素纵跨多个楼测过,则需要使用 IfcReferencedInSpatialStructure
关系来关联到其他楼层。
BIM 场景中大量应用都需要获取空间元素与包围空间元素的边界元素之间的关系,这些边界元素可以是墙体,层以及天花板。为了建模这种关系,IFC 数据模型引入了 IfcRelSpaceBoundary
关系。其属性 RelatingSpace
指向目标空间,而 RelatedBuildingElement
则指向其边界元素。同时,我们可以使用 IfcConnectionGeometery
对象来表述空间和边界元素接触的表面形态。
空间边界总是从空间对象的视角进行阐述,根据次视角可以将空间边界划分成两个不同的层级。
对于空间边界更精细的定义方式需要根据应用场景来进行进一步设计;
暂时我不关心这个问题,略过
IFC 模型严格区分了语义描述及其几何表示。语言描述表明了对象的语义身份,并且可以与一个或者多个几何表示相关联。因此对象身份将只与一个语义对象有关,而独立于其几何表示。
允许为同一个对象关联不同的几何表示,解决了在不同的应用场景下的不同需求。例如在一个纯显示程序中,立体对象只需要使用三角面表示即可完成渲染,但是在编辑场景中就需要使用高质量的 Brep 或者 CSG 表示。也可以为对象关联二维几何表达,使得 IFC 模型中可以保留二位图纸信息。
维护不同的几何表示之间的一致性是开发者的任务,IFC 标准并不解决这个问题。
所有设计几何建模的类型都可以被划分到三个类别,分别是 Geometric Model Resource, Geometry Resource,或者 Topology Resource。在多数情况下这些数据结构的定义都符合 STEP 标准或者 X3D 标准。
所有的几何类都集成自抽象基类 IfcGemetricRepresentationItem
。其子类可以分类成曲线表达 (IfcCurve
及其子类),曲面表达 (IfcSurface
及其子类),以及实体表达 (IfcSolidModel
及其紫烈)。几何表达的维度用 IfcGemetricRepresentationItem
的 Dim
属性表示。
IfcCartesianPoint
IfcCartesianPointList
IfcVector
IfcDirection
一共有三个IfcCurve
的子类可以用来描述线条,分别是 IfcBoundedCurve
, IfcConic
, IfcLine
。自由曲线可以使用 IfcBSplineCurve
表示。IfcCompositeCurve
可以用来表示由多段曲线构成的复合曲线。
上述类型可以用于三维和二位的曲线表示,但是需要将表示维度的属性设置正确。
Bounding Box 是高度简化的三维物体表达,通常被用来作为一个 Placeholder,或者连同一个更精细的几何表示一起使用。IfcBoundingBox
使用一个角点加上三个棱线长度来表示这个立方体。
面模型提供了描述有多个子面(sub-surfaces) 组成的复合曲面。疼吗通常被用来描述空间尺度交大的面(如地形)或者非常平整的面。三维实心体也可以使用他们的曲面表面来描述。
IFC 数据模型支持两种不同的表面模型变种(见下图)。IfcFaceBasedSurfaceModel
可以通过若干 IfcShell
对象来描述不含孔的实心体。这些壳对象可以开的 (IfcOpenShell
),也可以是闭的 (IfcClosedShell
)。
通过三角面来描述立体对象是一种广为接受的形式,这种数据格式几乎可以被所有的可视化软件解读。其缺陷在于立体对象的描述精度收到三角形数量的限制,其数据量较大,且很多软件提供的对这种数据结构的编辑功能比较有限。
IFC 数据模型提供了 IfcTriagnulatedFaceSet
类来描述三角面形式的曲面描述。其基类是 IfcTessellatedFaceSet
。
IFC 实现了 Indexed Face Set。类 IfcTriangulatedFaceSet
的属性 Coordinates
是一个 IfcCartesianPointList3D
类型的对象,记录了所有三角形面片的坐标。同时每个面片还有一个法向量属性(Normal
)。同时也可以链接颜色和纹理属性。
IFC 支持杜仲不同表示三维实体的方法,他们具有统一的基类 IfcSolidModel
。这些类包括 IfcCsgSolid
, IfcmanifoldSolidBRep
, IfcSweptAreaSolid
以及 IfcSwiptDiskSolid
。
manifold: a collection of points forming a certain kind of set, such as those of a topologically closed surface or an analog of this in three or more dimensions.
表示几何实体最有效、最灵活的方式是边界表示 (Boundary Representation, BRep)。IfcManifoldSolidBrep
的两个的子类 IfcFacetedBrep
和 IfcAdvancedBrep
实现了两种典型的 BRep 数据结构。其中,前者只能描述平直的面(指每个子面都是平的),但是后者可以描述存在弯曲的情况。
但是这两个结构都只能描述实心体的外壳,因此无法描述包含孔洞的情况。为了兼容这种情况,IFC 引入了 IfcFacetedBrepWithVoids
和 IfcAdvancedBrepWithVoids
类。
如下图所示,IfcFacetedBrep
被用来表示具有平整表面的实心体。IfcFacetBrep
对象表示了整体,其 Outer
属性是一个类型为 IfcClosedShell
的对象,而这个对象由进一步引用了一系列的 IfcFace
对象。每个 IfcFace
对象可有任意多个IfcFaceBound
对象描述的封闭曲面。每个 IfcFaceBound
对象指向一个 IfcLoop
类型的对象,这个对象描述了一个点组成的列表。注意在曲面描述中,相邻曲面共享的顶点和边这些对象只会存在一份,此份数据可以被多次引用。
下图展示了使用 IfcAdvancedFace
表示复杂曲面的数据结构的例子。注意在这个例子中,IfcClosedShell
引用的是 IfcAdvancedFace
类型的曲面表示对象。不同于 IfcFace
,这个类的对象本身具有几何描述属性,此属性为使用 NURBS 几何描述模型的 IfcBSplineSurface
类。在下方的各个层级的对象中,其几何描述都指向此描述模型中几何对象。
GSG 模型通过简单实心体元素的布尔操作来定义复杂实心体,这些操作包括并 (Union)、交 (Intersection)、差 (Difference)等操作。IFC 数据模型提供了 IfcCsgPrimitve3D
及其子类 IfcBlock
, IfcRectangularPyramid
, IfcRightCircularCone
, IfcRightCircularCylinder
和 IfcSphere
等类型类表示基础实心体元素。
IfcBooleanResult
被用来描述几何操作,其 Operator
属性可以在三个可选项中取值 -- UNION
, INTERSECTION
和 DIFFERENCE
。FirstOperand
和 SecondOperand
好随性则指向参与操作的元素,这些元素可以是 IfcSolidModel
, IfcHalfSpaceSolid
, IfcCsgPrimitive3D
或者 IfcBooleanResult
。我们可以看出 IfcBooleanResult
可以递归式地使用,从而具备强大的表示能力。
裁切可以用来表示实心体被一个平面切开的过程。这个过程使用一个特殊的 CSG 操作来实现。第一个操作对象是待裁切元素(IfcSolidModel
),第二个操作对象是一个特殊的半空间对象 IfcHalfSpaceSolid
,操作符总是 DIFFERENCE
。
IFC 中我们可以通过 IfcSweptAreaSolid
及其子类 IfcExtrudeAreaSolid
, IfcRevolveAreaSolid
, IfxFixedReferenceSweptAreaSoid
以及 IfcSurfaceCurveSweptAreaSolid
。除了这一支类族以外,还有直接继承自 IfcSweptDiskSolid
的 IfcSweptDiskSolid
。
每个操作的从基础元素形态以 IfcProfileDef
类型的对象给出(这是操作类的 SweptArea
属性)。IfcProfileDef
最常见子类是 IfcArbitraryClosedProfileDef
,这个类通过引用一个 IfcCruve
对象来指定一个基础的闭合图形。
通过使用 IfcExtrudedAreaSolid
,基础图形元素可以型一个指定的方向(ExtrudedDirection
属性)挤出指定的距离(Depth
属性)。如果是使用 IfcResolvedAreaSolid
,则基础图形会被绕着指定的轴(Axis
属性) 旋转指定的角度 (Angle
属性)。
使用 IfcFixedReferenceSweptAreaSolid
类,我们可以表示一个基础图形沿着空间中的指定曲线 (Directrix
属性) 运动扫略过的实心体。其特征是在扫略过程中,基础图形的不会发生形变,其朝向和一个固定的引用向量 (FixedReference
属性)保持一致。
使用 IfcSectionedSpine
可以表示一组连续的的基础图形之间的差值(上图左侧)。其类型为 IfcCompositeCurve
的属性对象描述了合成曲线的轨迹。CorssSections
属性记录了参与差值的各个基础图形,每个图形的位置由 CrossSectonPosition 给出
。
IfcSweptDiskSolid
直接继承自 `IfcSolidModel。其使用的扫掠基础图形总是圆形,且在扫略过程中基础图形的朝向会随着轨迹的变化而改变。
IFC 模型中的几何定位强烈地依赖于局部坐标系统。不同的坐标系统之间需要建立映射关系。例如墙体的定位坐标是相对于本层的坐标系统,而层的坐标系统可以映射到整个建筑物的坐标系。这个多层级的坐标系体系可以赋予模型更大的灵活性。IFC 中,我们将上述思想命名为 Local Placement。IFC 中定义了一系列派生自 IfcObjectPlacement
的类,如下图所示。
IfcLocalPlacemente
类继承自 IfcObjectPlacement
,此类提供了两个属性:一个可选属性 PlacementRelTo
属性指向提供父级坐标系的对象(这个对象也是 IfcObjectPlacement
类型的,若为空,则对象的定位是相对于全局坐标系的),和 RelateivePlacement
的属性(定义了从父级坐标系到局部坐标系的变换)。这里的变换可以是二维 (IfcAxis2Placement3D
) 的或者三维的 (IfcAxis2Placement3D
)。下图展示了 IfcAxis2Placement3D
工作的原理。
IfcLocalPlacement
构成递归体系和建筑空间对象之间的体系具有密切的联系。在一般实践中,只有 IfcSite
对象才会在全局坐标系中完成定位,而次级对象都以局部相对定位的方式来完成坐标描述。
除了定位系统以外,IFC 中还提供了对齐功能。IfcGrid
类提供了一种非常灵活的网格定义能力。此处所称的网格可以正方形的,辐条型的或者三角形的,如下图所示。非规则性的网格系统也能够被定义出来。基于网格的定位类 IfcGridPlacement
及其属性 PlacementLocation
可以描述在网格系统中的定位。
对于建筑对象的基础关键属性,如门、墙的属性,我们可以提前在 IFC 规范中通过成员属性的方式给出定义。不过针对不同业场景,这些基础建筑元素还可能衍生出大量不同的属性,这些属性如果直接以属性形式定义在其类定义中,无疑会使得整个模型变得臃肿,同时,这些扩展属性类型我们也无法提前全部定义。为了解决这个问题,IFC 将属性分为两类:一类是直接定义在 IFC 模型中的静态属性,另一类是可以动态创建的属性。动态属性可以通过 IfcProperty
的子类来完成定义(通常是 IfcPropertySingleValue
,这些属性可以根据需要而自由地谈加到实例中。动态属性的数量本身没有限制。新的对象属性的定义方式一简单的键值对形式进行。属性对象可以被 IfcPropertySet
组织起来并成组地被赋予给对象(通过 IfcRelDefinesByProperties
关系)。
IfcProxy
系统是对动态属性集机制的一个补充。代理机制使得我们可以在程序运行的动态环境下的赋予一个类以语义含义。
为了更加高效地描述在模型中频繁出现的对象,IFC 支持可复用对象(reusable types)。一个元素可以作为模板对象而被再次初始化,此时部分数据可以在新对象中被复用,而只有不同的部分需要被设置。IFC 支持在两种情况下的类型化。
若一个 IfcTypeObject
通过 IfRelDefinesByType
关系关联到一个对象,在对象被真正初始化之前,属性集对象会先被初始化并关联 IfcTypeObject
上,而具体的建筑元素对象通过建立和此对象的关系来索引扩展属性定义。
几何表示的复用可以通过 IfcMappedItems
来实现。同大多数 CAD 中引入的「块」概念类似,几何表示对象 IfcShapeReprestation
首先被常见出来,并存储在由一个局部坐标系定义的 IfcRepresentationMap
对象中。这个对象类似于 IfcPropertySet
,可以被分配给一个 IfcTypedObject
。以一个门的对象定义为例,当一个门被创建时,此对象会建立起对 IfcReprensentationMap
的引用,然后门对象会使用一个本地变换操作 IfcCartesianTransformationOperator
来得到自身的几何表示。
在处理的大型 JSON 文件的时候很容易发生内存爆掉的问题。即便原始数据力量上能够为内存所容纳,但是由于 Python 在内存中的记录方式,其内存消耗会比原始数据的体积要更大。如果遇到了开启 SWAP 的计算机,即便内存不爆掉,如果程序的运行内存进入缓冲区,也会导致运行速度的急剧下降。解决这个问题的方法是流式解析 (Stream parsing),也被称为 lazy parsing, iterative parsing,或者 chunked parsing。
考虑这样一个例子:一个大小是 24MB 的 JSON 文件,这个文件的内容代表了一系列的 Github 事件:
1 | [{"id":"2489651045","type":"CreateEvent","actor":{"id":665991,"login":"petroav","gravatar_id":"","url":"https://api.github.com/users/petroav","avatar_url":"https://avatars.githubusercontent.com/u/665991?"},"repo":{"id":28688495,"name":"petroav/6.828","url":"https://api.github.com/repos/petroav/6.828"},"payload":{"ref":"master","ref_type":"branch","master_branch":"master","description":"Solution to homework and assignments from MIT's 6.828 (Operating Systems Engineering). Done in my spare time.","pusher_type":"user"},"public":true,"created_at":"2015-01-01T15:00:00Z"}, |
我们的目标是找到指定用交互过的仓库,下面这个简单的 Python 程序可以达成这一目标:
1 | import json |
程序运行结果是用户名和仓库名的映射字典。当我们使用 File memory profile 分析的时候,我们可以得到如下结果:
观察内存峰值,我们可以看到两处主要的内存分配行为:
我们来看 Python 的 json 模块的实现可以发现,这个标准库中的 json.load()
函数会先把整个文件读入内存。
1 | def load(fp, *, cls=None, object_hook=None, parse_float=None, |
注意上面记录到的是内存峰值的现象,所以后续创建字典对象时,其内存占用已经不是峰值处。整个程序执行过程中峰值是读取文件产生的。
有意思的是,尽管文件本身只有 24MB,但是读入内存之后其产生的内存峰值却远高于 24MB。为什么呢?
Python 的字符串表达经过优化可以使用较少的内存(这取决于字符串的内容)。首先,每个字符串都由于一个固有的开销 (overhead)。其次,如果字符串能够以 ASCII 编码表达,那么每个字符都只需要占用一个字节的内存。单如果有更多种类的字符需要表示,则每个字符占用的内存就上升到 4 个字节。我们来看下面的代码执行过程:
1 | import sys |
这三个 case 中每个字符串的长度都是 1000,但是他们使用的内存大小是不同的,这与其内容有关。
显而易见将整个文件加载进入内存是一种内存的浪费。如果文件的体积非常大,那么我们甚至无法将整个文件读入内存。
如果 JSON 文件是一些对象组成的列表,那么理论上我们可以分片进行加载。个很多 Python 库支持这种行为,这里我们采用 ijson
库。
1 | import ijson |
在之前的标准库版本中,当数据被读入内存之后,文件就会被关闭。但是在现在这个场景下文件必须被保持打开。 这是因为文件的内容只有部分被读取,后续内容的读取取决后续的迭代过程。
items()
接口在此处的接受一个查询字符串用来指定加载的对象。在这个例子中 "item"
输入表示返回每个顶层对象。你可以参见 ijson
的文档来查询接口细节。
采用上面的代码我们可以发现程序的内存峰值降低到了 3.6MB。
]]>这个病毒会创建一个名为 biden1 的挖矿病毒,占满所有的 CPU 核心。如果用户尝试 kill 此进程,此进程会在短时间内自动拉起。这一机制是通过 systemd-run
命令的 transient 模式来实现的。我们可以通过 sudo systemctl status biden1-pid
查看到 biden1 进程的状态,可以看到该进程由 systemd 管理,但是处于 transient(abandon)
状态,这一状态可以让我们确定此进程是由 systemd-run
命令发起的。
查看 system-run
的文档,我们可以发现该工具有一个选项 --send-sighup
,其描述为:
When terminating the scope or service unit, send a SIGHUP immediately after SIGTERM. This is useful to indicate to shells and shell-like processes that the connection has been severed. Also see SendSIGHUP= in systemd.kill(5).
可以发现,如果设置了这个选项,那么如果此进程在收到 SIGTERM
信号时,systemd-run
会立刻给这个进程发出一个 SIGHUP
信号,将此进程再次拉起。同时,这意味着我们如果发送除 SIGTERM
以外其他的能够终止进程的信号,即可阻止此进程被再次拉起。我们可以选择 SIGKILL
信号,这一信号的编号是 9。故我们可以通过如下命令终止挖矿进程 biden1
:
1 | kill -9 biden1-pid |
注意,biden1
还有一个兄弟进程(名字忘记了,你可以在 systemctl status
中查看到二者处于同一 scope 内)需要以相同的方式终止。
除 biden1
以外,你可能会发现一个长时间保持 100% CPU 占用的 sshd 进程,这一进程事实上也是一个挖矿病毒,但是使用了更高阶的技巧来进行伪装。注意入侵者事实上并非替换了真正的 sshd 文件,可能是攻击者先将系统真正 sshd 文件移动到他处,然后在同一路径下设置同名的 sshd 病毒程序,并以 systemd-run
调起此进程,然后讲病毒文件删除,并恢复原 sshd 文件。此时你可以发现 sshd 文件的 md5 正确,但是运行中的 sshd 却是病毒程序的现象。事实上,使用 lsof
命令查看病毒 sshd 进程,可以发现其打开了一个 ESTABLISHED 的 http 链接(指向一个位于德国的 IP 地址),这是真正的 sshd 进程不会具有的行为。
既然此 sshd 进程同样是 systemd-run
发起,我们可以以前文类似的方式将其终止。注意有三个 httpd 进程与此 sshd 同组,同样需要终止。
这里给出的方法能够终止挖矿进程,但是并不一定能彻底清楚相关文件,在服务器重启后是 biden1 进程是否会再次调起,这仍有待进一步验证。
]]>