From e3a8d55850a9ec17c340737f7ab719c580b8f321 Mon Sep 17 00:00:00 2001 From: bklronin Date: Tue, 23 Jul 2024 15:25:50 +0200 Subject: [PATCH 01/16] - intial transit --- Gui.py | 431 ++++++++++++++++++++++-------------------- gui.ui | 566 ++++++++++++++++++++++++++++++-------------------------- main.py | 446 +++++++++++++++++++++++++++----------------- 3 files changed, 806 insertions(+), 637 deletions(-) diff --git a/Gui.py b/Gui.py index c58dfcd..42cd35a 100644 --- a/Gui.py +++ b/Gui.py @@ -11,139 +11,31 @@ from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, QMetaObject, QObject, QPoint, QRect, QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, - QFont, QFontDatabase, QGradient, QIcon, - QImage, QKeySequence, QLinearGradient, QPainter, - QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient, + QCursor, QFont, QFontDatabase, QGradient, + QIcon, QImage, QKeySequence, QLinearGradient, + QPainter, QPalette, QPixmap, QRadialGradient, + QTransform) from PySide6.QtWidgets import (QApplication, QGridLayout, QGroupBox, QHBoxLayout, - QListWidget, QListWidgetItem, QMainWindow, QMenuBar, - QPushButton, QSizePolicy, QStatusBar, QTabWidget, - QTextEdit, QVBoxLayout, QWidget) + QListWidget, QListWidgetItem, QMainWindow, QMenu, + QMenuBar, QPushButton, QSizePolicy, QStatusBar, + QTabWidget, QTextEdit, QVBoxLayout, QWidget) class Ui_fluencyCAD(object): def setupUi(self, fluencyCAD): if not fluencyCAD.objectName(): fluencyCAD.setObjectName(u"fluencyCAD") fluencyCAD.resize(2214, 803) + self.actionNew_Project = QAction(fluencyCAD) + self.actionNew_Project.setObjectName(u"actionNew_Project") + self.actionLoad_Project = QAction(fluencyCAD) + self.actionLoad_Project.setObjectName(u"actionLoad_Project") + self.actionRecent = QAction(fluencyCAD) + self.actionRecent.setObjectName(u"actionRecent") self.centralwidget = QWidget(fluencyCAD) self.centralwidget.setObjectName(u"centralwidget") self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setObjectName(u"gridLayout") - 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.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, 5, 10, 1, Qt.AlignTop) - - self.groupBox_5 = QGroupBox(self.centralwidget) - self.groupBox_5.setObjectName(u"groupBox_5") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth()) - self.groupBox_5.setSizePolicy(sizePolicy) - self.groupBox_5.setMaximumSize(QSize(300, 16777215)) - self.verticalLayout_3 = QVBoxLayout(self.groupBox_5) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") - self.sketch_list = QListWidget(self.groupBox_5) - self.sketch_list.setObjectName(u"sketch_list") - self.sketch_list.setSelectionRectVisible(True) - - self.verticalLayout_3.addWidget(self.sketch_list) - - self.groupBox_6 = QGroupBox(self.groupBox_5) - self.groupBox_6.setObjectName(u"groupBox_6") - sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) - self.groupBox_6.setSizePolicy(sizePolicy1) - self.gridLayout_6 = QGridLayout(self.groupBox_6) - self.gridLayout_6.setObjectName(u"gridLayout_6") - self.gridLayout_6.setContentsMargins(2, 2, 2, 2) - 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, 0, 2, 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, 0, 0, 1, 1) - - 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, 0, 1, 1, 1) - - - self.verticalLayout_3.addWidget(self.groupBox_6) - - self.body_list = QListWidget(self.groupBox_5) - self.body_list.setObjectName(u"body_list") - self.body_list.setSelectionRectVisible(True) - - self.verticalLayout_3.addWidget(self.body_list) - - self.groupBox_8 = QGroupBox(self.groupBox_5) - self.groupBox_8.setObjectName(u"groupBox_8") - sizePolicy1.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth()) - self.groupBox_8.setSizePolicy(sizePolicy1) - 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_3.addWidget(self.groupBox_8) - - - self.gridLayout.addWidget(self.groupBox_5, 0, 3, 12, 1) - self.groupBox_9 = QGroupBox(self.centralwidget) self.groupBox_9.setObjectName(u"groupBox_9") self.gridLayout_7 = QGridLayout(self.groupBox_9) @@ -173,8 +65,11 @@ class Ui_fluencyCAD(object): self.groupBox_3 = QGroupBox(self.centralwidget) self.groupBox_3.setObjectName(u"groupBox_3") - sizePolicy1.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) - self.groupBox_3.setSizePolicy(sizePolicy1) + sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy) self.groupBox_3.setMaximumSize(QSize(16777214, 16777213)) self.gridLayout_4 = QGridLayout(self.groupBox_3) self.gridLayout_4.setObjectName(u"gridLayout_4") @@ -236,6 +131,62 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1) + self.groupBox_2 = QGroupBox(self.centralwidget) + self.groupBox_2.setObjectName(u"groupBox_2") + sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) + self.groupBox_2.setSizePolicy(sizePolicy) + self.gridLayout_2 = QGridLayout(self.groupBox_2) + self.gridLayout_2.setObjectName(u"gridLayout_2") + 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, 1, 1, 1, 1, Qt.AlignTop) + + 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, 2, 0, 1, 1, Qt.AlignTop) + + 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, 2, 1, 1, 1, Qt.AlignTop) + + 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, 1, 0, 1, 1) + + + self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1) + + self.gl_box = QGroupBox(self.centralwidget) + self.gl_box.setObjectName(u"gl_box") + sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.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, 4, 12, 1) + self.InputTab = QTabWidget(self.centralwidget) self.InputTab.setObjectName(u"InputTab") sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) @@ -288,6 +239,43 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.InputTab, 0, 2, 12, 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.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, 5, 10, 1, Qt.AlignTop) + self.groupBox_4 = QGroupBox(self.centralwidget) self.groupBox_4.setObjectName(u"groupBox_4") self.verticalLayout_2 = QVBoxLayout(self.groupBox_4) @@ -300,71 +288,106 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.groupBox_4, 11, 5, 1, 1) - self.groupBox_2 = QGroupBox(self.centralwidget) - self.groupBox_2.setObjectName(u"groupBox_2") - sizePolicy1.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) - self.groupBox_2.setSizePolicy(sizePolicy1) - self.gridLayout_2 = QGridLayout(self.groupBox_2) - self.gridLayout_2.setObjectName(u"gridLayout_2") - 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, 1, 1, 1, 1, Qt.AlignTop) - - 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, 2, 0, 1, 1, Qt.AlignTop) - - 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, 2, 1, 1, 1, Qt.AlignTop) - - 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, 1, 0, 1, 1) - - - self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1) - - self.gl_box = QGroupBox(self.centralwidget) - self.gl_box.setObjectName(u"gl_box") + self.groupBox_5 = QGroupBox(self.centralwidget) + self.groupBox_5.setObjectName(u"groupBox_5") sizePolicy3 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy3.setHorizontalStretch(0) - sizePolicy3.setVerticalStretch(4) - sizePolicy3.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth()) - self.gl_box.setSizePolicy(sizePolicy3) - 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) + sizePolicy3.setVerticalStretch(0) + sizePolicy3.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth()) + self.groupBox_5.setSizePolicy(sizePolicy3) + self.groupBox_5.setMaximumSize(QSize(300, 16777215)) + self.verticalLayout_3 = QVBoxLayout(self.groupBox_5) + self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.sketch_list = QListWidget(self.groupBox_5) + self.sketch_list.setObjectName(u"sketch_list") + self.sketch_list.setSelectionRectVisible(True) - self.gridLayout.addWidget(self.gl_box, 0, 4, 12, 1) + self.verticalLayout_3.addWidget(self.sketch_list) + + self.groupBox_6 = QGroupBox(self.groupBox_5) + 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_del_sketch = QPushButton(self.groupBox_6) + self.pb_del_sketch.setObjectName(u"pb_del_sketch") + + self.gridLayout_6.addWidget(self.pb_del_sketch, 0, 2, 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, 0, 0, 1, 1) + + 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, 0, 1, 1, 1) + + + self.verticalLayout_3.addWidget(self.groupBox_6) + + self.body_list = QListWidget(self.groupBox_5) + self.body_list.setObjectName(u"body_list") + self.body_list.setSelectionRectVisible(True) + + self.verticalLayout_3.addWidget(self.body_list) + + self.groupBox_8 = QGroupBox(self.groupBox_5) + self.groupBox_8.setObjectName(u"groupBox_8") + sizePolicy.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth()) + self.groupBox_8.setSizePolicy(sizePolicy) + 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_3.addWidget(self.groupBox_8) + + + self.gridLayout.addWidget(self.groupBox_5, 0, 3, 12, 1) + + self.timeline_box = QGroupBox(self.centralwidget) + self.timeline_box.setObjectName(u"timeline_box") + + self.gridLayout.addWidget(self.timeline_box, 12, 2, 1, 3) fluencyCAD.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(fluencyCAD) self.menubar.setObjectName(u"menubar") self.menubar.setGeometry(QRect(0, 0, 2214, 24)) + self.menuFile = QMenu(self.menubar) + self.menuFile.setObjectName(u"menuFile") + self.menuSettings = QMenu(self.menubar) + self.menuSettings.setObjectName(u"menuSettings") fluencyCAD.setMenuBar(self.menubar) self.statusbar = QStatusBar(fluencyCAD) self.statusbar.setObjectName(u"statusbar") fluencyCAD.setStatusBar(self.statusbar) + self.menubar.addAction(self.menuFile.menuAction()) + self.menubar.addAction(self.menuSettings.menuAction()) + self.menuFile.addAction(self.actionNew_Project) + self.menuFile.addAction(self.actionLoad_Project) + self.menuFile.addAction(self.actionRecent) + self.menuFile.addSeparator() + self.retranslateUi(fluencyCAD) self.InputTab.setCurrentIndex(0) @@ -375,22 +398,9 @@ class Ui_fluencyCAD(object): def retranslateUi(self, fluencyCAD): fluencyCAD.setWindowTitle(QCoreApplication.translate("fluencyCAD", u"fluencyCAD", 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.groupBox_5.setTitle(QCoreApplication.translate("fluencyCAD", u"Sketch", None)) - self.groupBox_6.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None)) - self.pb_del_sketch.setText(QCoreApplication.translate("fluencyCAD", u"Del sketch", None)) - self.pb_nw_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Add Sktch", None)) - self.pb_edt_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Edt Sktch", None)) - self.groupBox_8.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None)) - self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del Bdy", None)) - self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Bdy Upd", None)) - self.pb_edt_sktch_3.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None)) + self.actionNew_Project.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) + self.actionLoad_Project.setText(QCoreApplication.translate("fluencyCAD", u"Load", None)) + self.actionRecent.setText(QCoreApplication.translate("fluencyCAD", u"Recent", 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)) @@ -450,15 +460,6 @@ class Ui_fluencyCAD(object): self.pb_con_perp.setToolTip(QCoreApplication.translate("fluencyCAD", u"Constrain Line perpendicular to another line.", None)) #endif // QT_CONFIG(tooltip) self.pb_con_perp.setText(QCoreApplication.translate("fluencyCAD", u"Perp_Lne", 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.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) - self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None)) self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None)) self.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None)) self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None)) @@ -471,5 +472,33 @@ class Ui_fluencyCAD(object): self.pb_linetool.setShortcut(QCoreApplication.translate("fluencyCAD", u"S", None)) #endif // QT_CONFIG(shortcut) self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", 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.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.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) + self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None)) + self.groupBox_5.setTitle(QCoreApplication.translate("fluencyCAD", u"Sketch", None)) + self.groupBox_6.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None)) + self.pb_del_sketch.setText(QCoreApplication.translate("fluencyCAD", u"Del sketch", None)) + self.pb_nw_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Add Sktch", None)) + self.pb_edt_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Edt Sktch", None)) + self.groupBox_8.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None)) + self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del Bdy", None)) + self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Bdy Upd", None)) + self.pb_edt_sktch_3.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None)) + self.timeline_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Timeline", None)) + self.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None)) + self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None)) # retranslateUi diff --git a/gui.ui b/gui.ui index fbeea87..c3f03d5 100644 --- a/gui.ui +++ b/gui.ui @@ -15,188 +15,6 @@ - - - - Modify - - - - - - Rev - - - - - - - Extrd - - - - - - - Arry - - - - - - - Cut - - - - - - - Comb - - - - - - - Mve - - - - - - - - - - - 0 - 0 - - - - - 300 - 16777215 - - - - Sketch - - - - - - true - - - - - - - - 0 - 0 - - - - Tools - - - - 2 - - - 2 - - - 2 - - - 2 - - - - - Del sketch - - - - - - - Add Sktch - - - - - - - Edt Sktch - - - - - - - - - - true - - - - - - - - 0 - 0 - - - - Tools - - - - 2 - - - 2 - - - 2 - - - 2 - - - - - Del Bdy - - - - - - - Bdy Upd - - - - - - - Nothing - - - - - - - - - @@ -401,88 +219,6 @@ - - - - - 0 - 0 - - - - 0 - - - - Sketch - - - - - - Code - - - - - - - - - Executive - - - - - - Load Code - - - - - - - Save code - - - - - - - Apply Code - - - - - - - Delete Code - - - - - - - - - - - - - - Export - - - - - - STL - - - - - - @@ -582,6 +318,277 @@ + + + + + 0 + 0 + + + + 0 + + + + Sketch + + + + + + Code + + + + + + + + + Executive + + + + + + Load Code + + + + + + + Save code + + + + + + + Apply Code + + + + + + + Delete Code + + + + + + + + + + + + + + Modify + + + + + + Rev + + + + + + + Extrd + + + + + + + Arry + + + + + + + Cut + + + + + + + Comb + + + + + + + Mve + + + + + + + + + + Export + + + + + + STL + + + + + + + + + + + 0 + 0 + + + + + 300 + 16777215 + + + + Sketch + + + + + + true + + + + + + + + 0 + 0 + + + + Tools + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Del sketch + + + + + + + Add Sktch + + + + + + + Edt Sktch + + + + + + + + + + true + + + + + + + + 0 + 0 + + + + Tools + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Del Bdy + + + + + + + Bdy Upd + + + + + + + Nothing + + + + + + + + + + + + + Timeline + + + @@ -593,8 +600,39 @@ 24 + + + File + + + + + + + + + Settings + + + + + + + New + + + + + Load + + + + + Recent + + diff --git a/main.py b/main.py index c069699..86513c8 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ import uuid import names -from PySide6.QtCore import Qt, QPoint, Signal +from PySide6.QtCore import Qt, QPoint, Signal, QSize from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QCheckBox, QPushButton from Gui import Ui_fluencyCAD # Import the generated GUI module from drawing_modules.vtk_widget import VTKWidget @@ -138,34 +138,18 @@ class MainWindow(QMainWindow): self.send_command.connect(self.custom_3D_Widget.on_receive_command) + self.ui.actionNew_Project.triggered.connect(self.new_project) + + self.project = Project() + self.new_project() + + """Project -> Timeline -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" + + + def on_flip_face(self): self.send_command.emit("flip") - def add_new_sketch_origin(self): - self.sketchWidget.clear_sketch() - self.sketchWidget.create_workplane() - - def add_new_sketch_wp(self): - self.sketchWidget.clear_sketch() - #edges = [((-158.0, -20.0, -25.0), (286.0, -195.0, -25.0)), ((-158.0, -20.0, 25.0), (-158.0, -20.0, -25.0))] - points = self.custom_3D_Widget.project_tosketch_points - normal = self.custom_3D_Widget.selected_normal - selected_lines = self.custom_3D_Widget.project_tosketch_lines - print("Selected lines", selected_lines) - - self.sketchWidget.create_workplane_projected() - self.sketchWidget.create_proj_points(points) - self.sketchWidget.create_proj_lines(selected_lines) - - # CLear all selections after it has been projected - self.custom_3D_Widget.project_tosketch_points.clear() - self.custom_3D_Widget.project_tosketch_lines.clear() - self.custom_3D_Widget.clear_actors_projection() - self.custom_3D_Widget.clear_actors_normals() - #self.custom_3D_Widget.clear_actors_projection() - - #self.sketchWidget.create_workplane_space(edges, normal) - def act_line_mode(self): if not self.ui.pb_linetool.isChecked(): self.sketchWidget.mouse_mode = 'line' @@ -224,7 +208,7 @@ class MainWindow(QMainWindow): def draw_mesh(self): name = self.ui.body_list.currentItem().text() print("selected_for disp", name) - model = self.model['operation'][name]['sdf_object'] + model = self.project.timeline[-1].body.sdf_body vesta = vesta_mesh model_data = vesta.generate_mesh_from_sdf(model, resolution=64, threshold=0) @@ -239,43 +223,74 @@ class MainWindow(QMainWindow): #self.view_update() print(f"Selected item: {name}") - def convert_points_for_sdf(self): - points_for_sdf = [] - for point_to_poly in self.sketchWidget.slv_points_main: - points_for_sdf.append(self.translate_points_tup(point_to_poly['ui_point'])) + def new_project(self): + print("New project") + timeline = [] + self.project.timeline = timeline + self.new_component() - return points_for_sdf + def new_component(self): + print("Compo") + compo = Component() + compo.id = "New Compo" + compo.descript = "Initial Component" + self.project.timeline.append(compo) - def convert_lines_for_interactor(self): - points_for_interact = [] - for point_to_poly in self.sketchWidget.slv_lines_main: - start, end = point_to_poly['ui_points'] - from_coord_start = self.sketchWidget.from_quadrant_coords_no_center(start) - from_coord_end = self.sketchWidget.from_quadrant_coords_no_center(end) - start_draw = self.translate_points_tup(from_coord_start) - end_draw = self.translate_points_tup(from_coord_end) - line = start_draw, end_draw - points_for_interact.append(line) + # Create a horizontal layout + horizontal_layout = QHBoxLayout() - print("packed_lines", points_for_interact) + # Set the layout for the timeline_box QFrame + self.ui.timeline_box.setLayout(horizontal_layout) - return points_for_interact + # Create the button + button = QPushButton() + + button.setToolTip(compo.id) + button.setText(str(len(self.project.timeline))) + + # Set the button's fixed size + button.setFixedSize(QSize(40, 40)) + + # Add the button to the horizontal layout + horizontal_layout.addWidget(button) + # Set the alignment of the layout to left + horizontal_layout.setAlignment(Qt.AlignLeft) + + def add_new_sketch_origin(self): + self.sketchWidget.clear_sketch() + self.sketchWidget.create_workplane() + + def add_new_sketch_wp(self): + self.sketchWidget.clear_sketch() + #edges = [((-158.0, -20.0, -25.0), (286.0, -195.0, -25.0)), ((-158.0, -20.0, 25.0), (-158.0, -20.0, -25.0))] + points = self.custom_3D_Widget.project_tosketch_points + normal = self.custom_3D_Widget.selected_normal + selected_lines = self.custom_3D_Widget.project_tosketch_lines + print("Selected lines", selected_lines) + + self.sketchWidget.create_workplane_projected() + self.sketchWidget.create_proj_points(points) + self.sketchWidget.create_proj_lines(selected_lines) + + # CLear all selections after it has been projected + self.custom_3D_Widget.project_tosketch_points.clear() + self.custom_3D_Widget.project_tosketch_lines.clear() + self.custom_3D_Widget.clear_actors_projection() + self.custom_3D_Widget.clear_actors_normals() + #self.custom_3D_Widget.clear_actors_projection() + + #self.sketchWidget.create_workplane_space(edges, normal) def add_sketch(self): name = f"sketch-{str(names.get_first_name())}" - points_for_sdf = self.convert_points_for_sdf() - element = { - 'id': name, - 'type': 'sketch', - 'point_list': self.sketchWidget.slv_points_main, - 'line_list': self.sketchWidget.slv_lines_main, - 'sketch_points': points_for_sdf, - 'solver': self.sketchWidget.solv - } + sketch = Sketch() + sketch.id = name + sketch.slv_points = self.sketchWidget.slv_points_main + sketch.slv_lines = self.sketchWidget.slv_lines_main + sketch.convert_points_for_sdf() - self.model['sketch'][element['id']] = element - print(self.model) + self.project.timeline[-1].sketch = sketch self.ui.sketch_list.addItem(name) self.ui.pb_linetool.setChecked(False) @@ -349,55 +364,18 @@ class MainWindow(QMainWindow): print(f"Removed operation: {item_name}") self.custom_3D_Widget.clear_mesh() - def translate_points_tup(self, point: QPoint): - """QPoints from Display to mesh data - input: Qpoints - output: Tuple X,Y - """ - if isinstance(point, QPoint): - return point.x(), point.y() - - def rotate_point_to_normal(self, centroid, normal): - # Ensure the normal is a unit vector - normal = normal / np.linalg.norm(normal) - - # Initial direction (assuming positive Z-axis) - initial_direction = np.array([0, 0, 1]) - - # Compute rotation axis - rotation_axis = np.cross(initial_direction, normal) - - # If rotation axis is zero (vectors are parallel), no rotation is needed - if np.allclose(rotation_axis, 0): - return centroid - - # Compute rotation angle - rotation_angle = np.arccos(np.dot(initial_direction, normal)) - - # Create rotation matrix using Rodrigues' formula - K = np.array([ - [0, -rotation_axis[2], rotation_axis[1]], - [rotation_axis[2], 0, -rotation_axis[0]], - [-rotation_axis[1], rotation_axis[0], 0] - ]) - rotation_matrix = ( - np.eye(3) + - np.sin(rotation_angle) * K + - (1 - np.cos(rotation_angle)) * np.dot(K, K) - ) - - # Apply rotation to centroid - rotated_centroid = np.dot(rotation_matrix, centroid) - - return rotated_centroid - def send_extrude(self): + # Dialog input is_symmetric = None length = None invert = None + selected = self.ui.sketch_list.currentItem() name = selected.text() - points = self.model['sketch'][name]['sketch_points'] + # TODO: add selected element from timeline + sel_compo = self.project.timeline[-1] + sketch = sel_compo.sketch + points = sketch.sdf_points if points[-1] == points[0]: #detect loop that causes problems in mesh generation @@ -411,7 +389,6 @@ class MainWindow(QMainWindow): length = 0 print("Extrude cancelled") - geo = Geometry() normal = self.custom_3D_Widget.selected_normal #print("Normie enter", normal) @@ -425,31 +402,33 @@ class MainWindow(QMainWindow): centroid = list(centroid) #print("This centroid ", centroid) - f = geo.extrude_shape(points, length, normal, centroid, is_symmetric, invert, 0) + sketch.origin = centroid + sketch.normal = normal + + f = sketch.extrude(length, is_symmetric, invert, 0) name_op = f"extrd-{name}" - element = { - 'id': name_op, - 'type': 'extrude', - 'sdf_object': f, - } + sel_compo.body = Body() + sel_compo.body.sketch = sketch #we add the sketch for reference here + sel_compo.body.id = name_op + sel_compo.body.sdf_body = f ### Interactor - lines = self.convert_lines_for_interactor() + sel_compo.interactor = Interactor() + sel_compo.interactor.add_lines_for_interactor(sketch.slv_lines) if not invert: - edges = interactor_mesh.generate_mesh(lines, 0, length) + edges = interactor_mesh.generate_mesh(sel_compo.interactor.lines, 0, length) else: - edges = interactor_mesh.generate_mesh(lines, 0, -length) + edges = interactor_mesh.generate_mesh(sel_compo.interactor.lines, 0, -length) - offset_vector = geo.vector_to_centroid(None, centroid, normal) + offset_vector = sel_compo.interactor.vector_to_centroid(None, centroid, normal) #print("off_ved", offset_vector) if len(offset_vector) == 0 : offset_vector = [0, 0, 0] self.custom_3D_Widget.load_interactor_mesh(edges, offset_vector) - self.model['operation'][name_op] = element self.ui.body_list.addItem(name_op) items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0] self.ui.body_list.setCurrentItem(items) @@ -488,7 +467,114 @@ class MainWindow(QMainWindow): self.custom_3D_Widget.update() -class Geometry: + +@dataclass +class Timeline: + """Timeline """ + timeline: list = None + + """add to time, + remove from time, """ + +class Assembly: + """Connecting Components in 3D space based on slvs solver""" + +@dataclass +class Component: + """The base container combining all related elements + id : The unique ID + sketch : the base sketch, bodys can contain additonal sketches for features + interactor : A smiplified model used as interactor + body : The body class that contains the actual 3d information + connector : Vector and Nomral information for assembly + descript : a basic description + materil : Speicfy a material for pbr rendering + """ + id = None + sketch = None + interactor = None + body = None + connector = None + + # Description + descript = None + + # PBR + material = None + + +class Connector: + """An Element that contains vectors and or normals as connection points. + These connection points can exist independently of bodies and other elements""" + id = None + vector = None + normal = None + + +class Code: + """A class that holds all information from the code based approach""" + command_list = None + + def generate_mesh_from_code(self, code_text: str): + local_vars = {} + + try: + print(code_text) + exec(code_text, globals(), local_vars) + # Retrieve the result from the captured local variables + result = local_vars.get('result') + print("Result:", result) + + except Exception as e: + print("Error executing code:", e) + + +@dataclass +class Sketch: + """All of the 2D Information of a sketch""" + id = None + + # Space Information + origin = None + slv_plane = None + normal = None + + # Points in UI form the sketch widget + ui_points: list = None + ui_lines: list = None + + # Points cartesian coming as result of the solver + slv_points: list = None + slv_lines: list = None + + sdf_points: list = None + + # Points coming back from the 3D-Widget as projection to draw on + proj_points: list = None + proj_lines: list = None + + def translate_points_tup(self, point: QPoint): + """QPoints from Display to mesh data + input: Qpoints + output: Tuple X,Y + """ + if isinstance(point, QPoint): + return point.x(), point.y() + + def vector_to_centroid(self, shape_center, centroid, normal): + + if not shape_center: + # Calculate the current center of the shape + shape_center = [0, 0, 0] + + # Calculate the vector from the shape's center to the centroid + center_to_centroid = np.array(centroid) - np.array(shape_center) + + # Project this vector onto the normal to get the required translation along the normal + translation_along_normal = np.dot(center_to_centroid, normal) * normal + + return translation_along_normal + def angle_between_normals(self, normal1, normal2): # Ensure the vectors are normalized n1 = normal1 / np.linalg.norm(normal1) @@ -519,44 +605,37 @@ class Geometry: print("p2", p2) return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) - def vector_to_centroid(self, shape_center, centroid, normal): + def convert_points_for_sdf(self): + points_for_sdf = [] + for point_to_poly in self.slv_points: + points_for_sdf.append(self.translate_points_tup(point_to_poly['ui_point'])) - if not shape_center: - # Calculate the current center of the shape - shape_center = [0, 0, 0] + self.sdf_points = points_for_sdf - # Calculate the vector from the shape's center to the centroid - center_to_centroid = np.array(centroid) - np.array(shape_center) - - # Project this vector onto the normal to get the required translation along the normal - translation_along_normal = np.dot(center_to_centroid, normal) * normal - - return translation_along_normal - - def extrude_shape(self, points, length: float, normal, centroid, symet: bool = True, invert: bool = False, - offset_length: float = None): + def extrude(self, height: float, symet: bool = True, invert: bool = False, offset_length: float = None): """ Extrude a 2D shape into 3D, orient it along the normal, and position it relative to the centroid. """ + # Normalize the normal vector - normal = np.array(normal) - normal = normal / np.linalg.norm(normal) + normal = np.array(self.normal) + normal = normal / np.linalg.norm(self.normal) # Create the 2D shape - f = polygon(points) + f = polygon(self.sdf_points) # Extrude the shape along the Z-axis - f = f.extrude(length) + f = f.extrude(height) # Center the shape along its extrusion axis - f = f.translate((0, 0, length / 2)) + f = f.translate((0, 0, height / 2)) # Orient the shape along the normal vector f = f.orient(normal) - offset_vector = self.vector_to_centroid(None, centroid, normal) + offset_vector = self.vector_to_centroid(None, self.origin, normal) # Adjust the offset vector by subtracting the inset distance along the normal direction - adjusted_offset = offset_vector - (normal * length) + adjusted_offset = offset_vector - (normal * height) if invert: # Translate the shape along the adjusted offset vector f = f.translate(adjusted_offset) @@ -580,15 +659,73 @@ class Geometry: return f + +@dataclass +class Interactor: + """Helper mesh consisting of edges for selection""" + lines = None + faces = None + body = None + + def translate_points_tup(self, point: QPoint): + """QPoints from Display to mesh data + input: Qpoints + output: Tuple X,Y + """ + if isinstance(point, QPoint): + return point.x(), point.y() + + def vector_to_centroid(self, shape_center, centroid, normal): + + if not shape_center: + # Calculate the current center of the shape + shape_center = [0, 0, 0] + + # Calculate the vector from the shape's center to the centroid + center_to_centroid = np.array(centroid) - np.array(shape_center) + + # Project this vector onto the normal to get the required translation along the normal + translation_along_normal = np.dot(center_to_centroid, normal) * normal + + return translation_along_normal + + def add_lines_for_interactor(self, input_lines: list): + """Expects slvs_lines main list""" + points_for_interact = [] + for point_to_poly in input_lines: + start, end = point_to_poly['ui_points'] + from_coord_start = window.sketchWidget.from_quadrant_coords_no_center(start) + from_coord_end = window.sketchWidget.from_quadrant_coords_no_center(end) + start_draw = self.translate_points_tup(from_coord_start) + end_draw = self.translate_points_tup(from_coord_end) + line = start_draw, end_draw + points_for_interact.append(line) + + print("packed_lines", points_for_interact) + + self.lines = points_for_interact + + +@dataclass +class Body: + """The actual body as sdf3 object""" + id = None + sketch = None + height = None + sdf_body = None + def mirror_body(self, sdf_object3d): f = sdf_object3d.rotate(pi) return f def cut_shapes(self, sdf_object1, sdf_object2): - f = difference(sdf_object1, sdf_object2) # equivalent + f = difference(sdf_object1, sdf_object2) # equivalent + return f + +class Output: def export_mesh(self, sdf_object): """FINAL EXPORT""" result_points = sdf_object.generate() @@ -608,46 +745,11 @@ class Geometry: print("Error executing code:", e) -@dataclass -class Component: - """The base container combinging all related elements""" - id = None - sketch = None - interactor = None - body = None +class Project: + """Project -> Timeline -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" + timeline: Timeline = None + assembly: Assembly = None -@dataclass -class Sketch: - """All of the 2D Information of a sketch""" - origin = None - plane = None - normal = None - ui_points = None - ui_lines = None - slv_points = None - proj_points = None - proj_lines = None - -@dataclass -class Interactor: - """Helper mesh consisting of edges for selection""" - edges = None - faces = None - -@dataclass -class Body: - """The actual body as sdf3 object""" - sketch = None - height = None - sdf_body = None - -class Boolean: - """Toolkit that contains the boolean operations for sdf3 objects""" - def extrude(self, sketch: Sketch): - pass - -class Helper: - pass if __name__ == "__main__": From cebe1b41e7a61eb16cd3aec8ec091528c060efd2 Mon Sep 17 00:00:00 2001 From: bklronin Date: Wed, 24 Jul 2024 08:39:39 +0200 Subject: [PATCH 02/16] - UI update sketch and body to the sides --- Gui.py | 489 ++++++++++++++++++++++++---------------------- gui.ui | 603 ++++++++++++++++++++++++++++++++------------------------- 2 files changed, 591 insertions(+), 501 deletions(-) diff --git a/Gui.py b/Gui.py index 42cd35a..0fbe5b0 100644 --- a/Gui.py +++ b/Gui.py @@ -25,7 +25,7 @@ class Ui_fluencyCAD(object): def setupUi(self, fluencyCAD): if not fluencyCAD.objectName(): fluencyCAD.setObjectName(u"fluencyCAD") - fluencyCAD.resize(2214, 803) + fluencyCAD.resize(2192, 957) self.actionNew_Project = QAction(fluencyCAD) self.actionNew_Project.setObjectName(u"actionNew_Project") self.actionLoad_Project = QAction(fluencyCAD) @@ -36,8 +36,134 @@ class Ui_fluencyCAD(object): self.centralwidget.setObjectName(u"centralwidget") self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setObjectName(u"gridLayout") + self.gl_box = QGroupBox(self.centralwidget) + self.gl_box.setObjectName(u"gl_box") + sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(4) + sizePolicy.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth()) + self.gl_box.setSizePolicy(sizePolicy) + 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, 7, 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.pushButton_2 = QPushButton(self.groupBox_4) + self.pushButton_2.setObjectName(u"pushButton_2") + + self.verticalLayout_2.addWidget(self.pushButton_2) + + + self.gridLayout.addWidget(self.groupBox_4, 12, 4, 1, 1) + + self.InputTab = QTabWidget(self.centralwidget) + self.InputTab.setObjectName(u"InputTab") + sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth()) + self.InputTab.setSizePolicy(sizePolicy1) + 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, 2, 7, 1) + + self.timeline_box = QGroupBox(self.centralwidget) + self.timeline_box.setObjectName(u"timeline_box") + + self.gridLayout.addWidget(self.timeline_box, 12, 2, 1, 2) + + 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.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, Qt.AlignTop) + self.groupBox_9 = QGroupBox(self.centralwidget) self.groupBox_9.setObjectName(u"groupBox_9") + self.groupBox_9.setMaximumSize(QSize(200, 16777215)) self.gridLayout_7 = QGridLayout(self.groupBox_9) self.gridLayout_7.setObjectName(u"gridLayout_7") self.pb_origin_wp = QPushButton(self.groupBox_9) @@ -61,16 +187,54 @@ class Ui_fluencyCAD(object): self.gridLayout_7.addWidget(self.pb_move_wp, 1, 1, 1, 1) - self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1) + self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1, Qt.AlignTop) + + self.groupBox_2 = QGroupBox(self.centralwidget) + self.groupBox_2.setObjectName(u"groupBox_2") + sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) + self.groupBox_2.setSizePolicy(sizePolicy2) + self.groupBox_2.setMaximumSize(QSize(200, 16777215)) + self.gridLayout_2 = QGridLayout(self.groupBox_2) + self.gridLayout_2.setObjectName(u"gridLayout_2") + 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, 1, 1, 1, 1, Qt.AlignTop) + + 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, 2, 0, 1, 1, Qt.AlignTop) + + 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, 2, 1, 1, 1, Qt.AlignTop) + + 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, 1, 0, 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") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) - self.groupBox_3.setSizePolicy(sizePolicy) - self.groupBox_3.setMaximumSize(QSize(16777214, 16777213)) + sizePolicy2.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy2) + self.groupBox_3.setMaximumSize(QSize(200, 16777213)) self.gridLayout_4 = QGridLayout(self.groupBox_3) self.gridLayout_4.setObjectName(u"gridLayout_4") self.pb_con_line = QPushButton(self.groupBox_3) @@ -131,214 +295,75 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1) - self.groupBox_2 = QGroupBox(self.centralwidget) - self.groupBox_2.setObjectName(u"groupBox_2") - sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) - self.groupBox_2.setSizePolicy(sizePolicy) - self.gridLayout_2 = QGridLayout(self.groupBox_2) - self.gridLayout_2.setObjectName(u"gridLayout_2") - 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, 1, 1, 1, 1, Qt.AlignTop) - - 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, 2, 0, 1, 1, Qt.AlignTop) - - 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, 2, 1, 1, 1, Qt.AlignTop) - - 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, 1, 0, 1, 1) - - - self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1) - - self.gl_box = QGroupBox(self.centralwidget) - self.gl_box.setObjectName(u"gl_box") - sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.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, 4, 12, 1) - - self.InputTab = QTabWidget(self.centralwidget) - self.InputTab.setObjectName(u"InputTab") - sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth()) - self.InputTab.setSizePolicy(sizePolicy2) - 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, 2, 12, 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.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, 5, 10, 1, Qt.AlignTop) - - 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.pushButton_2 = QPushButton(self.groupBox_4) - self.pushButton_2.setObjectName(u"pushButton_2") - - self.verticalLayout_2.addWidget(self.pushButton_2) - - - self.gridLayout.addWidget(self.groupBox_4, 11, 5, 1, 1) - - self.groupBox_5 = QGroupBox(self.centralwidget) - self.groupBox_5.setObjectName(u"groupBox_5") - sizePolicy3 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.groupBox_11 = QGroupBox(self.centralwidget) + self.groupBox_11.setObjectName(u"groupBox_11") + sizePolicy3 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) sizePolicy3.setHorizontalStretch(0) sizePolicy3.setVerticalStretch(0) - sizePolicy3.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth()) - self.groupBox_5.setSizePolicy(sizePolicy3) - self.groupBox_5.setMaximumSize(QSize(300, 16777215)) - self.verticalLayout_3 = QVBoxLayout(self.groupBox_5) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") - self.sketch_list = QListWidget(self.groupBox_5) + sizePolicy3.setHeightForWidth(self.groupBox_11.sizePolicy().hasHeightForWidth()) + self.groupBox_11.setSizePolicy(sizePolicy3) + 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") + sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy4.setHorizontalStretch(0) + sizePolicy4.setVerticalStretch(0) + sizePolicy4.setHeightForWidth(self.sketch_list.sizePolicy().hasHeightForWidth()) + self.sketch_list.setSizePolicy(sizePolicy4) self.sketch_list.setSelectionRectVisible(True) - self.verticalLayout_3.addWidget(self.sketch_list) + self.verticalLayout_7.addWidget(self.sketch_list) - self.groupBox_6 = QGroupBox(self.groupBox_5) + 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) + sizePolicy2.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) + self.groupBox_6.setSizePolicy(sizePolicy2) self.gridLayout_6 = QGridLayout(self.groupBox_6) self.gridLayout_6.setObjectName(u"gridLayout_6") self.gridLayout_6.setContentsMargins(2, 2, 2, 2) - self.pb_del_sketch = QPushButton(self.groupBox_6) - self.pb_del_sketch.setObjectName(u"pb_del_sketch") + self.pb_edt_sktch = QPushButton(self.groupBox_6) + self.pb_edt_sktch.setObjectName(u"pb_edt_sktch") - self.gridLayout_6.addWidget(self.pb_del_sketch, 0, 2, 1, 1) + 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, 0, 0, 1, 1) + self.gridLayout_6.addWidget(self.pb_nw_sktch, 1, 0, 1, 1) - self.pb_edt_sktch = QPushButton(self.groupBox_6) - self.pb_edt_sktch.setObjectName(u"pb_edt_sktch") + self.pb_del_sketch = QPushButton(self.groupBox_6) + self.pb_del_sketch.setObjectName(u"pb_del_sketch") - self.gridLayout_6.addWidget(self.pb_edt_sktch, 0, 1, 1, 1) + self.gridLayout_6.addWidget(self.pb_del_sketch, 1, 2, 1, 1) - self.verticalLayout_3.addWidget(self.groupBox_6) + self.verticalLayout_7.addWidget(self.groupBox_6) - self.body_list = QListWidget(self.groupBox_5) + + self.gridLayout.addWidget(self.groupBox_11, 5, 0, 2, 1) + + self.groupBox_10 = QGroupBox(self.centralwidget) + self.groupBox_10.setObjectName(u"groupBox_10") + sizePolicy3.setHeightForWidth(self.groupBox_10.sizePolicy().hasHeightForWidth()) + self.groupBox_10.setSizePolicy(sizePolicy3) + 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_3.addWidget(self.body_list) + self.verticalLayout_6.addWidget(self.body_list) - self.groupBox_8 = QGroupBox(self.groupBox_5) + 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) + sizePolicy2.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth()) + self.groupBox_8.setSizePolicy(sizePolicy2) + 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) @@ -358,20 +383,15 @@ class Ui_fluencyCAD(object): self.gridLayout_8.addWidget(self.pb_edt_sktch_3, 0, 1, 1, 1) - self.verticalLayout_3.addWidget(self.groupBox_8) + self.verticalLayout_6.addWidget(self.groupBox_8) - self.gridLayout.addWidget(self.groupBox_5, 0, 3, 12, 1) - - self.timeline_box = QGroupBox(self.centralwidget) - self.timeline_box.setObjectName(u"timeline_box") - - self.gridLayout.addWidget(self.timeline_box, 12, 2, 1, 3) + self.gridLayout.addWidget(self.groupBox_10, 5, 4, 2, 1) fluencyCAD.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(fluencyCAD) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 2214, 24)) + self.menubar.setGeometry(QRect(0, 0, 2192, 24)) self.menuFile = QMenu(self.menubar) self.menuFile.setObjectName(u"menuFile") self.menuSettings = QMenu(self.menubar) @@ -401,6 +421,24 @@ class Ui_fluencyCAD(object): self.actionNew_Project.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) self.actionLoad_Project.setText(QCoreApplication.translate("fluencyCAD", u"Load", None)) self.actionRecent.setText(QCoreApplication.translate("fluencyCAD", u"Recent", None)) + self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None)) + self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) + self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", 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.timeline_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Timeline", 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.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)) @@ -429,6 +467,17 @@ class Ui_fluencyCAD(object): self.pb_move_wp.setText(QCoreApplication.translate("fluencyCAD", u"WP Mve", None)) #if QT_CONFIG(shortcut) self.pb_move_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"M", None)) +#endif // QT_CONFIG(shortcut) + self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None)) + self.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None)) + self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None)) + self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None)) +#if QT_CONFIG(statustip) + self.pb_linetool.setStatusTip(QCoreApplication.translate("fluencyCAD", u"Line >SS 0 0 - 2214 - 803 + 2192 + 957 @@ -15,8 +15,180 @@ - + + + + + 0 + 4 + + + + + 12 + + + + Model Viewer + + + + -1 + + + 12 + + + + + + + + Export + + + + + + STL + + + + + + + + + + + 0 + 0 + + + + 0 + + + + Sketch + + + + + + Code + + + + + + + + + Executive + + + + + + Load Code + + + + + + + Save code + + + + + + + Apply Code + + + + + + + Delete Code + + + + + + + + + + + + + + Timeline + + + + + + + Modify + + + + + + Rev + + + + + + + Extrd + + + + + + + Arry + + + + + + + Cut + + + + + + + Comb + + + + + + + Mve + + + + + + + + + + 200 + 16777215 + + Workplanes @@ -76,6 +248,85 @@ + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Drawing + + + + + + Rctgl + + + true + + + false + + + + + + + Circle + + + true + + + false + + + + + + + Slot + + + true + + + false + + + + + + + Line >S<egment + + + Line + + + S + + + true + + + false + + + + + + @@ -86,7 +337,7 @@ - 16777214 + 200 16777213 @@ -219,258 +470,44 @@ - - + + - - 0 - 0 - - - - Drawing - - - - - - Rctgl - - - true - - - false - - - - - - - Circle - - - true - - - false - - - - - - - Slot - - - true - - - false - - - - - - - Line >S<egment - - - Line - - - S - - - true - - - false - - - - - - - - - - - 0 - 4 - - - - - 12 - - - - Model Viewer - - - - -1 - - - 12 - - - - - - - - - 0 - 0 - - - - 0 - - - - Sketch - - - - - - Code - - - - - - - - - Executive - - - - - - Load Code - - - - - - - Save code - - - - - - - Apply Code - - - - - - - Delete Code - - - - - - - - - - - - - - Modify - - - - - - Rev - - - - - - - Extrd - - - - - - - Arry - - - - - - - Cut - - - - - - - Comb - - - - - - - Mve - - - - - - - - - - Export - - - - - - STL - - - - - - - - - - + 0 0 - 300 + 200 16777215 Sketch - + + + 5 + + + 5 + + + 5 + + + 5 + + + + 0 + 0 + + true @@ -500,30 +537,63 @@ 2 - - - - Del sketch - - - - - - - Add Sktch - - - - + - Edt Sktch + Edt + + + + + + + Add + + + + + + + Del + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Bodys / Operations + + + + 5 + + + 5 + + + 5 + + + 5 + @@ -539,6 +609,12 @@ 0 + + + 200 + 16777215 + + Tools @@ -558,14 +634,14 @@ - Del Bdy + Del - Bdy Upd + Upd @@ -582,13 +658,6 @@ - - - - Timeline - - - @@ -596,7 +665,7 @@ 0 0 - 2214 + 2192 24 From b80185e93e1a3b02192b7b6407b453ab1e80c3f9 Mon Sep 17 00:00:00 2001 From: bklronin Date: Sun, 28 Jul 2024 13:46:55 +0200 Subject: [PATCH 03/16] - Bsic sketch to object approach --- drawing_modules/draw_widget2d.py | 243 ++++++++++++++----------------- drawing_modules/vtk_widget.py | 1 - main.py | 124 +++++++++------- 3 files changed, 182 insertions(+), 186 deletions(-) diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index b00ef32..5e48ef3 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -1,6 +1,7 @@ import math import re from copy import copy +from typing import Optional import numpy as np from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QInputDialog @@ -19,9 +20,6 @@ class SketchWidget(QWidget): self.drag_buffer = [None, None] self.main_buffer = [None, None] - self.proj_snap_points = [] - self.proj_snap_lines = [] - self.hovered_point = None self.selected_line = None @@ -30,11 +28,17 @@ class SketchWidget(QWidget): self.setMouseTracking(True) self.mouse_mode = False - self.wp = None self.solv = SolverSystem() - self.slv_points_main = [] - self.slv_lines_main = [] + self.sketch = None + + def set_sketch(self, sketch) -> None: + print(sketch) + self.sketch = sketch + self.create_workplane() + + def get_sketch(self): + return self.sketch def reset_buffers(self): self.line_draw_buffer = [None, None] @@ -46,44 +50,28 @@ class SketchWidget(QWidget): #self.update() def create_workplane(self): - self.wp = self.solv.create_2d_base() + self.sketch.working_plane = self.solv.create_2d_base() def create_workplane_projected(self): - self.wp = self.solv.create_2d_base() + self.sketch.working_plane = self.solv.create_2d_base() - def create_proj_points(self, proj_points): - """Lines as orientation projected from the sketch""" - - for point in proj_points: + def convert_proj_points(self): + out_points = [] + for point in self.sketch.proj_points: x, y = point coord = QPoint(x, y) - self.proj_snap_points.append(coord) + out_points.append(coord) - """relation_point = {} # Reinitialize the dictionary - #handle_nr = self.get_handle_nr(str(point)) - #relation_point['handle_nr'] = handle_nr - #relation_point['solv_handle'] = point - relation_point['ui_point'] = QPoint(x, y) + self.sketch.proj_points = out_points - self.slv_points_main.append(relation_point)""" - - def create_proj_lines(self, sel_edges): - """Lines as orientation projected from the sketch""" - print("Incoming corrd lines", sel_edges) - for line in sel_edges: - - start = QPoint(line[0][0], line[0][1] ) + def convert_proj_lines(self): + out_lines = [] + for line in self.sketch.proj_lines: + start = QPoint(line[0][0], line[0][1]) end = QPoint(line[1][0], line[1][1]) coord = QLine(start, end) - self.proj_snap_lines.append(coord) - - """relation_point = {} # Reinitialize the dictionary - #handle_nr = self.get_handle_nr(str(point)) - #relation_point['handle_nr'] = handle_nr - #relation_point['solv_handle'] = point - relation_point['ui_point'] = QPoint(x, y) - - self.slv_points_main.append(relation_point)""" + out_lines.append(coord) + self.sketch.proj_lines = out_lines def find_duplicate_points_2d(self, edges): points = [] @@ -132,8 +120,8 @@ class SketchWidget(QWidget): qw, qx, qy, qz = self.normal_to_quaternion(normal) slv_normal = self.solv.add_normal_3d(qw, qx, qy, qz) - self.wp = self.solv.add_work_plane(origin_handle, slv_normal) - print(self.wp) + self.sketch.working_plane = self.solv.add_work_plane(origin_handle, slv_normal) + print(self.sketch.working_plane) def get_handle_nr(self, input_str: str) -> int: # Define the regex pattern to extract the handle number @@ -168,7 +156,7 @@ class SketchWidget(QWidget): def get_handle_from_ui_point(self, ui_point: QPoint): """Input QPoint and you shall reveive a slvs entity handle!""" - for point in self.slv_points_main: + for point in self.sketch.slv_points: if ui_point == point['ui_point']: slv_handle = point['solv_handle'] @@ -176,7 +164,7 @@ class SketchWidget(QWidget): def get_line_handle_from_ui_point(self, ui_point: QPoint): """Input Qpoint that is on a line and you shall receive the handle of the line!""" - for target_line_con in self.slv_lines_main: + for target_line_con in self.sketch.slv_lines: if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): slv_handle = target_line_con['solv_handle'] @@ -184,7 +172,7 @@ class SketchWidget(QWidget): def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" - for target_line_con in self.slv_lines_main: + for target_line_con in self.sketch.slv_lines: if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): lines_to_cons = target_line_con['solv_entity_points'] @@ -231,7 +219,7 @@ class SketchWidget(QWidget): old_points_ui = [] new_points_ui = [] - for old_point_ui in self.slv_points_main: + for old_point_ui in self.sketch.slv_points: old_points_ui.append(old_point_ui['ui_point']) for i in range(self.solv.entity_len()): @@ -265,14 +253,14 @@ class SketchWidget(QWidget): index, old_point, new_point = tbu_points_idx # Update the point in slv_points_main - self.slv_points_main[index]['ui_point'] = new_point + self.sketch.slv_points[index]['ui_point'] = new_point # Print updated state # print("Updated slv_points_main:", self.slv_points_main) def check_all_lines_and_update(self,changed_points: list): for tbu_points_idx in changed_points: index, old_point, new_point = tbu_points_idx - for line_needs_update in self.slv_lines_main: + for line_needs_update in self.sketch.slv_lines: if old_point == line_needs_update['ui_points'][0]: line_needs_update['ui_points'][0] = new_point elif old_point == line_needs_update['ui_points'][1]: @@ -334,7 +322,7 @@ class SketchWidget(QWidget): u = clicked_pos.x() v = clicked_pos.y() - point = self.solv.add_point_2d(u, v, self.wp) + point = self.solv.add_point_2d(u, v, self.sketch.working_plane) relation_point = {} # Reinitialize the dictionary handle_nr = self.get_handle_nr(str(point)) @@ -342,17 +330,17 @@ class SketchWidget(QWidget): relation_point['solv_handle'] = point relation_point['ui_point'] = clicked_pos - self.slv_points_main.append(relation_point) + self.sketch.slv_points.append(relation_point) - print("points", self.slv_points_main) - print("lines", self.slv_lines_main) + print("points", self.sketch.slv_points) + print("lines", self.sketch.slv_lines) elif self.line_draw_buffer[0]: self.line_draw_buffer[1] = clicked_pos u = clicked_pos.x() v = clicked_pos.y() - point2 = self.solv.add_point_2d(u, v, self.wp) + point2 = self.solv.add_point_2d(u, v, self.sketch.working_plane) relation_point = {} # Reinitialize the dictionary handle_nr = self.get_handle_nr(str(point2)) @@ -360,19 +348,18 @@ class SketchWidget(QWidget): relation_point['solv_handle'] = point2 relation_point['ui_point'] = clicked_pos - self.slv_points_main.append(relation_point) + self.sketch.slv_points.append(relation_point) - print("points", self.slv_points_main) - print("lines", self.slv_lines_main) + print("points", self.sketch.slv_points) + print("lines", self.sketch.slv_lines) print("Buffer state", self.line_draw_buffer) + if self.line_draw_buffer[0] and self.line_draw_buffer[1]: point_slv1 = self.get_handle_from_ui_point(self.line_draw_buffer[0]) point_slv2 = self.get_handle_from_ui_point(self.line_draw_buffer[1]) - print(point_slv1) - print(point_slv2) - line = self.solv.add_line_2d(point_slv1, point_slv2, self.wp) + line = self.solv.add_line_2d(point_slv1, point_slv2, self.sketch.working_plane) relation_line = {} # Reinitialize the dictionary handle_nr_line = self.get_handle_nr(str(line)) @@ -384,7 +371,7 @@ class SketchWidget(QWidget): # Track relationship of point in line relation_point['part_of_entity'] = handle_nr_line - self.slv_lines_main.append(relation_line) + self.sketch.slv_lines.append(relation_line) # Reset the buffer for the next line segment self.line_draw_buffer[0] = self.line_draw_buffer[1] @@ -403,7 +390,7 @@ class SketchWidget(QWidget): if self.main_buffer[0] and self.main_buffer[1]: print("buf", self.main_buffer) - self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.wp) + self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) if self.solv.solve() == ResultFlag.OKAY: print("Fuck yeah") @@ -431,7 +418,7 @@ class SketchWidget(QWidget): # Contrain point to line if self.main_buffer[1]: - self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.wp) + self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) if self.solv.solve() == ResultFlag.OKAY: print("Fuck yeah") @@ -462,7 +449,7 @@ class SketchWidget(QWidget): # Contrain point to line if self.main_buffer[1]: - self.solv.midpoint(self.main_buffer[0], self.main_buffer[1], self.wp) + self.solv.midpoint(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) if self.solv.solve() == ResultFlag.OKAY: print("Fuck yeah") @@ -484,7 +471,7 @@ class SketchWidget(QWidget): line_selected = self.get_line_handle_from_ui_point(local_event_pos) if line_selected: - self.solv.horizontal(line_selected, self.wp) + self.solv.horizontal(line_selected, self.sketch.working_plane) if self.solv.solve() == ResultFlag.OKAY: print("Fuck yeah") @@ -502,7 +489,7 @@ class SketchWidget(QWidget): line_selected = self.get_line_handle_from_ui_point(local_event_pos) if line_selected: - self.solv.vertical(line_selected, self.wp) + self.solv.vertical(line_selected, self.sketch.working_plane) if self.solv.solve() == ResultFlag.OKAY: print("Fuck yeah") @@ -543,7 +530,7 @@ class SketchWidget(QWidget): if e1 and e2: # Ask fo the dimension and solve if both elements are present length, ok = QInputDialog.getDouble(self, 'Distance', 'Enter a mm value:', value=100, decimals=2) - self.solv.distance(e1, e2, length, self.wp) + self.solv.distance(e1, e2, length, self.sketch.working_plane) if self.solv.solve() == ResultFlag.OKAY: print("Fuck yeah") @@ -574,33 +561,35 @@ class SketchWidget(QWidget): min_distance = float('inf') threshold = 10 # Distance threshold for highlighting - for point in self.slv_points_main: - distance = (local_event_pos - point['ui_point']).manhattanLength() - if distance < threshold and distance < min_distance: - closest_point = point['ui_point'] - min_distance = distance + if self.sketch: - for point in self.proj_snap_points: - distance = (local_event_pos - point).manhattanLength() - if distance < threshold and distance < min_distance: - closest_point = point - min_distance = distance + for point in self.sketch.slv_points: + distance = (local_event_pos - point['ui_point']).manhattanLength() + if distance < threshold and distance < min_distance: + closest_point = point['ui_point'] + min_distance = distance - if closest_point != self.hovered_point: - self.hovered_point = closest_point - print(self.hovered_point) + for point in self.sketch.proj_points: + distance = (local_event_pos - point).manhattanLength() + if distance < threshold and distance < min_distance: + closest_point = point + min_distance = distance - for dic in self.slv_lines_main: - p1 = dic['ui_points'][0] - p2 = dic['ui_points'][1] + if closest_point != self.hovered_point: + self.hovered_point = closest_point + print(self.hovered_point) - if self.is_point_on_line(local_event_pos, p1, p2): - self.selected_line = p1, p2 - break - else: - self.selected_line = None + for dic in self.sketch.slv_lines: + p1 = dic['ui_points'][0] + p2 = dic['ui_points'][1] - self.update() + if self.is_point_on_line(local_event_pos, p1, p2): + self.selected_line = p1, p2 + break + else: + self.selected_line = None + + self.update() def mouseDoubleClickEvent(self, event): pass @@ -722,50 +711,51 @@ class SketchWidget(QWidget): painter.setPen(pen) # Draw points - for point in self.slv_points_main: - painter.drawEllipse(point['ui_point'], 3 / self.zoom, 3 / self.zoom) + if self.sketch: + for point in self.sketch.slv_points: + painter.drawEllipse(point['ui_point'], 3 / self.zoom, 3 / self.zoom) - for dic in self.slv_lines_main: - p1 = dic['ui_points'][0] - p2 = dic['ui_points'][1] - painter.drawLine(p1, p2) + for dic in self.sketch.slv_lines: + p1 = dic['ui_points'][0] + p2 = dic['ui_points'][1] + painter.drawLine(p1, p2) - dis = self.distance(p1, p2) - mid = self.calculate_midpoint(p1, p2) - painter.drawText(mid, str(round(dis, 2))) + dis = self.distance(p1, p2) + mid = self.calculate_midpoint(p1, p2) + painter.drawText(mid, str(round(dis, 2))) - pen = QPen(Qt.green) - pen.setWidthF(2 / self.zoom) - painter.setPen(pen) - - if self.solv.entity_len(): - for i in range(self.solv.entity_len()): - entity = self.solv.entity(i) - if entity.is_point_2d() and self.solv.params(entity.params): - x, y = self.solv.params(entity.params) - point = QPointF(x, y) - painter.drawEllipse(point, 6 / self.zoom, 6 / self.zoom) - - # Highlight point hovered - if self.hovered_point: - highlight_pen = QPen(QColor(255, 0, 0)) - highlight_pen.setWidthF(2 / self.zoom) - painter.setPen(highlight_pen) - painter.drawEllipse(self.hovered_point, 5 / self.zoom, 5 / self.zoom) - - # Highlight line hovered - if self.selected_line and not self.hovered_point: - p1, p2 = self.selected_line - painter.setPen(QPen(Qt.red, 2 / self.zoom)) - painter.drawLine(p1, p2) - - for cross in self.proj_snap_points: - self.draw_cross(painter, cross, 10 / self.zoom) - - for selected in self.proj_snap_lines: - pen = QPen(Qt.white, 1, Qt.DashLine) + pen = QPen(Qt.green) + pen.setWidthF(2 / self.zoom) painter.setPen(pen) - painter.drawLine(selected) + + if self.solv.entity_len(): + for i in range(self.solv.entity_len()): + entity = self.solv.entity(i) + if entity.is_point_2d() and self.solv.params(entity.params): + x, y = self.solv.params(entity.params) + point = QPointF(x, y) + painter.drawEllipse(point, 6 / self.zoom, 6 / self.zoom) + + # Highlight point hovered + if self.hovered_point: + highlight_pen = QPen(QColor(255, 0, 0)) + highlight_pen.setWidthF(2 / self.zoom) + painter.setPen(highlight_pen) + painter.drawEllipse(self.hovered_point, 5 / self.zoom, 5 / self.zoom) + + # Highlight line hovered + if self.selected_line and not self.hovered_point: + p1, p2 = self.selected_line + painter.setPen(QPen(Qt.red, 2 / self.zoom)) + painter.drawLine(p1, p2) + + for cross in self.sketch.proj_points: + self.draw_cross(painter, cross, 10 / self.zoom) + + for selected in self.sketch.proj_lines: + pen = QPen(Qt.white, 1, Qt.DashLine) + painter.setPen(pen) + painter.drawLine(selected) painter.end() @@ -777,13 +767,6 @@ class SketchWidget(QWidget): def aspect_ratio(self): return self.width() / self.height() * (1.0 / abs(self.zoom)) - def clear_sketch(self): - self.slv_points_main = [] - self.slv_lines_main = [] - self.proj_snap_lines.clear() - self.proj_snap_points.clear() - self.reset_buffers() - self.solv = SolverSystem() # Example usage if __name__ == "__main__": diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 658afa7..2f33bb5 100644 --- a/drawing_modules/vtk_widget.py +++ b/drawing_modules/vtk_widget.py @@ -146,7 +146,6 @@ class VTKWidget(QtWidgets.QWidget): self.renderer_indicators.ResetCameraClippingRange() self.vtk_widget.GetRenderWindow().Render() - def create_grid(self, size=100, spacing=10): # Create a vtkPoints object and store the points in it points = vtk.vtkPoints() diff --git a/main.py b/main.py index 86513c8..935998f 100644 --- a/main.py +++ b/main.py @@ -23,6 +23,7 @@ class ExtrudeDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle('Extrude Options') + def create_hline(): line = QLabel() line.setStyleSheet("border-top: 1px solid #cccccc;") # Light grey line @@ -76,6 +77,7 @@ class ExtrudeDialog(QDialog): def get_values(self): return self.length_input.value(), self.symmetric_checkbox.isChecked() ,self.invert_checkbox.isChecked(), self.cut_checkbox.isChecked(), self.union_checkbox.isChecked(), self.rounded_checkbox.isChecked() + class MainWindow(QMainWindow): send_command = Signal(str) @@ -100,7 +102,7 @@ class MainWindow(QMainWindow): ### Main Model self.model = { - 'sketch': {}, + 'sketches': {}, 'operation': {}, } self.list_selected = [] @@ -145,8 +147,6 @@ class MainWindow(QMainWindow): """Project -> Timeline -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" - - def on_flip_face(self): self.send_command.emit("flip") @@ -208,7 +208,7 @@ class MainWindow(QMainWindow): def draw_mesh(self): name = self.ui.body_list.currentItem().text() print("selected_for disp", name) - model = self.project.timeline[-1].body.sdf_body + model = self.project.timeline[-1].body[name].sdf_body vesta = vesta_mesh model_data = vesta.generate_mesh_from_sdf(model, resolution=64, threshold=0) @@ -234,6 +234,8 @@ class MainWindow(QMainWindow): compo = Component() compo.id = "New Compo" compo.descript = "Initial Component" + compo.sketches = {} + compo.body = {} self.project.timeline.append(compo) # Create a horizontal layout @@ -257,61 +259,67 @@ class MainWindow(QMainWindow): horizontal_layout.setAlignment(Qt.AlignLeft) def add_new_sketch_origin(self): - self.sketchWidget.clear_sketch() - self.sketchWidget.create_workplane() + name = f"sketches-{str(names.get_first_name())}" + sketch = Sketch() + sketch.id = name + sketch.origin = [0,0,0] + sketch.slv_points = [] + sketch.slv_lines = [] + sketch.proj_points = [] + sketch.proj_lines = [] + self.sketchWidget.set_sketch(sketch) def add_new_sketch_wp(self): - self.sketchWidget.clear_sketch() - #edges = [((-158.0, -20.0, -25.0), (286.0, -195.0, -25.0)), ((-158.0, -20.0, 25.0), (-158.0, -20.0, -25.0))] - points = self.custom_3D_Widget.project_tosketch_points - normal = self.custom_3D_Widget.selected_normal - selected_lines = self.custom_3D_Widget.project_tosketch_lines - print("Selected lines", selected_lines) - + name = f"sketches-{str(names.get_first_name())}" + sketch = Sketch() + sketch.id = name + sketch.origin = self.custom_3D_Widget.centroid + sketch.normal = self.custom_3D_Widget.selected_normal + sketch.slv_points = [] + sketch.slv_lines = [] + sketch.proj_points = self.custom_3D_Widget.project_tosketch_points + sketch.proj_lines = self.custom_3D_Widget.project_tosketch_lines + self.sketchWidget.set_sketch(sketch) self.sketchWidget.create_workplane_projected() - self.sketchWidget.create_proj_points(points) - self.sketchWidget.create_proj_lines(selected_lines) + self.sketchWidget.convert_proj_points() + self.sketchWidget.convert_proj_lines() + self.sketchWidget.update() # CLear all selections after it has been projected self.custom_3D_Widget.project_tosketch_points.clear() self.custom_3D_Widget.project_tosketch_lines.clear() self.custom_3D_Widget.clear_actors_projection() self.custom_3D_Widget.clear_actors_normals() - #self.custom_3D_Widget.clear_actors_projection() - - #self.sketchWidget.create_workplane_space(edges, normal) def add_sketch(self): - name = f"sketch-{str(names.get_first_name())}" - sketch = Sketch() - sketch.id = name - sketch.slv_points = self.sketchWidget.slv_points_main - sketch.slv_lines = self.sketchWidget.slv_lines_main + sketch = self.sketchWidget.get_sketch() sketch.convert_points_for_sdf() - self.project.timeline[-1].sketch = sketch + self.project.timeline[-1].sketches[sketch.id] = sketch - self.ui.sketch_list.addItem(name) + self.ui.sketch_list.addItem(sketch.id) self.ui.pb_linetool.setChecked(False) self.sketchWidget.line_mode = False - items = self.ui.sketch_list.findItems(name, Qt.MatchExactly)[0] + items = self.ui.sketch_list.findItems(sketch.id, Qt.MatchExactly)[0] self.ui.sketch_list.setCurrentItem(items) def edit_sketch(self): name = self.ui.sketch_list.currentItem().text() - #self.sketchWidget.clear_sketch() - self.sketchWidget.slv_points_main = self.model['sketch'][name]['point_list'] - self.sketchWidget.slv_lines_main = self.model['sketch'][name]['line_list'] - self.sketchWidget.solv = self.model['sketch'][name]['solver'] + selected = self.ui.sketch_list.currentItem() + name = selected.text() + # TODO: add selected element from timeline + sel_compo = self.project.timeline[-1] + sketch = sel_compo.sketches[name] + + self.sketchWidget.set_sketch(sketch) self.sketchWidget.update() - print("model",self.model) - print("widget", self.sketchWidget.slv_points_main) def del_sketch(self): + # Old print("Deleting") name = self.ui.sketch_list.currentItem() # Get the current item @@ -321,14 +329,14 @@ class MainWindow(QMainWindow): item_name = name.text() print("obj_name", item_name) - # Check if the 'sketch' key exists in the model dictionary - if 'sketch' in self.model and item_name in self.model['sketch']: - if self.model['sketch'][item_name]['id'] == item_name: + # Check if the 'sketches' key exists in the model dictionary + if 'sketches' in self.model and item_name in self.model['sketches']: + if self.model['sketches'][item_name]['id'] == item_name: row = self.ui.sketch_list.row(name) # Get the row of the current item self.ui.sketch_list.takeItem(row) # Remove the item from the list widget self.sketchWidget.clear_sketch() - self.model['sketch'].pop(item_name) # Remove the item from the sketch dictionary - print(f"Removed sketch: {item_name}") + self.model['sketches'].pop(item_name) # Remove the item from the sketches dictionary + print(f"Removed sketches: {item_name}") # Check if the 'operation' key exists in the model dictionary elif 'operation' in self.model and item_name in self.model['operation']: @@ -340,7 +348,7 @@ class MainWindow(QMainWindow): print(f"Removed operation: {item_name}") else: - print(f"Item '{item_name}' not found in either 'sketch' or 'operation' dictionary.") + print(f"Item '{item_name}' not found in either 'sketches' or 'operation' dictionary.") else: print("No item selected.") @@ -374,7 +382,7 @@ class MainWindow(QMainWindow): name = selected.text() # TODO: add selected element from timeline sel_compo = self.project.timeline[-1] - sketch = sel_compo.sketch + sketch = sel_compo.sketches[name] points = sketch.sdf_points if points[-1] == points[0]: @@ -389,7 +397,6 @@ class MainWindow(QMainWindow): length = 0 print("Extrude cancelled") - normal = self.custom_3D_Widget.selected_normal #print("Normie enter", normal) if normal is None: @@ -408,21 +415,25 @@ class MainWindow(QMainWindow): f = sketch.extrude(length, is_symmetric, invert, 0) name_op = f"extrd-{name}" - sel_compo.body = Body() - sel_compo.body.sketch = sketch #we add the sketch for reference here - sel_compo.body.id = name_op - sel_compo.body.sdf_body = f + sel_compo.body + body = Body() + body.sketch = sketch #we add the sketches for reference here + body.id = name_op + body.sdf_body = f ### Interactor - sel_compo.interactor = Interactor() - sel_compo.interactor.add_lines_for_interactor(sketch.slv_lines) + interactor = Interactor() + interactor.add_lines_for_interactor(sketch.slv_lines) if not invert: - edges = interactor_mesh.generate_mesh(sel_compo.interactor.lines, 0, length) + edges = interactor_mesh.generate_mesh(interactor.lines, 0, length) else: - edges = interactor_mesh.generate_mesh(sel_compo.interactor.lines, 0, -length) + edges = interactor_mesh.generate_mesh(interactor.lines, 0, -length) - offset_vector = sel_compo.interactor.vector_to_centroid(None, centroid, normal) + body.interactor = interactor + sel_compo.body[name_op] = body + + offset_vector = interactor.vector_to_centroid(None, centroid, normal) #print("off_ved", offset_vector) if len(offset_vector) == 0 : offset_vector = [0, 0, 0] @@ -483,7 +494,7 @@ class Assembly: class Component: """The base container combining all related elements id : The unique ID - sketch : the base sketch, bodys can contain additonal sketches for features + sketches : the base sketches, bodys can contain additonal sketches for features interactor : A smiplified model used as interactor body : The body class that contains the actual 3d information connector : Vector and Nomral information for assembly @@ -491,9 +502,8 @@ class Component: materil : Speicfy a material for pbr rendering """ id = None - sketch = None - interactor = None - body = None + sketches: dict = None + body: dict = None connector = None # Description @@ -531,7 +541,7 @@ class Code: @dataclass class Sketch: - """All of the 2D Information of a sketch""" + """All of the 2D Information of a sketches""" id = None # Space Information @@ -539,7 +549,7 @@ class Sketch: slv_plane = None normal = None - # Points in UI form the sketch widget + # Points in UI form the sketches widget ui_points: list = None ui_lines: list = None @@ -553,6 +563,9 @@ class Sketch: proj_points: list = None proj_lines: list = None + # Workingplane + working_plane = None + def translate_points_tup(self, point: QPoint): """QPoints from Display to mesh data input: Qpoints @@ -712,6 +725,7 @@ class Body: id = None sketch = None height = None + interactor = None sdf_body = None def mirror_body(self, sdf_object3d): From 511b5da78a95ac36bae2fed94c1f9a9a4c9920f3 Mon Sep 17 00:00:00 2001 From: bklronin Date: Fri, 16 Aug 2024 20:21:21 +0200 Subject: [PATCH 04/16] - Bsic sketch to object approach --- drawing_modules/draw_widget2d.py | 137 +++++++++++++++++++++++++- drawing_modules/solvespace_example.py | 28 ++++-- main.py | 3 +- modules/out.stl | Bin 233684 -> 0 bytes requirements.txt | 6 +- 5 files changed, 162 insertions(+), 12 deletions(-) delete mode 100644 modules/out.stl diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index 5e48ef3..25b5e8a 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -356,8 +356,11 @@ class SketchWidget(QWidget): print("Buffer state", self.line_draw_buffer) if self.line_draw_buffer[0] and self.line_draw_buffer[1]: + point_slv1 = self.get_handle_from_ui_point(self.line_draw_buffer[0]) point_slv2 = self.get_handle_from_ui_point(self.line_draw_buffer[1]) + print(point_slv1) + print(point_slv2) line = self.solv.add_line_2d(point_slv1, point_slv2, self.sketch.working_plane) @@ -768,7 +771,139 @@ class SketchWidget(QWidget): return self.width() / self.height() * (1.0 / abs(self.zoom)) -# Example usage +class Point2D: + """Improved oop aaproach?""" + def __init__(self): + self.ui_point = None + self.solve_handle_nr = None + self.solve_handle = None + self.part_of_entity = None + + def to_quadrant_coords(self, point): + """Translate linear coordinates to quadrant coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + quadrant_x = point.x() - center_x + quadrant_y = center_y - point.y() # Note the change here + + return QPoint(quadrant_x, quadrant_y) / self.zoom + + def from_quadrant_coords(self, point: QPoint): + """Translate quadrant coordinates to linear coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + widget_x = center_x + point.x() * self.zoom + widget_y = center_y - point.y() * self.zoom # Note the subtraction here + + return QPoint(int(widget_x), int(widget_y)) + + def from_quadrant_coords_no_center(self, point): + """Invert Y Coordinate for mesh""" + center_x = 0 + center_y = 0 + widget_x = point.x() + widget_y = -point.y() + + return QPoint(int(widget_x), int(widget_y)) + + def get_handle_nr(self, input_str: str) -> int: + # Define the regex pattern to extract the handle number + pattern = r"handle=(\d+)" + + # Use re.search to find the handle number in the string + match = re.search(pattern, input_str) + + if match: + handle_number = int(match.group(1)) + print(f"Handle number: {handle_number}") + return int(handle_number) + + else: + print("Handle number not found.") + return 0 + + def get_keys(self, d: dict, target: QPoint) -> list: + result = [] + path = [] + print(d) + print(target) + for k, v in d.items(): + path.append(k) + if isinstance(v, dict): + self.get_keys(v, target) + if v == target: + result.append(copy(path)) + path.pop() + + return result + + def get_handle_from_ui_point(self, ui_point: QPoint): + """Input QPoint and you shall reveive a slvs entity handle!""" + for point in self.sketch.slv_points: + if ui_point == point['ui_point']: + slv_handle = point['solv_handle'] + + return slv_handle + + def get_line_handle_from_ui_point(self, ui_point: QPoint): + """Input Qpoint that is on a line and you shall receive the handle of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + slv_handle = target_line_con['solv_handle'] + + return slv_handle + + def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: + """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + lines_to_cons = target_line_con['solv_entity_points'] + + return lines_to_cons + + def distance(self, p1, p2): + return math.sqrt((p1.x() - p2.x())**2 + (p1.y() - p2.y())**2) + + def calculate_midpoint(self, point1, point2): + mx = (point1.x() + point2.x()) // 2 + my = (point1.y() + point2.y()) // 2 + return QPoint(mx, my) + + def is_point_on_line(self, p, p1, p2, tolerance=5): + # Calculate the lengths of the sides of the triangle + a = self.distance(p, p1) + b = self.distance(p, p2) + c = self.distance(p1, p2) + + # Calculate the semi-perimeter + s = (a + b + c) / 2 + + # Calculate the area using Heron's formula + area = math.sqrt(s * (s - a) * (s - b) * (s - c)) + + # Calculate the height (perpendicular distance from the point to the line) + if c > 0: + height = (2 * area) / c + # Check if the height is within the tolerance distance to the line + if height > tolerance: + return False + + # Check if the projection of the point onto the line is within the line segment + dot_product = ((p.x() - p1.x()) * (p2.x() - p1.x()) + (p.y() - p1.y()) * (p2.y() - p1.y())) / (c ** 2) + + return 0 <= dot_product <= 1 + else: + return None + + def viewport_to_local_coord(self, qt_pos : QPoint) -> QPoint: + return QPoint(self.to_quadrant_coords(qt_pos)) + + +class Line2D: + pass + + + if __name__ == "__main__": import sys diff --git a/drawing_modules/solvespace_example.py b/drawing_modules/solvespace_example.py index e1aeb28..e405f59 100644 --- a/drawing_modules/solvespace_example.py +++ b/drawing_modules/solvespace_example.py @@ -1,25 +1,35 @@ from python_solvespace import SolverSystem, ResultFlag -def solve_constraint(self): +def solve_constraint(): solv = SolverSystem() wp = solv.create_2d_base() # Workplane (Entity) p0 = solv.add_point_2d(0, 0, wp) # Entity + p1 = solv.add_point_2d(10, 10, wp) # Entity + p2 = solv.add_point_2d(0, 10, wp) # Entity solv.dragged(p0, wp) # Make a constraint with the entity - ... + line0 = solv.add_line_2d(p0, p1, wp) # Create entity with others - ... - line1 = solv.add_line_2d(p0, p3, wp) - solv.angle(line0, line1, 45, wp) # Constrain two entities + line1 = solv.add_line_2d(p0, p2, wp) + #solv.angle(line0, line1, 45, wp) # Constrain two entities + solv.coincident(p0, p1, wp) + solv.add_constraint(100006, wp, 0, p1,p2, line0, line1) + line1 = solv.entity(-1) # Entity handle can be re-generated and negatively indexed - ... + solv. if solv.solve() == ResultFlag.OKAY: # Get the result (unpack from the entity or parameters) # x and y are actually float type dof = solv.dof() - x, y = solv.params(p2.params) - ... + x, y = solv.params(p1.params) + print(dof) + print(x) + print(y) + else: # Error! # Get the list of all constraints failures = solv.failures() - ... \ No newline at end of file + print(failures) + ... + +solve_constraint() \ No newline at end of file diff --git a/main.py b/main.py index 935998f..405349a 100644 --- a/main.py +++ b/main.py @@ -267,6 +267,7 @@ class MainWindow(QMainWindow): sketch.slv_lines = [] sketch.proj_points = [] sketch.proj_lines = [] + self.sketchWidget.reset_buffers() self.sketchWidget.set_sketch(sketch) def add_new_sketch_wp(self): @@ -279,6 +280,7 @@ class MainWindow(QMainWindow): sketch.slv_lines = [] sketch.proj_points = self.custom_3D_Widget.project_tosketch_points sketch.proj_lines = self.custom_3D_Widget.project_tosketch_lines + self.sketchWidget.reset_buffers() self.sketchWidget.set_sketch(sketch) self.sketchWidget.create_workplane_projected() self.sketchWidget.convert_proj_points() @@ -765,7 +767,6 @@ class Project: assembly: Assembly = None - if __name__ == "__main__": app = QApplication([]) window = MainWindow() diff --git a/modules/out.stl b/modules/out.stl deleted file mode 100644 index 811aa0d25fa3742f56ca5d923e75df6685085b41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233684 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�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 Date: Fri, 16 Aug 2024 22:18:39 +0200 Subject: [PATCH 05/16] - Updated requirements.txt --- requirements.txt | 62 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index a8f207e..b6000b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,66 @@ +certifi==2024.7.4 +cffi==1.16.0 +charset-normalizer==3.3.2 +contourpy==1.2.0 +cycler==0.12.1 +fonttools==4.47.0 +freetype-py==2.4.0 +hsluv==5.0.4 +idna==3.7 +imageio==2.33.1 +kiwisolver==1.4.5 +lazy_loader==0.3 +markdown-it-py==3.0.0 matplotlib==3.8.2 +mdurl==0.1.2 +meshio==5.3.4 +names==0.3.0 +networkx==3.2.1 +Nuitka==2.2.1 numpy==1.26.2 +numpy-stl==3.1.1 +ordered-set==4.1.0 +packaging==23.2 +panda3d-gltf==1.2.0 +panda3d-simplepbr==0.12.0 Pillow==10.1.0 Pint==0.22 +platformdirs==4.2.2 +pooch==1.8.2 +pycparser==2.21 +pygame==2.5.2 +Pygments==2.17.2 +PyOpenGL==3.1.7 +pyparsing==3.1.1 +PyQt6==6.7.0 +PyQt6-3D==6.7.0 +PyQt6-3D-Qt6==6.7.0 +PyQt6-Qt6==6.7.0 +PyQt6-sip==13.6.0 PySide6==6.6.1 +PySide6-Addons==6.6.1 +PySide6-Essentials==6.6.1 +python-dateutil==2.8.2 +python-solvespace==3.0.8 +python-utils==3.8.2 +pyvista==0.43.10 +pyvistaqt==0.11.1 +QtPy==2.4.1 +requests==2.32.3 rich==13.7.0 scikit-image==0.22.0 scipy==1.11.4 +scooby==0.10.0 +sdfcad @ git+https://gitlab.com/nobodyinperson/sdfCAD@9bd4e9021c6ee7e685ee28e8a3a5d2d2c028190c +shapely==2.0.4 +shiboken6==6.6.1 +six==1.16.0 +tifffile==2023.12.9 +trimesh==4.3.2 +tripy==1.0.0 +typing_extensions==4.9.0 +urllib3==2.2.2 +vispy==0.14.2 vtk==9.3.0 - -names~=0.3.0 -shapely~=2.0.4 -pyvista~=0.43.10 -pyvistaqt~=0.11.1 \ No newline at end of file +vulkan==1.3.275.0 +zstandard==0.22.0 From 79c1aa106fefbac05228e7b599a9301dc9a4d4a5 Mon Sep 17 00:00:00 2001 From: bklronin Date: Sat, 26 Oct 2024 14:40:58 +0200 Subject: [PATCH 06/16] -Changed multi layer render - added ignore - prepraed sketchwidget --- .gitignore | 2 ++ drawing_modules/draw_widget2d.py | 31 +++++++++++++++++++++++++++++-- drawing_modules/vtk_widget.py | 16 ++++++++++++++-- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb46844 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.xml +*.iml \ No newline at end of file diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index 25b5e8a..b42072f 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -383,6 +383,8 @@ class SketchWidget(QWidget): # Track Relationship # Points + # CONSTRAINTS + if event.button() == Qt.LeftButton and self.mouse_mode == "pt_pt": if self.hovered_point and not self.main_buffer[0]: self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) @@ -771,7 +773,7 @@ class SketchWidget(QWidget): return self.width() / self.height() * (1.0 / abs(self.zoom)) -class Point2D: +class Point2D_ALT: """Improved oop aaproach?""" def __init__(self): self.ui_point = None @@ -899,8 +901,33 @@ class Point2D: return QPoint(self.to_quadrant_coords(qt_pos)) + +class Point2D: + ui_x: int = None + ui_y: int = None + handle = None + class Line2D: - pass + crd1: Point2D = None + crd2: Point2D = None + handle = None + +class Sketch2d(SolverSystem): + def __init__(self): + self.wp = self.create_2d_base() + self.points = [] + self.lines = [] + + def add_point(self, point: Point2D): + point.handle = self.add_point_2d(point.ui_x, point.ui_y, self.wo) + self.points.append(point) + + def add_line(self, line: Line2D): + line.handle = self.add_line_2d(line.crd1, line.crd2, self.wp) + self.lines.append(line) + + + diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 2f33bb5..5d4cc86 100644 --- a/drawing_modules/vtk_widget.py +++ b/drawing_modules/vtk_widget.py @@ -52,6 +52,12 @@ class VTKWidget(QtWidgets.QWidget): self.renderer_projections.SetLayer(1) self.renderer_indicators.SetLayer(2) # This will be on top + # Preserve color and depth buffers for non-zero layers + self.renderer_projections.SetPreserveColorBuffer(True) + self.renderer_projections.SetPreserveDepthBuffer(True) + self.renderer_indicators.SetPreserveColorBuffer(True) + self.renderer_indicators.SetPreserveDepthBuffer(True) + # Add renderers to the render window render_window = self.vtk_widget.GetRenderWindow() render_window.SetNumberOfLayers(3) @@ -59,11 +65,11 @@ class VTKWidget(QtWidgets.QWidget): render_window.AddRenderer(self.renderer_projections) render_window.AddRenderer(self.renderer_indicators) - # Set up shared camera self.camera = vtk.vtkCamera() self.camera.SetPosition(5, 5, 1000) self.camera.SetFocalPoint(0, 0, 0) - self.camera.SetClippingRange(0.1, 100000) + self.camera.SetClippingRange(1, 10000) # Adjusted clipping range + self.renderer.SetActiveCamera(self.camera) self.renderer_projections.SetActiveCamera(self.camera) self.renderer_indicators.SetActiveCamera(self.camera) @@ -140,10 +146,16 @@ class VTKWidget(QtWidgets.QWidget): self.renderer.AddActor(actor) + def reset_camera(self): + self.renderer.ResetCamera() + self.camera.SetClippingRange(1, 100000) # Set your desired range + self.vtk_widget.GetRenderWindow().Render() + def update_render(self): self.renderer.ResetCameraClippingRange() self.renderer_projections.ResetCameraClippingRange() self.renderer_indicators.ResetCameraClippingRange() + self.camera.SetClippingRange(1, 100000) self.vtk_widget.GetRenderWindow().Render() def create_grid(self, size=100, spacing=10): From a5202e1630dd900ad74dab7269e6c51e4241cbe9 Mon Sep 17 00:00:00 2001 From: bklronin Date: Sat, 26 Oct 2024 18:02:06 +0200 Subject: [PATCH 07/16] - Basic oop sketch widget implement --- .gitignore | 3 +- drawing_modules/draw_widget2d.py | 32 +- drawing_modules/draw_widget_solve.py | 932 +++++++++++++++++++++++++++ main.py | 5 +- 4 files changed, 941 insertions(+), 31 deletions(-) create mode 100644 drawing_modules/draw_widget_solve.py diff --git a/.gitignore b/.gitignore index cb46844..3c40110 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.xml -*.iml \ No newline at end of file +*.iml +.idea \ No newline at end of file diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py index b42072f..ea7f469 100644 --- a/drawing_modules/draw_widget2d.py +++ b/drawing_modules/draw_widget2d.py @@ -383,8 +383,6 @@ class SketchWidget(QWidget): # Track Relationship # Points - # CONSTRAINTS - if event.button() == Qt.LeftButton and self.mouse_mode == "pt_pt": if self.hovered_point and not self.main_buffer[0]: self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) @@ -773,7 +771,7 @@ class SketchWidget(QWidget): return self.width() / self.height() * (1.0 / abs(self.zoom)) -class Point2D_ALT: +class Point2D: """Improved oop aaproach?""" def __init__(self): self.ui_point = None @@ -901,35 +899,11 @@ class Point2D_ALT: return QPoint(self.to_quadrant_coords(qt_pos)) - -class Point2D: - ui_x: int = None - ui_y: int = None - handle = None - class Line2D: - crd1: Point2D = None - crd2: Point2D = None - handle = None + pass class Sketch2d(SolverSystem): - def __init__(self): - self.wp = self.create_2d_base() - self.points = [] - self.lines = [] - - def add_point(self, point: Point2D): - point.handle = self.add_point_2d(point.ui_x, point.ui_y, self.wo) - self.points.append(point) - - def add_line(self, line: Line2D): - line.handle = self.add_line_2d(line.crd1, line.crd2, self.wp) - self.lines.append(line) - - - - - + if __name__ == "__main__": import sys diff --git a/drawing_modules/draw_widget_solve.py b/drawing_modules/draw_widget_solve.py new file mode 100644 index 0000000..8e060d8 --- /dev/null +++ b/drawing_modules/draw_widget_solve.py @@ -0,0 +1,932 @@ +import math +import re +from copy import copy +from typing import Optional + +import numpy as np +from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QInputDialog +from PySide6.QtGui import QPainter, QPen, QColor, QTransform +from PySide6.QtCore import Qt, QPoint, QPointF, Signal, QLine +from python_solvespace import SolverSystem, ResultFlag + + +class SketchWidget(QWidget): + constrain_done = Signal() + + def __init__(self): + super().__init__() + + self.line_draw_buffer = [None, None] + self.drag_buffer = [None, None] + self.main_buffer = [None, None] + + self.hovered_point = None + self.selected_line = None + + self.snapping_range = 20 # Range in pixels for snapping + self.zoom = 1 + + self.setMouseTracking(True) + self.mouse_mode = False + self.solv = SolverSystem() + + self.sketch = Sketch2d() + + def get_sketch(self): + return self.sketch + + def reset_buffers(self): + self.line_draw_buffer = [None, None] + self.drag_buffer = [None, None] + self.main_buffer = [None, None] + + def set_points(self, points: list): + self.points = points + #self.update() + + def create_workplane(self): + self.sketch.working_plane = self.solv.create_2d_base() + + def create_workplane_projected(self): + self.sketch.working_plane = self.solv.create_2d_base() + + def convert_proj_points(self): + out_points = [] + for point in self.sketch.proj_points: + x, y = point + coord = QPoint(x, y) + out_points.append(coord) + + self.sketch.proj_points = out_points + + def convert_proj_lines(self): + out_lines = [] + for line in self.sketch.proj_lines: + start = QPoint(line[0][0], line[0][1]) + end = QPoint(line[1][0], line[1][1]) + coord = QLine(start, end) + out_lines.append(coord) + self.sketch.proj_lines = out_lines + + def find_duplicate_points_2d(self, edges): + points = [] + seen = set() + duplicates = [] + + for edge in edges: + for point in edge: + # Extract only x and y coordinates + point_2d = (point[0], point[1]) + if point_2d in seen: + if point_2d not in duplicates: + duplicates.append(point_2d) + else: + seen.add(point_2d) + points.append(point_2d) + + return duplicates + + def normal_to_quaternion(self, normal): + normal = np.array(normal) + #normal = normal / np.linalg.norm(normal) + + axis = np.cross([0, 0, 1], normal) + if np.allclose(axis, 0): + axis = np.array([1, 0, 0]) + else: + axis = axis / np.linalg.norm(axis) # Normalize the axis + + angle = np.arccos(np.dot([0, 0, 1], normal)) + + qw = np.cos(angle / 2) + sin_half_angle = np.sin(angle / 2) + qx, qy, qz = axis * sin_half_angle # This will now work correctly + + return qw, qx, qy, qz + + def create_workplane_space(self, points, normal): + print("edges", points) + origin = self.find_duplicate_points_2d(points) + print(origin) + x, y = origin[0] + origin = QPoint(x, y) + + origin_handle = self.get_handle_from_ui_point(origin) + qw, qx, qy, qz = self.normal_to_quaternion(normal) + + slv_normal = self.solv.add_normal_3d(qw, qx, qy, qz) + self.sketch.working_plane = self.solv.add_work_plane(origin_handle, slv_normal) + print(self.sketch.working_plane) + + def get_handle_nr(self, input_str: str) -> int: + # Define the regex pattern to extract the handle number + pattern = r"handle=(\d+)" + + # Use re.search to find the handle number in the string + match = re.search(pattern, input_str) + + if match: + handle_number = int(match.group(1)) + print(f"Handle number: {handle_number}") + return int(handle_number) + + else: + print("Handle number not found.") + return 0 + + def get_keys(self, d: dict, target: QPoint) -> list: + result = [] + path = [] + print(d) + print(target) + for k, v in d.items(): + path.append(k) + if isinstance(v, dict): + self.get_keys(v, target) + if v == target: + result.append(copy(path)) + path.pop() + + return result + + def get_handle_from_ui_point(self, ui_point: QPoint): + """Input QPoint and you shall reveive a slvs entity handle!""" + for point in self.sketch.points: + if ui_point == point.ui_point: + slv_handle = point.handle + + return slv_handle + + def get_line_handle_from_ui_point(self, ui_point: QPoint): + """Input Qpoint that is on a line and you shall receive the handle of the line!""" + for target_line_con in self.sketch.lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + slv_handle = target_line_con['solv_handle'] + + return slv_handle + + def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: + """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + lines_to_cons = target_line_con['solv_entity_points'] + + return lines_to_cons + + def distance(self, p1, p2): + return math.sqrt((p1.x() - p2.x())**2 + (p1.y() - p2.y())**2) + + def calculate_midpoint(self, point1, point2): + mx = (point1.x() + point2.x()) // 2 + my = (point1.y() + point2.y()) // 2 + return QPoint(mx, my) + + def is_point_on_line(self, p, p1, p2, tolerance=5): + # Calculate the lengths of the sides of the triangle + a = self.distance(p, p1) + b = self.distance(p, p2) + c = self.distance(p1, p2) + + # Calculate the semi-perimeter + s = (a + b + c) / 2 + + # Calculate the area using Heron's formula + area = math.sqrt(s * (s - a) * (s - b) * (s - c)) + + # Calculate the height (perpendicular distance from the point to the line) + if c > 0: + height = (2 * area) / c + # Check if the height is within the tolerance distance to the line + if height > tolerance: + return False + + # Check if the projection of the point onto the line is within the line segment + dot_product = ((p.x() - p1.x()) * (p2.x() - p1.x()) + (p.y() - p1.y()) * (p2.y() - p1.y())) / (c ** 2) + + return 0 <= dot_product <= 1 + else: + return None + + def viewport_to_local_coord(self, qt_pos : QPoint) -> QPoint: + return QPoint(self.to_quadrant_coords(qt_pos)) + + def check_all_points(self,) -> list: + old_points_ui = [] + new_points_ui = [] + + for old_point_ui in self.sketch.points: + old_points_ui.append(old_point_ui.ui_point) + + for i in range(self.solv.entity_len()): + # Iterate though full length because mixed list from SS + entity = self.solv.entity(i) + if entity.is_point_2d() and self.solv.params(entity.params): + x_tbu, y_tbu = self.solv.params(entity.params) + point_solved = QPoint(x_tbu, y_tbu) + new_points_ui.append(point_solved) + + # Now we have old_points_ui and new_points_ui, let's compare them + differences = [] + + if len(old_points_ui) != len(new_points_ui): + print(f"Length mismatch {len(old_points_ui)} - {len(new_points_ui)}") + + for index, (old_point, new_point) in enumerate(zip(old_points_ui, new_points_ui)): + if old_point != new_point: + differences.append((index, old_point, new_point)) + + return differences + + def update_ui_points(self, point_list: list): + # Print initial state of slv_points_main + # print("Initial slv_points_main:", self.slv_points_main) + print("Change list:", point_list) + + if len(point_list) > 0: + for tbu_points_idx in point_list: + # Each tbu_points_idx is a tuple: (index, old_point, new_point) + index, old_point, new_point = tbu_points_idx + + # Update the point in slv_points_main + self.sketch.points[index].point = new_point + # Print updated state + # print("Updated slv_points_main:", self.slv_points_main) + + def check_all_lines_and_update(self,changed_points: list): + for tbu_points_idx in changed_points: + index, old_point, new_point = tbu_points_idx + for line_needs_update in self.sketch.lines: + if old_point == line_needs_update.points[0]: + line_needs_update['ui_points'][0] = new_point + elif old_point == line_needs_update.points[1]: + line_needs_update['ui_points'][1] = new_point + + def mouseReleaseEvent(self, event): + local_event_pos = self.viewport_to_local_coord(event.pos()) + + if event.button() == Qt.LeftButton and not self.mouse_mode: + self.drag_buffer[1] = local_event_pos + + print("Le main buffer", self.drag_buffer) + + if not None in self.main_buffer and len(self.main_buffer) == 2: + entry = self.drag_buffer[0] + new_params = self.drag_buffer[1].x(), self.drag_buffer[1].y() + self.sketch.set_params(entry.params, new_params) + + self.sketch.solve() + + points_need_update = self.check_all_points() + self.update_ui_points(points_need_update) + self.check_all_lines_and_update(points_need_update) + + self.update() + self.drag_buffer = [None, None] + + def mousePressEvent(self, event): + local_event_pos = self.viewport_to_local_coord(event.pos()) + + + if event.button() == Qt.LeftButton and not self.mouse_mode: + self.drag_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + if event.button() == Qt.RightButton and self.mouse_mode: + self.reset_buffers() + + if event.button() == Qt.LeftButton and self.mouse_mode == "line": + if self.hovered_point: + clicked_pos = self.hovered_point + else: + clicked_pos = local_event_pos + + if not self.line_draw_buffer[0]: + + u = clicked_pos.x() + v = clicked_pos.y() + + point = Point2D(u,v) + self.sketch.add_point(point) + + self.line_draw_buffer[0] = point + + elif self.line_draw_buffer[0]: + + u = clicked_pos.x() + v = clicked_pos.y() + + point = Point2D(u, v) + self.sketch.add_point(point) + + self.line_draw_buffer[1] = point + + print("Buffer state", self.line_draw_buffer) + + if self.line_draw_buffer[0] and self.line_draw_buffer[1]: + + line = Line2D(self.line_draw_buffer[0], self.line_draw_buffer[1]) + self.sketch.add_line(line) + + # Reset the buffer for the next line segment + self.line_draw_buffer[0] = self.line_draw_buffer[1] + self.line_draw_buffer[1] = None + + # Track Relationship + # Points + + # CONSTRAINTS + + if event.button() == Qt.LeftButton and self.mouse_mode == "pt_pt": + if self.hovered_point and not self.main_buffer[0]: + self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + elif self.main_buffer[0]: + self.main_buffer[1] = self.get_handle_from_ui_point(self.hovered_point) + + if self.main_buffer[0] and self.main_buffer[1]: + print("buf", self.main_buffer) + + self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.solv.solve() == ResultFlag.INCONSISTENT: + print("Solve_failed - Incons") + self.constrain_done.emit() + self.main_buffer = [None, None] + + if event.button() == Qt.LeftButton and self.mouse_mode == "pt_line": + print("ptline") + line_selected = None + + if self.hovered_point and not self.main_buffer[1]: + self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + elif self.main_buffer[0]: + self.main_buffer[1] = self.get_line_handle_from_ui_point(local_event_pos) + + # Contrain point to line + if self.main_buffer[1]: + self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + self.constrain_done.emit() + + elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.solv.solve() == ResultFlag.INCONSISTENT: + print("Solve_failed - Incons") + + self.constrain_done.emit() + # Clear saved_points after solve attempt + self.main_buffer = [None, None] + + if event.button() == Qt.LeftButton and self.mouse_mode == "pb_con_mid": + print("ptline") + line_selected = None + + if self.hovered_point and not self.main_buffer[1]: + self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point) + + elif self.main_buffer[0]: + self.main_buffer[1] = self.get_line_handle_from_ui_point(local_event_pos) + + # Contrain point to line + if self.main_buffer[1]: + self.solv.midpoint(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.solv.solve() == ResultFlag.INCONSISTENT: + print("Solve_failed - Incons") + self.constrain_done.emit() + + self.main_buffer = [None, None] + + if event.button() == Qt.LeftButton and self.mouse_mode == "horiz": + + line_selected = self.get_line_handle_from_ui_point(local_event_pos) + + if line_selected: + self.solv.horizontal(line_selected, self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.solv.solve() == ResultFlag.INCONSISTENT: + print("Solve_failed - Incons") + + if event.button() == Qt.LeftButton and self.mouse_mode == "vert": + line_selected = self.get_line_handle_from_ui_point(local_event_pos) + + if line_selected: + self.solv.vertical(line_selected, self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.solv.solve() == ResultFlag.INCONSISTENT: + print("Solve_failed - Incons") + + if event.button() == Qt.LeftButton and self.mouse_mode == "distance": + # Depending on selected elemnts either point line or line distance + #print("distance") + e1 = None + e2 = None + + if self.hovered_point: + print("buf point") + # Get the point as UI point as buffer + self.main_buffer[0] = self.hovered_point + + elif self.selected_line: + # Get the point as UI point as buffer + self.main_buffer[1] = local_event_pos + + if self.main_buffer[0] and self.main_buffer[1]: + # Define point line combination + e1 = self.get_handle_from_ui_point(self.main_buffer[0]) + e2 = self.get_line_handle_from_ui_point(self.main_buffer[1]) + + elif not self.main_buffer[0]: + # Define only line selection + e1, e2 = self.get_point_line_handles_from_ui_point(local_event_pos) + + if e1 and e2: + # Ask fo the dimension and solve if both elements are present + length, ok = QInputDialog.getDouble(self, 'Distance', 'Enter a mm value:', value=100, decimals=2) + self.solv.distance(e1, e2, length, self.sketch.working_plane) + + if self.solv.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.solv.solve() == ResultFlag.INCONSISTENT: + print("Solve_failed - Incons") + + self.constrain_done.emit() + self.main_buffer = [None, None] + + # Update the main point list with the new elements and draw them + points_need_update = self.check_all_points() + self.update_ui_points(points_need_update) + self.check_all_lines_and_update(points_need_update) + + self.update() + + def mouseMoveEvent(self, event): + local_event_pos = self.viewport_to_local_coord(event.pos()) + + closest_point = None + min_distance = float('inf') + threshold = 10 # Distance threshold for highlighting + + if len(self.sketch.points) > 0: + + for point in self.sketch.points: + distance = (local_event_pos - point.ui_point).manhattanLength() + if distance < threshold and distance < min_distance: + closest_point = point.ui_point + min_distance = distance + + """for point in self.sketch.proj_points: + distance = (local_event_pos - point).manhattanLength() + if distance < threshold and distance < min_distance: + closest_point = point + min_distance = distance""" + + if closest_point != self.hovered_point: + self.hovered_point = closest_point + print(self.hovered_point) + + for line in self.sketch.lines: + p1 = line.crd1.ui_point + p2 = line.crd2.ui_point + + if self.is_point_on_line(local_event_pos, p1, p2): + self.selected_line = p1, p2 + break + else: + self.selected_line = None + + self.update() + + def mouseDoubleClickEvent(self, event): + pass + + def drawBackgroundGrid(self, painter): + """Draw a background grid.""" + grid_spacing = 50 + pen = QPen(QColor(200, 200, 200), 1, Qt.SolidLine) + painter.setPen(pen) + + # Draw vertical grid lines + for x in range(-self.width() // 2, self.width() // 2, grid_spacing): + painter.drawLine(x, -self.height() // 2, x, self.height() // 2) + + # Draw horizontal grid lines + for y in range(-self.height() // 2, self.height() // 2, grid_spacing): + painter.drawLine(-self.width() // 2, y, self.width() // 2, y) + + def drawAxes(self, painter): + painter.setRenderHint(QPainter.Antialiasing) + + # Set up pen for dashed lines + pen = QPen(Qt.gray, 1, Qt.DashLine) + painter.setPen(pen) + + middle_x = self.width() // 2 + middle_y = self.height() // 2 + + # Draw X axis as dashed line + painter.drawLine(0, middle_y, self.width(), middle_y) + + # Draw Y axis as dashed line + painter.drawLine(middle_x, 0, middle_x, self.height()) + + # Draw tick marks + tick_length = int(10 * self.zoom) + tick_spacing = int(50 * self.zoom) + + pen = QPen(Qt.gray, 1, Qt.SolidLine) + painter.setPen(pen) + + # Draw tick marks on the X axis to the right and left from the middle point + for x in range(0, self.width() // 2, tick_spacing): + painter.drawLine(middle_x + x, middle_y - tick_length // 2, middle_x + x, middle_y + tick_length // 2) + painter.drawLine(middle_x - x, middle_y - tick_length // 2, middle_x - x, middle_y + tick_length // 2) + + # Draw tick marks on the Y axis upwards and downwards from the middle point + for y in range(0, self.height() // 2, tick_spacing): + painter.drawLine(middle_x - tick_length // 2, middle_y + y, middle_x + tick_length // 2, middle_y + y) + painter.drawLine(middle_x - tick_length // 2, middle_y - y, middle_x + tick_length // 2, middle_y - y) + + # Draw the origin point in red + painter.setPen(QPen(Qt.red, 4)) + painter.drawPoint(middle_x, middle_y) + + def draw_cross(self, painter, pos: QPoint, size=10): + # Set up the pen + pen = QPen(QColor('green')) # You can change the color as needed + pen.setWidth(int(2 / self.zoom)) # Set the line widt)h + painter.setPen(pen) + x = pos.x() + y = pos.y() + + # Calculate the endpoints of the cross + half_size = size // 2 + + # Draw the horizontal line + painter.drawLine(x - half_size, y, x + half_size, y) + + # Draw the vertical line + painter.drawLine(x, y - half_size, x, y + half_size) + + def to_quadrant_coords(self, point): + """Translate linear coordinates to quadrant coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + quadrant_x = point.x() - center_x + quadrant_y = center_y - point.y() # Note the change here + return QPoint(quadrant_x, quadrant_y) / self.zoom + + def from_quadrant_coords(self, point: QPoint): + """Translate quadrant coordinates to linear coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + widget_x = center_x + point.x() * self.zoom + widget_y = center_y - point.y() * self.zoom # Note the subtraction here + + return QPoint(int(widget_x), int(widget_y)) + + def from_quadrant_coords_no_center(self, point): + """Invert Y Coordinate for mesh""" + center_x = 0 + center_y = 0 + widget_x = point.x() + widget_y = -point.y() + return QPoint(int(widget_x), int(widget_y)) + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + self.drawAxes(painter) + + # Create a QTransform object + transform = QTransform() + + # Translate the origin to the center of the widget + center = QPointF(self.width() / 2, self.height() / 2) + transform.translate(center.x(), center.y()) + + # Apply the zoom factor + transform.scale(self.zoom, -self.zoom) # Negative y-scale to invert y-axis + + # Set the transform to the painter + painter.setTransform(transform) + + pen = QPen(Qt.gray) + pen.setWidthF(2 / self.zoom) + painter.setPen(pen) + + # Draw points + if self.sketch: + for point in self.sketch.points: + painter.drawEllipse(point.ui_point, 3 / self.zoom, 3 / self.zoom) + + for line in self.sketch.lines: + p1 = line.crd1.ui_point + p2 = line.crd2.ui_point + painter.drawLine(p1, p2) + + dis = self.distance(p1, p2) + mid = self.calculate_midpoint(p1, p2) + painter.drawText(mid, str(round(dis, 2))) + + pen = QPen(Qt.green) + pen.setWidthF(2 / self.zoom) + painter.setPen(pen) + + if self.solv.entity_len(): + for i in range(self.solv.entity_len()): + entity = self.solv.entity(i) + if entity.is_point_2d() and self.solv.params(entity.params): + x, y = self.solv.params(entity.params) + point = QPointF(x, y) + painter.drawEllipse(point, 6 / self.zoom, 6 / self.zoom) + + # Highlight point hovered + if self.hovered_point: + highlight_pen = QPen(QColor(255, 0, 0)) + highlight_pen.setWidthF(2 / self.zoom) + painter.setPen(highlight_pen) + painter.drawEllipse(self.hovered_point, 5 / self.zoom, 5 / self.zoom) + + # Highlight line hovered + if self.selected_line and not self.hovered_point: + p1, p2 = self.selected_line + painter.setPen(QPen(Qt.red, 2 / self.zoom)) + painter.drawLine(p1, p2) + + """for cross in self.sketch.proj_points: + self.draw_cross(painter, cross, 10 / self.zoom) + + for selected in self.sketch.proj_lines: + pen = QPen(Qt.white, 1, Qt.DashLine) + painter.setPen(pen) + painter.drawLine(selected)""" + + painter.end() + + def wheelEvent(self, event): + delta = event.angleDelta().y() + self.zoom += (delta / 200) * 0.1 + self.update() + + def aspect_ratio(self): + return self.width() / self.height() * (1.0 / abs(self.zoom)) + + +class Point2D_ALT: + """Improved oop aaproach?""" + def __init__(self): + self.ui_point = None + self.solve_handle_nr = None + self.solve_handle = None + self.part_of_entity = None + + def to_quadrant_coords(self, point): + """Translate linear coordinates to quadrant coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + quadrant_x = point.x() - center_x + quadrant_y = center_y - point.y() # Note the change here + + return QPoint(quadrant_x, quadrant_y) / self.zoom + + def from_quadrant_coords(self, point: QPoint): + """Translate quadrant coordinates to linear coordinates.""" + center_x = self.width() // 2 + center_y = self.height() // 2 + widget_x = center_x + point.x() * self.zoom + widget_y = center_y - point.y() * self.zoom # Note the subtraction here + + return QPoint(int(widget_x), int(widget_y)) + + def from_quadrant_coords_no_center(self, point): + """Invert Y Coordinate for mesh""" + center_x = 0 + center_y = 0 + widget_x = point.x() + widget_y = -point.y() + + return QPoint(int(widget_x), int(widget_y)) + + def get_handle_nr(self, input_str: str) -> int: + # Define the regex pattern to extract the handle number + pattern = r"handle=(\d+)" + + # Use re.search to find the handle number in the string + match = re.search(pattern, input_str) + + if match: + handle_number = int(match.group(1)) + print(f"Handle number: {handle_number}") + return int(handle_number) + + else: + print("Handle number not found.") + return 0 + + def get_keys(self, d: dict, target: QPoint) -> list: + result = [] + path = [] + print(d) + print(target) + for k, v in d.items(): + path.append(k) + if isinstance(v, dict): + self.get_keys(v, target) + if v == target: + result.append(copy(path)) + path.pop() + + return result + + def get_handle_from_ui_point(self, ui_point: QPoint): + """Input QPoint and you shall reveive a slvs entity handle!""" + for point in self.sketch.slv_points: + if ui_point == point['ui_point']: + slv_handle = point['solv_handle'] + + return slv_handle + + def get_line_handle_from_ui_point(self, ui_point: QPoint): + """Input Qpoint that is on a line and you shall receive the handle of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + slv_handle = target_line_con['solv_handle'] + + return slv_handle + + def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: + """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" + for target_line_con in self.sketch.slv_lines: + if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): + lines_to_cons = target_line_con['solv_entity_points'] + + return lines_to_cons + + def distance(self, p1, p2): + return math.sqrt((p1.x() - p2.x())**2 + (p1.y() - p2.y())**2) + + def calculate_midpoint(self, point1, point2): + mx = (point1.x() + point2.x()) // 2 + my = (point1.y() + point2.y()) // 2 + return QPoint(mx, my) + + def is_point_on_line(self, p, p1, p2, tolerance=5): + # Calculate the lengths of the sides of the triangle + a = self.distance(p, p1) + b = self.distance(p, p2) + c = self.distance(p1, p2) + + # Calculate the semi-perimeter + s = (a + b + c) / 2 + + # Calculate the area using Heron's formula + area = math.sqrt(s * (s - a) * (s - b) * (s - c)) + + # Calculate the height (perpendicular distance from the point to the line) + if c > 0: + height = (2 * area) / c + # Check if the height is within the tolerance distance to the line + if height > tolerance: + return False + + # Check if the projection of the point onto the line is within the line segment + dot_product = ((p.x() - p1.x()) * (p2.x() - p1.x()) + (p.y() - p1.y()) * (p2.y() - p1.y())) / (c ** 2) + + return 0 <= dot_product <= 1 + else: + return None + + def viewport_to_local_coord(self, qt_pos : QPoint) -> QPoint: + return QPoint(self.to_quadrant_coords(qt_pos)) + + + +class Point2D: + def __init__(self, x, y): + self.ui_x: int = x + self.ui_y: int = y + self.ui_point = QPoint(self.ui_x, self.ui_y) + self.handle = None + self.handle_nr: int = None + +class Line2D: + def __init__(self, point_s: Point2D, point_e: Point2D): + self.crd1: Point2D = point_s + self.crd2: Point2D = point_e + self.handle = None + self.handle_nr = None + +class Sketch2d(SolverSystem): + """ + Primary class for internal drawing based on the SolveSpace libray + """ + + def __init__(self): + self.wp = self.create_2d_base() + self.points = [] + self.lines = [] + + def add_point(self, point: Point2D): + """ + Adds a point into the solversystem and reurns the handle. + Appends the added point to the points list. + :param point: 2D point in Point2D class format + :return: + """ + + point.handle = self.add_point_2d(point.ui_x, point.ui_y, self.wp) + point.handle_nr = self.get_handle_nr(str(point.handle)) + + self.points.append(point) + + def add_line(self, line: Line2D): + """ + Adds a line into the solversystem and returns the handle. + Appends the added line to the line list. + :param line: + :param point: 2D point in Point2D class format + :return: + """ + + line.handle = self.add_line_2d(line.crd1.handle, line.crd2.handle, self.wp) + line.handle_nr = self.get_handle_nr(str(line.handle)) + + self.lines.append(line) + + ### HELPER AND TOOLS + def get_handle_nr(self, input_str: str) -> int: + # Define the regex pattern to extract the handle number + pattern = r"handle=(\d+)" + + # Use re.search to find the handle number in the string + match = re.search(pattern, input_str) + + if match: + handle_number = int(match.group(1)) + print(f"Handle number: {handle_number}") + return int(handle_number) + + else: + print("Handle number not found.") + return 0 + +if __name__ == "__main__": + import sys + + app = QApplication(sys.argv) + window = SketchWidget() + window.setWindowTitle("Snap Line Widget") + window.resize(800, 600) + window.show() + sys.exit(app.exec()) diff --git a/main.py b/main.py index 405349a..aa12c1f 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDial from Gui import Ui_fluencyCAD # Import the generated GUI module from drawing_modules.vtk_widget import VTKWidget from drawing_modules.vysta_widget import PyVistaWidget -from drawing_modules.draw_widget2d import SketchWidget +from drawing_modules.draw_widget_solve import SketchWidget from sdf import * from python_solvespace import SolverSystem, ResultFlag from mesh_modules import simple_mesh, vesta_mesh, interactor_mesh @@ -294,7 +294,10 @@ class MainWindow(QMainWindow): self.custom_3D_Widget.clear_actors_normals() def add_sketch(self): + """ + :return: + """ sketch = self.sketchWidget.get_sketch() sketch.convert_points_for_sdf() From 842799b35fcd0d3c7766a5af7cc12f66c3c86f7f Mon Sep 17 00:00:00 2001 From: bklronin Date: Mon, 30 Dec 2024 13:54:15 +0100 Subject: [PATCH 08/16] - Sketch projection partly works again :) --- doc/flow.md | 16 +- drawing_modules/draw_widget_solve.py | 353 +++++++++++---------------- main.py | 69 ++++-- 3 files changed, 199 insertions(+), 239 deletions(-) diff --git a/doc/flow.md b/doc/flow.md index 9d8e3d1..b7b9690 100644 --- a/doc/flow.md +++ b/doc/flow.md @@ -18,4 +18,18 @@ - Project cartesian flattened mesh into 2D - Transform to 2D xy - Transform to linear space for 2D widget to draw. -- Result into 2D cartesian for body interaction extrude etc \ No newline at end of file +- Result into 2D cartesian for body interaction extrude etc + +### Elements + +So far these are the elements: + +- Project: Main File +- Timeline : Used to track the steps +- Assembly: Uses Components and Connectors to from Assemblies +- Component: Container for multiple smaller elements "part" +- Connector: Preserves connections between parts even if the part in between is deleted +- Code: A special type that directly builds bodys from sdfCAD code. +- Body: The 3D meshed result from sdfCAD +- Sketch: The base to draw new entities. +- Interactor: A special component mesh that is used to manipulate the bodys in 3d view. \ No newline at end of file diff --git a/drawing_modules/draw_widget_solve.py b/drawing_modules/draw_widget_solve.py index 8e060d8..2b8cbcb 100644 --- a/drawing_modules/draw_widget_solve.py +++ b/drawing_modules/draw_widget_solve.py @@ -1,7 +1,7 @@ import math import re from copy import copy -from typing import Optional +import uuid import numpy as np from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QInputDialog @@ -32,6 +32,11 @@ class SketchWidget(QWidget): self.sketch = Sketch2d() + def create_sketch(self, sketch_in ): + self.sketch = Sketch2d() + self.sketch.id = sketch_in.id + self.sketch.origin = sketch_in.origin + def get_sketch(self): return self.sketch @@ -45,28 +50,38 @@ class SketchWidget(QWidget): #self.update() def create_workplane(self): - self.sketch.working_plane = self.solv.create_2d_base() + self.sketch.wp = self.sketch.create_2d_base() def create_workplane_projected(self): - self.sketch.working_plane = self.solv.create_2d_base() + self.sketch.wp = self.sketch.create_2d_base() - def convert_proj_points(self): + def convert_proj_points(self, proj_points: list): + ### This needs to create a proper Point2D class with bool construction enbaled out_points = [] - for point in self.sketch.proj_points: - x, y = point - coord = QPoint(x, y) - out_points.append(coord) + for point in proj_points: + pnt = Point2D(point[0], point[1]) + # Construction + pnt.is_helper = True + print(point) + self.sketch.add_point(pnt) - self.sketch.proj_points = out_points - - def convert_proj_lines(self): + def convert_proj_lines(self, proj_lines: list): + ### same as for point out_lines = [] - for line in self.sketch.proj_lines: - start = QPoint(line[0][0], line[0][1]) - end = QPoint(line[1][0], line[1][1]) - coord = QLine(start, end) - out_lines.append(coord) - self.sketch.proj_lines = out_lines + for line in proj_lines: + start = Point2D(line[0][0], line[0][1]) + end = Point2D(line[1][0], line[1][1]) + start.is_helper = True + end.is_helper = True + + self.sketch.add_point(start) + self.sketch.add_point(end) + + lne = Line2D(start, end) + + #Construction + lne.is_helper = True + self.sketch.add_line(lne) def find_duplicate_points_2d(self, edges): points = [] @@ -114,9 +129,9 @@ class SketchWidget(QWidget): origin_handle = self.get_handle_from_ui_point(origin) qw, qx, qy, qz = self.normal_to_quaternion(normal) - slv_normal = self.solv.add_normal_3d(qw, qx, qy, qz) - self.sketch.working_plane = self.solv.add_work_plane(origin_handle, slv_normal) - print(self.sketch.working_plane) + slv_normal = self.sketch.add_normal_3d(qw, qx, qy, qz) + self.sketch.wp = self.sketch.add_work_plane(origin_handle, slv_normal) + print(self.sketch.wp) def get_handle_nr(self, input_str: str) -> int: # Define the regex pattern to extract the handle number @@ -160,16 +175,16 @@ class SketchWidget(QWidget): def get_line_handle_from_ui_point(self, ui_point: QPoint): """Input Qpoint that is on a line and you shall receive the handle of the line!""" for target_line_con in self.sketch.lines: - if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): - slv_handle = target_line_con['solv_handle'] + if self.is_point_on_line(ui_point, target_line_con.crd1.ui_point, target_line_con.crd2.ui_point): + slv_handle = target_line_con.handle return slv_handle def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" for target_line_con in self.sketch.slv_lines: - if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): - lines_to_cons = target_line_con['solv_entity_points'] + if self.is_point_on_line(ui_point, target_line_con.crd1.ui_point, target_line_con.crd2.ui_point): + lines_to_cons = target_line_con.crd1.handle, target_line_con.crd2.handle return lines_to_cons @@ -210,18 +225,22 @@ class SketchWidget(QWidget): def viewport_to_local_coord(self, qt_pos : QPoint) -> QPoint: return QPoint(self.to_quadrant_coords(qt_pos)) - def check_all_points(self,) -> list: + def check_all_points(self) -> list: + """ + Go through solversystem and check points2d for changes in position after solving + :return: List with points that now have a different position + """ old_points_ui = [] new_points_ui = [] for old_point_ui in self.sketch.points: old_points_ui.append(old_point_ui.ui_point) - for i in range(self.solv.entity_len()): + for i in range(self.sketch.entity_len()): # Iterate though full length because mixed list from SS - entity = self.solv.entity(i) - if entity.is_point_2d() and self.solv.params(entity.params): - x_tbu, y_tbu = self.solv.params(entity.params) + entity = self.sketch.entity(i) + if entity.is_point_2d() and self.sketch.params(entity.params): + x_tbu, y_tbu = self.sketch.params(entity.params) point_solved = QPoint(x_tbu, y_tbu) new_points_ui.append(point_solved) @@ -248,7 +267,7 @@ class SketchWidget(QWidget): index, old_point, new_point = tbu_points_idx # Update the point in slv_points_main - self.sketch.points[index].point = new_point + self.sketch.points[index].ui_point = new_point # Print updated state # print("Updated slv_points_main:", self.slv_points_main) @@ -256,10 +275,10 @@ class SketchWidget(QWidget): for tbu_points_idx in changed_points: index, old_point, new_point = tbu_points_idx for line_needs_update in self.sketch.lines: - if old_point == line_needs_update.points[0]: - line_needs_update['ui_points'][0] = new_point - elif old_point == line_needs_update.points[1]: - line_needs_update['ui_points'][1] = new_point + if old_point == line_needs_update.crd1.ui_point: + line_needs_update.crd1.ui_point = new_point + elif old_point == line_needs_update.crd2.ui_point: + line_needs_update.crd2.ui_point = new_point def mouseReleaseEvent(self, event): local_event_pos = self.viewport_to_local_coord(event.pos()) @@ -345,19 +364,20 @@ class SketchWidget(QWidget): if self.main_buffer[0] and self.main_buffer[1]: print("buf", self.main_buffer) - self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + self.sketch.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.wp) - if self.solv.solve() == ResultFlag.OKAY: + if self.sketch.solve() == ResultFlag.OKAY: print("Fuck yeah") - elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: print("Solve_failed - Converge") - elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: print("Solve_failed - Unknowns") - elif self.solv.solve() == ResultFlag.INCONSISTENT: + elif self.sketch.solve() == ResultFlag.INCONSISTENT: print("Solve_failed - Incons") + self.constrain_done.emit() self.main_buffer = [None, None] @@ -373,19 +393,19 @@ class SketchWidget(QWidget): # Contrain point to line if self.main_buffer[1]: - self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + self.sketch.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.wp) - if self.solv.solve() == ResultFlag.OKAY: + if self.sketch.solve() == ResultFlag.OKAY: print("Fuck yeah") self.constrain_done.emit() - elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: print("Solve_failed - Converge") - elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: print("Solve_failed - Unknowns") - elif self.solv.solve() == ResultFlag.INCONSISTENT: + elif self.sketch.solve() == ResultFlag.INCONSISTENT: print("Solve_failed - Incons") self.constrain_done.emit() @@ -404,18 +424,18 @@ class SketchWidget(QWidget): # Contrain point to line if self.main_buffer[1]: - self.solv.midpoint(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane) + self.sketch.midpoint(self.main_buffer[0], self.main_buffer[1], self.sketch.wp) - if self.solv.solve() == ResultFlag.OKAY: + if self.sketch.solve() == ResultFlag.OKAY: print("Fuck yeah") - elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: print("Solve_failed - Converge") - elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: print("Solve_failed - Unknowns") - elif self.solv.solve() == ResultFlag.INCONSISTENT: + elif self.sketch.solve() == ResultFlag.INCONSISTENT: print("Solve_failed - Incons") self.constrain_done.emit() @@ -426,36 +446,36 @@ class SketchWidget(QWidget): line_selected = self.get_line_handle_from_ui_point(local_event_pos) if line_selected: - self.solv.horizontal(line_selected, self.sketch.working_plane) + self.sketch.horizontal(line_selected, self.sketch.wp) - if self.solv.solve() == ResultFlag.OKAY: + if self.sketch.solve() == ResultFlag.OKAY: print("Fuck yeah") - elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: print("Solve_failed - Converge") - elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: print("Solve_failed - Unknowns") - elif self.solv.solve() == ResultFlag.INCONSISTENT: + elif self.sketch.solve() == ResultFlag.INCONSISTENT: print("Solve_failed - Incons") if event.button() == Qt.LeftButton and self.mouse_mode == "vert": line_selected = self.get_line_handle_from_ui_point(local_event_pos) if line_selected: - self.solv.vertical(line_selected, self.sketch.working_plane) + self.sketch.vertical(line_selected, self.sketch.wp) - if self.solv.solve() == ResultFlag.OKAY: + if self.sketch.solve() == ResultFlag.OKAY: print("Fuck yeah") - elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: print("Solve_failed - Converge") - elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: print("Solve_failed - Unknowns") - elif self.solv.solve() == ResultFlag.INCONSISTENT: + elif self.sketch.solve() == ResultFlag.INCONSISTENT: print("Solve_failed - Incons") if event.button() == Qt.LeftButton and self.mouse_mode == "distance": @@ -485,18 +505,18 @@ class SketchWidget(QWidget): if e1 and e2: # Ask fo the dimension and solve if both elements are present length, ok = QInputDialog.getDouble(self, 'Distance', 'Enter a mm value:', value=100, decimals=2) - self.solv.distance(e1, e2, length, self.sketch.working_plane) + self.sketch.distance(e1, e2, length, self.sketch.wp) - if self.solv.solve() == ResultFlag.OKAY: + if self.sketch.solve() == ResultFlag.OKAY: print("Fuck yeah") - elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE: + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: print("Solve_failed - Converge") - elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: print("Solve_failed - Unknowns") - elif self.solv.solve() == ResultFlag.INCONSISTENT: + elif self.sketch.solve() == ResultFlag.INCONSISTENT: print("Solve_failed - Incons") self.constrain_done.emit() @@ -661,33 +681,53 @@ class SketchWidget(QWidget): # Set the transform to the painter painter.setTransform(transform) - pen = QPen(Qt.gray) - pen.setWidthF(2 / self.zoom) - painter.setPen(pen) + pen_normal = QPen(Qt.gray) + pen_normal.setWidthF(2 / self.zoom) + + pen_construct = QPen(Qt.blue) + pen_construct.setStyle(Qt.PenStyle.DashLine) + pen_construct.setWidthF(2 / self.zoom) + + pen_solver = QPen(Qt.green) + pen_solver.setWidthF(2 / self.zoom) # Draw points if self.sketch: + painter.setPen(pen_normal) for point in self.sketch.points: - painter.drawEllipse(point.ui_point, 3 / self.zoom, 3 / self.zoom) + if point.is_helper: + painter.setPen(pen_construct) + painter.drawEllipse(point.ui_point, 10 / self.zoom, 10 / self.zoom) + else: + #Normal point + painter.setPen(pen_normal) + painter.drawEllipse(point.ui_point, 3 / self.zoom, 3 / self.zoom) + for line in self.sketch.lines: - p1 = line.crd1.ui_point - p2 = line.crd2.ui_point - painter.drawLine(p1, p2) + if line.is_helper: + painter.setPen(pen_construct) + p1 = line.crd1.ui_point + p2 = line.crd2.ui_point + painter.drawLine(p1, p2) + else: + painter.setPen(pen_normal) + p1 = line.crd1.ui_point + p2 = line.crd2.ui_point + painter.drawLine(p1, p2) - dis = self.distance(p1, p2) - mid = self.calculate_midpoint(p1, p2) - painter.drawText(mid, str(round(dis, 2))) + dis = self.distance(p1, p2) + mid = self.calculate_midpoint(p1, p2) + painter.drawText(mid, str(round(dis, 2))) - pen = QPen(Qt.green) - pen.setWidthF(2 / self.zoom) - painter.setPen(pen) - if self.solv.entity_len(): - for i in range(self.solv.entity_len()): - entity = self.solv.entity(i) - if entity.is_point_2d() and self.solv.params(entity.params): - x, y = self.solv.params(entity.params) + # Draw all solver points + if self.sketch.entity_len(): + painter.setPen(pen_solver) + for i in range(self.sketch.entity_len()): + entity = self.sketch.entity(i) + if entity.is_point_2d() and self.sketch.params(entity.params): + x, y = self.sketch.params(entity.params) point = QPointF(x, y) painter.drawEllipse(point, 6 / self.zoom, 6 / self.zoom) @@ -723,149 +763,30 @@ class SketchWidget(QWidget): return self.width() / self.height() * (1.0 / abs(self.zoom)) -class Point2D_ALT: - """Improved oop aaproach?""" - def __init__(self): - self.ui_point = None - self.solve_handle_nr = None - self.solve_handle = None - self.part_of_entity = None - - def to_quadrant_coords(self, point): - """Translate linear coordinates to quadrant coordinates.""" - center_x = self.width() // 2 - center_y = self.height() // 2 - quadrant_x = point.x() - center_x - quadrant_y = center_y - point.y() # Note the change here - - return QPoint(quadrant_x, quadrant_y) / self.zoom - - def from_quadrant_coords(self, point: QPoint): - """Translate quadrant coordinates to linear coordinates.""" - center_x = self.width() // 2 - center_y = self.height() // 2 - widget_x = center_x + point.x() * self.zoom - widget_y = center_y - point.y() * self.zoom # Note the subtraction here - - return QPoint(int(widget_x), int(widget_y)) - - def from_quadrant_coords_no_center(self, point): - """Invert Y Coordinate for mesh""" - center_x = 0 - center_y = 0 - widget_x = point.x() - widget_y = -point.y() - - return QPoint(int(widget_x), int(widget_y)) - - def get_handle_nr(self, input_str: str) -> int: - # Define the regex pattern to extract the handle number - pattern = r"handle=(\d+)" - - # Use re.search to find the handle number in the string - match = re.search(pattern, input_str) - - if match: - handle_number = int(match.group(1)) - print(f"Handle number: {handle_number}") - return int(handle_number) - - else: - print("Handle number not found.") - return 0 - - def get_keys(self, d: dict, target: QPoint) -> list: - result = [] - path = [] - print(d) - print(target) - for k, v in d.items(): - path.append(k) - if isinstance(v, dict): - self.get_keys(v, target) - if v == target: - result.append(copy(path)) - path.pop() - - return result - - def get_handle_from_ui_point(self, ui_point: QPoint): - """Input QPoint and you shall reveive a slvs entity handle!""" - for point in self.sketch.slv_points: - if ui_point == point['ui_point']: - slv_handle = point['solv_handle'] - - return slv_handle - - def get_line_handle_from_ui_point(self, ui_point: QPoint): - """Input Qpoint that is on a line and you shall receive the handle of the line!""" - for target_line_con in self.sketch.slv_lines: - if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): - slv_handle = target_line_con['solv_handle'] - - return slv_handle - - def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: - """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" - for target_line_con in self.sketch.slv_lines: - if self.is_point_on_line(ui_point, target_line_con['ui_points'][0], target_line_con['ui_points'][1]): - lines_to_cons = target_line_con['solv_entity_points'] - - return lines_to_cons - - def distance(self, p1, p2): - return math.sqrt((p1.x() - p2.x())**2 + (p1.y() - p2.y())**2) - - def calculate_midpoint(self, point1, point2): - mx = (point1.x() + point2.x()) // 2 - my = (point1.y() + point2.y()) // 2 - return QPoint(mx, my) - - def is_point_on_line(self, p, p1, p2, tolerance=5): - # Calculate the lengths of the sides of the triangle - a = self.distance(p, p1) - b = self.distance(p, p2) - c = self.distance(p1, p2) - - # Calculate the semi-perimeter - s = (a + b + c) / 2 - - # Calculate the area using Heron's formula - area = math.sqrt(s * (s - a) * (s - b) * (s - c)) - - # Calculate the height (perpendicular distance from the point to the line) - if c > 0: - height = (2 * area) / c - # Check if the height is within the tolerance distance to the line - if height > tolerance: - return False - - # Check if the projection of the point onto the line is within the line segment - dot_product = ((p.x() - p1.x()) * (p2.x() - p1.x()) + (p.y() - p1.y()) * (p2.y() - p1.y())) / (c ** 2) - - return 0 <= dot_product <= 1 - else: - return None - - def viewport_to_local_coord(self, qt_pos : QPoint) -> QPoint: - return QPoint(self.to_quadrant_coords(qt_pos)) - - - +### GEOMETRY CLASSES class Point2D: def __init__(self, x, y): + self.id = None self.ui_x: int = x self.ui_y: int = y self.ui_point = QPoint(self.ui_x, self.ui_y) self.handle = None self.handle_nr: int = None + # Construction Geometry + self.is_helper: bool = False + class Line2D: def __init__(self, point_s: Point2D, point_e: Point2D): + self.id = None + self.crd1: Point2D = point_s self.crd2: Point2D = point_e self.handle = None - self.handle_nr = None + self.handle_nr: int = None + + # Construction Geometry + self.is_helper: bool = False class Sketch2d(SolverSystem): """ @@ -873,13 +794,16 @@ class Sketch2d(SolverSystem): """ def __init__(self): + self.id = uuid.uuid1() + self.wp = self.create_2d_base() self.points = [] self.lines = [] + self.origin = [0,0,0] def add_point(self, point: Point2D): """ - Adds a point into the solversystem and reurns the handle. + Adds a point into the solversystem and returns the handle. Appends the added point to the points list. :param point: 2D point in Point2D class format :return: @@ -887,6 +811,7 @@ class Sketch2d(SolverSystem): point.handle = self.add_point_2d(point.ui_x, point.ui_y, self.wp) point.handle_nr = self.get_handle_nr(str(point.handle)) + point.id = uuid.uuid1() self.points.append(point) @@ -899,6 +824,8 @@ class Sketch2d(SolverSystem): :return: """ + line.id = uuid.uuid1() + line.handle = self.add_line_2d(line.crd1.handle, line.crd2.handle, self.wp) line.handle_nr = self.get_handle_nr(str(line.handle)) diff --git a/main.py b/main.py index aa12c1f..b5e25da 100644 --- a/main.py +++ b/main.py @@ -49,7 +49,6 @@ class ExtrudeDialog(QDialog): self.rounded_checkbox = QCheckBox('Round Edges') self.seperator = create_hline() - # OK and Cancel buttons button_layout = QHBoxLayout() ok_button = QPushButton('OK') @@ -263,14 +262,12 @@ class MainWindow(QMainWindow): sketch = Sketch() sketch.id = name sketch.origin = [0,0,0] - sketch.slv_points = [] - sketch.slv_lines = [] - sketch.proj_points = [] - sketch.proj_lines = [] + self.sketchWidget.reset_buffers() - self.sketchWidget.set_sketch(sketch) + self.sketchWidget.create_sketch(sketch) def add_new_sketch_wp(self): + ## Sketch projected from 3d view into 2d name = f"sketches-{str(names.get_first_name())}" sketch = Sketch() sketch.id = name @@ -280,11 +277,13 @@ class MainWindow(QMainWindow): sketch.slv_lines = [] sketch.proj_points = self.custom_3D_Widget.project_tosketch_points sketch.proj_lines = self.custom_3D_Widget.project_tosketch_lines + self.sketchWidget.reset_buffers() - self.sketchWidget.set_sketch(sketch) + self.sketchWidget.create_sketch(sketch) self.sketchWidget.create_workplane_projected() - self.sketchWidget.convert_proj_points() - self.sketchWidget.convert_proj_lines() + if not sketch.proj_lines: + self.sketchWidget.convert_proj_points(sketch.proj_points) + self.sketchWidget.convert_proj_lines(sketch.proj_lines) self.sketchWidget.update() # CLear all selections after it has been projected @@ -295,15 +294,24 @@ class MainWindow(QMainWindow): def add_sketch(self): """ - :return: """ - sketch = self.sketchWidget.get_sketch() - sketch.convert_points_for_sdf() + sketch = Sketch() + sketch_from_widget = self.sketchWidget.get_sketch() + points = sketch_from_widget.points + sketch.convert_points_for_sdf(points) + sketch.id = sketch_from_widget.id + + sketch.filter_lines_for_interactor(sketch_from_widget.lines) + + # Register sketch to timeline self.project.timeline[-1].sketches[sketch.id] = sketch + # Add Item to slection menu self.ui.sketch_list.addItem(sketch.id) + + # Deactivate drawing self.ui.pb_linetool.setChecked(False) self.sketchWidget.line_mode = False @@ -387,7 +395,9 @@ class MainWindow(QMainWindow): name = selected.text() # TODO: add selected element from timeline sel_compo = self.project.timeline[-1] + print(sel_compo) sketch = sel_compo.sketches[name] + print(sketch) points = sketch.sdf_points if points[-1] == points[0]: @@ -428,7 +438,7 @@ class MainWindow(QMainWindow): ### Interactor interactor = Interactor() - interactor.add_lines_for_interactor(sketch.slv_lines) + interactor.add_lines_for_interactor(sketch.interactor_lines) if not invert: edges = interactor_mesh.generate_mesh(interactor.lines, 0, length) @@ -564,6 +574,8 @@ class Sketch: sdf_points: list = None + interactor_lines: list = None + # Points coming back from the 3D-Widget as projection to draw on proj_points: list = None proj_lines: list = None @@ -623,13 +635,24 @@ class Sketch: print("p2", p2) return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) - def convert_points_for_sdf(self): + def convert_points_for_sdf(self, points): points_for_sdf = [] - for point_to_poly in self.slv_points: - points_for_sdf.append(self.translate_points_tup(point_to_poly['ui_point'])) + for point in points: + if point.is_helper is False: + print("point", point) + points_for_sdf.append(self.translate_points_tup(point.ui_point)) self.sdf_points = points_for_sdf + def filter_lines_for_interactor(self, lines): + ### Filter lines that are not meant to be drawn for the interactor like contruction lines + filtered_lines = [] + for line in lines: + if not line.is_helper: + filtered_lines.append(line) + + self.interactor_lines = filtered_lines + def extrude(self, height: float, symet: bool = True, invert: bool = False, offset_length: float = None): """ Extrude a 2D shape into 3D, orient it along the normal, and position it relative to the centroid. @@ -677,7 +700,6 @@ class Sketch: return f - @dataclass class Interactor: """Helper mesh consisting of edges for selection""" @@ -708,12 +730,13 @@ class Interactor: return translation_along_normal def add_lines_for_interactor(self, input_lines: list): - """Expects slvs_lines main list""" + """Takes Line2D objects from the sketch widget and preparesit for interactor mesh. + Translates coordinates.""" + points_for_interact = [] for point_to_poly in input_lines: - start, end = point_to_poly['ui_points'] - from_coord_start = window.sketchWidget.from_quadrant_coords_no_center(start) - from_coord_end = window.sketchWidget.from_quadrant_coords_no_center(end) + from_coord_start = window.sketchWidget.from_quadrant_coords_no_center(point_to_poly.crd1.ui_point) + from_coord_end = window.sketchWidget.from_quadrant_coords_no_center(point_to_poly.crd2.ui_point) start_draw = self.translate_points_tup(from_coord_start) end_draw = self.translate_points_tup(from_coord_end) line = start_draw, end_draw @@ -723,7 +746,6 @@ class Interactor: self.lines = points_for_interact - @dataclass class Body: """The actual body as sdf3 object""" @@ -743,7 +765,6 @@ class Body: return f - class Output: def export_mesh(self, sdf_object): """FINAL EXPORT""" @@ -763,13 +784,11 @@ class Output: except Exception as e: print("Error executing code:", e) - class Project: """Project -> Timeline -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" timeline: Timeline = None assembly: Assembly = None - if __name__ == "__main__": app = QApplication([]) window = MainWindow() From d75d59f3118d2eee4cbc342a0d7b0357cf9197a6 Mon Sep 17 00:00:00 2001 From: bklronin Date: Mon, 30 Dec 2024 20:12:46 +0100 Subject: [PATCH 09/16] - Sketch projection partly works again :) --- drawing_modules/draw_widget_solve.py | 6 +-- drawing_modules/vtk_widget.py | 62 ++++++++++++++++++++++++++-- main.py | 10 +++-- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/drawing_modules/draw_widget_solve.py b/drawing_modules/draw_widget_solve.py index 2b8cbcb..834a0ed 100644 --- a/drawing_modules/draw_widget_solve.py +++ b/drawing_modules/draw_widget_solve.py @@ -684,9 +684,9 @@ class SketchWidget(QWidget): pen_normal = QPen(Qt.gray) pen_normal.setWidthF(2 / self.zoom) - pen_construct = QPen(Qt.blue) - pen_construct.setStyle(Qt.PenStyle.DashLine) - pen_construct.setWidthF(2 / self.zoom) + pen_construct = QPen(Qt.cyan) + pen_construct.setStyle(Qt.PenStyle.DotLine) + pen_construct.setWidthF(1 / self.zoom) pen_solver = QPen(Qt.green) pen_solver.setWidthF(2 / self.zoom) diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 5d4cc86..2c3a4da 100644 --- a/drawing_modules/vtk_widget.py +++ b/drawing_modules/vtk_widget.py @@ -436,8 +436,60 @@ class VTKWidget(QtWidgets.QWidget): return xy_coordinates - def compute_2d_coordinates_line(self, line_source, normal): + def compute_2d_coordinates_line(self, projected_mesh, normal): + # Normalize the normal vector + normal = np.array(normal) + normal = normal / np.linalg.norm(normal) + + # Create a vtkTransform + transform = vtk.vtkTransform() + transform.PostMultiply() # This ensures transforms are applied in the order we specify + + # Rotate so that the normal aligns with the Z-axis + rotation_axis = np.cross(normal, [0, 0, 1]) + angle = np.arccos(np.dot(normal, [0, 0, 1])) * 180 / np.pi # Convert to degrees + + if np.linalg.norm(rotation_axis) > 1e-6: # Check if rotation is needed + transform.RotateWXYZ(angle, rotation_axis[0], rotation_axis[1], rotation_axis[2]) + + # Get the transformation matrix + matrix = transform.GetMatrix() + self.local_matrix = [matrix.GetElement(i, j) for i in range(4) for j in range(4)] + + # Apply the transform to the polydata + transformFilter = vtk.vtkTransformPolyDataFilter() + transformFilter.SetInputData(projected_mesh) + transformFilter.SetTransform(transform) + transformFilter.Update() + + # Get the transformed points + transformed_polydata = transformFilter.GetOutput() + points = transformed_polydata.GetPoints() + lines = transformed_polydata.GetLines() + + # Extract 2D coordinates + xy_coordinates = [] + + if points and lines: + points_data = points.GetData() + line_ids = vtk.vtkIdList() + + # Loop through all the lines in the vtkCellArray + lines.InitTraversal() + while lines.GetNextCell(line_ids): + line_coordinates = [] + for j in range(line_ids.GetNumberOfIds()): + point_id = line_ids.GetId(j) + point = points.GetPoint(point_id) + line_coordinates.append((point[0], point[1])) # Only take x, y + xy_coordinates.append(line_coordinates) + + return xy_coordinates + + + def compute_2d_coordinates_line_bak(self, line_source, normal): # Ensure the input is a vtkLineSource + print("line", line_source) if not isinstance(line_source, vtk.vtkLineSource): raise ValueError("Input must be a vtkLineSource") @@ -702,12 +754,16 @@ class VTKWidget(QtWidgets.QWidget): # Extract 2D coordinates self.project_tosketch_points = self.compute_2d_coordinates(projected_polydata, self.selected_normal) - # Seperately rotate selected edges for drawing + # Green indicator mesh needs to be translated to xy point paris start end. + self.project_tosketch_lines = self.compute_2d_coordinates_line(projected_polydata, self.selected_normal) + + print("result", self.project_tosketch_lines) + """# Seperately rotate selected edges for drawing self.project_tosketch_lines.clear() for vtk_line in self.selected_vtk_line: proj_vtk_line = self.compute_2d_coordinates_line(vtk_line, self.selected_normal) self.project_tosketch_lines.append(proj_vtk_line) - print("outgoing lines", self.project_tosketch_lines) + print("outgoing lines", self.project_tosketch_lines)""" # Create a mapper and actor for the projected data mapper = vtk.vtkPolyDataMapper() diff --git a/main.py b/main.py index b5e25da..f703ed2 100644 --- a/main.py +++ b/main.py @@ -393,11 +393,12 @@ class MainWindow(QMainWindow): selected = self.ui.sketch_list.currentItem() name = selected.text() + # TODO: add selected element from timeline sel_compo = self.project.timeline[-1] - print(sel_compo) + #print(sel_compo) sketch = sel_compo.sketches[name] - print(sketch) + #print(sketch) points = sketch.sdf_points if points[-1] == points[0]: @@ -407,10 +408,10 @@ class MainWindow(QMainWindow): dialog = ExtrudeDialog(self) if dialog.exec(): length, is_symmetric, invert, cut, union_with, rounded = dialog.get_values() - print(f"Extrude length: {length}, Symmetric: {is_symmetric} Invert: {invert}") + #print(f"Extrude length: {length}, Symmetric: {is_symmetric} Invert: {invert}") else: length = 0 - print("Extrude cancelled") + #print("Extrude cancelled") normal = self.custom_3D_Widget.selected_normal #print("Normie enter", normal) @@ -429,6 +430,7 @@ class MainWindow(QMainWindow): f = sketch.extrude(length, is_symmetric, invert, 0) + # Create body element and assign known stuff name_op = f"extrd-{name}" sel_compo.body body = Body() From 6ef88925b187d0a8d1a4d3c88223495e06d4572f Mon Sep 17 00:00:00 2001 From: bklronin Date: Tue, 31 Dec 2024 00:33:30 +0100 Subject: [PATCH 10/16] - Added new componnt controls --- Gui.py | 589 ++++++++++++++++++++++------------------ doc/commands.md | 1 + gui.ui | 706 ++++++++++++++++++++++++++++-------------------- main.py | 58 ++-- 4 files changed, 775 insertions(+), 579 deletions(-) create mode 100644 doc/commands.md diff --git a/Gui.py b/Gui.py index 0fbe5b0..7a03417 100644 --- a/Gui.py +++ b/Gui.py @@ -25,7 +25,7 @@ class Ui_fluencyCAD(object): def setupUi(self, fluencyCAD): if not fluencyCAD.objectName(): fluencyCAD.setObjectName(u"fluencyCAD") - fluencyCAD.resize(2192, 957) + fluencyCAD.resize(2192, 1073) self.actionNew_Project = QAction(fluencyCAD) self.actionNew_Project.setObjectName(u"actionNew_Project") self.actionLoad_Project = QAction(fluencyCAD) @@ -36,204 +36,39 @@ class Ui_fluencyCAD(object): self.centralwidget.setObjectName(u"centralwidget") self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setObjectName(u"gridLayout") - self.gl_box = QGroupBox(self.centralwidget) - self.gl_box.setObjectName(u"gl_box") - sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + 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.new_compo = QPushButton(self.compo_tool_box) + self.new_compo.setObjectName(u"new_compo") + self.new_compo.setMinimumSize(QSize(50, 50)) + self.new_compo.setMaximumSize(QSize(50, 50)) + + self.gridLayout_9.addWidget(self.new_compo, 0, 0, 1, 1) + + self.del_compo = QPushButton(self.compo_tool_box) + self.del_compo.setObjectName(u"del_compo") + self.del_compo.setEnabled(True) + sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(4) - sizePolicy.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth()) - self.gl_box.setSizePolicy(sizePolicy) - 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) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.del_compo.sizePolicy().hasHeightForWidth()) + self.del_compo.setSizePolicy(sizePolicy) + self.del_compo.setMinimumSize(QSize(50, 50)) + self.del_compo.setMaximumSize(QSize(50, 50)) + self.del_compo.setLayoutDirection(Qt.LeftToRight) - self.gridLayout.addWidget(self.gl_box, 0, 3, 7, 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.pushButton_2 = QPushButton(self.groupBox_4) - self.pushButton_2.setObjectName(u"pushButton_2") - - self.verticalLayout_2.addWidget(self.pushButton_2) + self.gridLayout_9.addWidget(self.del_compo, 0, 1, 1, 1) - self.gridLayout.addWidget(self.groupBox_4, 12, 4, 1, 1) - - self.InputTab = QTabWidget(self.centralwidget) - self.InputTab.setObjectName(u"InputTab") - sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth()) - self.InputTab.setSizePolicy(sizePolicy1) - 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, 2, 7, 1) - - self.timeline_box = QGroupBox(self.centralwidget) - self.timeline_box.setObjectName(u"timeline_box") - - self.gridLayout.addWidget(self.timeline_box, 12, 2, 1, 2) - - 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.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, Qt.AlignTop) - - self.groupBox_9 = QGroupBox(self.centralwidget) - self.groupBox_9.setObjectName(u"groupBox_9") - self.groupBox_9.setMaximumSize(QSize(200, 16777215)) - self.gridLayout_7 = QGridLayout(self.groupBox_9) - self.gridLayout_7.setObjectName(u"gridLayout_7") - self.pb_origin_wp = QPushButton(self.groupBox_9) - self.pb_origin_wp.setObjectName(u"pb_origin_wp") - - self.gridLayout_7.addWidget(self.pb_origin_wp, 0, 0, 1, 1) - - self.pb_origin_face = QPushButton(self.groupBox_9) - self.pb_origin_face.setObjectName(u"pb_origin_face") - - self.gridLayout_7.addWidget(self.pb_origin_face, 0, 1, 1, 1) - - self.pb_flip_face = QPushButton(self.groupBox_9) - self.pb_flip_face.setObjectName(u"pb_flip_face") - - self.gridLayout_7.addWidget(self.pb_flip_face, 1, 0, 1, 1) - - self.pb_move_wp = QPushButton(self.groupBox_9) - self.pb_move_wp.setObjectName(u"pb_move_wp") - - self.gridLayout_7.addWidget(self.pb_move_wp, 1, 1, 1, 1) - - - self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1, Qt.AlignTop) - - self.groupBox_2 = QGroupBox(self.centralwidget) - self.groupBox_2.setObjectName(u"groupBox_2") - sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) - self.groupBox_2.setSizePolicy(sizePolicy2) - self.groupBox_2.setMaximumSize(QSize(200, 16777215)) - self.gridLayout_2 = QGridLayout(self.groupBox_2) - self.gridLayout_2.setObjectName(u"gridLayout_2") - 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, 1, 1, 1, 1, Qt.AlignTop) - - 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, 2, 0, 1, 1, Qt.AlignTop) - - 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, 2, 1, 1, 1, Qt.AlignTop) - - 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, 1, 0, 1, 1) - - - self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1) + self.gridLayout.addWidget(self.compo_tool_box, 11, 1, 1, 1, Qt.AlignLeft) self.groupBox_3 = QGroupBox(self.centralwidget) self.groupBox_3.setObjectName(u"groupBox_3") - sizePolicy2.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) - self.groupBox_3.setSizePolicy(sizePolicy2) + 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") @@ -295,32 +130,84 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1) + self.InputTab = QTabWidget(self.centralwidget) + self.InputTab.setObjectName(u"InputTab") + sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth()) + self.InputTab.setSizePolicy(sizePolicy1) + 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, 7, 1) + self.groupBox_11 = QGroupBox(self.centralwidget) self.groupBox_11.setObjectName(u"groupBox_11") - sizePolicy3 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) - sizePolicy3.setHorizontalStretch(0) - sizePolicy3.setVerticalStretch(0) - sizePolicy3.setHeightForWidth(self.groupBox_11.sizePolicy().hasHeightForWidth()) - self.groupBox_11.setSizePolicy(sizePolicy3) + sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.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") - sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - sizePolicy4.setHorizontalStretch(0) - sizePolicy4.setVerticalStretch(0) - sizePolicy4.setHeightForWidth(self.sketch_list.sizePolicy().hasHeightForWidth()) - self.sketch_list.setSizePolicy(sizePolicy4) + sizePolicy3 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.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") - sizePolicy2.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) - self.groupBox_6.setSizePolicy(sizePolicy2) + 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) @@ -345,10 +232,72 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.groupBox_11, 5, 0, 2, 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.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, 3, 1, 1, Qt.AlignTop) + + 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, 7, 1, 1, 2) + + self.gl_box = QGroupBox(self.centralwidget) + self.gl_box.setObjectName(u"gl_box") + sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy4.setHorizontalStretch(0) + sizePolicy4.setVerticalStretch(4) + sizePolicy4.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth()) + self.gl_box.setSizePolicy(sizePolicy4) + 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, 7, 1) + self.groupBox_10 = QGroupBox(self.centralwidget) self.groupBox_10.setObjectName(u"groupBox_10") - sizePolicy3.setHeightForWidth(self.groupBox_10.sizePolicy().hasHeightForWidth()) - self.groupBox_10.setSizePolicy(sizePolicy3) + 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") @@ -361,8 +310,8 @@ class Ui_fluencyCAD(object): self.groupBox_8 = QGroupBox(self.groupBox_10) self.groupBox_8.setObjectName(u"groupBox_8") - sizePolicy2.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth()) - self.groupBox_8.setSizePolicy(sizePolicy2) + 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") @@ -386,7 +335,107 @@ class Ui_fluencyCAD(object): self.verticalLayout_6.addWidget(self.groupBox_8) - self.gridLayout.addWidget(self.groupBox_10, 5, 4, 2, 1) + self.gridLayout.addWidget(self.groupBox_10, 5, 3, 2, 1) + + self.groupBox_2 = QGroupBox(self.centralwidget) + self.groupBox_2.setObjectName(u"groupBox_2") + 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.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, 1, 1, 1, 1, Qt.AlignTop) + + 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, 2, 0, 1, 1, Qt.AlignTop) + + 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, 2, 1, 1, 1, Qt.AlignTop) + + 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, 1, 0, 1, 1) + + + self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1) + + self.groupBox_9 = QGroupBox(self.centralwidget) + self.groupBox_9.setObjectName(u"groupBox_9") + self.groupBox_9.setMaximumSize(QSize(200, 16777215)) + self.gridLayout_7 = QGridLayout(self.groupBox_9) + self.gridLayout_7.setObjectName(u"gridLayout_7") + self.pb_origin_wp = QPushButton(self.groupBox_9) + self.pb_origin_wp.setObjectName(u"pb_origin_wp") + + self.gridLayout_7.addWidget(self.pb_origin_wp, 0, 0, 1, 1) + + self.pb_origin_face = QPushButton(self.groupBox_9) + self.pb_origin_face.setObjectName(u"pb_origin_face") + + self.gridLayout_7.addWidget(self.pb_origin_face, 0, 1, 1, 1) + + self.pb_flip_face = QPushButton(self.groupBox_9) + self.pb_flip_face.setObjectName(u"pb_flip_face") + + self.gridLayout_7.addWidget(self.pb_flip_face, 1, 0, 1, 1) + + self.pb_move_wp = QPushButton(self.groupBox_9) + self.pb_move_wp.setObjectName(u"pb_move_wp") + + self.gridLayout_7.addWidget(self.pb_move_wp, 1, 1, 1, 1) + + + self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1, Qt.AlignTop) + + 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.pushButton_2 = QPushButton(self.groupBox_4) + self.pushButton_2.setObjectName(u"pushButton_2") + + self.verticalLayout_2.addWidget(self.pushButton_2) + + + self.gridLayout.addWidget(self.groupBox_4, 7, 3, 1, 1) + + self.assmbly_box = QGroupBox(self.centralwidget) + self.assmbly_box.setObjectName(u"assmbly_box") + self.assmbly_box.setMinimumSize(QSize(0, 50)) + self.gridLayout_10 = QGridLayout(self.assmbly_box) + self.gridLayout_10.setObjectName(u"gridLayout_10") + self.pushButton_3 = QPushButton(self.assmbly_box) + self.pushButton_3.setObjectName(u"pushButton_3") + self.pushButton_3.setMinimumSize(QSize(50, 50)) + self.pushButton_3.setMaximumSize(QSize(50, 50)) + + self.gridLayout_10.addWidget(self.pushButton_3, 0, 0, 1, 1) + + self.pushButton_6 = QPushButton(self.assmbly_box) + self.pushButton_6.setObjectName(u"pushButton_6") + self.pushButton_6.setMinimumSize(QSize(50, 50)) + self.pushButton_6.setMaximumSize(QSize(50, 50)) + + self.gridLayout_10.addWidget(self.pushButton_6, 0, 1, 1, 1) + + + self.gridLayout.addWidget(self.assmbly_box, 11, 2, 1, 1, Qt.AlignLeft) fluencyCAD.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(fluencyCAD) @@ -421,64 +470,9 @@ class Ui_fluencyCAD(object): self.actionNew_Project.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) self.actionLoad_Project.setText(QCoreApplication.translate("fluencyCAD", u"Load", None)) self.actionRecent.setText(QCoreApplication.translate("fluencyCAD", u"Recent", None)) - self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None)) - self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) - self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", 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.timeline_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Timeline", 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.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)) -#endif // QT_CONFIG(tooltip) - self.pb_origin_wp.setText(QCoreApplication.translate("fluencyCAD", u"WP Origin", None)) -#if QT_CONFIG(shortcut) - self.pb_origin_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"W", None)) -#endif // QT_CONFIG(shortcut) -#if QT_CONFIG(tooltip) - self.pb_origin_face.setToolTip(QCoreApplication.translate("fluencyCAD", u"Working Plane >PNMSSorking Plane at 0, 0, 0", None)) +#endif // QT_CONFIG(tooltip) + self.pb_origin_wp.setText(QCoreApplication.translate("fluencyCAD", u"WP Origin", None)) +#if QT_CONFIG(shortcut) + self.pb_origin_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"W", None)) +#endif // QT_CONFIG(shortcut) +#if QT_CONFIG(tooltip) + self.pb_origin_face.setToolTip(QCoreApplication.translate("fluencyCAD", u"Working Plane >PNM Gui.py -g python diff --git a/gui.ui b/gui.ui index 55bb7aa..6c368c7 100644 --- a/gui.ui +++ b/gui.ui @@ -7,7 +7,7 @@ 0 0 2192 - 957 + 1073 @@ -15,312 +15,65 @@ - - - - - 0 - 4 - - - - - 12 - + + + + + 0 + 50 + - Model Viewer + Component Tools - - - -1 - - - 12 - - - - - - - - Export - - - - - - STL - - - - - - - - - - - 0 - 0 - - - - 0 - - - - Sketch - - - - - - Code - - - - - - - - - Executive - - - - - - Load Code - - - - - - - Save code - - - - - - - Apply Code - - - - - - - Delete Code - - - - - - - - - - - - - - Timeline - - - - - - - Modify - - - - - - Rev - - - + - - - Extrd + + + + 50 + 50 + + + + + 50 + 50 + - - - - - Arry + New - - - Cut - - - - - - - Comb - - - - - - - Mve - - - - - - - - - - - 200 - 16777215 - - - - Workplanes - - - - - - <W>orking Plane at 0, 0, 0 - - - WP Origin - - - W - - - - - - - Working Plane >P<rojection at selected edges face - - - WP Face - - - P - - - - - - - Flip >N<ormal of projected mesh. - - - WP Flip - - - N - - - - - - - >M<ove projected mesh workplane - - - WP Mve - - - M - - - - - - - - - - - 0 - 0 - - - - - 200 - 16777215 - - - - Drawing - - - - - - Rctgl - - + + true - - false + + + 0 + 0 + - - - - - - Circle + + + 50 + 50 + - - true + + + 50 + 50 + - - false - - - - - - - Slot - - - true - - - false - - - - - - - Line >S<egment + + Qt::LeftToRight - Line - - - S - - - true - - - false + Del @@ -470,6 +223,72 @@ + + + + + 0 + 0 + + + + 0 + + + + Sketch + + + + + + Code + + + + + + + + + Executive + + + + + + Load Code + + + + + + + Save code + + + + + + + Apply Code + + + + + + + Delete Code + + + + + + + + + + @@ -564,7 +383,97 @@ - + + + + Modify + + + + + + Rev + + + + + + + Extrd + + + + + + + Arry + + + + + + + Cut + + + + + + + Comb + + + + + + + Mve + + + + + + + + + + + 0 + 50 + + + + Components + + + + + + + + 0 + 4 + + + + + 12 + + + + Model Viewer + + + + -1 + + + 12 + + + + + @@ -658,6 +567,221 @@ + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Drawing + + + + + + Rctgl + + + true + + + false + + + + + + + Circle + + + true + + + false + + + + + + + Slot + + + true + + + false + + + + + + + Line >S<egment + + + Line + + + S + + + true + + + false + + + + + + + + + + + 200 + 16777215 + + + + Workplanes + + + + + + <W>orking Plane at 0, 0, 0 + + + WP Origin + + + W + + + + + + + Working Plane >P<rojection at selected edges face + + + WP Face + + + P + + + + + + + Flip >N<ormal of projected mesh. + + + WP Flip + + + N + + + + + + + >M<ove projected mesh workplane + + + WP Mve + + + M + + + + + + + + + + Export + + + + + + STL + + + + + + + + + + + 0 + 50 + + + + Assembly Tools + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + + Cnct + + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + - Cnct + + + + + + diff --git a/main.py b/main.py index f703ed2..489c444 100644 --- a/main.py +++ b/main.py @@ -99,11 +99,11 @@ class MainWindow(QMainWindow): size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.sketchWidget.setSizePolicy(size_policy) - ### Main Model - self.model = { + ### Main Model -OLD ? + """self.model = { 'sketches': {}, 'operation': {}, - } + }""" self.list_selected = [] #self.ui.pb_apply_code.pressed.connect(self.check_current_tab) @@ -144,7 +144,13 @@ class MainWindow(QMainWindow): self.project = Project() self.new_project() - """Project -> Timeline -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" + ### COMPOS + ### COMPOS + + self.ui.new_compo.pressed.connect(self.new_component) + + + """Project -> (Timeline) -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" def on_flip_face(self): self.send_command.emit("flip") @@ -229,33 +235,41 @@ class MainWindow(QMainWindow): self.new_component() def new_component(self): - print("Compo") + print("Creating a new component...") + + # Lazily initialize self.compo_layout if it doesn't exist + if not hasattr(self, 'compo_layout'): + print("Initializing compo_layout...") + self.compo_layout = QHBoxLayout() + + # Ensure the QGroupBox has a layout + if not self.ui.compo_box.layout(): + self.ui.compo_box.setLayout(QVBoxLayout()) # Set a default layout for QGroupBox + + # Add the horizontal layout to the QGroupBox's layout + self.ui.compo_box.layout().addLayout(self.compo_layout) + + # Align the layout to the left + self.compo_layout.setAlignment(Qt.AlignLeft) + + # Create and initialize a new Component compo = Component() - compo.id = "New Compo" + compo.id = f"Component {len(self.project.timeline) + 1}" compo.descript = "Initial Component" compo.sketches = {} compo.body = {} self.project.timeline.append(compo) - # Create a horizontal layout - horizontal_layout = QHBoxLayout() - - # Set the layout for the timeline_box QFrame - self.ui.timeline_box.setLayout(horizontal_layout) - - # Create the button + # Create a button for the new component button = QPushButton() - button.setToolTip(compo.id) button.setText(str(len(self.project.timeline))) + button.setFixedSize(QSize(40, 40)) # Set button size - # Set the button's fixed size - button.setFixedSize(QSize(40, 40)) + # Add the button to the layout + self.compo_layout.addWidget(button) - # Add the button to the horizontal layout - horizontal_layout.addWidget(button) - # Set the alignment of the layout to left - horizontal_layout.setAlignment(Qt.AlignLeft) + print(f"Added component {compo.id} to the layout.") def add_new_sketch_origin(self): name = f"sketches-{str(names.get_first_name())}" @@ -281,8 +295,10 @@ class MainWindow(QMainWindow): self.sketchWidget.reset_buffers() self.sketchWidget.create_sketch(sketch) self.sketchWidget.create_workplane_projected() + if not sketch.proj_lines: self.sketchWidget.convert_proj_points(sketch.proj_points) + self.sketchWidget.convert_proj_lines(sketch.proj_lines) self.sketchWidget.update() @@ -792,7 +808,7 @@ class Project: assembly: Assembly = None if __name__ == "__main__": - app = QApplication([]) + app = QApplication() window = MainWindow() window.show() app.exec() From 8530f6f8b9418379a0948f1bf4ce375a2047b2e8 Mon Sep 17 00:00:00 2001 From: bklronin Date: Tue, 31 Dec 2024 00:36:26 +0100 Subject: [PATCH 11/16] - Added new componnt controls --- Gui.py | 66 ++++++++++++++-------------- gui.ui | 134 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 100 insertions(+), 100 deletions(-) diff --git a/Gui.py b/Gui.py index 7a03417..937d55e 100644 --- a/Gui.py +++ b/Gui.py @@ -36,37 +36,11 @@ class Ui_fluencyCAD(object): self.centralwidget.setObjectName(u"centralwidget") self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setObjectName(u"gridLayout") - 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.new_compo = QPushButton(self.compo_tool_box) - self.new_compo.setObjectName(u"new_compo") - self.new_compo.setMinimumSize(QSize(50, 50)) - self.new_compo.setMaximumSize(QSize(50, 50)) - - self.gridLayout_9.addWidget(self.new_compo, 0, 0, 1, 1) - - self.del_compo = QPushButton(self.compo_tool_box) - self.del_compo.setObjectName(u"del_compo") - self.del_compo.setEnabled(True) + self.groupBox_3 = QGroupBox(self.centralwidget) + self.groupBox_3.setObjectName(u"groupBox_3") sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.del_compo.sizePolicy().hasHeightForWidth()) - self.del_compo.setSizePolicy(sizePolicy) - self.del_compo.setMinimumSize(QSize(50, 50)) - self.del_compo.setMaximumSize(QSize(50, 50)) - self.del_compo.setLayoutDirection(Qt.LeftToRight) - - self.gridLayout_9.addWidget(self.del_compo, 0, 1, 1, 1) - - - self.gridLayout.addWidget(self.compo_tool_box, 11, 1, 1, 1, Qt.AlignLeft) - - self.groupBox_3 = QGroupBox(self.centralwidget) - self.groupBox_3.setObjectName(u"groupBox_3") sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) self.groupBox_3.setSizePolicy(sizePolicy) self.groupBox_3.setMaximumSize(QSize(200, 16777213)) @@ -403,6 +377,32 @@ class Ui_fluencyCAD(object): self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1, Qt.AlignTop) + 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.new_compo = QPushButton(self.compo_tool_box) + self.new_compo.setObjectName(u"new_compo") + self.new_compo.setMinimumSize(QSize(50, 50)) + self.new_compo.setMaximumSize(QSize(50, 50)) + + self.gridLayout_9.addWidget(self.new_compo, 0, 0, 1, 1) + + self.del_compo = QPushButton(self.compo_tool_box) + self.del_compo.setObjectName(u"del_compo") + self.del_compo.setEnabled(True) + sizePolicy.setHeightForWidth(self.del_compo.sizePolicy().hasHeightForWidth()) + self.del_compo.setSizePolicy(sizePolicy) + self.del_compo.setMinimumSize(QSize(50, 50)) + self.del_compo.setMaximumSize(QSize(50, 50)) + self.del_compo.setLayoutDirection(Qt.LeftToRight) + + self.gridLayout_9.addWidget(self.del_compo, 0, 1, 1, 1) + + + self.gridLayout.addWidget(self.compo_tool_box, 7, 0, 1, 1) + self.groupBox_4 = QGroupBox(self.centralwidget) self.groupBox_4.setObjectName(u"groupBox_4") self.verticalLayout_2 = QVBoxLayout(self.groupBox_4) @@ -413,7 +413,7 @@ class Ui_fluencyCAD(object): self.verticalLayout_2.addWidget(self.pushButton_2) - self.gridLayout.addWidget(self.groupBox_4, 7, 3, 1, 1) + self.gridLayout.addWidget(self.groupBox_4, 4, 3, 1, 1) self.assmbly_box = QGroupBox(self.centralwidget) self.assmbly_box.setObjectName(u"assmbly_box") @@ -435,7 +435,7 @@ class Ui_fluencyCAD(object): self.gridLayout_10.addWidget(self.pushButton_6, 0, 1, 1, 1) - self.gridLayout.addWidget(self.assmbly_box, 11, 2, 1, 1, Qt.AlignLeft) + self.gridLayout.addWidget(self.assmbly_box, 7, 3, 1, 1) fluencyCAD.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(fluencyCAD) @@ -470,9 +470,6 @@ class Ui_fluencyCAD(object): self.actionNew_Project.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) self.actionLoad_Project.setText(QCoreApplication.translate("fluencyCAD", u"Load", None)) self.actionRecent.setText(QCoreApplication.translate("fluencyCAD", u"Recent", None)) - self.compo_tool_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Tools", None)) - self.new_compo.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) - self.del_compo.setText(QCoreApplication.translate("fluencyCAD", u"Del", None)) self.groupBox_3.setTitle(QCoreApplication.translate("fluencyCAD", u"Constrain", None)) #if QT_CONFIG(tooltip) self.pb_con_line.setToolTip(QCoreApplication.translate("fluencyCAD", u"Point to Line Constrain", None)) @@ -569,6 +566,9 @@ class Ui_fluencyCAD(object): #if QT_CONFIG(shortcut) self.pb_move_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"M", None)) #endif // QT_CONFIG(shortcut) + self.compo_tool_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Tools", None)) + self.new_compo.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) + self.del_compo.setText(QCoreApplication.translate("fluencyCAD", u"Del", None)) self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None)) self.assmbly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly Tools", None)) diff --git a/gui.ui b/gui.ui index 6c368c7..ac3e32a 100644 --- a/gui.ui +++ b/gui.ui @@ -15,71 +15,6 @@ - - - - - 0 - 50 - - - - Component Tools - - - - - - - 50 - 50 - - - - - 50 - 50 - - - - New - - - - - - - true - - - - 0 - 0 - - - - - 50 - 50 - - - - - 50 - 50 - - - - Qt::LeftToRight - - - Del - - - - - - @@ -713,7 +648,72 @@ - + + + + + 0 + 50 + + + + Component Tools + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + New + + + + + + + true + + + + 0 + 0 + + + + + 50 + 50 + + + + + 50 + 50 + + + + Qt::LeftToRight + + + Del + + + + + + + Export @@ -729,7 +729,7 @@ - + From 6c8462a7f3c32de17eb1202df3dafe8f70385eb4 Mon Sep 17 00:00:00 2001 From: bklronin Date: Tue, 31 Dec 2024 14:34:41 +0100 Subject: [PATCH 12/16] - changing compos for sketches works --- main.py | 347 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 189 insertions(+), 158 deletions(-) diff --git a/main.py b/main.py index 489c444..a7dcc22 100644 --- a/main.py +++ b/main.py @@ -114,7 +114,7 @@ class MainWindow(QMainWindow): self.ui.pb_origin_wp.pressed.connect(self.add_new_sketch_origin) self.ui.pb_origin_face.pressed.connect(self.add_new_sketch_wp) - self.ui.pb_nw_sktch.pressed.connect(self.add_sketch) + self.ui.pb_nw_sktch.pressed.connect(self.add_sketch_to_compo) self.ui.pb_del_sketch.pressed.connect(self.del_sketch) self.ui.pb_edt_sktch.pressed.connect(self.edit_sketch) @@ -149,9 +149,194 @@ class MainWindow(QMainWindow): self.ui.new_compo.pressed.connect(self.new_component) - """Project -> (Timeline) -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" + def new_project(self): + print("New project") + timeline = [] + self.project.timeline = timeline + self.new_component() + + def new_component(self): + print("Creating a new component...") + + # Lazily initialize self.compo_layout if it doesn't exist + if not hasattr(self, 'compo_layout'): + print("Initializing compo_layout...") + self.compo_layout = QHBoxLayout() + + # Ensure the QGroupBox has a layout + if not self.ui.compo_box.layout(): + self.ui.compo_box.setLayout(QVBoxLayout()) # Set a default layout for QGroupBox + + # Add the horizontal layout to the QGroupBox's layout + self.ui.compo_box.layout().addLayout(self.compo_layout) + + # Align the layout to the left + self.compo_layout.setAlignment(Qt.AlignLeft) + + # Create and initialize a new Component + compo = Component() + compo.id = f"Component {len(self.project.timeline) + 1}" + compo.descript = "Initial Component" + compo.sketches = {} + compo.body = {} + self.project.timeline.append(compo) + + # Create a button for the new component + button = QPushButton() + button.setToolTip(compo.id) + button.setText(str(len(self.project.timeline))) + button.setFixedSize(QSize(40, 40)) # Set button size + button.setCheckable(True) + button.setAutoExclusive(True) + button.setChecked(False) + button.released.connect(self.on_compo_change) + + # Add the button to the layout + self.compo_layout.addWidget(button) + + print(f"Added component {compo.id} to the layout.") + + def get_activated_compo(self): + # Iterate through all items in the layout + total_elements = self.compo_layout.count() + print(total_elements) + for i in range(total_elements): + widget = self.compo_layout.itemAt(i).widget() # Get the widget at the index + if widget: # Check if the widget is not None + if isinstance(widget, QPushButton) and widget.isCheckable(): + state = widget.isChecked() # Get the checked state + print(f"{widget.text()} is {'checked' if state else 'unchecked'}.") + if state: + return i + + def add_new_sketch_origin(self): + name = f"sketches-{str(names.get_first_name())}" + sketch = Sketch() + sketch.id = name + sketch.origin = [0,0,0] + + self.sketchWidget.reset_buffers() + self.sketchWidget.create_sketch(sketch) + + def add_new_sketch_wp(self): + ## Sketch projected from 3d view into 2d + name = f"sketches-{str(names.get_first_name())}" + sketch = Sketch() + sketch.id = name + sketch.origin = self.custom_3D_Widget.centroid + sketch.normal = self.custom_3D_Widget.selected_normal + sketch.slv_points = [] + sketch.slv_lines = [] + sketch.proj_points = self.custom_3D_Widget.project_tosketch_points + sketch.proj_lines = self.custom_3D_Widget.project_tosketch_lines + + self.sketchWidget.reset_buffers() + self.sketchWidget.create_sketch(sketch) + self.sketchWidget.create_workplane_projected() + + if not sketch.proj_lines: + self.sketchWidget.convert_proj_points(sketch.proj_points) + + self.sketchWidget.convert_proj_lines(sketch.proj_lines) + self.sketchWidget.update() + + # CLear all selections after it has been projected + self.custom_3D_Widget.project_tosketch_points.clear() + self.custom_3D_Widget.project_tosketch_lines.clear() + self.custom_3D_Widget.clear_actors_projection() + self.custom_3D_Widget.clear_actors_normals() + + def add_sketch_to_compo(self): + """ + Add sketch to component + :return: + """ + sketch = Sketch() + sketch_from_widget = self.sketchWidget.get_sketch() + points = sketch_from_widget.points + + sketch.convert_points_for_sdf(points) + sketch.id = sketch_from_widget.id + + sketch.filter_lines_for_interactor(sketch_from_widget.lines) + + # Register sketch to timeline + ### Add selection compo here + compo_id = self.get_activated_compo() + print("newsketch_name", sketch.id) + self.project.timeline[compo_id].sketches[sketch.id] = sketch + + # Add Item to slection menu + self.ui.sketch_list.addItem(sketch.id) + + # Deactivate drawing + self.ui.pb_linetool.setChecked(False) + self.sketchWidget.line_mode = False + + items = self.ui.sketch_list.findItems(sketch.id, Qt.MatchExactly)[0] + self.ui.sketch_list.setCurrentItem(items) + + def on_compo_change(self): + compo_id = self.get_activated_compo() + if compo_id: + self.ui.sketch_list.clear() + print("id", compo_id) + print("sketch_registry", self.project.timeline[compo_id].sketches) + + for sketch in self.project.timeline[compo_id].sketches: + print(sketch) + self.ui.sketch_list.addItem(sketch) + + def edit_sketch(self): + name = self.ui.sketch_list.currentItem().text() + + selected = self.ui.sketch_list.currentItem() + name = selected.text() + # TODO: add selected element from timeline + sel_compo = self.project.timeline[-1] + sketch = sel_compo.sketches[name] + + self.sketchWidget.set_sketch(sketch) + + self.sketchWidget.update() + + def del_sketch(self): + # Old + print("Deleting") + name = self.ui.sketch_list.currentItem() # Get the current item + + print(self.model) + + if name is not None: + item_name = name.text() + print("obj_name", item_name) + + # Check if the 'sketches' key exists in the model dictionary + if 'sketches' in self.model and item_name in self.model['sketches']: + if self.model['sketches'][item_name]['id'] == item_name: + row = self.ui.sketch_list.row(name) # Get the row of the current item + self.ui.sketch_list.takeItem(row) # Remove the item from the list widget + self.sketchWidget.clear_sketch() + self.model['sketches'].pop(item_name) # Remove the item from the sketches dictionary + print(f"Removed sketches: {item_name}") + + # Check if the 'operation' key exists in the model dictionary + elif 'operation' in self.model and item_name in self.model['operation']: + if self.model['operation'][item_name]['id'] == item_name: + row = self.ui.sketch_list.row(name) # Get the row of the current item + self.ui.sketch_list.takeItem(row) # Remove the item from the list widget + self.sketchWidget.clear_sketch() + self.model['operation'].pop(item_name) # Remove the item from the operation dictionary + print(f"Removed operation: {item_name}") + + else: + print(f"Item '{item_name}' not found in either 'sketches' or 'operation' dictionary.") + else: + print("No item selected.") + + def on_flip_face(self): self.send_command.emit("flip") @@ -228,159 +413,6 @@ class MainWindow(QMainWindow): #self.view_update() print(f"Selected item: {name}") - def new_project(self): - print("New project") - timeline = [] - self.project.timeline = timeline - self.new_component() - - def new_component(self): - print("Creating a new component...") - - # Lazily initialize self.compo_layout if it doesn't exist - if not hasattr(self, 'compo_layout'): - print("Initializing compo_layout...") - self.compo_layout = QHBoxLayout() - - # Ensure the QGroupBox has a layout - if not self.ui.compo_box.layout(): - self.ui.compo_box.setLayout(QVBoxLayout()) # Set a default layout for QGroupBox - - # Add the horizontal layout to the QGroupBox's layout - self.ui.compo_box.layout().addLayout(self.compo_layout) - - # Align the layout to the left - self.compo_layout.setAlignment(Qt.AlignLeft) - - # Create and initialize a new Component - compo = Component() - compo.id = f"Component {len(self.project.timeline) + 1}" - compo.descript = "Initial Component" - compo.sketches = {} - compo.body = {} - self.project.timeline.append(compo) - - # Create a button for the new component - button = QPushButton() - button.setToolTip(compo.id) - button.setText(str(len(self.project.timeline))) - button.setFixedSize(QSize(40, 40)) # Set button size - - # Add the button to the layout - self.compo_layout.addWidget(button) - - print(f"Added component {compo.id} to the layout.") - - def add_new_sketch_origin(self): - name = f"sketches-{str(names.get_first_name())}" - sketch = Sketch() - sketch.id = name - sketch.origin = [0,0,0] - - self.sketchWidget.reset_buffers() - self.sketchWidget.create_sketch(sketch) - - def add_new_sketch_wp(self): - ## Sketch projected from 3d view into 2d - name = f"sketches-{str(names.get_first_name())}" - sketch = Sketch() - sketch.id = name - sketch.origin = self.custom_3D_Widget.centroid - sketch.normal = self.custom_3D_Widget.selected_normal - sketch.slv_points = [] - sketch.slv_lines = [] - sketch.proj_points = self.custom_3D_Widget.project_tosketch_points - sketch.proj_lines = self.custom_3D_Widget.project_tosketch_lines - - self.sketchWidget.reset_buffers() - self.sketchWidget.create_sketch(sketch) - self.sketchWidget.create_workplane_projected() - - if not sketch.proj_lines: - self.sketchWidget.convert_proj_points(sketch.proj_points) - - self.sketchWidget.convert_proj_lines(sketch.proj_lines) - self.sketchWidget.update() - - # CLear all selections after it has been projected - self.custom_3D_Widget.project_tosketch_points.clear() - self.custom_3D_Widget.project_tosketch_lines.clear() - self.custom_3D_Widget.clear_actors_projection() - self.custom_3D_Widget.clear_actors_normals() - - def add_sketch(self): - """ - :return: - """ - sketch = Sketch() - sketch_from_widget = self.sketchWidget.get_sketch() - points = sketch_from_widget.points - - sketch.convert_points_for_sdf(points) - sketch.id = sketch_from_widget.id - - sketch.filter_lines_for_interactor(sketch_from_widget.lines) - - # Register sketch to timeline - self.project.timeline[-1].sketches[sketch.id] = sketch - - # Add Item to slection menu - self.ui.sketch_list.addItem(sketch.id) - - # Deactivate drawing - self.ui.pb_linetool.setChecked(False) - self.sketchWidget.line_mode = False - - items = self.ui.sketch_list.findItems(sketch.id, Qt.MatchExactly)[0] - self.ui.sketch_list.setCurrentItem(items) - - def edit_sketch(self): - name = self.ui.sketch_list.currentItem().text() - - selected = self.ui.sketch_list.currentItem() - name = selected.text() - # TODO: add selected element from timeline - sel_compo = self.project.timeline[-1] - sketch = sel_compo.sketches[name] - - self.sketchWidget.set_sketch(sketch) - - self.sketchWidget.update() - - def del_sketch(self): - # Old - print("Deleting") - name = self.ui.sketch_list.currentItem() # Get the current item - - print(self.model) - - if name is not None: - item_name = name.text() - print("obj_name", item_name) - - # Check if the 'sketches' key exists in the model dictionary - if 'sketches' in self.model and item_name in self.model['sketches']: - if self.model['sketches'][item_name]['id'] == item_name: - row = self.ui.sketch_list.row(name) # Get the row of the current item - self.ui.sketch_list.takeItem(row) # Remove the item from the list widget - self.sketchWidget.clear_sketch() - self.model['sketches'].pop(item_name) # Remove the item from the sketches dictionary - print(f"Removed sketches: {item_name}") - - # Check if the 'operation' key exists in the model dictionary - elif 'operation' in self.model and item_name in self.model['operation']: - if self.model['operation'][item_name]['id'] == item_name: - row = self.ui.sketch_list.row(name) # Get the row of the current item - self.ui.sketch_list.takeItem(row) # Remove the item from the list widget - self.sketchWidget.clear_sketch() - self.model['operation'].pop(item_name) # Remove the item from the operation dictionary - print(f"Removed operation: {item_name}") - - else: - print(f"Item '{item_name}' not found in either 'sketches' or 'operation' dictionary.") - else: - print("No item selected.") - def update_body(self): pass @@ -411,7 +443,7 @@ class MainWindow(QMainWindow): name = selected.text() # TODO: add selected element from timeline - sel_compo = self.project.timeline[-1] + sel_compo = self.project.timeline[self.get_activated_compo()] #print(sel_compo) sketch = sel_compo.sketches[name] #print(sketch) @@ -510,11 +542,10 @@ class MainWindow(QMainWindow): self.custom_3D_Widget.load_stl(file) self.custom_3D_Widget.update() - - @dataclass class Timeline: """Timeline """ + ### Collection of the Components timeline: list = None """add to time, From 601121dc150ca9dc258be27f1d470f7816b5323d Mon Sep 17 00:00:00 2001 From: bklronin Date: Tue, 31 Dec 2024 17:21:56 +0100 Subject: [PATCH 13/16] - changing compos including sketches and bodies --- main.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index a7dcc22..69ee514 100644 --- a/main.py +++ b/main.py @@ -177,10 +177,10 @@ class MainWindow(QMainWindow): # Create and initialize a new Component compo = Component() - compo.id = f"Component {len(self.project.timeline) + 1}" + compo.id = f"Component {len(self.project.timeline)}" compo.descript = "Initial Component" compo.sketches = {} - compo.body = {} + compo.bodies = {} self.project.timeline.append(compo) # Create a button for the new component @@ -201,7 +201,7 @@ class MainWindow(QMainWindow): def get_activated_compo(self): # Iterate through all items in the layout total_elements = self.compo_layout.count() - print(total_elements) + #print(total_elements) for i in range(total_elements): widget = self.compo_layout.itemAt(i).widget() # Get the widget at the index if widget: # Check if the widget is not None @@ -280,8 +280,10 @@ class MainWindow(QMainWindow): def on_compo_change(self): compo_id = self.get_activated_compo() - if compo_id: + if compo_id is not None: self.ui.sketch_list.clear() + self.ui.body_list.clear() + print("id", compo_id) print("sketch_registry", self.project.timeline[compo_id].sketches) @@ -289,6 +291,9 @@ class MainWindow(QMainWindow): print(sketch) self.ui.sketch_list.addItem(sketch) + for body in self.project.timeline[compo_id].bodies: + self.ui.body_list.addItem(body) + def edit_sketch(self): name = self.ui.sketch_list.currentItem().text() @@ -398,7 +403,9 @@ class MainWindow(QMainWindow): def draw_mesh(self): name = self.ui.body_list.currentItem().text() print("selected_for disp", name) - model = self.project.timeline[-1].body[name].sdf_body + + compo_id = self.get_activated_compo() + model = self.project.timeline[compo_id].bodies[name].sdf_body vesta = vesta_mesh model_data = vesta.generate_mesh_from_sdf(model, resolution=64, threshold=0) @@ -442,7 +449,6 @@ class MainWindow(QMainWindow): selected = self.ui.sketch_list.currentItem() name = selected.text() - # TODO: add selected element from timeline sel_compo = self.project.timeline[self.get_activated_compo()] #print(sel_compo) sketch = sel_compo.sketches[name] @@ -480,7 +486,7 @@ class MainWindow(QMainWindow): # Create body element and assign known stuff name_op = f"extrd-{name}" - sel_compo.body + body = Body() body.sketch = sketch #we add the sketches for reference here body.id = name_op @@ -496,7 +502,7 @@ class MainWindow(QMainWindow): edges = interactor_mesh.generate_mesh(interactor.lines, 0, -length) body.interactor = interactor - sel_compo.body[name_op] = body + sel_compo.bodies[name_op] = body offset_vector = interactor.vector_to_centroid(None, centroid, normal) #print("off_ved", offset_vector) @@ -567,7 +573,7 @@ class Component: """ id = None sketches: dict = None - body: dict = None + bodies: dict = None connector = None # Description From f5861b8bd1224e1f8ee0953e7f1af2253feca54a Mon Sep 17 00:00:00 2001 From: bklronin Date: Tue, 31 Dec 2024 23:48:20 +0100 Subject: [PATCH 14/16] - Drawing bodys depending on the selected compo - Cut working - Edit sketch working --- drawing_modules/draw_widget_solve.py | 4 ++ drawing_modules/vtk_widget.py | 7 +++ main.py | 74 +++++++++++++++++++++------- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/drawing_modules/draw_widget_solve.py b/drawing_modules/draw_widget_solve.py index 834a0ed..4a1d8e4 100644 --- a/drawing_modules/draw_widget_solve.py +++ b/drawing_modules/draw_widget_solve.py @@ -37,6 +37,10 @@ class SketchWidget(QWidget): self.sketch.id = sketch_in.id self.sketch.origin = sketch_in.origin + def set_sketch(self, sketch_in): + """Needs to be an already defined Sketch object coming from the widget itself""" + self.sketch = sketch_in + def get_sketch(self): return self.sketch diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 2c3a4da..1d0beb3 100644 --- a/drawing_modules/vtk_widget.py +++ b/drawing_modules/vtk_widget.py @@ -31,6 +31,7 @@ class VTKWidget(QtWidgets.QWidget): self.displayed_normal_actors = [] self.body_actors_orig = [] self.projected_mesh_actors = [] + self.interactor_actors = [] self.flip_toggle = False @@ -273,6 +274,7 @@ class VTKWidget(QtWidgets.QWidget): # Add the actor to the scene self.renderer.AddActor(actor) + self.interactor_actors.append(actor) mapper.Update() self.vtk_widget.GetRenderWindow().Render() @@ -724,6 +726,11 @@ class VTKWidget(QtWidgets.QWidget): for edge_line in self.picked_edge_actors: self.renderer_indicators.RemoveActor(edge_line) + def clear_actors_interactor(self): + ### Clear the outline of the mesh + for interactor in self.interactor_actors: + self.renderer.RemoveActor(interactor) + def compute_projection(self, direction_invert: bool = False): # Compute the normal from the two selected edges ) diff --git a/main.py b/main.py index 69ee514..fc05fd5 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,6 @@ from dataclasses import dataclass, field # main, draw_widget, gl_widget - class ExtrudeDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) @@ -255,6 +254,11 @@ class MainWindow(QMainWindow): """ sketch = Sketch() sketch_from_widget = self.sketchWidget.get_sketch() + + #Save original for editing later + sketch.original_sketch = sketch_from_widget + + #Get parameters points = sketch_from_widget.points sketch.convert_points_for_sdf(points) @@ -279,6 +283,9 @@ class MainWindow(QMainWindow): self.ui.sketch_list.setCurrentItem(items) def on_compo_change(self): + self.custom_3D_Widget.clear_body_actors() + self.custom_3D_Widget.clear_actors_interactor() + compo_id = self.get_activated_compo() if compo_id is not None: self.ui.sketch_list.clear() @@ -294,14 +301,22 @@ class MainWindow(QMainWindow): for body in self.project.timeline[compo_id].bodies: self.ui.body_list.addItem(body) - def edit_sketch(self): - name = self.ui.sketch_list.currentItem().text() + item = self.ui.body_list.findItems(body , Qt.MatchExactly)[0] + self.ui.body_list.setCurrentItem(item) + self.draw_mesh() + selected = self.ui.body_list.currentItem() + name = selected.text() + + edges = self.project.timeline[compo_id].bodies[name].interactor.edges + offset_vec = self.project.timeline[compo_id].bodies[name].interactor.offset_vector + self.custom_3D_Widget.load_interactor_mesh(edges, offset_vec) + + def edit_sketch(self): selected = self.ui.sketch_list.currentItem() name = selected.text() - # TODO: add selected element from timeline - sel_compo = self.project.timeline[-1] - sketch = sel_compo.sketches[name] + sel_compo = self.project.timeline[self.get_activated_compo()] + sketch = sel_compo.sketches[name].original_sketch self.sketchWidget.set_sketch(sketch) @@ -401,6 +416,7 @@ class MainWindow(QMainWindow): self.sketchWidget.reset_buffers() def draw_mesh(self): + name = self.ui.body_list.currentItem().text() print("selected_for disp", name) @@ -495,13 +511,13 @@ class MainWindow(QMainWindow): ### Interactor interactor = Interactor() interactor.add_lines_for_interactor(sketch.interactor_lines) + interactor.invert = invert if not invert: edges = interactor_mesh.generate_mesh(interactor.lines, 0, length) else: edges = interactor_mesh.generate_mesh(interactor.lines, 0, -length) - body.interactor = interactor sel_compo.bodies[name_op] = body offset_vector = interactor.vector_to_centroid(None, centroid, normal) @@ -509,6 +525,10 @@ class MainWindow(QMainWindow): if len(offset_vector) == 0 : offset_vector = [0, 0, 0] + interactor.edges = edges + interactor.offset_vector = offset_vector + body.interactor = interactor + self.custom_3D_Widget.load_interactor_mesh(edges, offset_vector) self.ui.body_list.addItem(name_op) @@ -518,13 +538,23 @@ class MainWindow(QMainWindow): self.draw_mesh() def send_cut(self): - name = self.ui.body_list.currentItem().text() + """name = self.ui.body_list.currentItem().text() points = self.model['operation'][name]['sdf_object'] - self.list_selected.append(points) + sel_compo = self.project.timeline[self.get_activated_compo()] + points = sel_compo.bodies[]. + self.list_selected.append(points)""" + + selected = self.ui.body_list.currentItem() + name = selected.text() + + sel_compo = self.project.timeline[self.get_activated_compo()] + # print(sel_compo) + body = sel_compo.bodies[name] + # print(sketch) + self.list_selected.append(body.sdf_body) if len(self.list_selected) == 2: - geo = Geometry() - f = geo.cut_shapes(self.list_selected[0], self.list_selected[1] ) + f = difference(self.list_selected[0], self.list_selected[1]) # equivalent element = { 'id': name, @@ -532,13 +562,22 @@ class MainWindow(QMainWindow): 'sdf_object': f, } + # Create body element and assign known stuff name_op = f"cut-{name}" - self.model['operation'][name_op] = element + + body = Body() + body.id = name_op + body.sdf_body = f + + ## Add to component + sel_compo.bodies[name_op] = body + self.ui.body_list.addItem(name_op) items = self.ui.body_list.findItems(name_op, Qt.MatchExactly) self.ui.body_list.setCurrentItem(items[-1]) self.custom_3D_Widget.clear_body_actors() self.draw_mesh() + elif len(self.list_selected) > 2: self.list_selected.clear() else: @@ -612,6 +651,10 @@ class Code: @dataclass class Sketch: """All of the 2D Information of a sketches""" + + # Save the incomng sketch from the 2D widget for late redit + original_sketch = None + id = None # Space Information @@ -761,6 +804,8 @@ class Interactor: lines = None faces = None body = None + offset_vector = None + edges = None def translate_points_tup(self, point: QPoint): """QPoints from Display to mesh data @@ -815,11 +860,6 @@ class Body: return f - def cut_shapes(self, sdf_object1, sdf_object2): - f = difference(sdf_object1, sdf_object2) # equivalent - - return f - class Output: def export_mesh(self, sdf_object): """FINAL EXPORT""" From e9383f76a227b5cf6edf711709c0145dd5fbd056 Mon Sep 17 00:00:00 2001 From: bklronin Date: Wed, 1 Jan 2025 21:35:43 +0100 Subject: [PATCH 15/16] - delete sketch working - added mid point snap - added hovering line with distance --- drawing_modules/draw_widget_solve.py | 50 +++++++++++++++---- main.py | 75 +++++++++++++--------------- 2 files changed, 75 insertions(+), 50 deletions(-) diff --git a/drawing_modules/draw_widget_solve.py b/drawing_modules/draw_widget_solve.py index 4a1d8e4..a1058da 100644 --- a/drawing_modules/draw_widget_solve.py +++ b/drawing_modules/draw_widget_solve.py @@ -19,6 +19,7 @@ class SketchWidget(QWidget): self.line_draw_buffer = [None, None] self.drag_buffer = [None, None] self.main_buffer = [None, None] + self.dynamic_line_end = None # Cursor position for dynamic drawing self.hovered_point = None self.selected_line = None @@ -186,7 +187,7 @@ class SketchWidget(QWidget): def get_point_line_handles_from_ui_point(self, ui_point: QPoint) -> tuple: """Input Qpoint that is on a line and you shall receive the handles of the points of the line!""" - for target_line_con in self.sketch.slv_lines: + for target_line_con in self.sketch.lines: if self.is_point_on_line(ui_point, target_line_con.crd1.ui_point, target_line_con.crd2.ui_point): lines_to_cons = target_line_con.crd1.handle, target_line_con.crd2.handle @@ -535,13 +536,18 @@ class SketchWidget(QWidget): def mouseMoveEvent(self, event): local_event_pos = self.viewport_to_local_coord(event.pos()) + #print(local_event_pos) closest_point = None min_distance = float('inf') threshold = 10 # Distance threshold for highlighting - if len(self.sketch.points) > 0: + if self.mouse_mode == "line" and self.line_draw_buffer[0]: + # Update the current cursor position as the second point + self.dynamic_line_end = self.viewport_to_local_coord(event.pos()) + self.update() # Trigger a repaint + if self.sketch.points is not None and len(self.sketch.points) > 0: for point in self.sketch.points: distance = (local_event_pos - point.ui_point).manhattanLength() if distance < threshold and distance < min_distance: @@ -564,6 +570,10 @@ class SketchWidget(QWidget): if self.is_point_on_line(local_event_pos, p1, p2): self.selected_line = p1, p2 + mid = self.calculate_midpoint(p1, p2) + distance = (local_event_pos - mid).manhattanLength() + if distance < threshold and distance < min_distance: + self.hovered_point = mid break else: self.selected_line = None @@ -695,7 +705,10 @@ class SketchWidget(QWidget): pen_solver = QPen(Qt.green) pen_solver.setWidthF(2 / self.zoom) - # Draw points + pen_text = QPen(Qt.white) + pen_text.setWidthF(1 / self.zoom) + + # Draw points and lines if self.sketch: painter.setPen(pen_normal) for point in self.sketch.points: @@ -703,10 +716,34 @@ class SketchWidget(QWidget): painter.setPen(pen_construct) painter.drawEllipse(point.ui_point, 10 / self.zoom, 10 / self.zoom) else: - #Normal point + # Normal point painter.setPen(pen_normal) painter.drawEllipse(point.ui_point, 3 / self.zoom, 3 / self.zoom) + # Draw the dynamic line + if self.mouse_mode == "line" and self.line_draw_buffer[0] and self.dynamic_line_end is not None: + start_point = self.line_draw_buffer[0].ui_point + end_point = self.dynamic_line_end + painter.setPen(Qt.red) # Use a different color for the dynamic line + painter.drawLine(start_point, end_point) + + # Save painter state + painter.save() + painter.setPen(pen_text) + + # Calculate the distance and midpoint + dis = self.distance(start_point, end_point) + mid = self.calculate_midpoint(start_point, end_point) + + # Transform for text + painter.translate(mid.x(), mid.y()) # Move to the midpoint + painter.scale(1, -1) # Flip y-axis back to make text readable + + # Draw the text + painter.drawText(0, 0, str(round(dis, 2))) # Draw text at transformed position + + # Restore painter state + painter.restore() for line in self.sketch.lines: if line.is_helper: @@ -720,11 +757,6 @@ class SketchWidget(QWidget): p2 = line.crd2.ui_point painter.drawLine(p1, p2) - dis = self.distance(p1, p2) - mid = self.calculate_midpoint(p1, p2) - painter.drawText(mid, str(round(dis, 2))) - - # Draw all solver points if self.sketch.entity_len(): painter.setPen(pen_solver) diff --git a/main.py b/main.py index fc05fd5..2b5588a 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ import uuid import names from PySide6.QtCore import Qt, QPoint, Signal, QSize -from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QCheckBox, QPushButton +from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QCheckBox, QPushButton, QButtonGroup from Gui import Ui_fluencyCAD # Import the generated GUI module from drawing_modules.vtk_widget import VTKWidget from drawing_modules.vysta_widget import PyVistaWidget @@ -156,6 +156,7 @@ class MainWindow(QMainWindow): self.project.timeline = timeline self.new_component() + def new_component(self): print("Creating a new component...") @@ -164,6 +165,10 @@ class MainWindow(QMainWindow): print("Initializing compo_layout...") self.compo_layout = QHBoxLayout() + # Create a button group + self.compo_group = QButtonGroup(self) + self.compo_group.setExclusive(True) # Ensure exclusivity + # Ensure the QGroupBox has a layout if not self.ui.compo_box.layout(): self.ui.compo_box.setLayout(QVBoxLayout()) # Set a default layout for QGroupBox @@ -188,13 +193,19 @@ class MainWindow(QMainWindow): button.setText(str(len(self.project.timeline))) button.setFixedSize(QSize(40, 40)) # Set button size button.setCheckable(True) - button.setAutoExclusive(True) - button.setChecked(False) + #button.setAutoExclusive(True) button.released.connect(self.on_compo_change) + button.setChecked(True) + + # Add button to the group + self.compo_group.addButton(button) # Add the button to the layout self.compo_layout.addWidget(button) + # We automatically switch to the new compo hence, refresh + self.on_compo_change() + print(f"Added component {compo.id} to the layout.") def get_activated_compo(self): @@ -285,6 +296,7 @@ class MainWindow(QMainWindow): def on_compo_change(self): self.custom_3D_Widget.clear_body_actors() self.custom_3D_Widget.clear_actors_interactor() + self.custom_3D_Widget.clear_actors_projection() compo_id = self.get_activated_compo() if compo_id is not None: @@ -301,16 +313,17 @@ class MainWindow(QMainWindow): for body in self.project.timeline[compo_id].bodies: self.ui.body_list.addItem(body) - item = self.ui.body_list.findItems(body , Qt.MatchExactly)[0] - self.ui.body_list.setCurrentItem(item) - self.draw_mesh() + if self.project.timeline[compo_id].bodies: + item = self.ui.body_list.findItems(body , Qt.MatchExactly)[0] + self.ui.body_list.setCurrentItem(item) + self.draw_mesh() - selected = self.ui.body_list.currentItem() - name = selected.text() + selected = self.ui.body_list.currentItem() + name = selected.text() - edges = self.project.timeline[compo_id].bodies[name].interactor.edges - offset_vec = self.project.timeline[compo_id].bodies[name].interactor.offset_vector - self.custom_3D_Widget.load_interactor_mesh(edges, offset_vec) + edges = self.project.timeline[compo_id].bodies[name].interactor.edges + offset_vec = self.project.timeline[compo_id].bodies[name].interactor.offset_vector + self.custom_3D_Widget.load_interactor_mesh(edges, offset_vec) def edit_sketch(self): selected = self.ui.sketch_list.currentItem() @@ -323,40 +336,20 @@ class MainWindow(QMainWindow): self.sketchWidget.update() def del_sketch(self): - # Old - print("Deleting") - name = self.ui.sketch_list.currentItem() # Get the current item + selected = self.ui.sketch_list.currentItem() + name = selected.text() + sel_compo = self.project.timeline[self.get_activated_compo()] + sketch = sel_compo.sketches[name] - print(self.model) - - if name is not None: - item_name = name.text() - print("obj_name", item_name) - - # Check if the 'sketches' key exists in the model dictionary - if 'sketches' in self.model and item_name in self.model['sketches']: - if self.model['sketches'][item_name]['id'] == item_name: - row = self.ui.sketch_list.row(name) # Get the row of the current item - self.ui.sketch_list.takeItem(row) # Remove the item from the list widget - self.sketchWidget.clear_sketch() - self.model['sketches'].pop(item_name) # Remove the item from the sketches dictionary - print(f"Removed sketches: {item_name}") - - # Check if the 'operation' key exists in the model dictionary - elif 'operation' in self.model and item_name in self.model['operation']: - if self.model['operation'][item_name]['id'] == item_name: - row = self.ui.sketch_list.row(name) # Get the row of the current item - self.ui.sketch_list.takeItem(row) # Remove the item from the list widget - self.sketchWidget.clear_sketch() - self.model['operation'].pop(item_name) # Remove the item from the operation dictionary - print(f"Removed operation: {item_name}") - - else: - print(f"Item '{item_name}' not found in either 'sketches' or 'operation' dictionary.") + if sketch is not None: + sel_compo.sketches.pop(name) + row = self.ui.sketch_list.row(selected) # Get the row of the current item + self.ui.sketch_list.takeItem(row) # Remove the item from the list widget + self.sketchWidget.sketch = None + print(sketch) else: print("No item selected.") - def on_flip_face(self): self.send_command.emit("flip") From 3e88e41e4b003f480b3b02cbe809b9999e6421c4 Mon Sep 17 00:00:00 2001 From: bklronin Date: Thu, 2 Jan 2025 14:39:36 +0100 Subject: [PATCH 16/16] - Added new buttons and settings --- Gui.py | 694 +++++++++++-------- drawing_modules/draw_widget_solve.py | 2 + gui.ui | 993 ++++++++++++++++----------- 3 files changed, 1015 insertions(+), 674 deletions(-) diff --git a/Gui.py b/Gui.py index 937d55e..941b27f 100644 --- a/Gui.py +++ b/Gui.py @@ -16,16 +16,17 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient, QIcon, QImage, QKeySequence, QLinearGradient, QPainter, QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QApplication, QGridLayout, QGroupBox, QHBoxLayout, - QListWidget, QListWidgetItem, QMainWindow, QMenu, - QMenuBar, QPushButton, QSizePolicy, QStatusBar, - QTabWidget, QTextEdit, QVBoxLayout, QWidget) +from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QGroupBox, + QHBoxLayout, QLabel, QListWidget, QListWidgetItem, + QMainWindow, QMenu, QMenuBar, QPushButton, + QSizePolicy, QSpinBox, QStatusBar, QTabWidget, + QTextEdit, QVBoxLayout, QWidget) class Ui_fluencyCAD(object): def setupUi(self, fluencyCAD): if not fluencyCAD.objectName(): fluencyCAD.setObjectName(u"fluencyCAD") - fluencyCAD.resize(2192, 1073) + fluencyCAD.resize(2192, 1109) self.actionNew_Project = QAction(fluencyCAD) self.actionNew_Project.setObjectName(u"actionNew_Project") self.actionLoad_Project = QAction(fluencyCAD) @@ -36,81 +37,13 @@ class Ui_fluencyCAD(object): self.centralwidget.setObjectName(u"centralwidget") self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setObjectName(u"gridLayout") - self.groupBox_3 = QGroupBox(self.centralwidget) - self.groupBox_3.setObjectName(u"groupBox_3") - sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - 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") - self.pb_con_line = QPushButton(self.groupBox_3) - self.pb_con_line.setObjectName(u"pb_con_line") - self.pb_con_line.setCheckable(True) - self.pb_con_line.setAutoExclusive(False) - - self.gridLayout_4.addWidget(self.pb_con_line, 0, 1, 1, 1) - - self.pb_con_ptpt = QPushButton(self.groupBox_3) - self.pb_con_ptpt.setObjectName(u"pb_con_ptpt") - self.pb_con_ptpt.setCheckable(True) - self.pb_con_ptpt.setAutoExclusive(False) - - self.gridLayout_4.addWidget(self.pb_con_ptpt, 0, 0, 1, 1) - - self.pb_con_horiz = QPushButton(self.groupBox_3) - self.pb_con_horiz.setObjectName(u"pb_con_horiz") - self.pb_con_horiz.setCheckable(True) - self.pb_con_horiz.setAutoExclusive(False) - - self.gridLayout_4.addWidget(self.pb_con_horiz, 2, 0, 1, 1) - - self.pb_con_vert = QPushButton(self.groupBox_3) - self.pb_con_vert.setObjectName(u"pb_con_vert") - self.pb_con_vert.setCheckable(True) - self.pb_con_vert.setAutoExclusive(False) - - self.gridLayout_4.addWidget(self.pb_con_vert, 2, 1, 1, 1) - - self.pb_con_sym = QPushButton(self.groupBox_3) - self.pb_con_sym.setObjectName(u"pb_con_sym") - self.pb_con_sym.setCheckable(True) - self.pb_con_sym.setAutoExclusive(False) - - self.gridLayout_4.addWidget(self.pb_con_sym, 3, 1, 1, 1) - - self.pb_con_dist = QPushButton(self.groupBox_3) - self.pb_con_dist.setObjectName(u"pb_con_dist") - self.pb_con_dist.setCheckable(True) - self.pb_con_dist.setAutoExclusive(False) - self.pb_con_dist.setAutoRepeatDelay(297) - - self.gridLayout_4.addWidget(self.pb_con_dist, 3, 0, 1, 1) - - self.pb_con_mid = QPushButton(self.groupBox_3) - self.pb_con_mid.setObjectName(u"pb_con_mid") - self.pb_con_mid.setCheckable(True) - - self.gridLayout_4.addWidget(self.pb_con_mid, 1, 0, 1, 1) - - self.pb_con_perp = QPushButton(self.groupBox_3) - self.pb_con_perp.setObjectName(u"pb_con_perp") - self.pb_con_perp.setCheckable(True) - - self.gridLayout_4.addWidget(self.pb_con_perp, 1, 1, 1, 1) - - - self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1) - self.InputTab = QTabWidget(self.centralwidget) self.InputTab.setObjectName(u"InputTab") - sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth()) - self.InputTab.setSizePolicy(sizePolicy1) + sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.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) @@ -154,57 +87,26 @@ class Ui_fluencyCAD(object): self.InputTab.addTab(self.code_tab, "") - self.gridLayout.addWidget(self.InputTab, 0, 1, 7, 1) + self.gridLayout.addWidget(self.InputTab, 0, 1, 9, 1) - self.groupBox_11 = QGroupBox(self.centralwidget) - self.groupBox_11.setObjectName(u"groupBox_11") - sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.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.Expanding, QSizePolicy.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.gl_box = QGroupBox(self.centralwidget) + self.gl_box.setObjectName(u"gl_box") + sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.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.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, 5, 0, 2, 1) + self.gridLayout.addWidget(self.gl_box, 0, 2, 9, 1) self.groupBox = QGroupBox(self.centralwidget) self.groupBox.setObjectName(u"groupBox") @@ -247,29 +149,13 @@ class Ui_fluencyCAD(object): self.compo_box.setObjectName(u"compo_box") self.compo_box.setMinimumSize(QSize(0, 50)) - self.gridLayout.addWidget(self.compo_box, 7, 1, 1, 2) - - self.gl_box = QGroupBox(self.centralwidget) - self.gl_box.setObjectName(u"gl_box") - sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - sizePolicy4.setHorizontalStretch(0) - sizePolicy4.setVerticalStretch(4) - sizePolicy4.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth()) - self.gl_box.setSizePolicy(sizePolicy4) - 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, 7, 1) + self.gridLayout.addWidget(self.compo_box, 9, 1, 1, 2) self.groupBox_10 = QGroupBox(self.centralwidget) self.groupBox_10.setObjectName(u"groupBox_10") + sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) sizePolicy2.setHeightForWidth(self.groupBox_10.sizePolicy().hasHeightForWidth()) self.groupBox_10.setSizePolicy(sizePolicy2) self.groupBox_10.setMaximumSize(QSize(200, 16777215)) @@ -284,8 +170,11 @@ class Ui_fluencyCAD(object): 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) + sizePolicy3 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + sizePolicy3.setHorizontalStretch(0) + sizePolicy3.setVerticalStretch(0) + sizePolicy3.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth()) + self.groupBox_8.setSizePolicy(sizePolicy3) self.groupBox_8.setMaximumSize(QSize(200, 16777215)) self.gridLayout_8 = QGridLayout(self.groupBox_8) self.gridLayout_8.setObjectName(u"gridLayout_8") @@ -309,45 +198,114 @@ class Ui_fluencyCAD(object): self.verticalLayout_6.addWidget(self.groupBox_8) - self.gridLayout.addWidget(self.groupBox_10, 5, 3, 2, 1) + self.gridLayout.addWidget(self.groupBox_10, 7, 3, 2, 1) - self.groupBox_2 = QGroupBox(self.centralwidget) - self.groupBox_2.setObjectName(u"groupBox_2") - 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.pb_rectool = QPushButton(self.groupBox_2) - self.pb_rectool.setObjectName(u"pb_rectool") - self.pb_rectool.setCheckable(True) - self.pb_rectool.setAutoExclusive(False) + self.groupBox_11 = QGroupBox(self.centralwidget) + self.groupBox_11.setObjectName(u"groupBox_11") + 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") + sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy4.setHorizontalStretch(0) + sizePolicy4.setVerticalStretch(0) + sizePolicy4.setHeightForWidth(self.sketch_list.sizePolicy().hasHeightForWidth()) + self.sketch_list.setSizePolicy(sizePolicy4) + self.sketch_list.setSelectionRectVisible(True) - self.gridLayout_2.addWidget(self.pb_rectool, 1, 1, 1, 1, Qt.AlignTop) + self.verticalLayout_7.addWidget(self.sketch_list) - 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.groupBox_6 = QGroupBox(self.groupBox_11) + self.groupBox_6.setObjectName(u"groupBox_6") + sizePolicy3.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth()) + self.groupBox_6.setSizePolicy(sizePolicy3) + 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_2.addWidget(self.pb_circtool, 2, 0, 1, 1, Qt.AlignTop) + self.gridLayout_6.addWidget(self.pb_edt_sktch, 1, 1, 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.pb_nw_sktch = QPushButton(self.groupBox_6) + self.pb_nw_sktch.setObjectName(u"pb_nw_sktch") - self.gridLayout_2.addWidget(self.pb_slotool, 2, 1, 1, 1, Qt.AlignTop) + self.gridLayout_6.addWidget(self.pb_nw_sktch, 1, 0, 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.pb_del_sketch = QPushButton(self.groupBox_6) + self.pb_del_sketch.setObjectName(u"pb_del_sketch") - self.gridLayout_2.addWidget(self.pb_linetool, 1, 0, 1, 1) + self.gridLayout_6.addWidget(self.pb_del_sketch, 1, 2, 1, 1) - self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1) + self.verticalLayout_7.addWidget(self.groupBox_6) + + + self.gridLayout.addWidget(self.groupBox_11, 6, 0, 3, 1) + + self.assmbly_box = QGroupBox(self.centralwidget) + self.assmbly_box.setObjectName(u"assmbly_box") + self.assmbly_box.setMinimumSize(QSize(0, 50)) + self.gridLayout_10 = QGridLayout(self.assmbly_box) + self.gridLayout_10.setObjectName(u"gridLayout_10") + self.pushButton_3 = QPushButton(self.assmbly_box) + self.pushButton_3.setObjectName(u"pushButton_3") + self.pushButton_3.setMinimumSize(QSize(50, 50)) + self.pushButton_3.setMaximumSize(QSize(50, 50)) + + self.gridLayout_10.addWidget(self.pushButton_3, 0, 0, 1, 1) + + self.pushButton_6 = QPushButton(self.assmbly_box) + self.pushButton_6.setObjectName(u"pushButton_6") + self.pushButton_6.setMinimumSize(QSize(50, 50)) + self.pushButton_6.setMaximumSize(QSize(50, 50)) + + self.gridLayout_10.addWidget(self.pushButton_6, 0, 1, 1, 1) + + + self.gridLayout.addWidget(self.assmbly_box, 9, 3, 1, 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.pushButton_2 = QPushButton(self.groupBox_4) + self.pushButton_2.setObjectName(u"pushButton_2") + + self.verticalLayout_2.addWidget(self.pushButton_2) + + + self.gridLayout.addWidget(self.groupBox_4, 6, 3, 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.new_compo = QPushButton(self.compo_tool_box) + self.new_compo.setObjectName(u"new_compo") + self.new_compo.setMinimumSize(QSize(50, 50)) + self.new_compo.setMaximumSize(QSize(50, 50)) + + self.gridLayout_9.addWidget(self.new_compo, 0, 0, 1, 1) + + self.del_compo = QPushButton(self.compo_tool_box) + self.del_compo.setObjectName(u"del_compo") + self.del_compo.setEnabled(True) + sizePolicy3.setHeightForWidth(self.del_compo.sizePolicy().hasHeightForWidth()) + self.del_compo.setSizePolicy(sizePolicy3) + self.del_compo.setMinimumSize(QSize(50, 50)) + self.del_compo.setMaximumSize(QSize(50, 50)) + self.del_compo.setLayoutDirection(Qt.LeftToRight) + + self.gridLayout_9.addWidget(self.del_compo, 0, 1, 1, 1) + + + self.gridLayout.addWidget(self.compo_tool_box, 9, 0, 1, 1) self.groupBox_9 = QGroupBox(self.centralwidget) self.groupBox_9.setObjectName(u"groupBox_9") @@ -375,67 +333,231 @@ class Ui_fluencyCAD(object): self.gridLayout_7.addWidget(self.pb_move_wp, 1, 1, 1, 1) - self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1, Qt.AlignTop) + self.gridLayout.addWidget(self.groupBox_9, 0, 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.new_compo = QPushButton(self.compo_tool_box) - self.new_compo.setObjectName(u"new_compo") - self.new_compo.setMinimumSize(QSize(50, 50)) - self.new_compo.setMaximumSize(QSize(50, 50)) + self.groupBox_2 = QGroupBox(self.centralwidget) + self.groupBox_2.setObjectName(u"groupBox_2") + sizePolicy3.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth()) + self.groupBox_2.setSizePolicy(sizePolicy3) + 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.line = QFrame(self.groupBox_2) + self.line.setObjectName(u"line") + self.line.setFrameShape(QFrame.HLine) + self.line.setFrameShadow(QFrame.Sunken) - self.gridLayout_9.addWidget(self.new_compo, 0, 0, 1, 1) + self.gridLayout_2.addWidget(self.line, 4, 0, 1, 2) - self.del_compo = QPushButton(self.compo_tool_box) - self.del_compo.setObjectName(u"del_compo") - self.del_compo.setEnabled(True) - sizePolicy.setHeightForWidth(self.del_compo.sizePolicy().hasHeightForWidth()) - self.del_compo.setSizePolicy(sizePolicy) - self.del_compo.setMinimumSize(QSize(50, 50)) - self.del_compo.setMaximumSize(QSize(50, 50)) - self.del_compo.setLayoutDirection(Qt.LeftToRight) + 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_9.addWidget(self.del_compo, 0, 1, 1, 1) + self.gridLayout_2.addWidget(self.pb_circtool, 2, 0, 1, 1, Qt.AlignTop) + + 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.pb_slotool.setAutoRepeatInterval(98) + + self.gridLayout_2.addWidget(self.pb_slotool, 2, 1, 1, 1, Qt.AlignTop) + + 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, 1, 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, 1, 1, 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, 5, 0, 1, 1) + + self.pb_enable_snap = QPushButton(self.groupBox_2) + self.pb_enable_snap.setObjectName(u"pb_enable_snap") + self.pb_enable_snap.setCheckable(True) + self.pb_enable_snap.setChecked(True) + + self.gridLayout_2.addWidget(self.pb_enable_snap, 5, 1, 1, 1) - self.gridLayout.addWidget(self.compo_tool_box, 7, 0, 1, 1) + self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 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.pushButton_2 = QPushButton(self.groupBox_4) - self.pushButton_2.setObjectName(u"pushButton_2") + self.groupBox_3 = QGroupBox(self.centralwidget) + self.groupBox_3.setObjectName(u"groupBox_3") + sizePolicy3.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth()) + self.groupBox_3.setSizePolicy(sizePolicy3) + self.groupBox_3.setMaximumSize(QSize(200, 16777213)) + self.gridLayout_4 = QGridLayout(self.groupBox_3) + self.gridLayout_4.setObjectName(u"gridLayout_4") + self.pb_con_sym = QPushButton(self.groupBox_3) + self.pb_con_sym.setObjectName(u"pb_con_sym") + self.pb_con_sym.setCheckable(True) + self.pb_con_sym.setAutoExclusive(False) - self.verticalLayout_2.addWidget(self.pushButton_2) + self.gridLayout_4.addWidget(self.pb_con_sym, 3, 1, 1, 1) + + self.pb_con_vert = QPushButton(self.groupBox_3) + self.pb_con_vert.setObjectName(u"pb_con_vert") + self.pb_con_vert.setCheckable(True) + self.pb_con_vert.setAutoExclusive(False) + + self.gridLayout_4.addWidget(self.pb_con_vert, 2, 1, 1, 1) + + self.pb_con_perp = QPushButton(self.groupBox_3) + self.pb_con_perp.setObjectName(u"pb_con_perp") + self.pb_con_perp.setCheckable(True) + + self.gridLayout_4.addWidget(self.pb_con_perp, 1, 1, 1, 1) + + self.pb_con_horiz = QPushButton(self.groupBox_3) + self.pb_con_horiz.setObjectName(u"pb_con_horiz") + self.pb_con_horiz.setCheckable(True) + self.pb_con_horiz.setAutoExclusive(False) + + self.gridLayout_4.addWidget(self.pb_con_horiz, 2, 0, 1, 1) + + self.pb_con_ptpt = QPushButton(self.groupBox_3) + self.pb_con_ptpt.setObjectName(u"pb_con_ptpt") + self.pb_con_ptpt.setCheckable(True) + self.pb_con_ptpt.setAutoExclusive(False) + + self.gridLayout_4.addWidget(self.pb_con_ptpt, 0, 0, 1, 1) + + self.pb_con_line = QPushButton(self.groupBox_3) + self.pb_con_line.setObjectName(u"pb_con_line") + self.pb_con_line.setCheckable(True) + self.pb_con_line.setAutoExclusive(False) + + self.gridLayout_4.addWidget(self.pb_con_line, 0, 1, 1, 1) + + self.pb_con_dist = QPushButton(self.groupBox_3) + self.pb_con_dist.setObjectName(u"pb_con_dist") + self.pb_con_dist.setCheckable(True) + self.pb_con_dist.setAutoExclusive(False) + self.pb_con_dist.setAutoRepeatDelay(297) + + self.gridLayout_4.addWidget(self.pb_con_dist, 3, 0, 1, 1) + + self.pb_con_mid = QPushButton(self.groupBox_3) + self.pb_con_mid.setObjectName(u"pb_con_mid") + self.pb_con_mid.setCheckable(True) + + self.gridLayout_4.addWidget(self.pb_con_mid, 1, 0, 1, 1) - self.gridLayout.addWidget(self.groupBox_4, 4, 3, 1, 1) + self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1) - self.assmbly_box = QGroupBox(self.centralwidget) - self.assmbly_box.setObjectName(u"assmbly_box") - self.assmbly_box.setMinimumSize(QSize(0, 50)) - self.gridLayout_10 = QGridLayout(self.assmbly_box) - self.gridLayout_10.setObjectName(u"gridLayout_10") - self.pushButton_3 = QPushButton(self.assmbly_box) - self.pushButton_3.setObjectName(u"pushButton_3") - self.pushButton_3.setMinimumSize(QSize(50, 50)) - self.pushButton_3.setMaximumSize(QSize(50, 50)) + self.tabWidget = QTabWidget(self.centralwidget) + self.tabWidget.setObjectName(u"tabWidget") + sizePolicy5 = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) + sizePolicy5.setHorizontalStretch(0) + sizePolicy5.setVerticalStretch(0) + sizePolicy5.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) + self.tabWidget.setSizePolicy(sizePolicy5) + self.tabWidget.setMaximumSize(QSize(200, 16777215)) + self.tabWidget.setTabPosition(QTabWidget.South) + self.widget = QWidget() + self.widget.setObjectName(u"widget") + self.verticalLayout_3 = QVBoxLayout(self.widget) + self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.groupBox_5 = QGroupBox(self.widget) + self.groupBox_5.setObjectName(u"groupBox_5") + sizePolicy6 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + sizePolicy6.setHorizontalStretch(0) + sizePolicy6.setVerticalStretch(0) + sizePolicy6.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth()) + self.groupBox_5.setSizePolicy(sizePolicy6) + self.gridLayout_11 = QGridLayout(self.groupBox_5) + self.gridLayout_11.setObjectName(u"gridLayout_11") + self.gridLayout_11.setContentsMargins(2, 2, 2, 2) + self.label = QLabel(self.groupBox_5) + self.label.setObjectName(u"label") - self.gridLayout_10.addWidget(self.pushButton_3, 0, 0, 1, 1) + self.gridLayout_11.addWidget(self.label, 5, 0, 1, 1) - self.pushButton_6 = QPushButton(self.assmbly_box) - self.pushButton_6.setObjectName(u"pushButton_6") - self.pushButton_6.setMinimumSize(QSize(50, 50)) - self.pushButton_6.setMaximumSize(QSize(50, 50)) + self.pb_snap_vert = QPushButton(self.groupBox_5) + self.pb_snap_vert.setObjectName(u"pb_snap_vert") + self.pb_snap_vert.setCheckable(True) - self.gridLayout_10.addWidget(self.pushButton_6, 0, 1, 1, 1) + self.gridLayout_11.addWidget(self.pb_snap_vert, 2, 1, 1, 1) + + self.line_2 = QFrame(self.groupBox_5) + self.line_2.setObjectName(u"line_2") + self.line_2.setFrameShape(QFrame.HLine) + self.line_2.setFrameShadow(QFrame.Sunken) + + self.gridLayout_11.addWidget(self.line_2, 4, 0, 1, 2) + + self.label_2 = QLabel(self.groupBox_5) + self.label_2.setObjectName(u"label_2") + + self.gridLayout_11.addWidget(self.label_2, 5, 1, 1, 1) + + self.spinbox_snap_distance = QSpinBox(self.groupBox_5) + self.spinbox_snap_distance.setObjectName(u"spinbox_snap_distance") + self.spinbox_snap_distance.setMaximum(30) + self.spinbox_snap_distance.setValue(10) + + self.gridLayout_11.addWidget(self.spinbox_snap_distance, 6, 0, 1, 1) + + self.pushButton_7 = QPushButton(self.groupBox_5) + self.pushButton_7.setObjectName(u"pushButton_7") + self.pushButton_7.setCheckable(True) + + self.gridLayout_11.addWidget(self.pushButton_7, 3, 0, 1, 1) + + self.pb_snap_horiz = QPushButton(self.groupBox_5) + self.pb_snap_horiz.setObjectName(u"pb_snap_horiz") + self.pb_snap_horiz.setCheckable(True) + + self.gridLayout_11.addWidget(self.pb_snap_horiz, 2, 0, 1, 1) + + self.spinbox_angle_steps = QSpinBox(self.groupBox_5) + self.spinbox_angle_steps.setObjectName(u"spinbox_angle_steps") + self.spinbox_angle_steps.setMaximum(180) + self.spinbox_angle_steps.setValue(15) + + self.gridLayout_11.addWidget(self.spinbox_angle_steps, 6, 1, 1, 1) + + self.pushButton_8 = QPushButton(self.groupBox_5) + self.pushButton_8.setObjectName(u"pushButton_8") + + self.gridLayout_11.addWidget(self.pushButton_8, 0, 0, 1, 1) + + self.pb_snap_midp = QPushButton(self.groupBox_5) + self.pb_snap_midp.setObjectName(u"pb_snap_midp") + self.pb_snap_midp.setCheckable(True) + + self.gridLayout_11.addWidget(self.pb_snap_midp, 0, 1, 1, 1) + + self.pb_snap_angle = QPushButton(self.groupBox_5) + self.pb_snap_angle.setObjectName(u"pb_snap_angle") + self.pb_snap_angle.setCheckable(True) + + self.gridLayout_11.addWidget(self.pb_snap_angle, 3, 1, 1, 1) - self.gridLayout.addWidget(self.assmbly_box, 7, 3, 1, 1) + self.verticalLayout_3.addWidget(self.groupBox_5) + + self.tabWidget.addTab(self.widget, "") + self.widget1 = QWidget() + self.widget1.setObjectName(u"widget1") + self.tabWidget.addTab(self.widget1, "") + + self.gridLayout.addWidget(self.tabWidget, 3, 0, 1, 1) fluencyCAD.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(fluencyCAD) @@ -460,6 +582,7 @@ class Ui_fluencyCAD(object): self.retranslateUi(fluencyCAD) self.InputTab.setCurrentIndex(0) + self.tabWidget.setCurrentIndex(1) QMetaObject.connectSlotsByName(fluencyCAD) @@ -470,36 +593,6 @@ class Ui_fluencyCAD(object): self.actionNew_Project.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) self.actionLoad_Project.setText(QCoreApplication.translate("fluencyCAD", u"Load", None)) self.actionRecent.setText(QCoreApplication.translate("fluencyCAD", u"Recent", None)) - self.groupBox_3.setTitle(QCoreApplication.translate("fluencyCAD", u"Constrain", None)) -#if QT_CONFIG(tooltip) - self.pb_con_line.setToolTip(QCoreApplication.translate("fluencyCAD", u"Point to Line Constrain", None)) -#endif // QT_CONFIG(tooltip) - self.pb_con_line.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Lne", None)) -#if QT_CONFIG(tooltip) - self.pb_con_ptpt.setToolTip(QCoreApplication.translate("fluencyCAD", u"Poin to Point Constrain", None)) -#endif // QT_CONFIG(tooltip) - self.pb_con_ptpt.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Pt", None)) -#if QT_CONFIG(tooltip) - self.pb_con_horiz.setToolTip(QCoreApplication.translate("fluencyCAD", u"Horizontal Constrain ", None)) -#endif // QT_CONFIG(tooltip) - self.pb_con_horiz.setText(QCoreApplication.translate("fluencyCAD", u"Horiz", None)) -#if QT_CONFIG(tooltip) - self.pb_con_vert.setToolTip(QCoreApplication.translate("fluencyCAD", u"Vertical Constrain", None)) -#endif // QT_CONFIG(tooltip) - self.pb_con_vert.setText(QCoreApplication.translate("fluencyCAD", u"Vert", None)) - self.pb_con_sym.setText(QCoreApplication.translate("fluencyCAD", u"Symetrc", None)) -#if QT_CONFIG(tooltip) - self.pb_con_dist.setToolTip(QCoreApplication.translate("fluencyCAD", u"Dimension of Line of Distance from Point to Line", None)) -#endif // QT_CONFIG(tooltip) - self.pb_con_dist.setText(QCoreApplication.translate("fluencyCAD", u"Distnce", None)) -#if QT_CONFIG(tooltip) - self.pb_con_mid.setToolTip(QCoreApplication.translate("fluencyCAD", u"Point to Middle Point Constrain", None)) -#endif // QT_CONFIG(tooltip) - self.pb_con_mid.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Mid_L", None)) -#if QT_CONFIG(tooltip) - self.pb_con_perp.setToolTip(QCoreApplication.translate("fluencyCAD", u"Constrain Line perpendicular to another line.", None)) -#endif // QT_CONFIG(tooltip) - self.pb_con_perp.setText(QCoreApplication.translate("fluencyCAD", u"Perp_Lne", 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)) @@ -507,11 +600,7 @@ class Ui_fluencyCAD(object): 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.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.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", 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)) @@ -520,23 +609,24 @@ class Ui_fluencyCAD(object): 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.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", 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_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None)) - self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None)) - self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None)) -#if QT_CONFIG(statustip) - self.pb_linetool.setStatusTip(QCoreApplication.translate("fluencyCAD", u"Line >Sorking Plane at 0, 0, 0", None)) @@ -566,14 +656,62 @@ class Ui_fluencyCAD(object): #if QT_CONFIG(shortcut) self.pb_move_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"M", None)) #endif // QT_CONFIG(shortcut) - self.compo_tool_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Tools", None)) - self.new_compo.setText(QCoreApplication.translate("fluencyCAD", u"New", None)) - self.del_compo.setText(QCoreApplication.translate("fluencyCAD", u"Del", None)) - self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) - self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None)) - self.assmbly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly Tools", None)) - self.pushButton_3.setText(QCoreApplication.translate("fluencyCAD", u"+ Cnct", None)) - self.pushButton_6.setText(QCoreApplication.translate("fluencyCAD", u"- Cnct", None)) + self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None)) + self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None)) + self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None)) +#if QT_CONFIG(statustip) + self.pb_linetool.setStatusTip(QCoreApplication.translate("fluencyCAD", u"Line >S0 0 2192 - 1073 + 1109 @@ -15,150 +15,7 @@ - - - - - 0 - 0 - - - - - 200 - 16777213 - - - - Constrain - - - - - - Point to Line Constrain - - - Pt_Lne - - - true - - - false - - - - - - - Poin to Point Constrain - - - Pt_Pt - - - true - - - false - - - - - - - Horizontal Constrain - - - Horiz - - - true - - - false - - - - - - - Vertical Constrain - - - Vert - - - true - - - false - - - - - - - Symetrc - - - true - - - false - - - - - - - Dimension of Line of Distance from Point to Line - - - Distnce - - - true - - - false - - - 297 - - - - - - - Point to Middle Point Constrain - - - Pt_Mid_L - - - true - - - - - - - Constrain Line perpendicular to another line. - - - Perp_Lne - - - true - - - - - - - + @@ -224,97 +81,29 @@ - - + + - + 0 - 0 + 4 - - - 200 - 16777215 - + + + 12 + - Sketch + Model Viewer - + + + -1 + - 5 + 12 - - 5 - - - 5 - - - 5 - - - - - - 0 - 0 - - - - true - - - - - - - - 0 - 0 - - - - Tools - - - - 2 - - - 2 - - - 2 - - - 2 - - - - - Edt - - - - - - - Add - - - - - - - Del - - - - - - @@ -369,7 +158,7 @@ - + @@ -382,33 +171,7 @@ - - - - - 0 - 4 - - - - - 12 - - - - Model Viewer - - - - -1 - - - 12 - - - - - + @@ -502,10 +265,10 @@ - - + + - + 0 0 @@ -517,71 +280,220 @@ - Drawing + Sketch - - - - - Rctgl + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + - + true - - false - - - + + + + + 0 + 0 + + + + Tools + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Edt + + + + + + + Add + + + + + + + Del + + + + + + + + + + + + + + 0 + 50 + + + + Assembly Tools + + + + + + + 50 + 50 + + + + + 50 + 50 + + - Circle - - - true - - - false + + Cnct - - - - Slot + + + + + 50 + 50 + - - true - - - false - - - - - - - Line >S<egment + + + 50 + 50 + - Line - - - S - - - true - - - false + - Cnct - + + + + Export + + + + + + STL + + + + + + + + + + + 0 + 50 + + + + Component Tools + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + New + + + + + + + true + + + + 0 + 0 + + + + + 50 + 50 + + + + + 50 + 50 + + + + Qt::LeftToRight + + + Del + + + + + + + @@ -648,140 +560,429 @@ - - - + + + + + 0 + 0 + + + - 0 - 50 + 200 + 16777215 - Component Tools + Drawing - - - - - - 50 - 50 - - - - - 50 - 50 - - - - New + + + 10 + + + + + Qt::Horizontal - - - + + + + Circle + + true - - - 0 - 0 - + + false - - - 50 - 50 - + + + + + + Slot - - - 50 - 50 - + + true - - Qt::LeftToRight + + false + + + 98 + + + + + + + Line >S<egment - Del + Line + + + S + + + true + + + false + + + + + + + Rctgl + + + true + + + false + + + + + + + Cstrct + + + true + + + + + + + Snap + + + true + + + true - - - - Export + + + + + 0 + 0 + - - - - - STL - - - - - - - - - + - 0 - 50 + 200 + 16777213 - Assembly Tools + Constrain - - - - - - 50 - 50 - + + + + + Symetrc - - - 50 - 50 - + + true + + + false + + + + + + + Vertical Constrain - + Cnct + Vert + + + true + + + false + + + + + + + Constrain Line perpendicular to another line. + + + Perp_Lne + + + true + + + + + + + Horizontal Constrain + + + Horiz + + + true + + + false + + + + + + + Poin to Point Constrain + + + Pt_Pt + + + true + + + false - - - - 50 - 50 - - - - - 50 - 50 - + + + Point to Line Constrain - - Cnct + Pt_Lne + + + true + + + false + + + + + + + Dimension of Line of Distance from Point to Line + + + Distnce + + + true + + + false + + + 297 + + + + + + + Point to Middle Point Constrain + + + Pt_Mid_L + + + true + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + QTabWidget::South + + + 1 + + + + Setg 1 + + + + + + + 0 + 0 + + + + Snapping Points + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Snp Dst + + + + + + + Vert + + + true + + + + + + + Qt::Horizontal + + + + + + + Angl Stps + + + + + + + mm + + + 30 + + + 10 + + + + + + + Grid + + + true + + + + + + + Horiz + + + true + + + + + + + ° + + + 180 + + + 15 + + + + + + + Pnt + + + + + + + MidP + + + true + + + + + + + Angles + + + true + + + + + + + + + + + Setg 2 + + + +