1+1=10

记记笔记,放松一下...

FreeCAD学习小记(二)

接前面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代码:

1
2
from PySide2.QtWidgets import QMessageBox
QMessageBox.aboutQt(Gui.getMainWindow(), '1+1=10')

两行代码,即可调用Qt的关于对话框,显示效果如下:

console

注意,代码中我们调用了 FreeCAD 的 Gui 模块,以获取当前的主窗口。当然,此处简单传入一个None也是可以的。

注意,为了兼容PySide、PySide2、PySide6,FreeCAD做了如下变换:

1
2
3
4
5
6
PySide2.QtCore -> PySide.QtCore
PySide2.QtGui -> PySide.QtGui
PySide2.QtSvg -> PySide.QtSvg
PySide2.QtUiTools -> PySide.QtUiTools

PySide2.QtWidgets -> PySide.QtGui

所以上面的代码,可以写作(建议写成?):

1
2
from PySide.QtGui import QMessageBox
QMessageBox.aboutQt(Gui.getMainWindow(), '1+1=10')

除了在控制台下面直接输入,我们还可以将其保存到文件(一个名为 .FCMacro 文件),而后通过 菜单中的 来执行它。

macro aboutQt

Gui 和 App

这两个兄弟是 FreeCADGui 和 FreeCAD 的别名,控制台下容易验证它:

1
2
3
4
5
6
>>> Gui
<module 'FreeCADGui'>
>>> App
<module 'FreeCAD' (built-in)>
>>> FreeCAD.Gui
<module 'FreeCADGui'>

另外,可以看出 FreeCAD.Gui 也是 FreeCADGui的别名。这些别名是在什么位置取的?我们可以从源码中找到...

在源码文件 FreeCAD/src/Gui/FreeCADGuiInit.py中,有:

1
2
# 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 中,可以看到:

1
2
3
4
5
6
7
8
9
# 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的经验,借助它,我们可以做试着做些简单的事情

控制窗口全屏显示或正常显示

1
2
Gui.getMainWindow().showFullScreen()
Gui.getMainWindow().showNormal()

改变标题

1
Gui.getMainWindow().setWindowTitle("Hello 1+1=10")

也可以直接放置一个停靠窗口

1
2
3
4
5
6
7
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)

dock widget

添加自定义控制台(Workbench)

先找找感觉,添加自定义控制台Hello 1+1=10,并增加一个自定义的菜单,对应代码如下:

1
2
3
4
5
w=Workbench()
w.MenuText = "Hello 1+1=10"
Gui.addWorkbench(w)
list = ["Std_Test1", "Std_Test2", "Std_Test3"]
w.appendMenu("Hello", list)

workbench

对界面的所有操作,都在Gui模块中。

freecad 与 freecadcmd

FreeCAD 有两个可执行程序

  • freecad:界面程序
  • freecadcmd:命令行程序

前者是常用的主程序,源码的入口文件在:

1
src/Main/MainGui.cpp

后者是一个全功能的命令行程序,入口文件位于:

1
src/Main/MainCmd.cpp

简单看一下后者

freecadcmd

FreeCADCmd 是 FreeCAD 的命令行模式(无图形界面模式),主要用于在没有图形用户界面(GUI)的情况下运行 FreeCAD 脚本、执行自动化任务、或者处理 CAD 文件。它适用于服务器环境、后台任务或只需要 FreeCAD 核心功能的场景。

直接运行起来之后,它直接就是一个python命令行:

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
doc = App.newDocument("TestDoc")

box = doc.addObject("Part::Box", "Box")
box.Length = 10
box.Width = 10
box.Height = 10

doc.saveAs("test.FCStd")

而后直接执行它:

1
freecadcmd mytest.py

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 的属性管理和文档系统中。

1
2
3
4
5
6
7
8
9
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()

支持单位,所以可以:

1
2
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 视图、任务面板、属性面板等)中显示和交互。

1
2
d = Gui.ActiveDocument
d = Gui.activeDocument()

Objects

和App中的DocumentObject对应,GUI中提供显示支持。

获取当前对象:

1
2
o = Gui.ActiveDocument.activeObject()
o.ShapeColor = (1.0, 0.0, 0.0, 1.0)

获取指定名称对象

1
o = Gui.ActiveDocument.getObject("MyCube")

获取顶层对象列表

1
root_objects = Gui.ActiveDocument.TreeRootObjects

View 视图

获取视图

1
Gui.ActiveDocument.ActiveView
  • 设置视角(如顶部视图、正视图、轴测视图等)。
  • 调整视图(如缩放、平移、适应所有对象)。
  • 获取鼠标及屏幕坐标信息。
  • 捕获当前视图为图像。
  • 获取用户交互信息(如鼠标点击位置)。
  • 实时控制相机和视图参数。

比如:

1
2
3
4
5
6
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