diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index e5d31d7..789cb0d 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,14 +4,16 @@
-
+
+
+
+
+
-
-
@@ -48,45 +50,45 @@
- {
- "keyToString": {
- "Python.2dtest.executor": "Run",
- "Python.3d_windows.executor": "Run",
- "Python.Unnamed.executor": "Run",
- "Python.base.executor": "Run",
- "Python.data_model.executor": "Run",
- "Python.draw_widget2d.executor": "Run",
- "Python.draw_widget_solve.executor": "Run",
- "Python.fluency.executor": "Run",
- "Python.fluencyb.executor": "Run",
- "Python.gl_widget.executor": "Run",
- "Python.gui_ui.executor": "Run",
- "Python.kernel.executor": "Run",
- "Python.main.executor": "Run",
- "Python.meshtest.executor": "Run",
- "Python.occ_renderer.executor": "Run",
- "Python.occ_to_mesh.executor": "Run",
- "Python.render_backend.executor": "Run",
- "Python.side_fluency.executor": "Run",
- "Python.simple_mesh.executor": "Run",
- "Python.sketch.executor": "Run",
- "Python.vtk_widget.executor": "Run",
- "Python.vulkan.executor": "Run",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
- "RunOnceActivity.git.unshallow": "true",
- "RunOnceActivity.typescript.service.memoryLimit.init": "true",
- "codeWithMe.voiceChat.enabledByDefault": "false",
- "git-widget-placeholder": "feature/occ-migration",
- "last_opened_file_path": "/Volumes/Data_drive/Programming/fluency/src/fluency/rendering",
- "node.js.detected.package.eslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
+
+}]]>
+
-
@@ -432,7 +434,15 @@
1783918463034
-
+
+
+ 1783976498520
+
+
+
+ 1783976498520
+
+
@@ -453,7 +463,6 @@
-
@@ -478,6 +487,7 @@
-
+
+
\ No newline at end of file
diff --git a/gui.ui b/gui.ui
index 0dc3b69..d8f251f 100644
--- a/gui.ui
+++ b/gui.ui
@@ -6,8 +6,8 @@
0
0
- 2359
- 1285
+ 2668
+ 1287
@@ -15,72 +15,167 @@
- -
-
+
-
+
+
+
+ 0
+ 120
+
+
+
+ Assembly
+
+
+
+ -
+
-
+
0
0
-
- 0
+
+
+ 200
+ 16777215
+
-
-
- Sketch
-
-
-
-
-
- Code
-
-
-
-
-
-
- -
-
-
- Executive
-
-
-
-
-
-
- Load Code
-
-
-
- -
-
-
- Save code
-
-
-
- -
-
-
- Apply Code
-
-
-
- -
-
-
- Delete Code
-
-
-
-
-
-
-
-
+
+ Drawing
+
+
+
+ 10
+
+ -
+
+
+ Arc
+
+
+ true
+
+
+
+ -
+
+
+ Rctgl
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ Circle
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ Cstrct
+
+
+ true
+
+
+
+ -
+
+
+ Snap
+
+
+
+ 13
+ 16
+
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+ Line
+
+
+ S
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ Slot
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Offset selected sketch face (duplicate + offset boundary)
+
+
+ Offst
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
-
@@ -196,696 +291,6 @@
- -
-
-
-
- 0
- 50
-
-
-
- Assembly
-
-
-
- -
-
-
-
- 0
- 50
-
-
-
- Joint Tools
-
-
-
-
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- - Cnct
-
-
-
- -
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- + Cnct
-
-
-
- -
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- +Jnt
-
-
-
- -
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- -Jnt
-
-
-
-
-
-
- -
-
-
-
- 0
- 4
-
-
-
-
- 12
-
-
-
- Model Viewer
-
-
-
- -1
-
-
- 12
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 200
- 16777215
-
-
-
- Sketch
-
-
-
- 5
-
-
- 5
-
-
- 5
-
-
- 5
-
-
-
-
-
-
- 0
- 0
-
-
-
- true
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Tools
-
-
-
- 2
-
-
- 2
-
-
- 2
-
-
- 2
-
-
-
-
-
- Edt
-
-
-
- -
-
-
- Add
-
-
-
- -
-
-
- Del
-
-
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 50
-
-
-
- Assembly Tools
-
-
-
-
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- Add
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- Qt::LeftToRight
-
-
- Rem
-
-
-
-
-
-
- -
-
-
-
- 0
- 50
-
-
-
- Component Tools
-
-
-
-
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- New
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 50
- 50
-
-
-
-
- 50
- 50
-
-
-
- Qt::LeftToRight
-
-
- Del
-
-
-
-
-
-
- -
-
-
-
- 0
- 50
-
-
-
- Components
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 200
- 16777215
-
-
-
- Component Connections
-
-
-
- 5
-
-
- 5
-
-
- 5
-
-
- 5
-
-
-
-
-
- true
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 200
- 16777215
-
-
-
- Tools
-
-
-
- 2
-
-
- 2
-
-
- 2
-
-
- 2
-
-
-
-
-
- Del
-
-
-
- -
-
-
- Upd
-
-
-
- -
-
-
- Nothing
-
-
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 200
- 16777215
-
-
-
- Bodys / Operations
-
-
-
- 5
-
-
- 5
-
-
- 5
-
-
- 5
-
-
-
-
-
- true
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 200
- 16777215
-
-
-
- Tools
-
-
-
- 2
-
-
- 2
-
-
- 2
-
-
- 2
-
-
-
-
-
- Del
-
-
-
- -
-
-
- Upd
-
-
-
- -
-
-
- Nothing
-
-
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 200
- 16777215
-
-
-
- Drawing
-
-
-
- 10
-
-
-
-
-
- Arc
-
-
- true
-
-
-
- -
-
-
- Rctgl
-
-
- true
-
-
- false
-
-
-
- -
-
-
- Circle
-
-
- true
-
-
- false
-
-
-
- -
-
-
- Cstrct
-
-
- true
-
-
-
- -
-
-
- Snap
-
-
-
- 13
- 16
-
-
-
- true
-
-
- true
-
-
-
- -
-
-
- Line
-
-
- S
-
-
- true
-
-
- false
-
-
-
- -
-
-
- Slot
-
-
- true
-
-
- false
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- -
-
-
- Offset selected sketch face (duplicate + offset boundary)
-
-
- Offst
-
-
-
-
-
-
-
@@ -1042,6 +447,147 @@
+ -
+
+
+
+ 200
+ 16777215
+
+
+
+ Modify
+
+
+
-
+
+
+ Rev
+
+
+
+ -
+
+
+ Extrd
+
+
+
+ -
+
+
+ Arry
+
+
+
+ -
+
+
+ Cut
+
+
+
+ -
+
+
+ Comb
+
+
+
+ -
+
+
+ Mve
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 120
+
+
+
+ Components
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 50
+
+
+
+ Component Tools
+
+
+
-
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
+
+
+ New
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
+
+
+ Qt::LeftToRight
+
+
+ Del
+
+
+
+
+
+
-
@@ -1194,82 +740,573 @@
- -
-
-
- Modify
+
-
+
+
+
+ 0
+ 50
+
-
-
-
-
-
- Rev
-
-
-
+
+ Joint Tools
+
+
-
-
-
- Extrd
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
-
-
- -
-
- Arry
+ + Cnct
-
-
-
- Cut
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
-
-
- -
-
- Comb
+ - Cnct
-
-
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
+
- Mve
+ -Jnt
+
+
+
+ -
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
+
+
+ +Jnt
- -
+
-
+
+
+ 200
+ 16777215
+
+
Export
-
-
-
+
+
-
STL
- -
+
-
+
+
+ IGES
+
+
+
+ -
STEP
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 113
+ 50
+
+
+
+ Assembly Tools
+
+
-
-
-
- IGES
+
+
+
+ 50
+ 50
+
+
+
+ 50
+ 50
+
+
+
+ Add
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
+
+
+ Qt::LeftToRight
+
+
+ Rem
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+ Sketch
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Tools
+
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ Edt
+
+
+
+ -
+
+
+ Add
+
+
+
+ -
+
+
+ Del
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 0
+
+
+
+ Sketch
+
+
+
+
+
+ Code
+
+
+
-
+
+
+ -
+
+
+ Executive
+
+
+
-
+
+
+ Load Code
+
+
+
+ -
+
+
+ Save code
+
+
+
+ -
+
+
+ Apply Code
+
+
+
+ -
+
+
+ Delete Code
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 4
+
+
+
+
+ 12
+
+
+
+ Model Viewer
+
+
+
+ -1
+
+
+ 12
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+ Component Connections
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+ Tools
+
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ Del
+
+
+
+ -
+
+
+ Upd
+
+
+
+ -
+
+
+ Nothing
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+ Bodys / Operations
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 200
+ 16777215
+
+
+
+ Tools
+
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ Del
+
+
+
+ -
+
+
+ Upd
+
+
+
+ -
+
+
+ Nothing
+
+
+
+
@@ -1282,7 +1319,7 @@
0
0
- 2359
+ 2668
24
diff --git a/gui_ui.py b/gui_ui.py
index d2346b7..aec3c76 100644
--- a/gui_ui.py
+++ b/gui_ui.py
@@ -19,14 +19,14 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QGroupBox,
QHBoxLayout, QLabel, QListWidget, QListWidgetItem,
QMainWindow, QMenu, QMenuBar, QPushButton,
- QSizePolicy, QSpinBox, QStatusBar, QTabWidget,
- QTextEdit, QVBoxLayout, QWidget)
+ QSizePolicy, QSpacerItem, QSpinBox, QStatusBar,
+ QTabWidget, QTextEdit, QVBoxLayout, QWidget)
class Ui_fluencyCAD(object):
def setupUi(self, fluencyCAD):
if not fluencyCAD.objectName():
fluencyCAD.setObjectName(u"fluencyCAD")
- fluencyCAD.resize(2359, 1285)
+ fluencyCAD.resize(2668, 1287)
self.actionNew_Project = QAction(fluencyCAD)
self.actionNew_Project.setObjectName(u"actionNew_Project")
self.actionOpen_Project = QAction(fluencyCAD)
@@ -49,57 +49,89 @@ class Ui_fluencyCAD(object):
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName(u"gridLayout")
- self.InputTab = QTabWidget(self.centralwidget)
- self.InputTab.setObjectName(u"InputTab")
- sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
+ self.assembly_box = QGroupBox(self.centralwidget)
+ self.assembly_box.setObjectName(u"assembly_box")
+ self.assembly_box.setMinimumSize(QSize(0, 120))
+
+ self.gridLayout.addWidget(self.assembly_box, 11, 1, 1, 3)
+
+ self.groupBox_2 = QGroupBox(self.centralwidget)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth())
- self.InputTab.setSizePolicy(sizePolicy)
- self.sketch_tab = QWidget()
- self.sketch_tab.setObjectName(u"sketch_tab")
- self.verticalLayout_4 = QVBoxLayout(self.sketch_tab)
- self.verticalLayout_4.setObjectName(u"verticalLayout_4")
- self.InputTab.addTab(self.sketch_tab, "")
- self.code_tab = QWidget()
- self.code_tab.setObjectName(u"code_tab")
- self.verticalLayout = QVBoxLayout(self.code_tab)
- self.verticalLayout.setObjectName(u"verticalLayout")
- self.textEdit = QTextEdit(self.code_tab)
- self.textEdit.setObjectName(u"textEdit")
+ sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth())
+ self.groupBox_2.setSizePolicy(sizePolicy)
+ self.groupBox_2.setMaximumSize(QSize(200, 16777215))
+ self.gridLayout_2 = QGridLayout(self.groupBox_2)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.gridLayout_2.setContentsMargins(10, -1, -1, -1)
+ self.pb_arc_tool = QPushButton(self.groupBox_2)
+ self.pb_arc_tool.setObjectName(u"pb_arc_tool")
+ self.pb_arc_tool.setCheckable(True)
- self.verticalLayout.addWidget(self.textEdit)
+ self.gridLayout_2.addWidget(self.pb_arc_tool, 2, 0, 1, 1)
- self.groupBox_7 = QGroupBox(self.code_tab)
- self.groupBox_7.setObjectName(u"groupBox_7")
- self.gridLayout_5 = QGridLayout(self.groupBox_7)
- self.gridLayout_5.setObjectName(u"gridLayout_5")
- self.pushButton_5 = QPushButton(self.groupBox_7)
- self.pushButton_5.setObjectName(u"pushButton_5")
+ self.pb_rectool = QPushButton(self.groupBox_2)
+ self.pb_rectool.setObjectName(u"pb_rectool")
+ self.pb_rectool.setCheckable(True)
+ self.pb_rectool.setAutoExclusive(False)
- self.gridLayout_5.addWidget(self.pushButton_5, 2, 0, 1, 1)
+ self.gridLayout_2.addWidget(self.pb_rectool, 0, 1, 1, 1)
- self.pushButton_4 = QPushButton(self.groupBox_7)
- self.pushButton_4.setObjectName(u"pushButton_4")
+ self.pb_circtool = QPushButton(self.groupBox_2)
+ self.pb_circtool.setObjectName(u"pb_circtool")
+ self.pb_circtool.setCheckable(True)
+ self.pb_circtool.setAutoExclusive(False)
- self.gridLayout_5.addWidget(self.pushButton_4, 2, 1, 1, 1)
+ self.gridLayout_2.addWidget(self.pb_circtool, 1, 0, 1, 1)
- self.pb_apply_code = QPushButton(self.groupBox_7)
- self.pb_apply_code.setObjectName(u"pb_apply_code")
+ self.pb_enable_construct = QPushButton(self.groupBox_2)
+ self.pb_enable_construct.setObjectName(u"pb_enable_construct")
+ self.pb_enable_construct.setCheckable(True)
- self.gridLayout_5.addWidget(self.pb_apply_code, 1, 0, 1, 1)
+ self.gridLayout_2.addWidget(self.pb_enable_construct, 4, 0, 1, 1)
- self.pushButton = QPushButton(self.groupBox_7)
- self.pushButton.setObjectName(u"pushButton")
+ self.pb_enable_snap = QPushButton(self.groupBox_2)
+ self.pb_enable_snap.setObjectName(u"pb_enable_snap")
+ self.pb_enable_snap.setIconSize(QSize(13, 16))
+ self.pb_enable_snap.setCheckable(True)
+ self.pb_enable_snap.setChecked(True)
- self.gridLayout_5.addWidget(self.pushButton, 1, 1, 1, 1)
+ self.gridLayout_2.addWidget(self.pb_enable_snap, 4, 1, 1, 1)
+
+ self.pb_linetool = QPushButton(self.groupBox_2)
+ self.pb_linetool.setObjectName(u"pb_linetool")
+ self.pb_linetool.setCheckable(True)
+ self.pb_linetool.setAutoExclusive(False)
+
+ self.gridLayout_2.addWidget(self.pb_linetool, 0, 0, 1, 1)
+
+ self.pb_slotool = QPushButton(self.groupBox_2)
+ self.pb_slotool.setObjectName(u"pb_slotool")
+ self.pb_slotool.setCheckable(True)
+ self.pb_slotool.setAutoExclusive(False)
+
+ self.gridLayout_2.addWidget(self.pb_slotool, 1, 1, 1, 1)
+
+ self.line = QFrame(self.groupBox_2)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.Shape.HLine)
+ self.line.setFrameShadow(QFrame.Shadow.Sunken)
+
+ self.gridLayout_2.addWidget(self.line, 3, 0, 1, 2)
+
+ self.pb_offset_tool = QPushButton(self.groupBox_2)
+ self.pb_offset_tool.setObjectName(u"pb_offset_tool")
+
+ self.gridLayout_2.addWidget(self.pb_offset_tool, 2, 1, 1, 1)
- self.verticalLayout.addWidget(self.groupBox_7)
+ self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1)
- self.InputTab.addTab(self.code_tab, "")
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
- self.gridLayout.addWidget(self.InputTab, 0, 1, 12, 1)
+ self.gridLayout.addItem(self.horizontalSpacer, 6, 0, 1, 5)
self.groupBox_9 = QGroupBox(self.centralwidget)
self.groupBox_9.setObjectName(u"groupBox_9")
@@ -150,339 +182,10 @@ class Ui_fluencyCAD(object):
self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1)
- self.assembly_box = QGroupBox(self.centralwidget)
- self.assembly_box.setObjectName(u"assembly_box")
- self.assembly_box.setMinimumSize(QSize(0, 50))
-
- self.gridLayout.addWidget(self.assembly_box, 13, 1, 1, 2)
-
- self.joint_tools = QGroupBox(self.centralwidget)
- self.joint_tools.setObjectName(u"joint_tools")
- self.joint_tools.setMinimumSize(QSize(0, 50))
- self.gridLayout_10 = QGridLayout(self.joint_tools)
- self.gridLayout_10.setObjectName(u"gridLayout_10")
- self.pb_remove_connector = QPushButton(self.joint_tools)
- self.pb_remove_connector.setObjectName(u"pb_remove_connector")
- self.pb_remove_connector.setMinimumSize(QSize(50, 50))
- self.pb_remove_connector.setMaximumSize(QSize(50, 50))
-
- self.gridLayout_10.addWidget(self.pb_remove_connector, 0, 2, 1, 1)
-
- self.pb_add_connector = QPushButton(self.joint_tools)
- self.pb_add_connector.setObjectName(u"pb_add_connector")
- self.pb_add_connector.setMinimumSize(QSize(50, 50))
- self.pb_add_connector.setMaximumSize(QSize(50, 50))
-
- self.gridLayout_10.addWidget(self.pb_add_connector, 0, 1, 1, 1)
-
- self.pb_add_connector_2 = QPushButton(self.joint_tools)
- self.pb_add_connector_2.setObjectName(u"pb_add_connector_2")
- self.pb_add_connector_2.setMinimumSize(QSize(50, 50))
- self.pb_add_connector_2.setMaximumSize(QSize(50, 50))
-
- self.gridLayout_10.addWidget(self.pb_add_connector_2, 1, 1, 1, 1)
-
- self.pb_add_connector_3 = QPushButton(self.joint_tools)
- self.pb_add_connector_3.setObjectName(u"pb_add_connector_3")
- self.pb_add_connector_3.setMinimumSize(QSize(50, 50))
- self.pb_add_connector_3.setMaximumSize(QSize(50, 50))
-
- self.gridLayout_10.addWidget(self.pb_add_connector_3, 1, 2, 1, 1)
-
-
- self.gridLayout.addWidget(self.joint_tools, 12, 3, 2, 1)
-
- self.gl_box = QGroupBox(self.centralwidget)
- self.gl_box.setObjectName(u"gl_box")
- sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
- sizePolicy1.setHorizontalStretch(0)
- sizePolicy1.setVerticalStretch(4)
- sizePolicy1.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth())
- self.gl_box.setSizePolicy(sizePolicy1)
- font = QFont()
- font.setPointSize(12)
- self.gl_box.setFont(font)
- self.horizontalLayout_4 = QHBoxLayout(self.gl_box)
-#ifndef Q_OS_MAC
- self.horizontalLayout_4.setSpacing(-1)
-#endif
- self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
- self.horizontalLayout_4.setContentsMargins(12, -1, -1, -1)
-
- self.gridLayout.addWidget(self.gl_box, 0, 2, 12, 1)
-
- self.groupBox_11 = QGroupBox(self.centralwidget)
- self.groupBox_11.setObjectName(u"groupBox_11")
- sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
- sizePolicy2.setHorizontalStretch(0)
- sizePolicy2.setVerticalStretch(0)
- sizePolicy2.setHeightForWidth(self.groupBox_11.sizePolicy().hasHeightForWidth())
- self.groupBox_11.setSizePolicy(sizePolicy2)
- self.groupBox_11.setMaximumSize(QSize(200, 16777215))
- self.verticalLayout_7 = QVBoxLayout(self.groupBox_11)
- self.verticalLayout_7.setObjectName(u"verticalLayout_7")
- self.verticalLayout_7.setContentsMargins(5, 5, 5, 5)
- self.sketch_list = QListWidget(self.groupBox_11)
- self.sketch_list.setObjectName(u"sketch_list")
- sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
- sizePolicy3.setHorizontalStretch(0)
- sizePolicy3.setVerticalStretch(0)
- sizePolicy3.setHeightForWidth(self.sketch_list.sizePolicy().hasHeightForWidth())
- self.sketch_list.setSizePolicy(sizePolicy3)
- self.sketch_list.setSelectionRectVisible(True)
-
- self.verticalLayout_7.addWidget(self.sketch_list)
-
- self.groupBox_6 = QGroupBox(self.groupBox_11)
- self.groupBox_6.setObjectName(u"groupBox_6")
- sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
- sizePolicy4.setHorizontalStretch(0)
- sizePolicy4.setVerticalStretch(0)
- sizePolicy4.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth())
- self.groupBox_6.setSizePolicy(sizePolicy4)
- self.gridLayout_6 = QGridLayout(self.groupBox_6)
- self.gridLayout_6.setObjectName(u"gridLayout_6")
- self.gridLayout_6.setContentsMargins(2, 2, 2, 2)
- self.pb_edt_sktch = QPushButton(self.groupBox_6)
- self.pb_edt_sktch.setObjectName(u"pb_edt_sktch")
-
- self.gridLayout_6.addWidget(self.pb_edt_sktch, 1, 1, 1, 1)
-
- self.pb_nw_sktch = QPushButton(self.groupBox_6)
- self.pb_nw_sktch.setObjectName(u"pb_nw_sktch")
-
- self.gridLayout_6.addWidget(self.pb_nw_sktch, 1, 0, 1, 1)
-
- self.pb_del_sketch = QPushButton(self.groupBox_6)
- self.pb_del_sketch.setObjectName(u"pb_del_sketch")
-
- self.gridLayout_6.addWidget(self.pb_del_sketch, 1, 2, 1, 1)
-
-
- self.verticalLayout_7.addWidget(self.groupBox_6)
-
-
- self.gridLayout.addWidget(self.groupBox_11, 6, 0, 6, 1)
-
- self.assembly_tools = QGroupBox(self.centralwidget)
- self.assembly_tools.setObjectName(u"assembly_tools")
- self.assembly_tools.setMinimumSize(QSize(0, 50))
- self.gridLayout_12 = QGridLayout(self.assembly_tools)
- self.gridLayout_12.setObjectName(u"gridLayout_12")
- self.pb_compo_to_assembly = QPushButton(self.assembly_tools)
- self.pb_compo_to_assembly.setObjectName(u"pb_compo_to_assembly")
- self.pb_compo_to_assembly.setMinimumSize(QSize(50, 50))
- self.pb_compo_to_assembly.setMaximumSize(QSize(50, 50))
-
- self.gridLayout_12.addWidget(self.pb_compo_to_assembly, 0, 0, 1, 1)
-
- self.pb_remove_compo_from_assembly = QPushButton(self.assembly_tools)
- self.pb_remove_compo_from_assembly.setObjectName(u"pb_remove_compo_from_assembly")
- self.pb_remove_compo_from_assembly.setEnabled(True)
- sizePolicy4.setHeightForWidth(self.pb_remove_compo_from_assembly.sizePolicy().hasHeightForWidth())
- self.pb_remove_compo_from_assembly.setSizePolicy(sizePolicy4)
- self.pb_remove_compo_from_assembly.setMinimumSize(QSize(50, 50))
- self.pb_remove_compo_from_assembly.setMaximumSize(QSize(50, 50))
- self.pb_remove_compo_from_assembly.setLayoutDirection(Qt.LeftToRight)
-
- self.gridLayout_12.addWidget(self.pb_remove_compo_from_assembly, 0, 1, 1, 1)
-
-
- self.gridLayout.addWidget(self.assembly_tools, 13, 0, 1, 1)
-
- self.compo_tool_box = QGroupBox(self.centralwidget)
- self.compo_tool_box.setObjectName(u"compo_tool_box")
- self.compo_tool_box.setMinimumSize(QSize(0, 50))
- self.gridLayout_9 = QGridLayout(self.compo_tool_box)
- self.gridLayout_9.setObjectName(u"gridLayout_9")
- self.pb_new_compo = QPushButton(self.compo_tool_box)
- self.pb_new_compo.setObjectName(u"pb_new_compo")
- self.pb_new_compo.setMinimumSize(QSize(50, 50))
- self.pb_new_compo.setMaximumSize(QSize(50, 50))
-
- self.gridLayout_9.addWidget(self.pb_new_compo, 0, 0, 1, 1)
-
- self.pb_del_compo = QPushButton(self.compo_tool_box)
- self.pb_del_compo.setObjectName(u"pb_del_compo")
- self.pb_del_compo.setEnabled(True)
- sizePolicy4.setHeightForWidth(self.pb_del_compo.sizePolicy().hasHeightForWidth())
- self.pb_del_compo.setSizePolicy(sizePolicy4)
- self.pb_del_compo.setMinimumSize(QSize(50, 50))
- self.pb_del_compo.setMaximumSize(QSize(50, 50))
- self.pb_del_compo.setLayoutDirection(Qt.LeftToRight)
-
- self.gridLayout_9.addWidget(self.pb_del_compo, 0, 1, 1, 1)
-
-
- self.gridLayout.addWidget(self.compo_tool_box, 12, 0, 1, 1)
-
- self.compo_box = QGroupBox(self.centralwidget)
- self.compo_box.setObjectName(u"compo_box")
- self.compo_box.setMinimumSize(QSize(0, 50))
-
- self.gridLayout.addWidget(self.compo_box, 12, 1, 1, 2)
-
- self.groupBox_12 = QGroupBox(self.centralwidget)
- self.groupBox_12.setObjectName(u"groupBox_12")
- sizePolicy2.setHeightForWidth(self.groupBox_12.sizePolicy().hasHeightForWidth())
- self.groupBox_12.setSizePolicy(sizePolicy2)
- self.groupBox_12.setMaximumSize(QSize(200, 16777215))
- self.verticalLayout_8 = QVBoxLayout(self.groupBox_12)
- self.verticalLayout_8.setObjectName(u"verticalLayout_8")
- self.verticalLayout_8.setContentsMargins(5, 5, 5, 5)
- self.connection_list = QListWidget(self.groupBox_12)
- self.connection_list.setObjectName(u"connection_list")
- self.connection_list.setSelectionRectVisible(True)
-
- self.verticalLayout_8.addWidget(self.connection_list)
-
- self.groupBox_13 = QGroupBox(self.groupBox_12)
- self.groupBox_13.setObjectName(u"groupBox_13")
- sizePolicy4.setHeightForWidth(self.groupBox_13.sizePolicy().hasHeightForWidth())
- self.groupBox_13.setSizePolicy(sizePolicy4)
- self.groupBox_13.setMaximumSize(QSize(200, 16777215))
- self.gridLayout_13 = QGridLayout(self.groupBox_13)
- self.gridLayout_13.setObjectName(u"gridLayout_13")
- self.gridLayout_13.setContentsMargins(2, 2, 2, 2)
- self.pb_del_connection = QPushButton(self.groupBox_13)
- self.pb_del_connection.setObjectName(u"pb_del_connection")
-
- self.gridLayout_13.addWidget(self.pb_del_connection, 0, 2, 1, 1)
-
- self.pb_update_connection = QPushButton(self.groupBox_13)
- self.pb_update_connection.setObjectName(u"pb_update_connection")
-
- self.gridLayout_13.addWidget(self.pb_update_connection, 0, 0, 1, 1)
-
- self.pb_edt_sktch_4 = QPushButton(self.groupBox_13)
- self.pb_edt_sktch_4.setObjectName(u"pb_edt_sktch_4")
-
- self.gridLayout_13.addWidget(self.pb_edt_sktch_4, 0, 1, 1, 1)
-
-
- self.verticalLayout_8.addWidget(self.groupBox_13)
-
-
- self.gridLayout.addWidget(self.groupBox_12, 6, 3, 6, 1)
-
- self.groupBox_10 = QGroupBox(self.centralwidget)
- self.groupBox_10.setObjectName(u"groupBox_10")
- sizePolicy2.setHeightForWidth(self.groupBox_10.sizePolicy().hasHeightForWidth())
- self.groupBox_10.setSizePolicy(sizePolicy2)
- self.groupBox_10.setMaximumSize(QSize(200, 16777215))
- self.verticalLayout_6 = QVBoxLayout(self.groupBox_10)
- self.verticalLayout_6.setObjectName(u"verticalLayout_6")
- self.verticalLayout_6.setContentsMargins(5, 5, 5, 5)
- self.body_list = QListWidget(self.groupBox_10)
- self.body_list.setObjectName(u"body_list")
- self.body_list.setSelectionRectVisible(True)
-
- self.verticalLayout_6.addWidget(self.body_list)
-
- self.groupBox_8 = QGroupBox(self.groupBox_10)
- self.groupBox_8.setObjectName(u"groupBox_8")
- sizePolicy4.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth())
- self.groupBox_8.setSizePolicy(sizePolicy4)
- self.groupBox_8.setMaximumSize(QSize(200, 16777215))
- self.gridLayout_8 = QGridLayout(self.groupBox_8)
- self.gridLayout_8.setObjectName(u"gridLayout_8")
- self.gridLayout_8.setContentsMargins(2, 2, 2, 2)
- self.pb_del_body = QPushButton(self.groupBox_8)
- self.pb_del_body.setObjectName(u"pb_del_body")
-
- self.gridLayout_8.addWidget(self.pb_del_body, 0, 2, 1, 1)
-
- self.pb_update_body = QPushButton(self.groupBox_8)
- self.pb_update_body.setObjectName(u"pb_update_body")
-
- self.gridLayout_8.addWidget(self.pb_update_body, 0, 0, 1, 1)
-
- self.pb_edt_sktch_3 = QPushButton(self.groupBox_8)
- self.pb_edt_sktch_3.setObjectName(u"pb_edt_sktch_3")
-
- self.gridLayout_8.addWidget(self.pb_edt_sktch_3, 0, 1, 1, 1)
-
-
- self.verticalLayout_6.addWidget(self.groupBox_8)
-
-
- self.gridLayout.addWidget(self.groupBox_10, 3, 3, 3, 1)
-
- self.groupBox_2 = QGroupBox(self.centralwidget)
- self.groupBox_2.setObjectName(u"groupBox_2")
- sizePolicy4.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth())
- self.groupBox_2.setSizePolicy(sizePolicy4)
- self.groupBox_2.setMaximumSize(QSize(200, 16777215))
- self.gridLayout_2 = QGridLayout(self.groupBox_2)
- self.gridLayout_2.setObjectName(u"gridLayout_2")
- self.gridLayout_2.setContentsMargins(10, -1, -1, -1)
- self.pb_arc_tool = QPushButton(self.groupBox_2)
- self.pb_arc_tool.setObjectName(u"pb_arc_tool")
- self.pb_arc_tool.setCheckable(True)
-
- self.gridLayout_2.addWidget(self.pb_arc_tool, 2, 0, 1, 1)
-
- self.pb_rectool = QPushButton(self.groupBox_2)
- self.pb_rectool.setObjectName(u"pb_rectool")
- self.pb_rectool.setCheckable(True)
- self.pb_rectool.setAutoExclusive(False)
-
- self.gridLayout_2.addWidget(self.pb_rectool, 0, 1, 1, 1)
-
- self.pb_circtool = QPushButton(self.groupBox_2)
- self.pb_circtool.setObjectName(u"pb_circtool")
- self.pb_circtool.setCheckable(True)
- self.pb_circtool.setAutoExclusive(False)
-
- self.gridLayout_2.addWidget(self.pb_circtool, 1, 0, 1, 1, Qt.AlignTop)
-
- self.pb_enable_construct = QPushButton(self.groupBox_2)
- self.pb_enable_construct.setObjectName(u"pb_enable_construct")
- self.pb_enable_construct.setCheckable(True)
-
- self.gridLayout_2.addWidget(self.pb_enable_construct, 4, 0, 1, 1)
-
- self.pb_enable_snap = QPushButton(self.groupBox_2)
- self.pb_enable_snap.setObjectName(u"pb_enable_snap")
- self.pb_enable_snap.setIconSize(QSize(13, 16))
- self.pb_enable_snap.setCheckable(True)
- self.pb_enable_snap.setChecked(True)
-
- self.gridLayout_2.addWidget(self.pb_enable_snap, 4, 1, 1, 1)
-
- self.pb_linetool = QPushButton(self.groupBox_2)
- self.pb_linetool.setObjectName(u"pb_linetool")
- self.pb_linetool.setCheckable(True)
- self.pb_linetool.setAutoExclusive(False)
-
- self.gridLayout_2.addWidget(self.pb_linetool, 0, 0, 1, 1)
-
- self.pb_slotool = QPushButton(self.groupBox_2)
- self.pb_slotool.setObjectName(u"pb_slotool")
- self.pb_slotool.setCheckable(True)
- self.pb_slotool.setAutoExclusive(False)
-
- self.gridLayout_2.addWidget(self.pb_slotool, 1, 1, 1, 1, Qt.AlignTop)
-
- self.line = QFrame(self.groupBox_2)
- self.line.setObjectName(u"line")
- self.line.setFrameShape(QFrame.Shape.HLine)
- self.line.setFrameShadow(QFrame.Shadow.Sunken)
-
- self.gridLayout_2.addWidget(self.line, 3, 0, 1, 2)
-
- self.pb_offset_tool = QPushButton(self.groupBox_2)
- self.pb_offset_tool.setObjectName(u"pb_offset_tool")
-
- self.gridLayout_2.addWidget(self.pb_offset_tool, 2, 1, 1, 1)
-
-
- self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1)
-
self.groupBox_3 = QGroupBox(self.centralwidget)
self.groupBox_3.setObjectName(u"groupBox_3")
- sizePolicy4.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth())
- self.groupBox_3.setSizePolicy(sizePolicy4)
+ sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth())
+ self.groupBox_3.setSizePolicy(sizePolicy)
self.groupBox_3.setMaximumSize(QSize(200, 16777213))
self.gridLayout_4 = QGridLayout(self.groupBox_3)
self.gridLayout_4.setObjectName(u"gridLayout_4")
@@ -551,10 +254,85 @@ class Ui_fluencyCAD(object):
self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1)
+ self.groupBox = QGroupBox(self.centralwidget)
+ self.groupBox.setObjectName(u"groupBox")
+ self.groupBox.setMaximumSize(QSize(200, 16777215))
+ self.gridLayout_3 = QGridLayout(self.groupBox)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.pb_revop = QPushButton(self.groupBox)
+ self.pb_revop.setObjectName(u"pb_revop")
+
+ self.gridLayout_3.addWidget(self.pb_revop, 2, 1, 1, 1)
+
+ self.pb_extrdop = QPushButton(self.groupBox)
+ self.pb_extrdop.setObjectName(u"pb_extrdop")
+
+ self.gridLayout_3.addWidget(self.pb_extrdop, 0, 0, 1, 1)
+
+ self.pb_arrayop = QPushButton(self.groupBox)
+ self.pb_arrayop.setObjectName(u"pb_arrayop")
+
+ self.gridLayout_3.addWidget(self.pb_arrayop, 2, 0, 1, 1)
+
+ self.pb_cutop = QPushButton(self.groupBox)
+ self.pb_cutop.setObjectName(u"pb_cutop")
+
+ self.gridLayout_3.addWidget(self.pb_cutop, 0, 1, 1, 1)
+
+ self.pb_combop = QPushButton(self.groupBox)
+ self.pb_combop.setObjectName(u"pb_combop")
+
+ self.gridLayout_3.addWidget(self.pb_combop, 1, 0, 1, 1)
+
+ self.pb_moveop = QPushButton(self.groupBox)
+ self.pb_moveop.setObjectName(u"pb_moveop")
+
+ self.gridLayout_3.addWidget(self.pb_moveop, 1, 1, 1, 1)
+
+
+ self.gridLayout.addWidget(self.groupBox, 0, 4, 1, 1)
+
+ self.compo_box = QGroupBox(self.centralwidget)
+ self.compo_box.setObjectName(u"compo_box")
+ self.compo_box.setMinimumSize(QSize(0, 120))
+
+ self.gridLayout.addWidget(self.compo_box, 8, 1, 2, 3)
+
+ self.compo_tool_box = QGroupBox(self.centralwidget)
+ self.compo_tool_box.setObjectName(u"compo_tool_box")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.compo_tool_box.sizePolicy().hasHeightForWidth())
+ self.compo_tool_box.setSizePolicy(sizePolicy1)
+ self.compo_tool_box.setMinimumSize(QSize(0, 50))
+ self.horizontalLayout = QHBoxLayout(self.compo_tool_box)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.pb_new_compo = QPushButton(self.compo_tool_box)
+ self.pb_new_compo.setObjectName(u"pb_new_compo")
+ self.pb_new_compo.setMinimumSize(QSize(50, 50))
+ self.pb_new_compo.setMaximumSize(QSize(50, 50))
+
+ self.horizontalLayout.addWidget(self.pb_new_compo)
+
+ self.pb_del_compo = QPushButton(self.compo_tool_box)
+ self.pb_del_compo.setObjectName(u"pb_del_compo")
+ self.pb_del_compo.setEnabled(True)
+ sizePolicy.setHeightForWidth(self.pb_del_compo.sizePolicy().hasHeightForWidth())
+ self.pb_del_compo.setSizePolicy(sizePolicy)
+ self.pb_del_compo.setMinimumSize(QSize(50, 50))
+ self.pb_del_compo.setMaximumSize(QSize(50, 50))
+ self.pb_del_compo.setLayoutDirection(Qt.LeftToRight)
+
+ self.horizontalLayout.addWidget(self.pb_del_compo)
+
+
+ self.gridLayout.addWidget(self.compo_tool_box, 8, 0, 2, 1)
+
self.groupBox_5 = QGroupBox(self.centralwidget)
self.groupBox_5.setObjectName(u"groupBox_5")
- sizePolicy4.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth())
- self.groupBox_5.setSizePolicy(sizePolicy4)
+ sizePolicy.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth())
+ self.groupBox_5.setSizePolicy(sizePolicy)
self.gridLayout_11 = QGridLayout(self.groupBox_5)
self.gridLayout_11.setObjectName(u"gridLayout_11")
self.gridLayout_11.setContentsMargins(12, 12, 12, 12)
@@ -634,69 +412,304 @@ class Ui_fluencyCAD(object):
self.gridLayout.addWidget(self.groupBox_5, 3, 0, 1, 1)
- self.groupBox = QGroupBox(self.centralwidget)
- self.groupBox.setObjectName(u"groupBox")
- self.gridLayout_3 = QGridLayout(self.groupBox)
- self.gridLayout_3.setObjectName(u"gridLayout_3")
- self.pb_revop = QPushButton(self.groupBox)
- self.pb_revop.setObjectName(u"pb_revop")
+ self.joint_tools = QGroupBox(self.centralwidget)
+ self.joint_tools.setObjectName(u"joint_tools")
+ self.joint_tools.setMinimumSize(QSize(0, 50))
+ self.gridLayout_10 = QGridLayout(self.joint_tools)
+ self.gridLayout_10.setObjectName(u"gridLayout_10")
+ self.pb_add_connector = QPushButton(self.joint_tools)
+ self.pb_add_connector.setObjectName(u"pb_add_connector")
+ self.pb_add_connector.setMinimumSize(QSize(50, 50))
+ self.pb_add_connector.setMaximumSize(QSize(50, 50))
- self.gridLayout_3.addWidget(self.pb_revop, 2, 1, 1, 1)
+ self.gridLayout_10.addWidget(self.pb_add_connector, 0, 0, 1, 1)
- self.pb_extrdop = QPushButton(self.groupBox)
- self.pb_extrdop.setObjectName(u"pb_extrdop")
+ self.pb_remove_connector = QPushButton(self.joint_tools)
+ self.pb_remove_connector.setObjectName(u"pb_remove_connector")
+ self.pb_remove_connector.setMinimumSize(QSize(50, 50))
+ self.pb_remove_connector.setMaximumSize(QSize(50, 50))
- self.gridLayout_3.addWidget(self.pb_extrdop, 0, 0, 1, 1)
+ self.gridLayout_10.addWidget(self.pb_remove_connector, 0, 1, 1, 1)
- self.pb_arrayop = QPushButton(self.groupBox)
- self.pb_arrayop.setObjectName(u"pb_arrayop")
+ self.pb_add_connector_3 = QPushButton(self.joint_tools)
+ self.pb_add_connector_3.setObjectName(u"pb_add_connector_3")
+ self.pb_add_connector_3.setMinimumSize(QSize(50, 50))
+ self.pb_add_connector_3.setMaximumSize(QSize(50, 50))
- self.gridLayout_3.addWidget(self.pb_arrayop, 2, 0, 1, 1)
+ self.gridLayout_10.addWidget(self.pb_add_connector_3, 1, 1, 1, 1)
- self.pb_cutop = QPushButton(self.groupBox)
- self.pb_cutop.setObjectName(u"pb_cutop")
+ self.pb_add_connector_2 = QPushButton(self.joint_tools)
+ self.pb_add_connector_2.setObjectName(u"pb_add_connector_2")
+ self.pb_add_connector_2.setMinimumSize(QSize(50, 50))
+ self.pb_add_connector_2.setMaximumSize(QSize(50, 50))
- self.gridLayout_3.addWidget(self.pb_cutop, 0, 1, 1, 1)
-
- self.pb_combop = QPushButton(self.groupBox)
- self.pb_combop.setObjectName(u"pb_combop")
-
- self.gridLayout_3.addWidget(self.pb_combop, 1, 0, 1, 1)
-
- self.pb_moveop = QPushButton(self.groupBox)
- self.pb_moveop.setObjectName(u"pb_moveop")
-
- self.gridLayout_3.addWidget(self.pb_moveop, 1, 1, 1, 1)
+ self.gridLayout_10.addWidget(self.pb_add_connector_2, 1, 0, 1, 1)
- self.gridLayout.addWidget(self.groupBox, 0, 3, 1, 1)
+ self.gridLayout.addWidget(self.joint_tools, 8, 4, 4, 1)
self.groupBox_4 = QGroupBox(self.centralwidget)
self.groupBox_4.setObjectName(u"groupBox_4")
- self.verticalLayout_2 = QVBoxLayout(self.groupBox_4)
- self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.groupBox_4.setMaximumSize(QSize(200, 16777215))
+ self.gridLayout_9 = QGridLayout(self.groupBox_4)
+ self.gridLayout_9.setObjectName(u"gridLayout_9")
self.pushButton_2 = QPushButton(self.groupBox_4)
self.pushButton_2.setObjectName(u"pushButton_2")
- self.verticalLayout_2.addWidget(self.pushButton_2)
-
- self.pb_export_step = QPushButton(self.groupBox_4)
- self.pb_export_step.setObjectName(u"pb_export_step")
-
- self.verticalLayout_2.addWidget(self.pb_export_step)
+ self.gridLayout_9.addWidget(self.pushButton_2, 0, 0, 1, 1)
self.pb_export_iges = QPushButton(self.groupBox_4)
self.pb_export_iges.setObjectName(u"pb_export_iges")
- self.verticalLayout_2.addWidget(self.pb_export_iges)
+ self.gridLayout_9.addWidget(self.pb_export_iges, 2, 0, 1, 1)
+
+ self.pb_export_step = QPushButton(self.groupBox_4)
+ self.pb_export_step.setObjectName(u"pb_export_step")
+
+ self.gridLayout_9.addWidget(self.pb_export_step, 0, 1, 1, 1)
- self.gridLayout.addWidget(self.groupBox_4, 2, 3, 1, 1)
+ self.gridLayout.addWidget(self.groupBox_4, 1, 4, 1, 1)
+
+ self.assembly_tools = QGroupBox(self.centralwidget)
+ self.assembly_tools.setObjectName(u"assembly_tools")
+ sizePolicy1.setHeightForWidth(self.assembly_tools.sizePolicy().hasHeightForWidth())
+ self.assembly_tools.setSizePolicy(sizePolicy1)
+ self.assembly_tools.setMinimumSize(QSize(113, 50))
+ self.horizontalLayout_2 = QHBoxLayout(self.assembly_tools)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.pb_compo_to_assembly = QPushButton(self.assembly_tools)
+ self.pb_compo_to_assembly.setObjectName(u"pb_compo_to_assembly")
+ self.pb_compo_to_assembly.setMinimumSize(QSize(50, 50))
+ self.pb_compo_to_assembly.setMaximumSize(QSize(50, 50))
+
+ self.horizontalLayout_2.addWidget(self.pb_compo_to_assembly)
+
+ self.pb_remove_compo_from_assembly = QPushButton(self.assembly_tools)
+ self.pb_remove_compo_from_assembly.setObjectName(u"pb_remove_compo_from_assembly")
+ self.pb_remove_compo_from_assembly.setEnabled(True)
+ sizePolicy.setHeightForWidth(self.pb_remove_compo_from_assembly.sizePolicy().hasHeightForWidth())
+ self.pb_remove_compo_from_assembly.setSizePolicy(sizePolicy)
+ self.pb_remove_compo_from_assembly.setMinimumSize(QSize(50, 50))
+ self.pb_remove_compo_from_assembly.setMaximumSize(QSize(50, 50))
+ self.pb_remove_compo_from_assembly.setLayoutDirection(Qt.LeftToRight)
+
+ self.horizontalLayout_2.addWidget(self.pb_remove_compo_from_assembly)
+
+
+ self.gridLayout.addWidget(self.assembly_tools, 11, 0, 1, 1)
+
+ self.groupBox_11 = QGroupBox(self.centralwidget)
+ self.groupBox_11.setObjectName(u"groupBox_11")
+ sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
+ sizePolicy2.setHorizontalStretch(0)
+ sizePolicy2.setVerticalStretch(0)
+ sizePolicy2.setHeightForWidth(self.groupBox_11.sizePolicy().hasHeightForWidth())
+ self.groupBox_11.setSizePolicy(sizePolicy2)
+ self.groupBox_11.setMaximumSize(QSize(200, 16777215))
+ self.verticalLayout_7 = QVBoxLayout(self.groupBox_11)
+ self.verticalLayout_7.setObjectName(u"verticalLayout_7")
+ self.verticalLayout_7.setContentsMargins(5, 5, 5, 5)
+ self.sketch_list = QListWidget(self.groupBox_11)
+ self.sketch_list.setObjectName(u"sketch_list")
+ sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy3.setHorizontalStretch(0)
+ sizePolicy3.setVerticalStretch(0)
+ sizePolicy3.setHeightForWidth(self.sketch_list.sizePolicy().hasHeightForWidth())
+ self.sketch_list.setSizePolicy(sizePolicy3)
+ self.sketch_list.setSelectionRectVisible(True)
+
+ self.verticalLayout_7.addWidget(self.sketch_list)
+
+ self.groupBox_6 = QGroupBox(self.groupBox_11)
+ self.groupBox_6.setObjectName(u"groupBox_6")
+ sizePolicy.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth())
+ self.groupBox_6.setSizePolicy(sizePolicy)
+ self.gridLayout_6 = QGridLayout(self.groupBox_6)
+ self.gridLayout_6.setObjectName(u"gridLayout_6")
+ self.gridLayout_6.setContentsMargins(2, 2, 2, 2)
+ self.pb_edt_sktch = QPushButton(self.groupBox_6)
+ self.pb_edt_sktch.setObjectName(u"pb_edt_sktch")
+
+ self.gridLayout_6.addWidget(self.pb_edt_sktch, 1, 1, 1, 1)
+
+ self.pb_nw_sktch = QPushButton(self.groupBox_6)
+ self.pb_nw_sktch.setObjectName(u"pb_nw_sktch")
+
+ self.gridLayout_6.addWidget(self.pb_nw_sktch, 1, 0, 1, 1)
+
+ self.pb_del_sketch = QPushButton(self.groupBox_6)
+ self.pb_del_sketch.setObjectName(u"pb_del_sketch")
+
+ self.gridLayout_6.addWidget(self.pb_del_sketch, 1, 2, 1, 1)
+
+
+ self.verticalLayout_7.addWidget(self.groupBox_6)
+
+
+ self.gridLayout.addWidget(self.groupBox_11, 4, 0, 1, 1)
+
+ self.InputTab = QTabWidget(self.centralwidget)
+ self.InputTab.setObjectName(u"InputTab")
+ sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
+ sizePolicy4.setHorizontalStretch(0)
+ sizePolicy4.setVerticalStretch(0)
+ sizePolicy4.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth())
+ self.InputTab.setSizePolicy(sizePolicy4)
+ self.sketch_tab = QWidget()
+ self.sketch_tab.setObjectName(u"sketch_tab")
+ self.verticalLayout_4 = QVBoxLayout(self.sketch_tab)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.InputTab.addTab(self.sketch_tab, "")
+ self.code_tab = QWidget()
+ self.code_tab.setObjectName(u"code_tab")
+ self.verticalLayout = QVBoxLayout(self.code_tab)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.textEdit = QTextEdit(self.code_tab)
+ self.textEdit.setObjectName(u"textEdit")
+
+ self.verticalLayout.addWidget(self.textEdit)
+
+ self.groupBox_7 = QGroupBox(self.code_tab)
+ self.groupBox_7.setObjectName(u"groupBox_7")
+ self.gridLayout_5 = QGridLayout(self.groupBox_7)
+ self.gridLayout_5.setObjectName(u"gridLayout_5")
+ self.pushButton_5 = QPushButton(self.groupBox_7)
+ self.pushButton_5.setObjectName(u"pushButton_5")
+
+ self.gridLayout_5.addWidget(self.pushButton_5, 2, 0, 1, 1)
+
+ self.pushButton_4 = QPushButton(self.groupBox_7)
+ self.pushButton_4.setObjectName(u"pushButton_4")
+
+ self.gridLayout_5.addWidget(self.pushButton_4, 2, 1, 1, 1)
+
+ self.pb_apply_code = QPushButton(self.groupBox_7)
+ self.pb_apply_code.setObjectName(u"pb_apply_code")
+
+ self.gridLayout_5.addWidget(self.pb_apply_code, 1, 0, 1, 1)
+
+ self.pushButton = QPushButton(self.groupBox_7)
+ self.pushButton.setObjectName(u"pushButton")
+
+ self.gridLayout_5.addWidget(self.pushButton, 1, 1, 1, 1)
+
+
+ self.verticalLayout.addWidget(self.groupBox_7)
+
+ self.InputTab.addTab(self.code_tab, "")
+
+ self.gridLayout.addWidget(self.InputTab, 0, 1, 5, 1)
+
+ self.gl_box = QGroupBox(self.centralwidget)
+ self.gl_box.setObjectName(u"gl_box")
+ sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy5.setHorizontalStretch(0)
+ sizePolicy5.setVerticalStretch(4)
+ sizePolicy5.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth())
+ self.gl_box.setSizePolicy(sizePolicy5)
+ font = QFont()
+ font.setPointSize(12)
+ self.gl_box.setFont(font)
+ self.horizontalLayout_4 = QHBoxLayout(self.gl_box)
+#ifndef Q_OS_MAC
+ self.horizontalLayout_4.setSpacing(-1)
+#endif
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.horizontalLayout_4.setContentsMargins(12, -1, -1, -1)
+
+ self.gridLayout.addWidget(self.gl_box, 0, 3, 5, 1)
+
+ self.groupBox_12 = QGroupBox(self.centralwidget)
+ self.groupBox_12.setObjectName(u"groupBox_12")
+ sizePolicy2.setHeightForWidth(self.groupBox_12.sizePolicy().hasHeightForWidth())
+ self.groupBox_12.setSizePolicy(sizePolicy2)
+ self.groupBox_12.setMaximumSize(QSize(200, 16777215))
+ self.verticalLayout_8 = QVBoxLayout(self.groupBox_12)
+ self.verticalLayout_8.setObjectName(u"verticalLayout_8")
+ self.verticalLayout_8.setContentsMargins(5, 5, 5, 5)
+ self.connection_list = QListWidget(self.groupBox_12)
+ self.connection_list.setObjectName(u"connection_list")
+ self.connection_list.setSelectionRectVisible(True)
+
+ self.verticalLayout_8.addWidget(self.connection_list)
+
+ self.groupBox_13 = QGroupBox(self.groupBox_12)
+ self.groupBox_13.setObjectName(u"groupBox_13")
+ sizePolicy.setHeightForWidth(self.groupBox_13.sizePolicy().hasHeightForWidth())
+ self.groupBox_13.setSizePolicy(sizePolicy)
+ self.groupBox_13.setMaximumSize(QSize(200, 16777215))
+ self.gridLayout_13 = QGridLayout(self.groupBox_13)
+ self.gridLayout_13.setObjectName(u"gridLayout_13")
+ self.gridLayout_13.setContentsMargins(2, 2, 2, 2)
+ self.pb_del_connection = QPushButton(self.groupBox_13)
+ self.pb_del_connection.setObjectName(u"pb_del_connection")
+
+ self.gridLayout_13.addWidget(self.pb_del_connection, 0, 2, 1, 1)
+
+ self.pb_update_connection = QPushButton(self.groupBox_13)
+ self.pb_update_connection.setObjectName(u"pb_update_connection")
+
+ self.gridLayout_13.addWidget(self.pb_update_connection, 0, 0, 1, 1)
+
+ self.pb_edt_sktch_4 = QPushButton(self.groupBox_13)
+ self.pb_edt_sktch_4.setObjectName(u"pb_edt_sktch_4")
+
+ self.gridLayout_13.addWidget(self.pb_edt_sktch_4, 0, 1, 1, 1)
+
+
+ self.verticalLayout_8.addWidget(self.groupBox_13)
+
+
+ self.gridLayout.addWidget(self.groupBox_12, 4, 4, 1, 1)
+
+ self.groupBox_10 = QGroupBox(self.centralwidget)
+ self.groupBox_10.setObjectName(u"groupBox_10")
+ sizePolicy2.setHeightForWidth(self.groupBox_10.sizePolicy().hasHeightForWidth())
+ self.groupBox_10.setSizePolicy(sizePolicy2)
+ self.groupBox_10.setMaximumSize(QSize(200, 16777215))
+ self.verticalLayout_6 = QVBoxLayout(self.groupBox_10)
+ self.verticalLayout_6.setObjectName(u"verticalLayout_6")
+ self.verticalLayout_6.setContentsMargins(5, 5, 5, 5)
+ self.body_list = QListWidget(self.groupBox_10)
+ self.body_list.setObjectName(u"body_list")
+ self.body_list.setSelectionRectVisible(True)
+
+ self.verticalLayout_6.addWidget(self.body_list)
+
+ self.groupBox_8 = QGroupBox(self.groupBox_10)
+ self.groupBox_8.setObjectName(u"groupBox_8")
+ sizePolicy.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth())
+ self.groupBox_8.setSizePolicy(sizePolicy)
+ self.groupBox_8.setMaximumSize(QSize(200, 16777215))
+ self.gridLayout_8 = QGridLayout(self.groupBox_8)
+ self.gridLayout_8.setObjectName(u"gridLayout_8")
+ self.gridLayout_8.setContentsMargins(2, 2, 2, 2)
+ self.pb_del_body = QPushButton(self.groupBox_8)
+ self.pb_del_body.setObjectName(u"pb_del_body")
+
+ self.gridLayout_8.addWidget(self.pb_del_body, 0, 2, 1, 1)
+
+ self.pb_update_body = QPushButton(self.groupBox_8)
+ self.pb_update_body.setObjectName(u"pb_update_body")
+
+ self.gridLayout_8.addWidget(self.pb_update_body, 0, 0, 1, 1)
+
+ self.pb_edt_sktch_3 = QPushButton(self.groupBox_8)
+ self.pb_edt_sktch_3.setObjectName(u"pb_edt_sktch_3")
+
+ self.gridLayout_8.addWidget(self.pb_edt_sktch_3, 0, 1, 1, 1)
+
+
+ self.verticalLayout_6.addWidget(self.groupBox_8)
+
+
+ self.gridLayout.addWidget(self.groupBox_10, 3, 4, 1, 1)
fluencyCAD.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(fluencyCAD)
self.menubar.setObjectName(u"menubar")
- self.menubar.setGeometry(QRect(0, 0, 2359, 24))
+ self.menubar.setGeometry(QRect(0, 0, 2668, 24))
self.menuFile = QMenu(self.menubar)
self.menuFile.setObjectName(u"menuFile")
self.menuSettings = QMenu(self.menubar)
@@ -755,13 +768,22 @@ class Ui_fluencyCAD(object):
#if QT_CONFIG(shortcut)
self.actionExit.setShortcut(QCoreApplication.translate("fluencyCAD", u"Ctrl+Q", None))
#endif // QT_CONFIG(shortcut)
- self.InputTab.setTabText(self.InputTab.indexOf(self.sketch_tab), QCoreApplication.translate("fluencyCAD", u"Sketch", None))
- self.groupBox_7.setTitle(QCoreApplication.translate("fluencyCAD", u"Executive", None))
- self.pushButton_5.setText(QCoreApplication.translate("fluencyCAD", u"Load Code", None))
- self.pushButton_4.setText(QCoreApplication.translate("fluencyCAD", u"Save code", None))
- self.pb_apply_code.setText(QCoreApplication.translate("fluencyCAD", u"Apply Code", None))
- self.pushButton.setText(QCoreApplication.translate("fluencyCAD", u"Delete Code", None))
- self.InputTab.setTabText(self.InputTab.indexOf(self.code_tab), QCoreApplication.translate("fluencyCAD", u"Code", None))
+ self.assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None))
+ self.pb_arc_tool.setText(QCoreApplication.translate("fluencyCAD", u"Arc", None))
+ self.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None))
+ self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None))
+ self.pb_enable_construct.setText(QCoreApplication.translate("fluencyCAD", u"Cstrct", None))
+ self.pb_enable_snap.setText(QCoreApplication.translate("fluencyCAD", u"Snap", None))
+ self.pb_linetool.setText(QCoreApplication.translate("fluencyCAD", u"Line", None))
+#if QT_CONFIG(shortcut)
+ self.pb_linetool.setShortcut(QCoreApplication.translate("fluencyCAD", u"S", None))
+#endif // QT_CONFIG(shortcut)
+ self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None))
+#if QT_CONFIG(tooltip)
+ self.pb_offset_tool.setToolTip(QCoreApplication.translate("fluencyCAD", u"Offset selected sketch face (duplicate + offset boundary)", None))
+#endif // QT_CONFIG(tooltip)
+ self.pb_offset_tool.setText(QCoreApplication.translate("fluencyCAD", u"Offst", None))
self.groupBox_9.setTitle(QCoreApplication.translate("fluencyCAD", u"Workplanes", None))
#if QT_CONFIG(tooltip)
self.pb_origin_wp.setToolTip(QCoreApplication.translate("fluencyCAD", u"orking Plane at 0, 0, 0", None))
@@ -803,50 +825,6 @@ class Ui_fluencyCAD(object):
#if QT_CONFIG(shortcut)
self.pb_wp_new.setShortcut(QCoreApplication.translate("fluencyCAD", u"Shift+W", None))
#endif // QT_CONFIG(shortcut)
- self.assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly", None))
- self.joint_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Joint Tools", None))
- self.pb_remove_connector.setText(QCoreApplication.translate("fluencyCAD", u"- Cnct", None))
- self.pb_add_connector.setText(QCoreApplication.translate("fluencyCAD", u"+ Cnct", None))
- self.pb_add_connector_2.setText(QCoreApplication.translate("fluencyCAD", u"+Jnt", None))
- self.pb_add_connector_3.setText(QCoreApplication.translate("fluencyCAD", u"-Jnt", None))
- self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None))
- self.groupBox_11.setTitle(QCoreApplication.translate("fluencyCAD", u"Sketch", None))
- self.groupBox_6.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
- self.pb_edt_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Edt", None))
- self.pb_nw_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Add", None))
- self.pb_del_sketch.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
- self.assembly_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly Tools", None))
- self.pb_compo_to_assembly.setText(QCoreApplication.translate("fluencyCAD", u"Add", None))
- self.pb_remove_compo_from_assembly.setText(QCoreApplication.translate("fluencyCAD", u"Rem", None))
- self.compo_tool_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Tools", None))
- self.pb_new_compo.setText(QCoreApplication.translate("fluencyCAD", u"New", None))
- self.pb_del_compo.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
- self.compo_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components", None))
- self.groupBox_12.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Connections", None))
- self.groupBox_13.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
- self.pb_del_connection.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
- self.pb_update_connection.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
- self.pb_edt_sktch_4.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None))
- self.groupBox_10.setTitle(QCoreApplication.translate("fluencyCAD", u"Bodys / Operations", None))
- self.groupBox_8.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
- self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
- self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
- self.pb_edt_sktch_3.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None))
- self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None))
- self.pb_arc_tool.setText(QCoreApplication.translate("fluencyCAD", u"Arc", None))
- self.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None))
- self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None))
- self.pb_enable_construct.setText(QCoreApplication.translate("fluencyCAD", u"Cstrct", None))
- self.pb_enable_snap.setText(QCoreApplication.translate("fluencyCAD", u"Snap", None))
- self.pb_linetool.setText(QCoreApplication.translate("fluencyCAD", u"Line", None))
-#if QT_CONFIG(shortcut)
- self.pb_linetool.setShortcut(QCoreApplication.translate("fluencyCAD", u"S", None))
-#endif // QT_CONFIG(shortcut)
- self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None))
-#if QT_CONFIG(tooltip)
- self.pb_offset_tool.setToolTip(QCoreApplication.translate("fluencyCAD", u"Offset selected sketch face (duplicate + offset boundary)", None))
-#endif // QT_CONFIG(tooltip)
- self.pb_offset_tool.setText(QCoreApplication.translate("fluencyCAD", u"Offst", None))
self.groupBox_3.setTitle(QCoreApplication.translate("fluencyCAD", u"Constrain", None))
#if QT_CONFIG(tooltip)
self.pb_con_ptpt.setToolTip(QCoreApplication.translate("fluencyCAD", u"Poin to Point Constrain", None))
@@ -878,6 +856,17 @@ class Ui_fluencyCAD(object):
#endif // QT_CONFIG(tooltip)
self.pb_con_perp.setText(QCoreApplication.translate("fluencyCAD", u"Perp_Lne", None))
self.pb_con_diameter.setText(QCoreApplication.translate("fluencyCAD", u"Diameter", None))
+ self.groupBox.setTitle(QCoreApplication.translate("fluencyCAD", u"Modify", None))
+ self.pb_revop.setText(QCoreApplication.translate("fluencyCAD", u"Rev", None))
+ self.pb_extrdop.setText(QCoreApplication.translate("fluencyCAD", u"Extrd", None))
+ self.pb_arrayop.setText(QCoreApplication.translate("fluencyCAD", u"Arry", None))
+ self.pb_cutop.setText(QCoreApplication.translate("fluencyCAD", u"Cut", None))
+ self.pb_combop.setText(QCoreApplication.translate("fluencyCAD", u"Comb", None))
+ self.pb_moveop.setText(QCoreApplication.translate("fluencyCAD", u"Mve", None))
+ self.compo_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components", None))
+ self.compo_tool_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Tools", None))
+ self.pb_new_compo.setText(QCoreApplication.translate("fluencyCAD", u"New", None))
+ self.pb_del_compo.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
self.groupBox_5.setTitle(QCoreApplication.translate("fluencyCAD", u"Snapping Points", None))
self.label.setText(QCoreApplication.translate("fluencyCAD", u"Snp Dst", None))
self.pb_snap_vert.setText(QCoreApplication.translate("fluencyCAD", u"Vert", None))
@@ -889,17 +878,41 @@ class Ui_fluencyCAD(object):
self.pushButton_8.setText(QCoreApplication.translate("fluencyCAD", u"Pnt", None))
self.pb_snap_midp.setText(QCoreApplication.translate("fluencyCAD", u"MidP", None))
self.pb_snap_angle.setText(QCoreApplication.translate("fluencyCAD", u"Angles", None))
- self.groupBox.setTitle(QCoreApplication.translate("fluencyCAD", u"Modify", None))
- self.pb_revop.setText(QCoreApplication.translate("fluencyCAD", u"Rev", None))
- self.pb_extrdop.setText(QCoreApplication.translate("fluencyCAD", u"Extrd", None))
- self.pb_arrayop.setText(QCoreApplication.translate("fluencyCAD", u"Arry", None))
- self.pb_cutop.setText(QCoreApplication.translate("fluencyCAD", u"Cut", None))
- self.pb_combop.setText(QCoreApplication.translate("fluencyCAD", u"Comb", None))
- self.pb_moveop.setText(QCoreApplication.translate("fluencyCAD", u"Mve", None))
+ self.joint_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Joint Tools", None))
+ self.pb_add_connector.setText(QCoreApplication.translate("fluencyCAD", u"+ Cnct", None))
+ self.pb_remove_connector.setText(QCoreApplication.translate("fluencyCAD", u"- Cnct", None))
+ self.pb_add_connector_3.setText(QCoreApplication.translate("fluencyCAD", u"-Jnt", None))
+ self.pb_add_connector_2.setText(QCoreApplication.translate("fluencyCAD", u"+Jnt", None))
self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None))
self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None))
- self.pb_export_step.setText(QCoreApplication.translate("fluencyCAD", u"STEP", None))
self.pb_export_iges.setText(QCoreApplication.translate("fluencyCAD", u"IGES", None))
+ self.pb_export_step.setText(QCoreApplication.translate("fluencyCAD", u"STEP", None))
+ self.assembly_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly Tools", None))
+ self.pb_compo_to_assembly.setText(QCoreApplication.translate("fluencyCAD", u"Add", None))
+ self.pb_remove_compo_from_assembly.setText(QCoreApplication.translate("fluencyCAD", u"Rem", None))
+ self.groupBox_11.setTitle(QCoreApplication.translate("fluencyCAD", u"Sketch", None))
+ self.groupBox_6.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
+ self.pb_edt_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Edt", None))
+ self.pb_nw_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Add", None))
+ self.pb_del_sketch.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
+ self.InputTab.setTabText(self.InputTab.indexOf(self.sketch_tab), QCoreApplication.translate("fluencyCAD", u"Sketch", None))
+ self.groupBox_7.setTitle(QCoreApplication.translate("fluencyCAD", u"Executive", None))
+ self.pushButton_5.setText(QCoreApplication.translate("fluencyCAD", u"Load Code", None))
+ self.pushButton_4.setText(QCoreApplication.translate("fluencyCAD", u"Save code", None))
+ self.pb_apply_code.setText(QCoreApplication.translate("fluencyCAD", u"Apply Code", None))
+ self.pushButton.setText(QCoreApplication.translate("fluencyCAD", u"Delete Code", None))
+ self.InputTab.setTabText(self.InputTab.indexOf(self.code_tab), QCoreApplication.translate("fluencyCAD", u"Code", None))
+ self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None))
+ self.groupBox_12.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Connections", None))
+ self.groupBox_13.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
+ self.pb_del_connection.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
+ self.pb_update_connection.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
+ self.pb_edt_sktch_4.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None))
+ self.groupBox_10.setTitle(QCoreApplication.translate("fluencyCAD", u"Bodys / Operations", None))
+ self.groupBox_8.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
+ self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
+ self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
+ self.pb_edt_sktch_3.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None))
self.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None))
self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None))
# retranslateUi
diff --git a/src/fluency/geometry_occ/kernel.py b/src/fluency/geometry_occ/kernel.py
index b11bf96..74e42fa 100644
--- a/src/fluency/geometry_occ/kernel.py
+++ b/src/fluency/geometry_occ/kernel.py
@@ -54,6 +54,7 @@ class OCGeometryKernel(GeometryKernel):
def create_point(self, x: float, y: float) -> GeometryObject:
"""Create a 2D point."""
from OCP.gp import gp_Pnt
+
return OCCGeometryObject(gp_Pnt(x, y, 0))
def create_line(self, start: Point2D, end: Point2D) -> GeometryObject:
@@ -61,9 +62,7 @@ class OCGeometryKernel(GeometryKernel):
from OCP.gp import gp_Pnt
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
- edge = BRepBuilderAPI_MakeEdge(
- gp_Pnt(start.x, start.y, 0), gp_Pnt(end.x, end.y, 0)
- ).Edge()
+ edge = BRepBuilderAPI_MakeEdge(gp_Pnt(start.x, start.y, 0), gp_Pnt(end.x, end.y, 0)).Edge()
return OCCGeometryObject(edge, {"type": "line"})
def create_circle(self, center: Point2D, radius: float) -> GeometryObject:
@@ -71,9 +70,7 @@ class OCGeometryKernel(GeometryKernel):
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2, gp_Circ
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
- circ = gp_Circ(
- gp_Ax2(gp_Pnt(center.x, center.y, 0), gp_Dir(0, 0, 1)), radius
- )
+ circ = gp_Circ(gp_Ax2(gp_Pnt(center.x, center.y, 0), gp_Dir(0, 0, 1)), radius)
edge = BRepBuilderAPI_MakeEdge(circ).Edge()
return OCCGeometryObject(edge, {"type": "circle"})
@@ -88,9 +85,7 @@ class OCGeometryKernel(GeometryKernel):
start_rad = math.radians(start_angle)
end_rad = math.radians(end_angle)
- circ = gp_Circ(
- gp_Ax2(gp_Pnt(center.x, center.y, 0), gp_Dir(0, 0, 1)), radius
- )
+ circ = gp_Circ(gp_Ax2(gp_Pnt(center.x, center.y, 0), gp_Dir(0, 0, 1)), radius)
edge = BRepBuilderAPI_MakeEdge(circ, start_rad, end_rad).Edge()
return OCCGeometryObject(edge, {"type": "arc"})
@@ -159,8 +154,7 @@ class OCGeometryKernel(GeometryKernel):
face = self._get_shape(sketch)
if face is None:
raise ValueError(
- "Cannot extrude: sketch has no geometry. "
- "Draw a closed profile before extruding."
+ "Cannot extrude: sketch has no geometry. Draw a closed profile before extruding."
)
# If the wrapper class itself leaked through somehow, surface a
# clear error instead of letting BRepPrimAPI_MakePrism raise an
@@ -204,6 +198,7 @@ class OCGeometryKernel(GeometryKernel):
def _sketch_normal(obj: GeometryObject) -> Tuple[float, float, float]:
"""Return the normal stored on a sketch-derived geometry object, else +Z."""
import numpy as np
+
meta = getattr(obj, "metadata", None) or {}
n = meta.get("normal")
if n is None:
@@ -453,7 +448,6 @@ class OCGeometryKernel(GeometryKernel):
origin: Tuple[float, float, float] = (0, 0, 0),
) -> GeometryObject:
"""Rotate a body around an axis."""
- import math
shape = self._get_shape(body)
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.gp import gp_Trsf, gp_Ax1, gp_Pnt, gp_Dir
@@ -562,6 +556,52 @@ class OCGeometryKernel(GeometryKernel):
shape = reader.OneShape()
return OCCGeometryObject(shape, {"type": "imported_step"})
+ def import_step_components(self, filepath: str) -> list:
+ """Import a STEP file and return each solid as a separate ``(name, shape)`` pair.
+
+ The STEP reader transfers the entire root shape, then we iterate
+ over individual ``TopAbs_SOLID`` entities so that each solid gets
+ its own ``OCCGeometryObject``. If the file contains only a single
+ solid the list will have one entry.
+
+ Returns a list of ``(name, OCCGeometryObject)`` tuples.
+ """
+ from OCP.STEPControl import STEPControl_Reader
+ from OCP.IFSelect import IFSelect_RetDone
+ from OCP.TopExp import TopExp_Explorer
+ from OCP.TopAbs import TopAbs_SOLID
+ from OCP.TopoDS import TopoDS
+
+ reader = STEPControl_Reader()
+ status = reader.ReadFile(filepath)
+
+ if status != IFSelect_RetDone:
+ raise ValueError(f"Failed to read STEP file: {filepath}")
+
+ reader.TransferRoots()
+ shape = reader.OneShape()
+
+ # Extract individual solids
+ solids: list = []
+ explorer = TopExp_Explorer(shape, TopAbs_SOLID)
+ idx = 0
+ while explorer.More():
+ solid = TopoDS.Solid_s(explorer.Current())
+ idx += 1
+ solids.append(
+ (
+ f"Part {idx}",
+ OCCGeometryObject(solid, {"type": "imported_step"}),
+ )
+ )
+ explorer.Next()
+
+ # Fallback: no individual solids found — return the whole shape
+ if not solids:
+ solids = [("Imported", OCCGeometryObject(shape, {"type": "imported_step"}))]
+
+ return solids
+
def import_iges(self, filepath: str) -> GeometryObject:
"""Import from IGES format."""
from OCP.IGESControl import IGESControl_Reader
@@ -643,10 +683,8 @@ class OCGeometryKernel(GeometryKernel):
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_EDGE
- from OCP.BRep import BRep_Tool
- from OCP.TopLoc import TopLoc_Location
from OCP.BRepAdaptor import BRepAdaptor_Curve
- from OCP.GeomAbs import GeomAbs_Line, GeomAbs_Circle, GeomAbs_Ellipse, GeomAbs_BSplineCurve
+ from OCP.GeomAbs import GeomAbs_Line
vertices_list: List[List[float]] = []
edges_list: List[List[int]] = []
@@ -678,6 +716,7 @@ class OCGeometryKernel(GeometryKernel):
explorer = TopExp_Explorer(shape, TopAbs_EDGE)
while explorer.More():
from OCP.TopoDS import TopoDS
+
edge = TopoDS.Edge_s(explorer.Current())
edge_points = discretize_edge(edge)
diff --git a/src/fluency/models/data_model.py b/src/fluency/models/data_model.py
index fdf79bf..6eb662e 100644
--- a/src/fluency/models/data_model.py
+++ b/src/fluency/models/data_model.py
@@ -13,9 +13,7 @@ import numpy as np
from fluency.geometry.base import (
Point2D,
- Point3D,
GeometryObject,
- SketchInterface,
)
from fluency.geometry_occ.kernel import OCGeometryKernel, OCCGeometryObject
from fluency.geometry_occ.sketch import OCCSketch
@@ -48,6 +46,7 @@ class Workplane:
def __post_init__(self):
# Normalise normal and x_dir on construction.
import numpy as np
+
n = np.asarray(self.normal, dtype=float)
n = n / np.linalg.norm(n)
x = np.asarray(self.x_dir, dtype=float)
@@ -84,6 +83,7 @@ class Workplane:
def world_to_uv(self, p: Tuple[float, float, float]) -> Tuple[float, float]:
"""Map a 3D world point to UV coordinates on this plane."""
import numpy as np
+
ox, oy, oz = self.origin
v = np.array([p[0] - ox, p[1] - oy, p[2] - oz])
xd = np.array(self.x_dir, dtype=float)
@@ -244,6 +244,7 @@ class Body:
visible: bool = True
render_object: Any = None
+ render_material: Optional[str] = None # material preset name for render backend
created_at: datetime = field(default_factory=datetime.now)
modified_at: datetime = field(default_factory=datetime.now)
@@ -471,8 +472,8 @@ class AssemblyConnection:
"""
id: str = field(default_factory=lambda: str(uuid.uuid4()))
- first_ac_id: str = "" # grounded reference side
- second_ac_id: str = "" # solved side
+ first_ac_id: str = "" # grounded reference side
+ second_ac_id: str = "" # solved side
first_connector_id: Optional[str] = None
second_connector_id: Optional[str] = None
created_at: datetime = field(default_factory=datetime.now)
@@ -512,8 +513,9 @@ class Assembly:
"""
# Guard: deduplicate — same pair in either order
for c in self.connections:
- if (c.first_ac_id == first_ac_id and c.second_ac_id == second_ac_id) or \
- (c.first_ac_id == second_ac_id and c.second_ac_id == first_ac_id):
+ if (c.first_ac_id == first_ac_id and c.second_ac_id == second_ac_id) or (
+ c.first_ac_id == second_ac_id and c.second_ac_id == first_ac_id
+ ):
return c
conn = AssemblyConnection(
first_ac_id=first_ac_id,
@@ -526,8 +528,7 @@ class Assembly:
def remove_connections_for(self, ac_id: str) -> None:
"""Drop every connection that involves *ac_id* (e.g. on removal)."""
self.connections = [
- c for c in self.connections
- if c.first_ac_id != ac_id and c.second_ac_id != ac_id
+ c for c in self.connections if c.first_ac_id != ac_id and c.second_ac_id != ac_id
]
def get_rigid_group(self, ac_id: str) -> List[str]:
@@ -591,19 +592,14 @@ class Assembly:
# the rigid-group graph and point at a missing component.
self.remove_connections_for(assembly_component_id)
if self.active_assembly_component == assembly_component_id:
- self.active_assembly_component = next(
- iter(self.components.keys()), None
- )
+ self.active_assembly_component = next(iter(self.components.keys()), None)
self.modified_at = datetime.now()
return True
return False
def get_active_instance(self) -> Optional[AssemblyComponent]:
"""Get the currently active assembly component instance."""
- if (
- self.active_assembly_component
- and self.active_assembly_component in self.components
- ):
+ if self.active_assembly_component and self.active_assembly_component in self.components:
return self.components[self.active_assembly_component]
return None
diff --git a/src/fluency/rendering/mitsuba_backend.py b/src/fluency/rendering/mitsuba_backend.py
index 4bff801..390486c 100644
--- a/src/fluency/rendering/mitsuba_backend.py
+++ b/src/fluency/rendering/mitsuba_backend.py
@@ -39,47 +39,31 @@ class MitsubaBackend(RenderBackend):
# ── Scene construction ──────────────────────────────────────────
- def _build_scene_dict(
+ def _base_scene_dict(
self,
- mesh_path: str,
- material: RenderMaterial,
camera: RenderCamera,
settings: RenderSettings,
+ first_mesh_path: Optional[str] = None,
) -> dict:
- """Build a Mitsuba scene dictionary from our data classes.
+ """Return a scene dict with everything *except* the shape entries.
- Uses a 3-point lighting setup (key + fill + rim) plus an ambient
- environment emitter for soft fill, giving well-balanced shading on
- all faces of the model. Lighting intensities and colors come from
- ``settings.lighting``; ground plane comes from ``settings.ground_plane``.
+ When *first_mesh_path* is given the ground plane / backdrop is sized
+ from its bounding box; otherwise a large default is used.
"""
import mitsuba as mi
lighting = settings.lighting
ground = settings.ground_plane
- # Map our BSDF types to Mitsuba BSDF dicts
- bsdf = self._make_bsdf(material)
-
- # Determine mesh file type from extension
- ext = os.path.splitext(mesh_path)[1].lower()
- shape_type = "ply" if ext == ".ply" else "obj"
-
- # Build camera-to-world transform using the Python API
cam_to_world = mi.ScalarTransform4f.look_at(
origin=list(camera.origin),
target=list(camera.target),
up=list(camera.up),
)
- scene = {
+ scene: dict = {
"type": "scene",
- # Integrator
- "integrator": {
- "type": "path",
- "max_depth": settings.max_depth,
- },
- # Camera
+ "integrator": {"type": "path", "max_depth": settings.max_depth},
"sensor": {
"type": "perspective",
"fov": camera.fov,
@@ -95,7 +79,6 @@ class MitsubaBackend(RenderBackend):
"sample_count": settings.spp,
},
},
- # Ambient environment fill
"emitter": {
"type": "constant",
"radiance": {
@@ -107,59 +90,50 @@ class MitsubaBackend(RenderBackend):
],
},
},
- # Shape
- "shape": {
- "type": shape_type,
- "filename": mesh_path,
- "bsdf": bsdf,
- },
}
- # ── 3-point lighting (colors and intensities from config) ───
+ # ── 3-point lighting ──────────────────────────────────────────
key_rgb = [c * lighting.key_intensity for c in lighting.key_color]
- key_to_world = mi.ScalarTransform4f.look_at(
- origin=[1.0, -0.8, 1.2],
- target=[0.0, 0.0, 0.0],
- up=[0.0, 0.0, 1.0],
- )
scene["key_light"] = {
"type": "directional",
- "to_world": key_to_world,
+ "to_world": mi.ScalarTransform4f.look_at(
+ origin=[1.0, -0.8, 1.2],
+ target=[0.0, 0.0, 0.0],
+ up=[0.0, 0.0, 1.0],
+ ),
"irradiance": {"type": "rgb", "value": key_rgb},
}
fill_rgb = [c * lighting.fill_intensity for c in lighting.fill_color]
- fill_to_world = mi.ScalarTransform4f.look_at(
- origin=[-1.0, 0.6, 0.8],
- target=[0.0, 0.0, 0.0],
- up=[0.0, 0.0, 1.0],
- )
scene["fill_light"] = {
"type": "directional",
- "to_world": fill_to_world,
+ "to_world": mi.ScalarTransform4f.look_at(
+ origin=[-1.0, 0.6, 0.8],
+ target=[0.0, 0.0, 0.0],
+ up=[0.0, 0.0, 1.0],
+ ),
"irradiance": {"type": "rgb", "value": fill_rgb},
}
rim_rgb = [c * lighting.rim_intensity for c in lighting.rim_color]
- rim_to_world = mi.ScalarTransform4f.look_at(
- origin=[-0.3, 1.2, -0.8],
- target=[0.0, 0.0, 0.0],
- up=[0.0, 0.0, 1.0],
- )
scene["rim_light"] = {
"type": "directional",
- "to_world": rim_to_world,
+ "to_world": mi.ScalarTransform4f.look_at(
+ origin=[-0.3, 1.2, -0.8],
+ target=[0.0, 0.0, 0.0],
+ up=[0.0, 0.0, 1.0],
+ ),
"irradiance": {"type": "rgb", "value": rim_rgb},
}
- # ── Ground plane / backdrop (optional) ─────────────────────
+ # ── Ground plane / backdrop ───────────────────────────────────
if ground.enabled:
try:
- # Load mesh to compute bounds for ground/backdrop placement
- mesh_shape = mi.load_dict({"type": shape_type, "filename": mesh_path})
+ ext = os.path.splitext(first_mesh_path)[1].lower() if first_mesh_path else ""
+ shape_type = "ply" if ext == ".ply" else "obj"
+ mesh_shape = mi.load_dict({"type": shape_type, "filename": first_mesh_path})
bbox = mesh_shape.bbox()
bbox_min, bbox_max = bbox[0], bbox[1]
- # Ground at model's lowest Z with 0.1% offset
model_height = bbox_max[2] - bbox_min[2]
ground_z = bbox_min[2] - 0.001 * model_height
dx = bbox_max[0] - bbox_min[0]
@@ -167,7 +141,6 @@ class MitsubaBackend(RenderBackend):
dz = bbox_max[2] - bbox_min[2]
diag = float((dx * dx + dy * dy + dz * dz) ** 0.5)
except Exception:
- # Fallback: place at origin with large default size
ground_z = -ground.distance_below
diag = 1000.0
@@ -177,19 +150,15 @@ class MitsubaBackend(RenderBackend):
}
if ground.curved_backdrop:
- # Photo booth style curved leinwand:
- # - Flat floor section in front of the model
- # - Curved cylinder behind that sweeps up and over
- half_size = diag * 50.0 # huge floor
- radius = diag * 3.0 # curvature radius
- cyl_height = diag * 20.0 # width of the cylinder (along its axis)
+ half_size = diag * 50.0
+ radius = diag * 3.0
+ cyl_height = diag * 20.0
scene["ground_floor"] = {
"type": "rectangle",
"to_world": mi.ScalarTransform4f.translate([0.0, 0.0, ground_z])
@ mi.ScalarTransform4f.scale([half_size, half_size, 1.0]),
"bsdf": bsdf_ground,
}
- # Cylinder: axis along Y, positioned behind model, radius sweeps up
scene["ground_backdrop"] = {
"type": "cylinder",
"radius": radius,
@@ -200,7 +169,6 @@ class MitsubaBackend(RenderBackend):
"bsdf": bsdf_ground,
}
else:
- # Simple flat ground plane — very large so edges aren't visible
half_size = diag * 50.0
scene["ground_plane"] = {
"type": "rectangle",
@@ -211,6 +179,48 @@ class MitsubaBackend(RenderBackend):
return scene
+ def _build_scene_dict(
+ self,
+ mesh_path: str,
+ material: RenderMaterial,
+ camera: RenderCamera,
+ settings: RenderSettings,
+ ) -> dict:
+ """Build a single-shape Mitsuba scene dictionary."""
+ ext = os.path.splitext(mesh_path)[1].lower()
+ shape_type = "ply" if ext == ".ply" else "obj"
+
+ scene = self._base_scene_dict(camera, settings, first_mesh_path=mesh_path)
+ scene["shape"] = {
+ "type": shape_type,
+ "filename": mesh_path,
+ "bsdf": self._make_bsdf(material),
+ }
+ return scene
+
+ def _build_assembly_scene_dict(
+ self,
+ parts: list,
+ camera: RenderCamera,
+ settings: RenderSettings,
+ ) -> dict:
+ """Build a multi-shape Mitsuba scene dictionary.
+
+ *parts* is a list of ``(mesh_path, RenderMaterial)`` tuples.
+ """
+ first_path = parts[0][0] if parts else None
+ scene = self._base_scene_dict(camera, settings, first_mesh_path=first_path)
+
+ for i, (mesh_path, material) in enumerate(parts):
+ ext = os.path.splitext(mesh_path)[1].lower()
+ shape_type = "ply" if ext == ".ply" else "obj"
+ scene[f"shape_{i}"] = {
+ "type": shape_type,
+ "filename": mesh_path,
+ "bsdf": self._make_bsdf(material),
+ }
+ return scene
+
def _make_bsdf(self, material: RenderMaterial) -> dict:
"""Convert a RenderMaterial to a Mitsuba BSDF dict."""
mt = material.bsdf_type
@@ -282,9 +292,8 @@ class MitsubaBackend(RenderBackend):
logger.info(f"Rendering {settings.width}x{settings.height} @ {settings.spp} spp")
- # Render
try:
- image = mi.render(scene, spp=settings.spp, seed=int(settings.seed or 0)) # type: ignore[arg-type] # Mitsuba accepts int at runtime
+ image = mi.render(scene, spp=settings.spp, seed=int(settings.seed or 0))
except Exception as e:
logger.error(f"Mitsuba render failed: {e}")
raise
@@ -292,14 +301,48 @@ class MitsubaBackend(RenderBackend):
if progress_callback:
progress_callback(1.0)
- # Convert to numpy (H, W, 3)
arr = np.array(image, dtype=np.float32)
-
- # Apply approximate sRGB tonemapping
arr = np.clip(arr, 0.0, None)
- arr = np.power(arr, 1.0 / 2.2) # gamma
+ arr = np.power(arr, 1.0 / 2.2)
arr = np.clip(arr, 0.0, 1.0)
+ return arr
+ def render_assembly(
+ self,
+ parts: list,
+ camera: RenderCamera,
+ settings: RenderSettings,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ ) -> np.ndarray:
+ """Render multiple meshes with individual materials.
+
+ *parts* is a list of ``(mesh_path, RenderMaterial)`` tuples.
+ Returns (H, W, 3) float32 RGB array.
+ """
+ self._set_variant()
+ import mitsuba as mi
+
+ scene_dict = self._build_assembly_scene_dict(parts, camera, settings)
+ scene = mi.load_dict(scene_dict)
+
+ logger.info(
+ f"Rendering assembly ({len(parts)} parts) "
+ f"{settings.width}x{settings.height} @ {settings.spp} spp"
+ )
+
+ try:
+ image = mi.render(scene, spp=settings.spp, seed=int(settings.seed or 0))
+ except Exception as e:
+ logger.error(f"Mitsuba assembly render failed: {e}")
+ raise
+
+ if progress_callback:
+ progress_callback(1.0)
+
+ arr = np.array(image, dtype=np.float32)
+ arr = np.clip(arr, 0.0, None)
+ arr = np.power(arr, 1.0 / 2.2)
+ arr = np.clip(arr, 0.0, 1.0)
return arr
def render_preview(
diff --git a/src/fluency/rendering/occ_renderer.py b/src/fluency/rendering/occ_renderer.py
index 049566d..575f263 100644
--- a/src/fluency/rendering/occ_renderer.py
+++ b/src/fluency/rendering/occ_renderer.py
@@ -17,6 +17,86 @@ from .base import Renderer, RenderObject, RenderColor
logger = logging.getLogger(__name__)
+def _compute_viewport_aligned_xdir(
+ normal: Tuple[float, float, float],
+ view: Any = None,
+) -> Tuple[float, float, float]:
+ """Compute an in-plane x_dir that appears screen-horizontal.
+
+ When *view* (a V3d_View) is provided, derives a "screen-right" direction
+ from the camera's Eye/At/Up vectors and projects it onto the face plane.
+ This makes the sketch U-axis align with what the user sees as horizontal
+ on screen, eliminating the 90° rotation caused by OCC internal edge order.
+
+ Falls back to projecting world +X onto the plane when view is None
+ (headless tests or unavailable camera state).
+ """
+ import numpy as np
+
+ n = np.asarray(normal, dtype=float)
+ n /= np.linalg.norm(n)
+
+ # Derive screen-right from camera: cross(view_up, -view_dir) gives the
+ # direction that appears horizontal on screen.
+ if view is not None:
+ try:
+ eye_obj = view.Eye()
+ at_obj = view.At()
+ up_obj = view.Up()
+
+ eye = np.array([eye_obj.X(), eye_obj.Y(), eye_obj.Z()], dtype=float)
+ at = np.array([at_obj.X(), at_obj.Y(), at_obj.Z()], dtype=float)
+ up = np.array([up_obj.X(), up_obj.Y(), up_obj.Z()], dtype=float)
+
+ view_dir = at - eye
+ vd_norm = np.linalg.norm(view_dir)
+ if vd_norm > 1e-9:
+ view_dir /= vd_norm
+ else:
+ view_dir = np.array([0.0, 0.0, 1.0])
+
+ up_norm = np.linalg.norm(up)
+ if up_norm < 1e-9:
+ up = np.array([0.0, 1.0, 0.0])
+ else:
+ up /= up_norm
+
+ # Screen-right in world coords: cross(view_up, view_dir) gives
+ # the direction that appears horizontal on screen.
+ screen_right = np.cross(up, view_dir)
+ sr_norm = np.linalg.norm(screen_right)
+ if sr_norm > 1e-9:
+ screen_right /= sr_norm
+ else:
+ screen_right = np.array([1.0, 0.0, 0.0])
+ except Exception:
+ screen_right = np.array([1.0, 0.0, 0.0])
+ else:
+ # No camera available — use world +X as best guess.
+ screen_right = np.array([1.0, 0.0, 0.0])
+
+ # Project screen-right onto the face plane (remove normal component).
+ x_dir = screen_right - np.dot(screen_right, n) * n
+ xn = np.linalg.norm(x_dir)
+ if xn < 1e-9:
+ # screen_right is parallel to normal — pick any orthogonal direction.
+ fallback = np.array([0.0, 1.0, 0.0])
+ x_dir = fallback - np.dot(fallback, n) * n
+ xn = np.linalg.norm(x_dir)
+ if xn > 1e-9:
+ unit = x_dir / xn
+ return (float(unit[0]), float(unit[1]), float(unit[2]))
+ # Last resort: cross with any non-parallel axis.
+ for ax in (np.array([1, 0, 0]), np.array([0, 1, 0]), np.array([0, 0, 1])):
+ x_dir = ax - np.dot(ax, n) * n
+ xn = np.linalg.norm(x_dir)
+ if xn > 1e-9:
+ unit = x_dir / xn
+ return (float(unit[0]), float(unit[1]), float(unit[2]))
+ # Should never reach here for a valid normal.
+ return (1.0, 0.0, 0.0)
+
+
@dataclass
class OCCRenderObject(RenderObject):
"""Internal object state for the OCC renderer."""
@@ -1046,28 +1126,12 @@ class OCCRenderer(Renderer):
d = (cx - pln_origin.X()) * nx + (cy - pln_origin.Y()) * ny + (cz - pln_origin.Z()) * nz
origin = (cx - d * nx, cy - d * ny, cz - d * nz)
- # x_dir: direction of the face's first edge (stable, in-plane).
- x_dir = None
- try:
- from OCP.TopExp import TopExp
- from OCP.BRep import BRep_Tool
- expl = TopExp_Explorer(face, TopAbs_EDGE)
- if expl.More():
- edge = TopoDS.Edge_s(expl.Current())
- v1 = TopExp.FirstVertex_s(edge, True)
- v2 = TopExp.LastVertex_s(edge, True)
- p1 = BRep_Tool.Pnt_s(v1)
- p2 = BRep_Tool.Pnt_s(v2)
- ex, ey, ez = p2.X() - p1.X(), p2.Y() - p1.Y(), p2.Z() - p1.Z()
- elen = (ex * ex + ey * ey + ez * ez) ** 0.5
- if elen > 1e-9:
- x_dir = (ex / elen, ey / elen, ez / elen)
- except Exception:
- pass
- if x_dir is None:
- # Fall back to the plane's own X axis.
- px = pln.XAxis().Direction()
- x_dir = (px.X(), px.Y(), px.Z())
+ # x_dir: project a "screen-horizontal" direction onto the face plane
+ # so that the sketch's U-axis appears horizontal in the current 3D
+ # viewport. This eliminates the 90° rotation that occurred when we
+ # used the first edge / plane X axis (which follow OCC internal order,
+ # not the viewer perspective).
+ x_dir = _compute_viewport_aligned_xdir((nx, ny, nz), self._view)
# Identify the displayed body that owns this face, so the host can
# auto-target it as the cut/union body when the user extrudes the
@@ -1258,25 +1322,8 @@ class OCCRenderer(Renderer):
d = (cx - pln_origin.X()) * nx + (cy - pln_origin.Y()) * ny + (cz - pln_origin.Z()) * nz
origin = (cx - d * nx, cy - d * ny, cz - d * nz)
- # x_dir from first edge.
- x_dir = None
- try:
- expl = TopExp_Explorer(face, TopAbs_EDGE_TYPE)
- if expl.More():
- edge = TopoDS.Edge_s(expl.Current())
- v1 = TopExp.FirstVertex_s(edge, True)
- v2 = TopExp.LastVertex_s(edge, True)
- p1 = BRep_Tool.Pnt_s(v1)
- p2 = BRep_Tool.Pnt_s(v2)
- ex, ey, ez = p2.X() - p1.X(), p2.Y() - p1.Y(), p2.Z() - p1.Z()
- elen = (ex * ex + ey * ey + ez * ez) ** 0.5
- if elen > 1e-9:
- x_dir = (ex / elen, ey / elen, ez / elen)
- except Exception:
- pass
- if x_dir is None:
- px = pln.XAxis().Direction()
- x_dir = (px.X(), px.Y(), px.Z())
+ # x_dir: viewport-aligned so connector gizmo matches screen.
+ x_dir = _compute_viewport_aligned_xdir((nx, ny, nz), self._view)
return [{
"type": "planar_face",
diff --git a/src/fluency/ui/main_window.py b/src/fluency/ui/main_window.py
index 9bda0b1..52cc57c 100644
--- a/src/fluency/ui/main_window.py
+++ b/src/fluency/ui/main_window.py
@@ -33,6 +33,7 @@ from PySide6.QtWidgets import (
QMenu,
QMessageBox,
QPushButton,
+ QScrollArea,
QVBoxLayout,
QWidget,
)
@@ -56,6 +57,221 @@ from gui_ui import Ui_fluencyCAD # auto-generated Qt form (project root on sys.
logger = logging.getLogger(__name__)
+def _make_body_thumbnail(
+ body,
+ kernel,
+ size: QSize = QSize(64, 48),
+):
+ """Render a small isometric thumbnail of a body using Pillow.
+
+ Returns a QPixmap or None on failure.
+ """
+ try:
+ import numpy as np
+ from PIL import Image, ImageDraw
+ from PySide6.QtGui import QImage, QPixmap
+
+ verts, faces = body.get_mesh(kernel)
+ if verts is None or len(verts) == 0:
+ return None
+
+ verts = np.asarray(verts, dtype=np.float64)
+ faces = np.asarray(faces, dtype=np.int32)
+
+ mins = verts.min(axis=0)
+ maxs = verts.max(axis=0)
+ center = (mins + maxs) / 2
+ extent = maxs - mins
+ max_dim = max(extent)
+ if max_dim < 1e-10:
+ return None
+
+ # Normalize vertices to [-1, 1] range centered at origin
+ v = (verts - center) / (max_dim * 0.7)
+
+ # Simple isometric projection (rotation + orthographic)
+ angle_y = np.radians(-45)
+ angle_x = np.radians(25)
+ cos_y, sin_y = np.cos(angle_y), np.sin(angle_y)
+ cos_x, sin_x = np.cos(angle_x), np.sin(angle_x)
+
+ # Rotate Y
+ x1 = v[:, 0] * cos_y - v[:, 2] * sin_y
+ z1 = v[:, 0] * sin_y + v[:, 2] * cos_y
+ y1 = v[:, 1]
+
+ # Rotate X
+ y2 = y1 * cos_x - z1 * sin_x
+ z2 = y1 * sin_x + z1 * cos_x
+ x2 = x1
+
+ # Project to 2D (orthographic)
+ w_px, h_px = size.width(), size.height()
+
+ # Compute 2D bounding box of projected vertices for tight framing
+ all_px = x2
+ all_py = -y2
+ px_min, px_max = all_px.min(), all_px.max()
+ py_min, py_max = all_py.min(), all_py.max()
+ span_x = px_max - px_min
+ span_y = py_max - py_min
+ if span_x < 1e-10 or span_y < 1e-10:
+ return None
+
+ # Scale to fill ~90% of the image
+ margin = 0.10
+ avail_w = w_px * (1.0 - margin)
+ avail_h = h_px * (1.0 - margin)
+ scale = min(avail_w / span_x, avail_h / span_y)
+
+ center_x = (px_min + px_max) / 2
+ center_y = (py_min + py_max) / 2
+ px = (all_px * scale + w_px / 2 - center_x * scale).astype(np.float64)
+ py = (all_py * scale + h_px / 2 - center_y * scale).astype(np.float64)
+ depth = z2 # for painter's algorithm
+
+ # Dark grey background
+ img = Image.new("RGBA", (w_px, h_px), (55, 55, 60, 255))
+ draw = ImageDraw.Draw(img)
+
+ # Compute face normals for backface culling & shading
+ v0 = np.stack([px[faces[:, 0]], py[faces[:, 0]], depth[faces[:, 0]]], axis=1)
+ v1 = np.stack([px[faces[:, 1]], py[faces[:, 1]], depth[faces[:, 1]]], axis=1)
+ v2 = np.stack([px[faces[:, 2]], py[faces[:, 2]], depth[faces[:, 2]]], axis=1)
+
+ # 2D cross product for winding
+ cross = (v1[:, 0] - v0[:, 0]) * (v2[:, 1] - v0[:, 1]) - (v1[:, 1] - v0[:, 1]) * (
+ v2[:, 0] - v0[:, 0]
+ )
+
+ # Average depth per face
+ avg_depth = (v0[:, 2] + v1[:, 2] + v2[:, 2]) / 3.0
+
+ # Sort faces by depth (painter's algorithm: draw far faces first)
+ order = np.argsort(-avg_depth)
+
+ # Ceramic white body with shading
+ base_r, base_g, base_b = 220, 218, 215
+
+ for i in order:
+ # Backface culling
+ if cross[i] <= 0:
+ continue
+
+ pts = [
+ (float(px[faces[i, 0]]), float(py[faces[i, 0]])),
+ (float(px[faces[i, 1]]), float(py[faces[i, 1]])),
+ (float(px[faces[i, 2]]), float(py[faces[i, 2]])),
+ ]
+
+ # Shading: stronger contrast for depth perception
+ brightness = 0.5 + 0.5 * max(0.0, min(1.0, (avg_depth[i] + 1) / 2))
+ r = int(base_r * brightness)
+ g = int(base_g * brightness)
+ b = int(base_b * brightness)
+
+ draw.polygon(pts, fill=(r, g, b, 255))
+
+ # Convert PIL image to QPixmap
+ data = img.tobytes("raw", "RGBA")
+ qimg = QImage(data, w_px, h_px, w_px * 4, QImage.Format_RGBA8888)
+ pixmap = QPixmap.fromImage(qimg.copy())
+ return pixmap
+
+ except Exception as e:
+ logger.debug(f"Thumbnail generation failed: {e}")
+ return None
+
+
+# ── Button sizing & styling constants ────────────────────────────────
+_BTN_MIN = 40 # minimum button dimension (px)
+_BTN_MAX = 160 # maximum button dimension (px)
+_BTN_PAD = 4 # padding between icon and button edge
+_BTN_UNSELECTED_STYLE = (
+ "QPushButton {"
+ " background-color: #3a3a3e;"
+ " border: 2px solid #555560;"
+ " border-radius: 6px;"
+ "}"
+ "QPushButton:hover {"
+ " background-color: #4a4a50;"
+ "}"
+ "QPushButton:checked {"
+ " background-color: #2d5f8a;"
+ " border: 2px solid #4da6ff;"
+ "}"
+)
+
+
+def _set_button_style(btn: QPushButton) -> None:
+ """Apply the standard button style sheet."""
+ btn.setStyleSheet(_BTN_UNSELECTED_STYLE)
+
+
+def _resize_row_buttons(scroll_area: QScrollArea, buttons: list[QPushButton]) -> None:
+ """Resize every button in *buttons* to fill the scroll area height.
+
+ Computes a square size from the parent viewport height so buttons
+ always use all available vertical space. Width follows height
+ (1∶1 aspect) clamped to [_BTN_MIN, _BTN_MAX].
+ """
+ vh = scroll_area.viewport().height()
+ if vh < 20 or not buttons:
+ return
+ size = max(_BTN_MIN, min(_BTN_MAX, vh - 8)) # 8 px margin
+ for btn in buttons:
+ btn.setFixedSize(QSize(size, size))
+ icon_size = QSize(size - _BTN_PAD, size - _BTN_PAD)
+ if btn.icon().isNull():
+ continue
+ btn.setIconSize(icon_size)
+
+
+def _scroll_to_button(btn: QPushButton, scroll_area: QScrollArea) -> None:
+ """Ensure *btn* is visible inside a horizontal scroll area."""
+ ha = scroll_area.horizontalScrollBar()
+ viewport_w = scroll_area.viewport().width()
+ parent = btn.parent()
+ if parent:
+ x = btn.pos().x()
+ right = x + btn.width()
+ scroll_left = ha.value()
+ scroll_right = scroll_left + viewport_w
+ if x < scroll_left:
+ ha.setValue(x)
+ elif right > scroll_right:
+ ha.setValue(right - viewport_w)
+
+
+def _create_component_button(
+ num: int,
+ name: str,
+ body,
+ kernel,
+ group: QButtonGroup,
+ layout: QHBoxLayout,
+ click_handler,
+ scroll_area: QScrollArea,
+) -> QPushButton:
+ """Create a component button with thumbnail."""
+ btn = QPushButton(str(num))
+ btn.setCheckable(True)
+ btn.setFixedSize(QSize(100, 100))
+ btn.setToolTip(name)
+ btn.clicked.connect(click_handler)
+ _set_button_style(btn)
+
+ pixmap = _make_body_thumbnail(body, kernel, QSize(96, 96))
+ if pixmap is not None:
+ btn.setIcon(pixmap)
+ btn.setIconSize(QSize(96, 96))
+ btn.setText("")
+
+ group.addButton(btn)
+ layout.addWidget(btn)
+ return btn
+
+
def _project_body_to_workplane(
body_shape: Any,
workplane: Tuple[
@@ -388,32 +604,51 @@ class MainWindow(QMainWindow):
self._ui.InputTab.addTab(self._render_tab, "Render")
# Component buttons (dynamically generated per component, not in UI).
+ # Wrapped in a QScrollArea so many components can scroll horizontally.
self._component_box = QWidget()
self._component_box_layout = QHBoxLayout(self._component_box)
self._component_box_layout.setAlignment(Qt.AlignLeft)
+ self._component_box_layout.setContentsMargins(2, 2, 2, 2)
self._component_group = QButtonGroup(self)
self._component_group.setExclusive(True)
+
+ self._component_scroll = QScrollArea()
+ self._component_scroll.setWidget(self._component_box)
+ self._component_scroll.setWidgetResizable(True)
+ self._component_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self._component_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self._component_scroll.setFrameShape(QFrame.NoFrame)
+ self._component_scroll.setMaximumHeight(95)
+
# Add to the Components group box from the UI.
compo_layout = self._ui.compo_box.layout()
if compo_layout is None:
compo_layout = QHBoxLayout(self._ui.compo_box)
compo_layout.setContentsMargins(0, 0, 0, 0)
- compo_layout.addWidget(self._component_box)
- compo_layout.addStretch()
+ compo_layout.addWidget(self._component_scroll)
# ── Assembly box (dynamic buttons like component box) ──
self._assembly_box = QWidget()
self._assembly_box_layout = QHBoxLayout(self._assembly_box)
self._assembly_box_layout.setAlignment(Qt.AlignLeft)
+ self._assembly_box_layout.setContentsMargins(2, 2, 2, 2)
self._assembly_component_group = QButtonGroup(self)
self._assembly_component_group.setExclusive(True)
+
+ self._assembly_scroll = QScrollArea()
+ self._assembly_scroll.setWidget(self._assembly_box)
+ self._assembly_scroll.setWidgetResizable(True)
+ self._assembly_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self._assembly_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self._assembly_scroll.setFrameShape(QFrame.NoFrame)
+ self._assembly_scroll.setMaximumHeight(95)
+
# Add to the Assembly group box from the UI.
asm_layout = self._ui.assembly_box.layout()
if asm_layout is None:
asm_layout = QHBoxLayout(self._ui.assembly_box)
asm_layout.setContentsMargins(0, 0, 0, 0)
- asm_layout.addWidget(self._assembly_box)
- asm_layout.addStretch()
+ asm_layout.addWidget(self._assembly_scroll)
# ── Assembly Move button (programmatic, in assembly_tools) ──
self._btn_asm_move = QPushButton("Pos")
@@ -425,7 +660,7 @@ class MainWindow(QMainWindow):
)
asm_tools_layout = self._ui.assembly_tools.layout()
if asm_tools_layout is not None:
- asm_tools_layout.addWidget(self._btn_asm_move, 0, 2, 1, 1)
+ asm_tools_layout.addWidget(self._btn_asm_move)
# Panel-focus mode (equal | sketch | viewer).
self._panel_focus: str = "equal"
@@ -755,6 +990,24 @@ class MainWindow(QMainWindow):
return i
return 0
+ def _update_component_thumbnail(self, component_index: int) -> None:
+ """Update the thumbnail icon on a component button."""
+ if not (0 <= component_index < len(self._component_buttons)):
+ return
+ comp_ids = list(self._project.components.keys())
+ if component_index >= len(comp_ids):
+ return
+ comp = self._project.components[comp_ids[component_index]]
+ first_body = next(iter(comp.bodies.values()), None)
+ if not first_body or not first_body.geometry:
+ return
+ btn = self._component_buttons[component_index]
+ pixmap = _make_body_thumbnail(first_body, self._kernel, QSize(96, 96))
+ if pixmap is not None:
+ btn.setIcon(pixmap)
+ btn.setIconSize(QSize(96, 96))
+ btn.setText("")
+
def _new_component(self):
logger.info("=== NEW COMPONENT ===")
comp = self._project.add_component()
@@ -762,11 +1015,14 @@ class MainWindow(QMainWindow):
self._mark_dirty()
logger.info(f"Created component: {comp.name}")
- btn = QPushButton(str(len(self._project.components)))
+ btn_num = len(self._project.components)
+ btn = QPushButton(str(btn_num))
btn.setCheckable(True)
- btn.setFixedSize(QSize(40, 40))
+ btn.setFixedSize(QSize(100, 100))
+ btn.setToolTip(comp.name)
btn.clicked.connect(self._on_component_button_clicked)
btn.setChecked(True)
+ _set_button_style(btn)
for b in self._component_buttons:
b.setChecked(False)
@@ -774,6 +1030,8 @@ class MainWindow(QMainWindow):
self._component_buttons.append(btn)
self._component_group.addButton(btn)
self._component_box_layout.addWidget(btn)
+ # Scroll to the new button.
+ _scroll_to_button(btn, self._component_scroll)
self._refresh_lists()
logger.info(f"Created component: {comp.name}")
@@ -804,6 +1062,9 @@ class MainWindow(QMainWindow):
self._assembly_view_active = False
self._refresh_lists()
self._redraw_bodies()
+ # Scroll to the selected button.
+ if 0 <= idx < len(self._component_buttons):
+ _scroll_to_button(self._component_buttons[idx], self._component_scroll)
def _refresh_lists(self):
self._sketch_list.clear()
@@ -846,6 +1107,7 @@ class MainWindow(QMainWindow):
self._propagate_to_assembly()
self._recalculate_connectors()
self._refresh_lists()
+ self._update_component_thumbnail(self._get_active_component_index())
def _update_bodies_from_sketch(self):
"""Re-extrude bodies whose source sketch has been edited.
@@ -1121,11 +1383,22 @@ class MainWindow(QMainWindow):
label = f"{instance_num}"
btn = QPushButton(label)
btn.setCheckable(True)
- btn.setFixedSize(QSize(40, 40))
+ btn.setFixedSize(QSize(100, 100))
btn.setToolTip(f"{ac.name} (instance {list(assembly.components.keys()).index(ac.id) + 1})")
# Store the assembly component id in the button.
btn._assembly_component_id = ac.id
btn.clicked.connect(self._on_assembly_component_clicked)
+ _set_button_style(btn)
+
+ # Thumbnail from the component's first body.
+ src_comp = self._current_component
+ if src_comp:
+ first_body = next(iter(src_comp.bodies.values()), None)
+ if first_body and first_body.geometry:
+ pixmap = _make_body_thumbnail(first_body, self._kernel, QSize(96, 96))
+ if pixmap is not None:
+ btn.setIcon(pixmap)
+ btn.setIconSize(QSize(96, 96))
# Uncheck all other assembly buttons, check this one.
for b in self._assembly_component_buttons:
@@ -1213,6 +1486,12 @@ class MainWindow(QMainWindow):
self._show_assembly_in_viewer(fit=True)
+ # Scroll to the selected button.
+ for btn in self._assembly_component_buttons:
+ if getattr(btn, "_assembly_component_id", None) == active_id:
+ _scroll_to_button(btn, self._assembly_scroll)
+ break
+
def _apply_transform(self, shape: Any, position, rotation) -> Any:
"""Apply a position translation and rotation matrix to a shape.
@@ -3419,6 +3698,10 @@ class MainWindow(QMainWindow):
logger.warning("No sketch entity found")
QMessageBox.warning(self, "No Sketch", "Please create a sketch first")
return
+ if sketch is None:
+ sketch = Sketch()
+ self._current_sketch = sketch
+ self._current_component.add_sketch(sketch)
sketch.occ_sketch = sketch_entity
# Resolve the profile geometry *before* opening the dialog so the
@@ -3526,6 +3809,7 @@ class MainWindow(QMainWindow):
body_name = body.name
self._refresh_lists()
+ self._update_component_thumbnail(self._get_active_component_index())
self._viewer_3d.fit_camera()
logger.info(f"Extruded: {body_name}")
logger.info("=== EXTRUDE COMPLETE ===")
@@ -3546,6 +3830,10 @@ class MainWindow(QMainWindow):
if not sketch_entity:
QMessageBox.warning(self, "No Sketch", "Please create a sketch first")
return
+ if sketch is None:
+ sketch = Sketch()
+ self._current_sketch = sketch
+ self._current_component.add_sketch(sketch)
sketch.occ_sketch = sketch_entity
dialog = RevolveDialog(self)
@@ -3582,6 +3870,7 @@ class MainWindow(QMainWindow):
logger.info(f"Render object: {body.render_object}")
self._refresh_lists()
+ self._update_component_thumbnail(self._get_active_component_index())
self._viewer_3d.fit_camera()
logger.info(f"Revolved: {body.name}")
@@ -3656,6 +3945,7 @@ class MainWindow(QMainWindow):
logger.info(f"Render object: {new_body.render_object}")
self._refresh_lists()
+ self._update_component_thumbnail(self._get_active_component_index())
self._viewer_3d.fit_camera()
logger.info(f"Cut complete: {new_body.name}")
@@ -3696,6 +3986,7 @@ class MainWindow(QMainWindow):
logger.info(f"Render object: {new_body.render_object}")
self._refresh_lists()
+ self._update_component_thumbnail(self._get_active_component_index())
self._viewer_3d.fit_camera()
logger.info(f"Union complete: {new_body.name}")
@@ -4063,15 +4354,30 @@ class MainWindow(QMainWindow):
self._viewer_3d.clear_scene()
self._refresh_lists()
- # Rebuild component buttons (one per component, numbered).
+ # Rebuild component buttons (one per component, with thumbnails).
for idx, comp in enumerate(self._project.components.values(), start=1):
- btn = QPushButton(str(idx))
- btn.setCheckable(True)
- btn.setFixedSize(QSize(40, 40))
- btn.clicked.connect(self._on_component_button_clicked)
+ first_body = next(iter(comp.bodies.values()), None)
+ if first_body and first_body.geometry:
+ btn = _create_component_button(
+ idx,
+ comp.name,
+ first_body,
+ self._kernel,
+ self._component_group,
+ self._component_box_layout,
+ self._on_component_button_clicked,
+ self._component_scroll,
+ )
+ else:
+ btn = QPushButton(str(idx))
+ btn.setCheckable(True)
+ btn.setFixedSize(QSize(100, 100))
+ btn.setToolTip(comp.name)
+ btn.clicked.connect(self._on_component_button_clicked)
+ _set_button_style(btn)
+ self._component_group.addButton(btn)
+ self._component_box_layout.addWidget(btn)
self._component_buttons.append(btn)
- self._component_group.addButton(btn)
- self._component_box_layout.addWidget(btn)
# Pick which component to activate: explicit saved selection,
# falling back to the project's active_component, then the first.
@@ -4097,10 +4403,22 @@ class MainWindow(QMainWindow):
instance_num = len(self._assembly_component_buttons) + 1
btn = QPushButton(str(instance_num))
btn.setCheckable(True)
- btn.setFixedSize(QSize(40, 40))
+ btn.setFixedSize(QSize(100, 100))
btn.setToolTip(f"{ac.name} (instance {instance_num})")
btn._assembly_component_id = ac.id
btn.clicked.connect(self._on_assembly_component_clicked)
+ _set_button_style(btn)
+
+ # Thumbnail from the source component's first body.
+ src_comp = self._project.components.get(ac.component_id)
+ if src_comp:
+ first_body = next(iter(src_comp.bodies.values()), None)
+ if first_body and first_body.geometry:
+ pixmap = _make_body_thumbnail(first_body, self._kernel, QSize(96, 96))
+ if pixmap is not None:
+ btn.setIcon(pixmap)
+ btn.setIconSize(QSize(96, 96))
+
self._assembly_component_buttons.append(btn)
self._assembly_component_group.addButton(btn)
self._assembly_box_layout.addWidget(btn)
@@ -4166,32 +4484,59 @@ class MainWindow(QMainWindow):
filepath, _ = QFileDialog.getOpenFileName(
self, "Import File", "", "STEP Files (*.step *.stp);;IGES Files (*.iges *.igs)"
)
- if filepath:
- try:
- if filepath.lower().endswith((".step", ".stp")):
- geometry = self._kernel.import_step(filepath)
- else:
- geometry = self._kernel.import_iges(filepath)
+ if not filepath:
+ return
- if not self._current_component:
- self._current_component = self._project.add_component()
+ try:
+ if filepath.lower().endswith((".step", ".stp")):
+ parts = self._kernel.import_step_components(filepath)
+ else:
+ geometry = self._kernel.import_iges(filepath)
+ parts = [("Imported", geometry)]
- body = self._current_component.add_body(
- Body(name="Imported", geometry=geometry, source_operation="import")
- )
- self._mark_dirty()
+ num_before = len(self._project.components)
+
+ for name, geometry in parts:
+ comp = self._project.add_component(Component(name=name))
+ self._current_component = comp
+
+ body = comp.add_body(Body(name=name, geometry=geometry, source_operation="import"))
vertices, faces = body.get_mesh(self._kernel)
body.render_object = self._viewer_3d.add_mesh(
vertices, faces, body.color, body.name
)
- self._refresh_lists()
- self._viewer_3d.fit_camera()
- logger.info(f"Imported: {filepath}")
+ # Add a numbered button to the component bar
+ btn_num = len(self._project.components)
+ btn = _create_component_button(
+ btn_num,
+ name,
+ body,
+ self._kernel,
+ self._component_group,
+ self._component_box_layout,
+ self._on_component_button_clicked,
+ self._component_scroll,
+ )
+ self._component_buttons.append(btn)
- except Exception as e:
- QMessageBox.critical(self, "Error", f"Failed to import: {e}")
+ # Select the first newly imported component
+ if self._component_buttons and num_before < len(self._component_buttons):
+ for b in self._component_buttons:
+ b.setChecked(False)
+ self._component_buttons[num_before].setChecked(True)
+ self._current_component = self._project.components[
+ list(self._project.components.keys())[num_before]
+ ]
+
+ self._refresh_lists()
+ self._viewer_3d.fit_camera()
+ self._mark_dirty()
+ logger.info(f"Imported {len(parts)} part(s) from {filepath}")
+
+ except Exception as e:
+ QMessageBox.critical(self, "Error", f"Failed to import: {e}")
def _export_step(self):
if not self._selected_body:
@@ -4241,32 +4586,21 @@ class MainWindow(QMainWindow):
def _open_render_window(self):
"""Populate the render tab with the selected body or assembly and switch to it."""
- shape = None
- # Try selected body first
- if self._selected_body and self._selected_body.geometry:
- try:
- shape = self._kernel._get_shape(self._selected_body.geometry)
- except Exception as e:
- logger.warning(f"Failed to get shape for render: {e}")
+ # Collect all visible bodies across all components
+ assembly_parts = [] # list of (TopoDS_Shape, Optional[str])
+ single_shape = None
- # Try selected assembly component
- if shape is None and self._selected_assembly_component_id:
- ac_id = self._selected_assembly_component_id
- for assembly in self._project.assemblies.values():
- ac = assembly.components.get(ac_id)
- if ac and ac.component_id:
- src_comp = self._project.components.get(ac.component_id)
- if src_comp:
- for body in src_comp.bodies.values():
- if body.geometry:
- try:
- shape = self._kernel._get_shape(body.geometry)
- break
- except Exception:
- pass
- break
+ for comp in self._project.components.values():
+ for body in comp.bodies.values():
+ if not body.visible or not body.geometry:
+ continue
+ try:
+ occ_shape = self._kernel._get_shape(body.geometry)
+ assembly_parts.append((occ_shape, body.render_material))
+ except Exception as e:
+ logger.warning(f"Failed to get shape for render: {e}")
- if shape is None:
+ if not assembly_parts:
QMessageBox.information(
self,
"Render",
@@ -4274,16 +4608,12 @@ class MainWindow(QMainWindow):
)
return
- # Capture the current 3D viewport camera so the render matches what the user sees.
- # get_render_camera() handles both perspective and orthographic modes — in
- # orthographic mode it translates view.Scale() (mouse-wheel zoom) into a
- # proper perspective camera distance, so the render framing matches the viewport.
+ # Capture the current 3D viewport camera
try:
renderer = self._viewer_3d.get_renderer()
if hasattr(renderer, "get_render_camera"):
viewport_camera = renderer.get_render_camera()
else:
- # Fallback for pygfx or other renderers without the method.
from fluency.rendering.render_backend import RenderCamera
eye, at_, up = self._viewer_3d.get_camera_position()
@@ -4297,38 +4627,31 @@ class MainWindow(QMainWindow):
except Exception:
viewport_camera = None
- # Populate the render tab and switch to it.
- self._render_tab.set_shape(shape, camera=viewport_camera)
+ # Single body → use set_shape; multiple → use set_assembly
+ if len(assembly_parts) == 1:
+ self._render_tab.set_shape(assembly_parts[0][0], camera=viewport_camera)
+ else:
+ self._render_tab.set_assembly(assembly_parts, camera=viewport_camera)
self._ui.InputTab.setCurrentWidget(self._render_tab)
def _load_render_tab_shape(self) -> None:
"""Auto-load the selected body or assembly component into the render tab."""
- shape = None
- # Try selected body first.
- if self._selected_body and self._selected_body.geometry:
- try:
- shape = self._kernel._get_shape(self._selected_body.geometry)
- except Exception as e:
- logger.warning(f"Failed to get shape for render: {e}")
- # Try selected assembly component.
- if shape is None and self._selected_assembly_component_id:
- ac_id = self._selected_assembly_component_id
- for assembly in self._project.assemblies.values():
- ac = assembly.components.get(ac_id)
- if ac and ac.component_id:
- src_comp = self._project.components.get(ac.component_id)
- if src_comp:
- for body in src_comp.bodies.values():
- if body.geometry:
- try:
- shape = self._kernel._get_shape(body.geometry)
- break
- except Exception:
- pass
- break
- if shape is None:
+ # Collect all visible bodies across all components
+ assembly_parts = []
+ for comp in self._project.components.values():
+ for body in comp.bodies.values():
+ if not body.visible or not body.geometry:
+ continue
+ try:
+ occ_shape = self._kernel._get_shape(body.geometry)
+ assembly_parts.append((occ_shape, body.render_material))
+ except Exception as e:
+ logger.warning(f"Failed to get shape for render: {e}")
+
+ if not assembly_parts:
return
- # Capture the viewport camera so the render matches what the user sees.
+
+ # Capture the viewport camera
try:
renderer = self._viewer_3d.get_renderer()
if hasattr(renderer, "get_render_camera"):
@@ -4346,7 +4669,11 @@ class MainWindow(QMainWindow):
)
except Exception:
viewport_camera = None
- self._render_tab.set_shape(shape, camera=viewport_camera)
+
+ if len(assembly_parts) == 1:
+ self._render_tab.set_shape(assembly_parts[0][0], camera=viewport_camera)
+ else:
+ self._render_tab.set_assembly(assembly_parts, camera=viewport_camera)
def _on_tab_changed(self, index: int) -> None:
"""When the user switches to the Render tab, auto-load the selected body."""
diff --git a/src/fluency/ui/render_window.py b/src/fluency/ui/render_window.py
index cee6c4f..22667bd 100644
--- a/src/fluency/ui/render_window.py
+++ b/src/fluency/ui/render_window.py
@@ -104,6 +104,55 @@ class _RenderThread(QThread):
self.error.emit(str(e))
+class _AssemblyRenderThread(QThread):
+ """Background thread for assembly rendering (multiple meshes)."""
+
+ finished = Signal(np.ndarray)
+ error = Signal(str)
+ progress = Signal(float)
+
+ def __init__(
+ self,
+ backend: RenderBackend,
+ parts: list,
+ camera: RenderCamera,
+ settings: RenderSettings,
+ parent=None,
+ is_preview: bool = False,
+ ):
+ super().__init__(parent)
+ self._backend = backend
+ self._parts = parts # list of (mesh_path, RenderMaterial)
+ self._camera = camera
+ self._settings = settings
+ self._is_preview = is_preview
+ self._cancelled = False
+
+ def cancel(self):
+ self._cancelled = True
+
+ def run(self):
+ try:
+ if self._is_preview:
+ image = self._backend.render_assembly(
+ self._parts,
+ self._camera,
+ self._settings,
+ )
+ else:
+ image = self._backend.render_assembly(
+ self._parts,
+ self._camera,
+ self._settings,
+ progress_callback=lambda p: self.progress.emit(p),
+ )
+ if not self._cancelled:
+ self.finished.emit(image)
+ except Exception as e:
+ if not self._cancelled:
+ self.error.emit(str(e))
+
+
# ── Render window ───────────────────────────────────────────────────
@@ -960,6 +1009,9 @@ class RenderTabContent(QWidget):
self._backend = None
self._mesh_path: Optional[str] = None
+ # Assembly support: list of (mesh_path, RenderMaterial)
+ self._assembly_parts: list = []
+
# Rendering threads & images
self._render_thread: Optional[_RenderThread] = None
self._preview_thread: Optional[_RenderThread] = None
@@ -1000,6 +1052,31 @@ class RenderTabContent(QWidget):
"""Return the current camera (from UI controls or initial)."""
return self._build_camera_from_ui()
+ def set_assembly(
+ self,
+ parts: list,
+ camera: Optional[RenderCamera] = None,
+ ) -> None:
+ """Load multiple shapes for assembly rendering.
+
+ *parts* is a list of ``(TopoDS_Shape, Optional[str])`` tuples
+ where the second element is an optional material preset name.
+ """
+ self._shape = None
+ self._mesh_path = None
+ self._assembly_parts = []
+ if camera is not None:
+ self._camera = camera
+ self._last_image = None
+ self._last_preview = None
+ self._image_label.setPixmap(QPixmap())
+ self._image_label.setText("Click Preview or Render to start")
+ self._status_badge.setText("")
+ self._export_btn.setEnabled(False)
+ self._prepare_assembly_mesh(parts)
+ self._populate_camera_controls()
+ self._schedule_auto_preview()
+
def set_camera(self, camera: RenderCamera) -> None:
"""Update the render camera from an external source (e.g. 3D viewport).
@@ -1390,6 +1467,30 @@ class RenderTabContent(QWidget):
logger.error(f"Failed to prepare mesh: {e}")
QMessageBox.warning(self, "Render Error", f"Failed to tessellate shape:\n{e}")
+ def _prepare_assembly_mesh(self, parts: list):
+ """Tessellate multiple shapes to separate PLY files.
+
+ *parts* is a list of ``(TopoDS_Shape, Optional[str])`` tuples.
+ Each material preset name is resolved via ``get_preset``.
+ """
+ from fluency.rendering.material_presets import get_preset
+
+ self._assembly_parts = []
+ first_bounds = None
+ for shape, mat_name in parts:
+ try:
+ mesh_path = occ_shape_to_ply(shape, linear_deflection=0.1, angular_deflection=0.15)
+ material = get_preset(mat_name) if mat_name else get_preset("Brushed Steel")
+ self._assembly_parts.append((mesh_path, material))
+ if first_bounds is None:
+ first_bounds = occ_shape_bounds(shape)
+ except Exception as e:
+ logger.warning(f"Failed to tessellate assembly part: {e}")
+ if first_bounds and self._camera is None:
+ mn, mx = first_bounds
+ self._camera = self._backend.default_camera_from_bounds(mn, mx)
+ logger.info(f"Prepared assembly: {len(self._assembly_parts)} parts")
+
def _setup_auto_preview(self):
self._auto_preview_timer = QTimer(self)
self._auto_preview_timer.setSingleShot(True)
@@ -1489,10 +1590,13 @@ class RenderTabContent(QWidget):
@Slot()
def _on_preview(self):
- if self._backend is None or self._mesh_path is None:
+ if self._backend is None:
+ return
+ has_single = self._mesh_path is not None
+ has_assembly = len(self._assembly_parts) > 0
+ if not has_single and not has_assembly:
return
self._cancel_active_thread()
- material = get_preset(self._material_combo.currentText())
res_text = self._res_combo.currentText()
w, h = [int(x) for x in res_text.split("×")]
settings = RenderSettings(
@@ -1504,15 +1608,28 @@ class RenderTabContent(QWidget):
)
camera = self._build_camera_from_ui()
self._set_buttons_rendering("preview")
- self._preview_thread = _RenderThread(
- self._backend,
- self._mesh_path,
- material,
- camera,
- settings,
- parent=self,
- is_preview=True,
- )
+
+ if has_assembly:
+ # Assembly: render all parts with their assigned materials
+ self._preview_thread = _AssemblyRenderThread(
+ self._backend,
+ self._assembly_parts,
+ camera,
+ settings,
+ parent=self,
+ is_preview=True,
+ )
+ else:
+ material = get_preset(self._material_combo.currentText())
+ self._preview_thread = _RenderThread(
+ self._backend,
+ self._mesh_path,
+ material,
+ camera,
+ settings,
+ parent=self,
+ is_preview=True,
+ )
self._preview_thread.finished.connect(self._on_preview_done)
self._preview_thread.error.connect(self._on_render_error)
self._preview_thread.start()
@@ -1526,11 +1643,12 @@ class RenderTabContent(QWidget):
"No render backend available.\nInstall with: pip install mitsuba",
)
return
- if self._mesh_path is None:
+ has_single = self._mesh_path is not None
+ has_assembly = len(self._assembly_parts) > 0
+ if not has_single and not has_assembly:
QMessageBox.warning(self, "No Shape", "No shape to render.")
return
self._cancel_active_thread()
- material = get_preset(self._material_combo.currentText())
spp = self._spp_spin.value()
res_text = self._res_combo.currentText()
w, h = [int(x) for x in res_text.split("×")]
@@ -1543,15 +1661,27 @@ class RenderTabContent(QWidget):
)
camera = self._build_camera_from_ui()
self._set_buttons_rendering("render")
- self._render_thread = _RenderThread(
- self._backend,
- self._mesh_path,
- material,
- camera,
- settings,
- parent=self,
- is_preview=False,
- )
+
+ if has_assembly:
+ self._render_thread = _AssemblyRenderThread(
+ self._backend,
+ self._assembly_parts,
+ camera,
+ settings,
+ parent=self,
+ is_preview=False,
+ )
+ else:
+ material = get_preset(self._material_combo.currentText())
+ self._render_thread = _RenderThread(
+ self._backend,
+ self._mesh_path,
+ material,
+ camera,
+ settings,
+ parent=self,
+ is_preview=False,
+ )
self._render_thread.finished.connect(self._on_render_done)
self._render_thread.error.connect(self._on_render_error)
self._render_thread.progress.connect(self._on_render_progress)