diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c40110 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.xml +*.iml +.idea \ No newline at end of file diff --git a/Gui.py b/Gui.py index c58dfcd..941b27f 100644 --- a/Gui.py +++ b/Gui.py @@ -11,238 +11,39 @@ 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.QtWidgets import (QApplication, QGridLayout, QGroupBox, QHBoxLayout, - QListWidget, QListWidgetItem, QMainWindow, QMenuBar, - QPushButton, QSizePolicy, QStatusBar, QTabWidget, +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, 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(2214, 803) + fluencyCAD.resize(2192, 1109) + 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) - 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) - - 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) - self.groupBox_3.setMaximumSize(QSize(16777214, 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") - sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth()) - self.InputTab.setSizePolicy(sizePolicy2) + 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) @@ -286,7 +87,187 @@ class Ui_fluencyCAD(object): self.InputTab.addTab(self.code_tab, "") - self.gridLayout.addWidget(self.InputTab, 0, 2, 12, 1) + self.gridLayout.addWidget(self.InputTab, 0, 1, 9, 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, 2, 9, 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, 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)) + self.verticalLayout_6 = QVBoxLayout(self.groupBox_10) + self.verticalLayout_6.setObjectName(u"verticalLayout_6") + self.verticalLayout_6.setContentsMargins(5, 5, 5, 5) + self.body_list = QListWidget(self.groupBox_10) + self.body_list.setObjectName(u"body_list") + self.body_list.setSelectionRectVisible(True) + + self.verticalLayout_6.addWidget(self.body_list) + + self.groupBox_8 = QGroupBox(self.groupBox_10) + self.groupBox_8.setObjectName(u"groupBox_8") + 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") + self.gridLayout_8.setContentsMargins(2, 2, 2, 2) + self.pb_del_body = QPushButton(self.groupBox_8) + self.pb_del_body.setObjectName(u"pb_del_body") + + self.gridLayout_8.addWidget(self.pb_del_body, 0, 2, 1, 1) + + self.pb_update_body = QPushButton(self.groupBox_8) + self.pb_update_body.setObjectName(u"pb_update_body") + + self.gridLayout_8.addWidget(self.pb_update_body, 0, 0, 1, 1) + + self.pb_edt_sktch_3 = QPushButton(self.groupBox_8) + self.pb_edt_sktch_3.setObjectName(u"pb_edt_sktch_3") + + self.gridLayout_8.addWidget(self.pb_edt_sktch_3, 0, 1, 1, 1) + + + self.verticalLayout_6.addWidget(self.groupBox_8) + + + self.gridLayout.addWidget(self.groupBox_10, 7, 3, 2, 1) + + 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.verticalLayout_7.addWidget(self.sketch_list) + + 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_6.addWidget(self.pb_edt_sktch, 1, 1, 1, 1) + + self.pb_nw_sktch = QPushButton(self.groupBox_6) + self.pb_nw_sktch.setObjectName(u"pb_nw_sktch") + + self.gridLayout_6.addWidget(self.pb_nw_sktch, 1, 0, 1, 1) + + self.pb_del_sketch = QPushButton(self.groupBox_6) + self.pb_del_sketch.setObjectName(u"pb_del_sketch") + + self.gridLayout_6.addWidget(self.pb_del_sketch, 1, 2, 1, 1) + + + self.verticalLayout_7.addWidget(self.groupBox_6) + + + self.gridLayout.addWidget(self.groupBox_11, 6, 0, 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") @@ -298,20 +279,76 @@ class Ui_fluencyCAD(object): self.verticalLayout_2.addWidget(self.pushButton_2) - self.gridLayout.addWidget(self.groupBox_4, 11, 5, 1, 1) + 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") + 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) 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) + 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.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.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_2.addWidget(self.pb_rectool, 1, 1, 1, 1, Qt.AlignTop) + self.gridLayout_2.addWidget(self.line, 4, 0, 1, 2) self.pb_circtool = QPushButton(self.groupBox_2) self.pb_circtool.setObjectName(u"pb_circtool") @@ -324,6 +361,7 @@ class Ui_fluencyCAD(object): 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) @@ -334,40 +372,217 @@ class Ui_fluencyCAD(object): 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.groupBox_2, 1, 0, 1, 1) - self.gl_box = QGroupBox(self.centralwidget) - self.gl_box.setObjectName(u"gl_box") - 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) + 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.gridLayout.addWidget(self.gl_box, 0, 4, 12, 1) + 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_3, 2, 0, 1, 1) + + 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_11.addWidget(self.label, 5, 0, 1, 1) + + 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_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.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) 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) + 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) + self.tabWidget.setCurrentIndex(1) QMetaObject.connectSlotsByName(fluencyCAD) @@ -375,6 +590,17 @@ class Ui_fluencyCAD(object): def retranslateUi(self, fluencyCAD): fluencyCAD.setWindowTitle(QCoreApplication.translate("fluencyCAD", u"fluencyCAD", 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.InputTab.setTabText(self.InputTab.indexOf(self.sketch_tab), QCoreApplication.translate("fluencyCAD", u"Sketch", None)) + self.groupBox_7.setTitle(QCoreApplication.translate("fluencyCAD", u"Executive", None)) + self.pushButton_5.setText(QCoreApplication.translate("fluencyCAD", u"Load Code", None)) + self.pushButton_4.setText(QCoreApplication.translate("fluencyCAD", u"Save code", None)) + self.pb_apply_code.setText(QCoreApplication.translate("fluencyCAD", u"Apply Code", None)) + self.pushButton.setText(QCoreApplication.translate("fluencyCAD", u"Delete Code", None)) + self.InputTab.setTabText(self.InputTab.indexOf(self.code_tab), QCoreApplication.translate("fluencyCAD", u"Code", None)) + self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None)) self.groupBox.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)) @@ -382,15 +608,25 @@ class Ui_fluencyCAD(object): 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.compo_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components", 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 Bdy", None)) - self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Bdy Upd", 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_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.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_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None)) + self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", 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_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)) @@ -420,47 +656,7 @@ class Ui_fluencyCAD(object): #if QT_CONFIG(shortcut) self.pb_move_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"M", None)) #endif // QT_CONFIG(shortcut) - 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)) - 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)) self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None)) #if QT_CONFIG(statustip) @@ -470,6 +666,53 @@ class Ui_fluencyCAD(object): #if QT_CONFIG(shortcut) 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.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None)) + self.pb_enable_construct.setText(QCoreApplication.translate("fluencyCAD", u"Cstrct", None)) + self.pb_enable_snap.setText(QCoreApplication.translate("fluencyCAD", u"Snap", None)) + self.groupBox_3.setTitle(QCoreApplication.translate("fluencyCAD", u"Constrain", None)) + self.pb_con_sym.setText(QCoreApplication.translate("fluencyCAD", u"Symetrc", 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)) +#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)) +#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_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_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_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)) + self.groupBox_5.setTitle(QCoreApplication.translate("fluencyCAD", u"Snapping Points", None)) + self.label.setText(QCoreApplication.translate("fluencyCAD", u"Snp Dst", None)) + self.pb_snap_vert.setText(QCoreApplication.translate("fluencyCAD", u"Vert", None)) + self.label_2.setText(QCoreApplication.translate("fluencyCAD", u"Angl Stps", None)) + self.spinbox_snap_distance.setSuffix(QCoreApplication.translate("fluencyCAD", u"mm", None)) + self.pushButton_7.setText(QCoreApplication.translate("fluencyCAD", u"Grid", None)) + self.pb_snap_horiz.setText(QCoreApplication.translate("fluencyCAD", u"Horiz", None)) + self.spinbox_angle_steps.setSuffix(QCoreApplication.translate("fluencyCAD", u"\u00b0", None)) + self.pushButton_8.setText(QCoreApplication.translate("fluencyCAD", u"Pnt", None)) + self.pb_snap_midp.setText(QCoreApplication.translate("fluencyCAD", u"MidP", None)) + self.pb_snap_angle.setText(QCoreApplication.translate("fluencyCAD", u"Angles", None)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.widget), QCoreApplication.translate("fluencyCAD", u"Setg 1", None)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.widget1), QCoreApplication.translate("fluencyCAD", u"Setg 2", None)) + self.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None)) + self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None)) # retranslateUi diff --git a/doc/commands.md b/doc/commands.md new file mode 100644 index 0000000..646f0a3 --- /dev/null +++ b/doc/commands.md @@ -0,0 +1 @@ + pyside6-uic gui.ui > Gui.py -g python 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_widget2d.py b/drawing_modules/draw_widget2d.py index b00ef32..ea7f469 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,21 @@ 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 +374,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 +393,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 +421,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 +452,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 +474,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 +492,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 +533,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 +564,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 +714,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,15 +770,141 @@ 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 +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 + +class Sketch2d(SolverSystem): + + 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..e7429fb --- /dev/null +++ b/drawing_modules/draw_widget_solve.py @@ -0,0 +1,897 @@ +import math +import re +from copy import copy +import uuid + +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.dynamic_line_end = None # Cursor position for dynamic drawing + + 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 create_sketch(self, sketch_in ): + self.sketch = Sketch2d() + 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 + + 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.wp = self.sketch.create_2d_base() + + def create_workplane_projected(self): + self.sketch.wp = self.sketch.create_2d_base() + + 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 proj_points: + pnt = Point2D(point[0], point[1]) + # Construction + pnt.is_helper = True + print(point) + self.sketch.add_point(pnt) + + def convert_proj_lines(self, proj_lines: list): + ### same as for point + 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 = [] + 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.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 + 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.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.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 + + 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: + """ + 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.sketch.entity_len()): + # Iterate though full length because mixed list from SS + 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) + + # 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].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.sketch.lines: + 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()) + + 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.sketch.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.wp) + + if self.sketch.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.sketch.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.sketch.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.wp) + + if self.sketch.solve() == ResultFlag.OKAY: + print("Fuck yeah") + self.constrain_done.emit() + + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.sketch.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.sketch.midpoint(self.main_buffer[0], self.main_buffer[1], self.sketch.wp) + + if self.sketch.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.sketch.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.sketch.horizontal(line_selected, self.sketch.wp) + + if self.sketch.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + 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.sketch.vertical(line_selected, self.sketch.wp) + + if self.sketch.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.sketch.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.sketch.distance(e1, e2, length, self.sketch.wp) + + if self.sketch.solve() == ResultFlag.OKAY: + print("Fuck yeah") + + elif self.sketch.solve() == ResultFlag.DIDNT_CONVERGE: + print("Solve_failed - Converge") + + elif self.sketch.solve() == ResultFlag.TOO_MANY_UNKNOWNS: + print("Solve_failed - Unknowns") + + elif self.sketch.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()) + #print(local_event_pos) + + closest_point = None + min_distance = float('inf') + threshold = 10 # Distance threshold for highlighting + + 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: + 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 + + # Midpointsnap only in drawer not solver + 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 + + 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_normal = QPen(Qt.gray) + pen_normal.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) + + 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: + 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) + + # 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: + 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) + + # 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) + + # 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)) + + +### 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: int = None + + # Construction Geometry + self.is_helper: bool = False + +class Sketch2d(SolverSystem): + """ + Primary class for internal drawing based on the SolveSpace libray + """ + + 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 returns 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)) + point.id = uuid.uuid1() + + 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.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)) + + 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/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/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py index 658afa7..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 @@ -52,6 +53,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 +66,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,13 +147,18 @@ 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): # Create a vtkPoints object and store the points in it points = vtk.vtkPoints() @@ -262,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() @@ -425,8 +438,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") @@ -661,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 ) @@ -691,12 +761,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/gui.ui b/gui.ui index fbeea87..4cfc2c0 100644 --- a/gui.ui +++ b/gui.ui @@ -6,8 +6,8 @@ 0 0 - 2214 - 803 + 2192 + 1109 @@ -15,393 +15,7 @@ - - - - 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 - - - - - - - - - - - - - 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 - - - - - 16777214 - 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 - - - - - - - + @@ -467,7 +81,338 @@ - + + + + + 0 + 4 + + + + + 12 + + + + Model Viewer + + + + -1 + + + 12 + + + + + + + + Modify + + + + + + Rev + + + + + + + Extrd + + + + + + + Arry + + + + + + + Cut + + + + + + + Comb + + + + + + + Mve + + + + + + + + + + + 0 + 50 + + + + Components + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Bodys / Operations + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + true + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Tools + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Del + + + + + + + Upd + + + + + + + Nothing + + + + + + + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + Sketch + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + true + + + + + + + + 0 + 0 + + + + Tools + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Edt + + + + + + + Add + + + + + + + Del + + + + + + + + + + + + + + 0 + 50 + + + + Assembly Tools + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + + Cnct + + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + - Cnct + + + + + + + Export @@ -483,6 +428,138 @@ + + + + + 0 + 50 + + + + Component Tools + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + New + + + + + + + true + + + + 0 + 0 + + + + + 50 + 50 + + + + + 50 + 50 + + + + Qt::LeftToRight + + + Del + + + + + + + + + + + 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 + + + + + + @@ -491,20 +568,23 @@ 0 + + + 200 + 16777215 + + Drawing - - - - - Rctgl - - - true - - - false + + + 10 + + + + + Qt::Horizontal @@ -532,6 +612,9 @@ false + + 98 + @@ -553,35 +636,353 @@ + + + + Rctgl + + + true + + + false + + + + + + + Cstrct + + + true + + + + + + + Snap + + + true + + + true + + + - - + + - + 0 - 4 + 0 - - - 12 - + + + 200 + 16777213 + - Model Viewer + Constrain - - - -1 - - - 12 - + + + + + Symetrc + + + true + + + false + + + + + + + Vertical Constrain + + + 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 + + + + + + + Point to Line Constrain + + + 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 + + + + @@ -589,12 +990,43 @@ 0 0 - 2214 + 2192 24 + + + File + + + + + + + + + Settings + + + + + + + New + + + + + Load + + + + + Recent + + diff --git a/main.py b/main.py index c069699..2b5588a 100644 --- a/main.py +++ b/main.py @@ -5,12 +5,12 @@ import uuid import names -from PySide6.QtCore import Qt, QPoint, Signal -from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QCheckBox, QPushButton +from PySide6.QtCore import Qt, QPoint, Signal, QSize +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 -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 @@ -18,11 +18,11 @@ from dataclasses import dataclass, field # main, draw_widget, gl_widget - 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 @@ -48,7 +48,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') @@ -76,6 +75,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) @@ -98,11 +98,11 @@ class MainWindow(QMainWindow): size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.sketchWidget.setSizePolicy(size_policy) - ### Main Model - self.model = { - 'sketch': {}, + ### Main Model -OLD ? + """self.model = { + 'sketches': {}, 'operation': {}, - } + }""" self.list_selected = [] #self.ui.pb_apply_code.pressed.connect(self.check_current_tab) @@ -113,7 +113,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) @@ -138,33 +138,220 @@ class MainWindow(QMainWindow): self.send_command.connect(self.custom_3D_Widget.on_receive_command) - def on_flip_face(self): - self.send_command.emit("flip") + self.ui.actionNew_Project.triggered.connect(self.new_project) + + self.project = Project() + self.new_project() + + ### COMPOS + ### COMPOS + + 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() + + # 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 + + # 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)}" + compo.descript = "Initial Component" + compo.sketches = {} + compo.bodies = {} + 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.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): + # 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): - 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] + + self.sketchWidget.reset_buffers() + self.sketchWidget.create_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) + ## 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() - self.sketchWidget.create_proj_points(points) - self.sketchWidget.create_proj_lines(selected_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 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_to_compo(self): + """ + Add sketch to component + :return: + """ + 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) + 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): + 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: + self.ui.sketch_list.clear() + self.ui.body_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) + + for body in self.project.timeline[compo_id].bodies: + self.ui.body_list.addItem(body) + + 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() + + 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() + sel_compo = self.project.timeline[self.get_activated_compo()] + sketch = sel_compo.sketches[name].original_sketch + + self.sketchWidget.set_sketch(sketch) + + self.sketchWidget.update() + + def del_sketch(self): + selected = self.ui.sketch_list.currentItem() + name = selected.text() + sel_compo = self.project.timeline[self.get_activated_compo()] + sketch = sel_compo.sketches[name] + + 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") def act_line_mode(self): if not self.ui.pb_linetool.isChecked(): @@ -222,9 +409,12 @@ class MainWindow(QMainWindow): self.sketchWidget.reset_buffers() def draw_mesh(self): + name = self.ui.body_list.currentItem().text() print("selected_for disp", name) - model = self.model['operation'][name]['sdf_object'] + + 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) @@ -239,96 +429,6 @@ 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'])) - - return points_for_sdf - - 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) - - print("packed_lines", points_for_interact) - - return points_for_interact - - 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 - } - - self.model['sketch'][element['id']] = element - print(self.model) - - self.ui.sketch_list.addItem(name) - self.ui.pb_linetool.setChecked(False) - self.sketchWidget.line_mode = False - - items = self.ui.sketch_list.findItems(name, 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'] - - self.sketchWidget.update() - print("model",self.model) - print("widget", self.sketchWidget.slv_points_main) - - def del_sketch(self): - 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 '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: - 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}") - - # 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 'sketch' or 'operation' dictionary.") - else: - print("No item selected.") - def update_body(self): pass @@ -349,55 +449,20 @@ 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'] + + sel_compo = self.project.timeline[self.get_activated_compo()] + #print(sel_compo) + sketch = sel_compo.sketches[name] + #print(sketch) + points = sketch.sdf_points if points[-1] == points[0]: #detect loop that causes problems in mesh generation @@ -406,12 +471,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") - - geo = Geometry() + #print("Extrude cancelled") normal = self.custom_3D_Widget.selected_normal #print("Normie enter", normal) @@ -425,31 +488,42 @@ 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) + + # Create body element and assign known stuff name_op = f"extrd-{name}" - element = { - 'id': name_op, - 'type': 'extrude', - 'sdf_object': f, - } + + body = Body() + body.sketch = sketch #we add the sketches for reference here + body.id = name_op + body.sdf_body = f ### Interactor - lines = self.convert_lines_for_interactor() + interactor = Interactor() + interactor.add_lines_for_interactor(sketch.interactor_lines) + interactor.invert = invert if not invert: - edges = interactor_mesh.generate_mesh(lines, 0, length) + edges = interactor_mesh.generate_mesh(interactor.lines, 0, length) else: - edges = interactor_mesh.generate_mesh(lines, 0, -length) + edges = interactor_mesh.generate_mesh(interactor.lines, 0, -length) - offset_vector = geo.vector_to_centroid(None, centroid, normal) + sel_compo.bodies[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] + interactor.edges = edges + interactor.offset_vector = offset_vector + body.interactor = interactor + 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) @@ -457,13 +531,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, @@ -471,13 +555,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: @@ -487,8 +580,122 @@ 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, + 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 + 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 + descript : a basic description + materil : Speicfy a material for pbr rendering + """ + id = None + sketches: dict = None + bodies: dict = 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 sketches""" + + # Save the incomng sketch from the 2D widget for late redit + original_sketch = None + + id = None + + # Space Information + origin = None + slv_plane = None + normal = None + + # Points in UI form the sketches 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 + + interactor_lines: list = None + + # Points coming back from the 3D-Widget as projection to draw on + 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 + 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 -class Geometry: def angle_between_normals(self, normal1, normal2): # Ensure the vectors are normalized n1 = normal1 / np.linalg.norm(normal1) @@ -519,44 +726,48 @@ 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): + points_for_sdf = [] + for point in points: + if point.is_helper is False: + print("point", point) + points_for_sdf.append(self.translate_points_tup(point.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) + 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) - # Project this vector onto the normal to get the required translation along the normal - translation_along_normal = np.dot(center_to_centroid, normal) * normal + self.interactor_lines = filtered_lines - 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 +791,69 @@ class Geometry: return f +@dataclass +class Interactor: + """Helper mesh consisting of edges for selection""" + lines = None + faces = None + body = None + offset_vector = None + edges = 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): + """Takes Line2D objects from the sketch widget and preparesit for interactor mesh. + Translates coordinates.""" + + points_for_interact = [] + for point_to_poly in input_lines: + 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 + 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 + interactor = 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 - return f - +class Output: def export_mesh(self, sdf_object): """FINAL EXPORT""" result_points = sdf_object.generate() @@ -607,51 +872,13 @@ class Geometry: except Exception as e: print("Error executing code:", e) - -@dataclass -class Component: - """The base container combinging all related elements""" - id = None - sketch = None - interactor = None - body = 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 - +class Project: + """Project -> Timeline -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render""" + timeline: Timeline = None + assembly: Assembly = None if __name__ == "__main__": - app = QApplication([]) + app = QApplication() window = MainWindow() window.show() app.exec() diff --git a/modules/out.stl b/modules/out.stl deleted file mode 100644 index 811aa0d..0000000 Binary files a/modules/out.stl and /dev/null differ diff --git a/requirements.txt b/requirements.txt index ff39233..b6000b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +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 +vulkan==1.3.275.0 +zstandard==0.22.0