接前面FreeCAD学习小记(一),继续看看 FreeCAD 的python接口 与 PySide
PySide 走马观花
FreeCAD 早期使用 PyQt,2013年换成了 PySide,而后随着Qt5升级到PySide2,现在源码中已经适配到了PySide6。
FreeCAD 深度使用 Qt 和 PySide。FreeCAD 1.0 在 Windows下安装包使用的是 Qt 5.15.15和PySide2 5.15.15。
控制台下调出 aboutQt 对话框
FreeCAD 提供了一个 Python 控制台,在该控制台下,我们可以直接输如python和pyside代码:
| from PySide2.QtWidgets import QMessageBox
QMessageBox.aboutQt(Gui.getMainWindow(), '1+1=10')
|
两行代码,即可调用Qt的关于对话框,显示效果如下:
注意,代码中我们调用了 FreeCAD 的 Gui
模块,以获取当前的主窗口。当然,此处简单传入一个None也是可以的。
注意,为了兼容PySide、PySide2、PySide6,FreeCAD做了如下变换:
| PySide2.QtCore -> PySide.QtCore
PySide2.QtGui -> PySide.QtGui
PySide2.QtSvg -> PySide.QtSvg
PySide2.QtUiTools -> PySide.QtUiTools
PySide2.QtWidgets -> PySide.QtGui
|
所以上面的代码,可以写作(建议写成?):
| from PySide.QtGui import QMessageBox
QMessageBox.aboutQt(Gui.getMainWindow(), '1+1=10')
|
除了在控制台下面直接输入,我们还可以将其保存到文件(一个名为 .FCMacro 文件),而后通过 菜单中的 宏
来执行它。
Gui 和 App
这两个兄弟是 FreeCADGui 和 FreeCAD 的别名,控制台下容易验证它:
| >>> Gui
<module 'FreeCADGui'>
>>> App
<module 'FreeCAD' (built-in)>
>>> FreeCAD.Gui
<module 'FreeCADGui'>
|
另外,可以看出 FreeCAD.Gui 也是 FreeCADGui的别名。这些别名是在什么位置取的?我们可以从源码中找到...
在源码文件 FreeCAD/src/Gui/FreeCADGuiInit.py
中,有:
| # shortcuts
Gui = FreeCADGui
|
和
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | # init the gui
# signal that the gui is up
App.GuiUp = 1
App.Gui = FreeCADGui
FreeCADGui.Workbench = Workbench
Gui.addWorkbench(NoneWorkbench())
# init modules
InitApplications()
# set standard workbench (needed as fallback)
Gui.activateWorkbench("NoneWorkbench")
|
在源码 FreeCAD/src/App/FreeCADInit.py
中,可以看到:
| # some often used shortcuts (for lazy people like me ;-)
App = FreeCAD
Log = FreeCAD.Console.PrintLog
Msg = FreeCAD.Console.PrintMessage
Err = FreeCAD.Console.PrintError
Wrn = FreeCAD.Console.PrintWarning
Crt = FreeCAD.Console.PrintCritical
Ntf = FreeCAD.Console.PrintNotification
Tnf = FreeCAD.Console.PrintTranslatedNotification
|
修改主窗口
上面的例子中通过Gui可以获得主窗口对象,根据PySide的经验,借助它,我们可以做试着做些简单的事情
控制窗口全屏显示或正常显示
| Gui.getMainWindow().showFullScreen()
Gui.getMainWindow().showNormal()
|
改变标题
| Gui.getMainWindow().setWindowTitle("Hello 1+1=10")
|
也可以直接放置一个停靠窗口
| from PySide.QtCore import Qt
from PySide import QtGui
d = QtGui.QDockWidget("Hello Dock")
d.setWidget(QtGui.QLabel("1+1=10"))
mw = Gui.getMainWindow()
mw.addDockWidget(Qt.LeftDockWidgetArea, d)
|
添加自定义控制台(Workbench)
先找找感觉,添加自定义控制台Hello 1+1=10
,并增加一个自定义的菜单,对应代码如下:
| w=Workbench()
w.MenuText = "Hello 1+1=10"
Gui.addWorkbench(w)
list = ["Std_Test1", "Std_Test2", "Std_Test3"]
w.appendMenu("Hello", list)
|
对界面的所有操作,都在Gui模块中。
freecad 与 freecadcmd
FreeCAD 有两个可执行程序
- freecad:界面程序
- freecadcmd:命令行程序
前者是常用的主程序,源码的入口文件在:
后者是一个全功能的命令行程序,入口文件位于:
简单看一下后者
freecadcmd
FreeCADCmd 是 FreeCAD 的命令行模式(无图形界面模式),主要用于在没有图形用户界面(GUI)的情况下运行 FreeCAD 脚本、执行自动化任务、或者处理 CAD 文件。它适用于服务器环境、后台任务或只需要 FreeCAD 核心功能的场景。
直接运行起来之后,它直接就是一个python命令行:
| PS D:\Program Files\FreeCAD 1.0\bin> ./freecadcmd
FreeCAD 1.0.0, Libs: 1.0.0R39109 (Git)
(C) 2001-2024 FreeCAD contributors
FreeCAD is free and open-source software licensed under the terms of LGPL2+ license.
[FreeCAD Console mode <Use Ctrl-Z plus Return to exit.>]
>>> dir(App)
['ActiveDocument', 'Axis', 'Base', 'BoundBox', 'ConfigDump', 'ConfigGet', 'ConfigSet', 'Console', 'Document', 'DocumentObject', 'DocumentObjectExtension', 'DocumentObjectGroup', 'Extension', 'ExtensionContainer', 'GeoFeature', 'GeoFeatureGroupExtension', 'GroupExtension', 'GuiUp', 'LinkBaseExtension', 'Logger', 'Material', 'Matrix', 'MeasureManager', 'Metadata', 'OriginGroupExtension', 'ParamGet', 'Placement', 'PropertyContainer', 'PropertyType', 'Qt', 'ReturnType', 'Rotation', 'ScaleType', 'StringHasher', 'StringID', 'Units', 'Vector', 'Version', '__ModDirs__', '__cmake__', '__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__unit_test__', '_importFromFreeCAD', 'activeDocument', 'addDocumentObserver', 'addExportType', 'addImportType', 'changeExportModule', 'changeImportModule', 'checkAbort', 'checkLinkDepth', 'closeActiveTransaction', 'closeDocument', 'getActiveTransaction', 'getDependentObjects', 'getDocument', 'getExportType', 'getHelpDir', 'getHomePath', 'getImportType', 'getLibraryDir', 'getLinksTo', 'getLogLevel', 'getResourceDir', 'getTempPath', 'getUserAppDataDir', 'getUserCachePath', 'getUserConfigDir', 'getUserMacroDir', 'isRestoring', 'listDocuments', 'loadFile', 'newDocument', 'open', 'openDocument', 'removeDocumentObserver', 'saveParameter', 'setActiveDocument', 'setActiveTransaction', 'setLogLevel']
>>>
|
在这个界面下,可以绘制一个立方体(操作的核心类是 document):
1
2
3
4
5
6
7
8
9
10
11
12
13 | > ./freecadcmd
FreeCAD 1.0.0, Libs: 1.0.0R39109 (Git)
(C) 2001-2024 FreeCAD contributors
FreeCAD is free and open-source software licensed under the terms of LGPL2+ license.
[FreeCAD Console mode <Use Ctrl-Z plus Return to exit.>]
>>> doc = App.newDocument("Test")
>>> box = doc.addObject("Part::Box", "Box")
>>> box.Length = 10
>>> box.Width = 10
>>> box.Height = 10
>>> doc.saveAs("test.FCStd")
>>>
|
还可以先写一个python脚本 mytest.py
:
| doc = App.newDocument("TestDoc")
box = doc.addObject("Part::Box", "Box")
box.Length = 10
box.Width = 10
box.Height = 10
doc.saveAs("test.FCStd")
|
而后直接执行它:
API文档
似乎很不完整
- https://freecad.github.io/SourceDoc/modules.html
APP 模块
Document 类
Document 是 FreeCAD 数据结构的顶层容器,代表一个 FreeCAD 项目或文件(通常以 .FCStd 格式保存)。它是一个逻辑上的工作空间,包含了所有的建模对象、参数、视图信息等。
每个 FreeCAD 文档都可以包含多个对象(DocumentObject),这些对象构成了文档的内容,例如几何体、草图、装配体等。
- 管理和组织 DocumentObject。
- 提供文件的保存、加载和重新计算功能。
- 允许通过脚本访问和操作项目中的所有对象。
- doc.addObject(type, name) - 向文档添加一个新对象。
- doc.getObject(name) - 根据名称获取对象。
- doc.removeObject(name) - 删除指定名称的对象。
- doc.recompute() - 重新计算文档中的所有对象。
- doc.saveAs(filename) - 保存文档到指定路径。
- doc.save() - 保存文档(覆盖当前路径)。
- doc.Label - 获取或设置文档的标签。
- doc.Objects - 返回文档中所有对象的列表。
- doc.getObjectsByLabel(label) - 根据标签获取对象。
- doc.getDependentDocuments(objName) - 获取指定对象的依赖关系。
- doc.undo() - 撤销上一步操作。
- doc.redo() - 重做上一步操作。
- doc.clear() - 清空文档中的所有内容。
- doc.isModified() - 检查文档是否已被修改。
- doc.FileName - 返回文档的文件路径。
- doc.ActiveObject - 当前活动的对象。
DocumentObject 类
DocumentObject 是 Document 中包含的实际对象,每个对象代表具体的建模元素或功能,例如一个立方体、草图、约束、几何体等。
DocumentObject 是 FreeCAD 的核心建模单位,所有的几何体、关系和属性都存储在这些对象中。
- 定义具体的建模元素(如立方体、球体、草图等)。
- 提供对象的属性(如形状、尺寸、位置等)。
- 支持对象之间的关联和依赖关系。
一些接口:
- obj.Name - 对象的唯一标识名称。
- obj.Label - 对象的标签(可用于显示)。
- obj.TypeId - 对象的类型(如 Part::Box)。
- obj.Shape - 获取对象的几何形状(如立方体的形状)。
- obj.Placement - 获取或设置对象的位置和方向。
- obj.ViewObject - 控制对象的显示属性(颜色、透明度等)。
- obj.Visibility - 设置对象是否可见。
- obj.touch() - 标记对象为已修改(需要重新计算)。
- obj.recompute() - 重新计算对象。
- obj.getLinkedObject() - 获取与对象链接在一起的对象。
- obj.Document - 获取对象所属的文档。
- obj.getParentGeoFeatureGroup() - 获取对象所在的父组。
- obj.isDerivedFrom(type) - 检查对象是否派生自某类型。
- obj.PropertiesList - 返回对象的所有属性。
- obj.getPropertyByName(name) - 根据属性名称获取值。
- obj.addObject(type, name) - 添加子对象。
- obj.removeObject(name) - 删除子对象。
Expressions Framework
FreeCAD 自己实现了一个功能模块。尽管它使用 Python 的语法和表达式风格,但它是专门为 FreeCAD 的参数化建模功能设计的,并深度集成到了 FreeCAD 的属性管理和文档系统中。
| doc = App.newDocument("Hello")
# App.setActiveDocument("Hello")
# App.ActiveDocument=App.getDocument("Hello")
# Gui.ActiveDocument=Gui.getDocument("Hello")
box = doc.addObject("Part::Box", "MyCube")
box.Length = 100 # set to 100 mm
box.setExpression("Width", "Length *2")
box.setExpression("Height", "Width / 2")
doc.recompute()
|
支持单位,所以可以:
| box.Length = "2 in"
doc.recompute()
|
核心依赖于:
- Property 系统:管理对象的属性,支持绑定表达式并动态计算值。
- Expression Evaluator:解析表达式,计算属性值。建立属性之间的依赖关系,确保重计算时值的正确性。
- Unit Framework:处理单位并确保单位一致性。支持物理单位(如 mm, in, deg)的自动转换。
Gui 模块
FreeCADGui(即Gui)是 FreeCAD 中的图形用户界面模块,用于管理和操作 FreeCAD 的 GUI(图形用户界面)部分。
它与 FreeCAD 核心模块(即App模块)不同,
- FreeCAD 主要用于后台建模和管理文档数据,而
- FreeCADGui 则负责与用户交互的可视化相关内容,例如 3D 视图、工具栏、任务面板、属性编辑等。
GUI Document
GUI Document 是一个与普通文档(Document)相对应的图形用户界面表示。它由 FreeCAD 的 FreeCADGui.ActiveDocument 管理,负责处理文档中的对象如何在 FreeCAD 的图形界面(如 3D 视图、任务面板、属性面板等)中显示和交互。
| d = Gui.ActiveDocument
d = Gui.activeDocument()
|
Objects
和App中的DocumentObject对应,GUI中提供显示支持。
获取当前对象:
| o = Gui.ActiveDocument.activeObject()
o.ShapeColor = (1.0, 0.0, 0.0, 1.0)
|
获取指定名称对象
| o = Gui.ActiveDocument.getObject("MyCube")
|
获取顶层对象列表
| root_objects = Gui.ActiveDocument.TreeRootObjects
|
View 视图
获取视图
| Gui.ActiveDocument.ActiveView
|
- 设置视角(如顶部视图、正视图、轴测视图等)。
- 调整视图(如缩放、平移、适应所有对象)。
- 获取鼠标及屏幕坐标信息。
- 捕获当前视图为图像。
- 获取用户交互信息(如鼠标点击位置)。
- 实时控制相机和视图参数。
比如:
| view = Gui.ActiveDocument.ActiveView
view.viewTop()
view.viewBottom()
view.viewFront()
view.viewAxometric()
|
Command Framework?
Qt Creator 内部似乎也实现了类似的 Command 框架。
Qt中菜单项和工具按钮,都是QAction,在FreeCAD中,为了让QAction和workbench等模组解耦,它定义了一套所谓的命令框架(Command Framework)。
在C++下,Open命令定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | class OpenCommand : public Command
{
public:
OpenCommand() : Command("Std_Open")
{
// set up menu text, status tip, ...
sMenuText = "&Open";
sToolTipText = "Open a file";
sWhatsThis = "Open a file";
sStatusTip = "Open a file";
sPixmap = "Open"; // name of a registered pixmap
sAccel = "Shift+P"; // or "P" or "P, L" or "Ctrl+X, Ctrl+C" for a sequence
}
protected:
void activated(int)
{
QString filter ... // make a filter of all supported file formats
QStringList FileList = QFileDialog::getOpenFileNames( filter,QString(), getMainWindow() );
for ( QStringList::Iterator it = FileList.begin(); it != FileList.end(); ++it ) {
getGuiApplication()->open((*it).latin1());
}
}
};
|
workbench
Workbench 主要配置菜单栏、工具栏、命令、停靠窗口
- setupMenuBar()
- setupToolBars()
- setupCommandBars()
- setupDockWindows()
参考
- https://wiki.freecad.org/PySide
- https://freecad.github.io/SourceDoc/modules.html
- https://github.com/FreeCAD/FreeCAD
- https://github.com/FreeCAD/FreeCAD-library
- https://github.com/shaise/FreeCAD_FastenersWB
- https://github.com/boltsparts/boltsparts