Compare commits
No commits in common. "3ab3078d395e64eac5ce39f18b2ff8963a0eb424" and "15cc30edac596c492b58a8e58b0d7cd018315308" have entirely different histories.
3ab3078d39
...
15cc30edac
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.xml
|
||||||
|
*.iml
|
||||||
|
.idea
|
11
.idea/fluency.iml
generated
Normal file
11
.idea/fluency.iml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/sdfcad" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
23
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
23
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="10">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="python-rtmidi" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="PyAudio" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="rtmidi" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="scikit-image" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="python" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="PySide6" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="PySide6-Essentials" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="PySide6-Addons" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="lazy_loader" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="typing_extensions" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
10
.idea/misc.xml
generated
Normal file
10
.idea/misc.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.11 (fluency)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (fluency)" project-jdk-type="Python SDK" />
|
||||||
|
<component name="PyCharmProfessionalAdvertiser">
|
||||||
|
<option name="shown" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/fluency.iml" filepath="$PROJECT_DIR$/.idea/fluency.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/sdfcad" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
132
.idea/workspace.xml
generated
Normal file
132
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="init">
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/fluency.iml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/2dtest.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/fluencyb.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/main.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/meshtest.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/modules/gl_widget.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/modules/out.stl" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/side_fluency.py" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/vulkan.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/fluency.py" beforeDir="false" afterPath="$PROJECT_DIR$/fluency.py" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Python Script" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
<option name="ROOT_SYNC" value="DONT_SYNC" />
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownSettingsMigration">
|
||||||
|
<option name="stateVersion" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="ProblemsViewState">
|
||||||
|
<option name="selectedTabId" value="QODANA_PROBLEMS_VIEW_TAB" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 6
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2aDywQvESFCKbJK4JUVHIhkN4S6" />
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"Python.2dtest.executor": "Run",
|
||||||
|
"Python.3d_windows.executor": "Run",
|
||||||
|
"Python.fluency.executor": "Run",
|
||||||
|
"Python.fluencyb.executor": "Run",
|
||||||
|
"Python.gl_widget.executor": "Run",
|
||||||
|
"Python.main.executor": "Run",
|
||||||
|
"Python.meshtest.executor": "Run",
|
||||||
|
"Python.side_fluency.executor": "Run",
|
||||||
|
"Python.vulkan.executor": "Run",
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"last_opened_file_path": "/Volumes/Data_drive/Programming/fluency/modules",
|
||||||
|
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="QodanaReportsService">
|
||||||
|
<option name="descriptions">
|
||||||
|
<ReportDescription localRun="true" path="/private/var/folders/kg/zm48w_r96yb68mlbzvb9gtq40000gn/T/qodana_output/qodana.sarif.json" reportGuid="5f5b823c-c594-48c5-ae1f-062e30303918" reportId="fluency/qodana/2024-02-04" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/modules" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-python-sdk-09665e90c3a7-d3b881c8e49f-com.jetbrains.pycharm.community.sharedIndexes.bundled-PC-233.15026.15" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="" />
|
||||||
|
<created>1703867682707</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1703867682707</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00001" summary="init">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1703951701948</created>
|
||||||
|
<option name="number" value="00001" />
|
||||||
|
<option name="presentableId" value="LOCAL-00001" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1703951701948</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="2" />
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<ignored-roots>
|
||||||
|
<path value="$PROJECT_DIR$/pythonProject" />
|
||||||
|
</ignored-roots>
|
||||||
|
<MESSAGE value="init" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="init" />
|
||||||
|
</component>
|
||||||
|
</project>
|
718
Gui.py
Normal file
718
Gui.py
Normal file
@ -0,0 +1,718 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Form generated from reading UI file 'gui.ui'
|
||||||
|
##
|
||||||
|
## Created by: Qt User Interface Compiler version 6.6.1
|
||||||
|
##
|
||||||
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||||
|
QMetaObject, QObject, QPoint, QRect,
|
||||||
|
QSize, QTime, QUrl, Qt)
|
||||||
|
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(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.InputTab = QTabWidget(self.centralwidget)
|
||||||
|
self.InputTab.setObjectName(u"InputTab")
|
||||||
|
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)
|
||||||
|
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
|
||||||
|
self.InputTab.addTab(self.sketch_tab, "")
|
||||||
|
self.code_tab = QWidget()
|
||||||
|
self.code_tab.setObjectName(u"code_tab")
|
||||||
|
self.verticalLayout = QVBoxLayout(self.code_tab)
|
||||||
|
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||||
|
self.textEdit = QTextEdit(self.code_tab)
|
||||||
|
self.textEdit.setObjectName(u"textEdit")
|
||||||
|
|
||||||
|
self.verticalLayout.addWidget(self.textEdit)
|
||||||
|
|
||||||
|
self.groupBox_7 = QGroupBox(self.code_tab)
|
||||||
|
self.groupBox_7.setObjectName(u"groupBox_7")
|
||||||
|
self.gridLayout_5 = QGridLayout(self.groupBox_7)
|
||||||
|
self.gridLayout_5.setObjectName(u"gridLayout_5")
|
||||||
|
self.pushButton_5 = QPushButton(self.groupBox_7)
|
||||||
|
self.pushButton_5.setObjectName(u"pushButton_5")
|
||||||
|
|
||||||
|
self.gridLayout_5.addWidget(self.pushButton_5, 2, 0, 1, 1)
|
||||||
|
|
||||||
|
self.pushButton_4 = QPushButton(self.groupBox_7)
|
||||||
|
self.pushButton_4.setObjectName(u"pushButton_4")
|
||||||
|
|
||||||
|
self.gridLayout_5.addWidget(self.pushButton_4, 2, 1, 1, 1)
|
||||||
|
|
||||||
|
self.pb_apply_code = QPushButton(self.groupBox_7)
|
||||||
|
self.pb_apply_code.setObjectName(u"pb_apply_code")
|
||||||
|
|
||||||
|
self.gridLayout_5.addWidget(self.pb_apply_code, 1, 0, 1, 1)
|
||||||
|
|
||||||
|
self.pushButton = QPushButton(self.groupBox_7)
|
||||||
|
self.pushButton.setObjectName(u"pushButton")
|
||||||
|
|
||||||
|
self.gridLayout_5.addWidget(self.pushButton, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.verticalLayout.addWidget(self.groupBox_7)
|
||||||
|
|
||||||
|
self.InputTab.addTab(self.code_tab, "")
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.InputTab, 0, 1, 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")
|
||||||
|
self.verticalLayout_2 = QVBoxLayout(self.groupBox_4)
|
||||||
|
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
|
||||||
|
self.pushButton_2 = QPushButton(self.groupBox_4)
|
||||||
|
self.pushButton_2.setObjectName(u"pushButton_2")
|
||||||
|
|
||||||
|
self.verticalLayout_2.addWidget(self.pushButton_2)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.groupBox_4, 6, 3, 1, 1)
|
||||||
|
|
||||||
|
self.compo_tool_box = QGroupBox(self.centralwidget)
|
||||||
|
self.compo_tool_box.setObjectName(u"compo_tool_box")
|
||||||
|
self.compo_tool_box.setMinimumSize(QSize(0, 50))
|
||||||
|
self.gridLayout_9 = QGridLayout(self.compo_tool_box)
|
||||||
|
self.gridLayout_9.setObjectName(u"gridLayout_9")
|
||||||
|
self.new_compo = QPushButton(self.compo_tool_box)
|
||||||
|
self.new_compo.setObjectName(u"new_compo")
|
||||||
|
self.new_compo.setMinimumSize(QSize(50, 50))
|
||||||
|
self.new_compo.setMaximumSize(QSize(50, 50))
|
||||||
|
|
||||||
|
self.gridLayout_9.addWidget(self.new_compo, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
self.del_compo = QPushButton(self.compo_tool_box)
|
||||||
|
self.del_compo.setObjectName(u"del_compo")
|
||||||
|
self.del_compo.setEnabled(True)
|
||||||
|
sizePolicy3.setHeightForWidth(self.del_compo.sizePolicy().hasHeightForWidth())
|
||||||
|
self.del_compo.setSizePolicy(sizePolicy3)
|
||||||
|
self.del_compo.setMinimumSize(QSize(50, 50))
|
||||||
|
self.del_compo.setMaximumSize(QSize(50, 50))
|
||||||
|
self.del_compo.setLayoutDirection(Qt.LeftToRight)
|
||||||
|
|
||||||
|
self.gridLayout_9.addWidget(self.del_compo, 0, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.compo_tool_box, 9, 0, 1, 1)
|
||||||
|
|
||||||
|
self.groupBox_9 = QGroupBox(self.centralwidget)
|
||||||
|
self.groupBox_9.setObjectName(u"groupBox_9")
|
||||||
|
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")
|
||||||
|
sizePolicy3.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth())
|
||||||
|
self.groupBox_2.setSizePolicy(sizePolicy3)
|
||||||
|
self.groupBox_2.setMaximumSize(QSize(200, 16777215))
|
||||||
|
self.gridLayout_2 = QGridLayout(self.groupBox_2)
|
||||||
|
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||||
|
self.gridLayout_2.setContentsMargins(10, -1, -1, -1)
|
||||||
|
self.line = QFrame(self.groupBox_2)
|
||||||
|
self.line.setObjectName(u"line")
|
||||||
|
self.line.setFrameShape(QFrame.HLine)
|
||||||
|
self.line.setFrameShadow(QFrame.Sunken)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.line, 4, 0, 1, 2)
|
||||||
|
|
||||||
|
self.pb_circtool = QPushButton(self.groupBox_2)
|
||||||
|
self.pb_circtool.setObjectName(u"pb_circtool")
|
||||||
|
self.pb_circtool.setCheckable(True)
|
||||||
|
self.pb_circtool.setAutoExclusive(False)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.pb_circtool, 2, 0, 1, 1, Qt.AlignTop)
|
||||||
|
|
||||||
|
self.pb_slotool = QPushButton(self.groupBox_2)
|
||||||
|
self.pb_slotool.setObjectName(u"pb_slotool")
|
||||||
|
self.pb_slotool.setCheckable(True)
|
||||||
|
self.pb_slotool.setAutoExclusive(False)
|
||||||
|
self.pb_slotool.setAutoRepeatInterval(98)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.pb_slotool, 2, 1, 1, 1, Qt.AlignTop)
|
||||||
|
|
||||||
|
self.pb_linetool = QPushButton(self.groupBox_2)
|
||||||
|
self.pb_linetool.setObjectName(u"pb_linetool")
|
||||||
|
self.pb_linetool.setCheckable(True)
|
||||||
|
self.pb_linetool.setAutoExclusive(False)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.pb_linetool, 1, 0, 1, 1)
|
||||||
|
|
||||||
|
self.pb_rectool = QPushButton(self.groupBox_2)
|
||||||
|
self.pb_rectool.setObjectName(u"pb_rectool")
|
||||||
|
self.pb_rectool.setCheckable(True)
|
||||||
|
self.pb_rectool.setAutoExclusive(False)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.pb_rectool, 1, 1, 1, 1, Qt.AlignTop)
|
||||||
|
|
||||||
|
self.pb_enable_construct = QPushButton(self.groupBox_2)
|
||||||
|
self.pb_enable_construct.setObjectName(u"pb_enable_construct")
|
||||||
|
self.pb_enable_construct.setCheckable(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.pb_enable_construct, 5, 0, 1, 1)
|
||||||
|
|
||||||
|
self.pb_enable_snap = QPushButton(self.groupBox_2)
|
||||||
|
self.pb_enable_snap.setObjectName(u"pb_enable_snap")
|
||||||
|
self.pb_enable_snap.setCheckable(True)
|
||||||
|
self.pb_enable_snap.setChecked(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.pb_enable_snap, 5, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout.addWidget(self.groupBox_2, 1, 0, 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_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, 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)
|
||||||
|
# setupUi
|
||||||
|
|
||||||
|
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))
|
||||||
|
self.pb_arrayop.setText(QCoreApplication.translate("fluencyCAD", u"Arry", None))
|
||||||
|
self.pb_cutop.setText(QCoreApplication.translate("fluencyCAD", u"Cut", None))
|
||||||
|
self.pb_combop.setText(QCoreApplication.translate("fluencyCAD", u"Comb", None))
|
||||||
|
self.pb_moveop.setText(QCoreApplication.translate("fluencyCAD", u"Mve", None))
|
||||||
|
self.compo_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components", None))
|
||||||
|
self.groupBox_10.setTitle(QCoreApplication.translate("fluencyCAD", u"Bodys / Operations", None))
|
||||||
|
self.groupBox_8.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
|
||||||
|
self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
|
||||||
|
self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
|
||||||
|
self.pb_edt_sktch_3.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None))
|
||||||
|
self.groupBox_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"<W>orking Plane at 0, 0, 0", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.pb_origin_wp.setText(QCoreApplication.translate("fluencyCAD", u"WP Origin", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.pb_origin_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"W", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.pb_origin_face.setToolTip(QCoreApplication.translate("fluencyCAD", u"Working Plane >P<rojection at selected edges face", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.pb_origin_face.setText(QCoreApplication.translate("fluencyCAD", u" WP Face", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.pb_origin_face.setShortcut(QCoreApplication.translate("fluencyCAD", u"P", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.pb_flip_face.setToolTip(QCoreApplication.translate("fluencyCAD", u"Flip >N<ormal of projected mesh.", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.pb_flip_face.setText(QCoreApplication.translate("fluencyCAD", u"WP Flip", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.pb_flip_face.setShortcut(QCoreApplication.translate("fluencyCAD", u"N", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.pb_move_wp.setToolTip(QCoreApplication.translate("fluencyCAD", u">M<ove projected mesh workplane", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.pb_move_wp.setText(QCoreApplication.translate("fluencyCAD", u"WP Mve", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.pb_move_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"M", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None))
|
||||||
|
self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None))
|
||||||
|
self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None))
|
||||||
|
#if QT_CONFIG(statustip)
|
||||||
|
self.pb_linetool.setStatusTip(QCoreApplication.translate("fluencyCAD", u"Line >S<egment", None))
|
||||||
|
#endif // QT_CONFIG(statustip)
|
||||||
|
self.pb_linetool.setText(QCoreApplication.translate("fluencyCAD", u"Line", None))
|
||||||
|
#if QT_CONFIG(shortcut)
|
||||||
|
self.pb_linetool.setShortcut(QCoreApplication.translate("fluencyCAD", u"S", None))
|
||||||
|
#endif // QT_CONFIG(shortcut)
|
||||||
|
self.pb_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
|
||||||
|
|
9
LICENSE
9
LICENSE
@ -1,9 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2025 Thomas Herrmann
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,4 +0,0 @@
|
|||||||
# fluencyCAD
|
|
||||||
|
|
||||||
A CAD program based on QT - sdfCAD - Solvespace and VTK meant to deliver a fluent distraction free CAD experience with alot of freedom thanks to sdf based meshes.
|
|
||||||
|
|
1
doc/commands.md
Normal file
1
doc/commands.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
pyside6-uic gui.ui > Gui.py -g python
|
35
doc/flow.md
Normal file
35
doc/flow.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Signal Flow
|
||||||
|
## 2D SketchWidget
|
||||||
|
|
||||||
|
- 2D QPoint form custom Qpainter widget in linear space
|
||||||
|
- 2D QPoint ot cartesian space
|
||||||
|
- 2D tuple into slvspace dict system and solvespace
|
||||||
|
- get calced position from Solvespace solver
|
||||||
|
- add to internal reference dict
|
||||||
|
- Transform to linear QPainter space for display to show
|
||||||
|
|
||||||
|
## 3D custom Widget
|
||||||
|
|
||||||
|
- Take Tuple points form solvespace main dict
|
||||||
|
- Draw Interactor and sdfCAD model
|
||||||
|
|
||||||
|
### Select and Project
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
### 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.
|
3
doc/helper_commands.md
Normal file
3
doc/helper_commands.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## Compile ui file
|
||||||
|
pyside6-uic gui.ui > Gui.py -g python
|
||||||
|
|
916
drawing_modules/draw_widget2d.py
Normal file
916
drawing_modules/draw_widget2d.py
Normal file
@ -0,0 +1,916 @@
|
|||||||
|
import math
|
||||||
|
import re
|
||||||
|
from copy import copy
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PySide6.QtWidgets import QApplication, QWidget, QMessageBox, QInputDialog
|
||||||
|
from PySide6.QtGui import QPainter, QPen, QColor, QTransform
|
||||||
|
from PySide6.QtCore import Qt, QPoint, QPointF, Signal, QLine
|
||||||
|
from python_solvespace import SolverSystem, ResultFlag
|
||||||
|
|
||||||
|
|
||||||
|
class SketchWidget(QWidget):
|
||||||
|
constrain_done = Signal()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.line_draw_buffer = [None, None]
|
||||||
|
self.drag_buffer = [None, None]
|
||||||
|
self.main_buffer = [None, None]
|
||||||
|
|
||||||
|
self.hovered_point = None
|
||||||
|
self.selected_line = None
|
||||||
|
|
||||||
|
self.snapping_range = 20 # Range in pixels for snapping
|
||||||
|
self.zoom = 1
|
||||||
|
|
||||||
|
self.setMouseTracking(True)
|
||||||
|
self.mouse_mode = False
|
||||||
|
self.solv = SolverSystem()
|
||||||
|
|
||||||
|
self.sketch = 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]
|
||||||
|
self.drag_buffer = [None, None]
|
||||||
|
self.main_buffer = [None, None]
|
||||||
|
|
||||||
|
def set_points(self, points: list):
|
||||||
|
self.points = points
|
||||||
|
#self.update()
|
||||||
|
|
||||||
|
def create_workplane(self):
|
||||||
|
self.sketch.working_plane = self.solv.create_2d_base()
|
||||||
|
|
||||||
|
def create_workplane_projected(self):
|
||||||
|
self.sketch.working_plane = self.solv.create_2d_base()
|
||||||
|
|
||||||
|
def convert_proj_points(self):
|
||||||
|
out_points = []
|
||||||
|
for point in self.sketch.proj_points:
|
||||||
|
x, y = point
|
||||||
|
coord = QPoint(x, y)
|
||||||
|
out_points.append(coord)
|
||||||
|
|
||||||
|
self.sketch.proj_points = out_points
|
||||||
|
|
||||||
|
def convert_proj_lines(self):
|
||||||
|
out_lines = []
|
||||||
|
for line in self.sketch.proj_lines:
|
||||||
|
start = QPoint(line[0][0], line[0][1])
|
||||||
|
end = QPoint(line[1][0], line[1][1])
|
||||||
|
coord = QLine(start, end)
|
||||||
|
out_lines.append(coord)
|
||||||
|
self.sketch.proj_lines = out_lines
|
||||||
|
|
||||||
|
def find_duplicate_points_2d(self, edges):
|
||||||
|
points = []
|
||||||
|
seen = set()
|
||||||
|
duplicates = []
|
||||||
|
|
||||||
|
for edge in edges:
|
||||||
|
for point in edge:
|
||||||
|
# Extract only x and y coordinates
|
||||||
|
point_2d = (point[0], point[1])
|
||||||
|
if point_2d in seen:
|
||||||
|
if point_2d not in duplicates:
|
||||||
|
duplicates.append(point_2d)
|
||||||
|
else:
|
||||||
|
seen.add(point_2d)
|
||||||
|
points.append(point_2d)
|
||||||
|
|
||||||
|
return duplicates
|
||||||
|
|
||||||
|
def normal_to_quaternion(self, normal):
|
||||||
|
normal = np.array(normal)
|
||||||
|
#normal = normal / np.linalg.norm(normal)
|
||||||
|
|
||||||
|
axis = np.cross([0, 0, 1], normal)
|
||||||
|
if np.allclose(axis, 0):
|
||||||
|
axis = np.array([1, 0, 0])
|
||||||
|
else:
|
||||||
|
axis = axis / np.linalg.norm(axis) # Normalize the axis
|
||||||
|
|
||||||
|
angle = np.arccos(np.dot([0, 0, 1], normal))
|
||||||
|
|
||||||
|
qw = np.cos(angle / 2)
|
||||||
|
sin_half_angle = np.sin(angle / 2)
|
||||||
|
qx, qy, qz = axis * sin_half_angle # This will now work correctly
|
||||||
|
|
||||||
|
return qw, qx, qy, qz
|
||||||
|
|
||||||
|
def create_workplane_space(self, points, normal):
|
||||||
|
print("edges", points)
|
||||||
|
origin = self.find_duplicate_points_2d(points)
|
||||||
|
print(origin)
|
||||||
|
x, y = origin[0]
|
||||||
|
origin = QPoint(x, y)
|
||||||
|
|
||||||
|
origin_handle = self.get_handle_from_ui_point(origin)
|
||||||
|
qw, qx, qy, qz = self.normal_to_quaternion(normal)
|
||||||
|
|
||||||
|
slv_normal = self.solv.add_normal_3d(qw, qx, qy, qz)
|
||||||
|
self.sketch.working_plane = self.solv.add_work_plane(origin_handle, slv_normal)
|
||||||
|
print(self.sketch.working_plane)
|
||||||
|
|
||||||
|
def get_handle_nr(self, input_str: str) -> int:
|
||||||
|
# Define the regex pattern to extract the handle number
|
||||||
|
pattern = r"handle=(\d+)"
|
||||||
|
|
||||||
|
# Use re.search to find the handle number in the string
|
||||||
|
match = re.search(pattern, input_str)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
handle_number = int(match.group(1))
|
||||||
|
print(f"Handle number: {handle_number}")
|
||||||
|
return int(handle_number)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Handle number not found.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_keys(self, d: dict, target: QPoint) -> list:
|
||||||
|
result = []
|
||||||
|
path = []
|
||||||
|
print(d)
|
||||||
|
print(target)
|
||||||
|
for k, v in d.items():
|
||||||
|
path.append(k)
|
||||||
|
if isinstance(v, dict):
|
||||||
|
self.get_keys(v, target)
|
||||||
|
if v == target:
|
||||||
|
result.append(copy(path))
|
||||||
|
path.pop()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_handle_from_ui_point(self, ui_point: QPoint):
|
||||||
|
"""Input QPoint and you shall reveive a slvs entity handle!"""
|
||||||
|
for point in self.sketch.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))
|
||||||
|
|
||||||
|
def check_all_points(self,) -> list:
|
||||||
|
old_points_ui = []
|
||||||
|
new_points_ui = []
|
||||||
|
|
||||||
|
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()):
|
||||||
|
# Iterate though full length because mixed list from SS
|
||||||
|
entity = self.solv.entity(i)
|
||||||
|
if entity.is_point_2d() and self.solv.params(entity.params):
|
||||||
|
x_tbu, y_tbu = self.solv.params(entity.params)
|
||||||
|
point_solved = QPoint(x_tbu, y_tbu)
|
||||||
|
new_points_ui.append(point_solved)
|
||||||
|
|
||||||
|
# Now we have old_points_ui and new_points_ui, let's compare them
|
||||||
|
differences = []
|
||||||
|
|
||||||
|
if len(old_points_ui) != len(new_points_ui):
|
||||||
|
print(f"Length mismatch {len(old_points_ui)} - {len(new_points_ui)}")
|
||||||
|
|
||||||
|
for index, (old_point, new_point) in enumerate(zip(old_points_ui, new_points_ui)):
|
||||||
|
if old_point != new_point:
|
||||||
|
differences.append((index, old_point, new_point))
|
||||||
|
|
||||||
|
return differences
|
||||||
|
|
||||||
|
def update_ui_points(self, point_list: list):
|
||||||
|
# Print initial state of slv_points_main
|
||||||
|
# print("Initial slv_points_main:", self.slv_points_main)
|
||||||
|
print("Change list:", point_list)
|
||||||
|
|
||||||
|
if len(point_list) > 0:
|
||||||
|
for tbu_points_idx in point_list:
|
||||||
|
# Each tbu_points_idx is a tuple: (index, old_point, new_point)
|
||||||
|
index, old_point, new_point = tbu_points_idx
|
||||||
|
|
||||||
|
# Update the point in slv_points_main
|
||||||
|
self.sketch.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.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]:
|
||||||
|
line_needs_update['ui_points'][1] = new_point
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
local_event_pos = self.viewport_to_local_coord(event.pos())
|
||||||
|
|
||||||
|
if event.button() == Qt.LeftButton and not self.mouse_mode:
|
||||||
|
self.drag_buffer[1] = local_event_pos
|
||||||
|
|
||||||
|
print("Le main buffer", self.drag_buffer)
|
||||||
|
|
||||||
|
if len(self.main_buffer) == 2:
|
||||||
|
entry = self.drag_buffer[0]
|
||||||
|
new_params = self.drag_buffer[1].x(), self.drag_buffer[1].y()
|
||||||
|
self.solv.set_params(entry.params, new_params)
|
||||||
|
|
||||||
|
self.solv.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())
|
||||||
|
|
||||||
|
relation_point = {
|
||||||
|
'handle_nr': None,
|
||||||
|
'solv_handle': None,
|
||||||
|
'ui_point': None,
|
||||||
|
'part_of_entity': None
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_line = {
|
||||||
|
'handle_nr': None,
|
||||||
|
'solv_handle': None,
|
||||||
|
'solv_entity_points': None,
|
||||||
|
'ui_points': None
|
||||||
|
}
|
||||||
|
|
||||||
|
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]:
|
||||||
|
self.line_draw_buffer[0] = clicked_pos
|
||||||
|
u = clicked_pos.x()
|
||||||
|
v = clicked_pos.y()
|
||||||
|
|
||||||
|
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))
|
||||||
|
relation_point['handle_nr'] = handle_nr
|
||||||
|
relation_point['solv_handle'] = point
|
||||||
|
relation_point['ui_point'] = clicked_pos
|
||||||
|
|
||||||
|
self.sketch.slv_points.append(relation_point)
|
||||||
|
|
||||||
|
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.sketch.working_plane)
|
||||||
|
|
||||||
|
relation_point = {} # Reinitialize the dictionary
|
||||||
|
handle_nr = self.get_handle_nr(str(point2))
|
||||||
|
relation_point['handle_nr'] = handle_nr
|
||||||
|
relation_point['solv_handle'] = point2
|
||||||
|
relation_point['ui_point'] = clicked_pos
|
||||||
|
|
||||||
|
self.sketch.slv_points.append(relation_point)
|
||||||
|
|
||||||
|
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.sketch.working_plane)
|
||||||
|
|
||||||
|
relation_line = {} # Reinitialize the dictionary
|
||||||
|
handle_nr_line = self.get_handle_nr(str(line))
|
||||||
|
relation_line['handle_nr'] = handle_nr_line
|
||||||
|
relation_line['solv_handle'] = line
|
||||||
|
relation_line['solv_entity_points'] = (point_slv1, point_slv2)
|
||||||
|
relation_line['ui_points'] = [self.line_draw_buffer[0], self.line_draw_buffer[1]]
|
||||||
|
|
||||||
|
# Track relationship of point in line
|
||||||
|
relation_point['part_of_entity'] = handle_nr_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]
|
||||||
|
self.line_draw_buffer[1] = None
|
||||||
|
|
||||||
|
# Track Relationship
|
||||||
|
# Points
|
||||||
|
|
||||||
|
if event.button() == Qt.LeftButton and self.mouse_mode == "pt_pt":
|
||||||
|
if self.hovered_point and not self.main_buffer[0]:
|
||||||
|
self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point)
|
||||||
|
|
||||||
|
elif self.main_buffer[0]:
|
||||||
|
self.main_buffer[1] = self.get_handle_from_ui_point(self.hovered_point)
|
||||||
|
|
||||||
|
if self.main_buffer[0] and self.main_buffer[1]:
|
||||||
|
print("buf", self.main_buffer)
|
||||||
|
|
||||||
|
self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane)
|
||||||
|
|
||||||
|
if self.solv.solve() == ResultFlag.OKAY:
|
||||||
|
print("Fuck yeah")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE:
|
||||||
|
print("Solve_failed - Converge")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS:
|
||||||
|
print("Solve_failed - Unknowns")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.INCONSISTENT:
|
||||||
|
print("Solve_failed - Incons")
|
||||||
|
self.constrain_done.emit()
|
||||||
|
self.main_buffer = [None, None]
|
||||||
|
|
||||||
|
if event.button() == Qt.LeftButton and self.mouse_mode == "pt_line":
|
||||||
|
print("ptline")
|
||||||
|
line_selected = None
|
||||||
|
|
||||||
|
if self.hovered_point and not self.main_buffer[1]:
|
||||||
|
self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point)
|
||||||
|
|
||||||
|
elif self.main_buffer[0]:
|
||||||
|
self.main_buffer[1] = self.get_line_handle_from_ui_point(local_event_pos)
|
||||||
|
|
||||||
|
# Contrain point to line
|
||||||
|
if self.main_buffer[1]:
|
||||||
|
self.solv.coincident(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane)
|
||||||
|
|
||||||
|
if self.solv.solve() == ResultFlag.OKAY:
|
||||||
|
print("Fuck yeah")
|
||||||
|
self.constrain_done.emit()
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE:
|
||||||
|
print("Solve_failed - Converge")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS:
|
||||||
|
print("Solve_failed - Unknowns")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.INCONSISTENT:
|
||||||
|
print("Solve_failed - Incons")
|
||||||
|
|
||||||
|
self.constrain_done.emit()
|
||||||
|
# Clear saved_points after solve attempt
|
||||||
|
self.main_buffer = [None, None]
|
||||||
|
|
||||||
|
if event.button() == Qt.LeftButton and self.mouse_mode == "pb_con_mid":
|
||||||
|
print("ptline")
|
||||||
|
line_selected = None
|
||||||
|
|
||||||
|
if self.hovered_point and not self.main_buffer[1]:
|
||||||
|
self.main_buffer[0] = self.get_handle_from_ui_point(self.hovered_point)
|
||||||
|
|
||||||
|
elif self.main_buffer[0]:
|
||||||
|
self.main_buffer[1] = self.get_line_handle_from_ui_point(local_event_pos)
|
||||||
|
|
||||||
|
# Contrain point to line
|
||||||
|
if self.main_buffer[1]:
|
||||||
|
self.solv.midpoint(self.main_buffer[0], self.main_buffer[1], self.sketch.working_plane)
|
||||||
|
|
||||||
|
if self.solv.solve() == ResultFlag.OKAY:
|
||||||
|
print("Fuck yeah")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE:
|
||||||
|
print("Solve_failed - Converge")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS:
|
||||||
|
print("Solve_failed - Unknowns")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.INCONSISTENT:
|
||||||
|
print("Solve_failed - Incons")
|
||||||
|
self.constrain_done.emit()
|
||||||
|
|
||||||
|
self.main_buffer = [None, None]
|
||||||
|
|
||||||
|
if event.button() == Qt.LeftButton and self.mouse_mode == "horiz":
|
||||||
|
|
||||||
|
line_selected = self.get_line_handle_from_ui_point(local_event_pos)
|
||||||
|
|
||||||
|
if line_selected:
|
||||||
|
self.solv.horizontal(line_selected, self.sketch.working_plane)
|
||||||
|
|
||||||
|
if self.solv.solve() == ResultFlag.OKAY:
|
||||||
|
print("Fuck yeah")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE:
|
||||||
|
print("Solve_failed - Converge")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS:
|
||||||
|
print("Solve_failed - Unknowns")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.INCONSISTENT:
|
||||||
|
print("Solve_failed - Incons")
|
||||||
|
|
||||||
|
if event.button() == Qt.LeftButton and self.mouse_mode == "vert":
|
||||||
|
line_selected = self.get_line_handle_from_ui_point(local_event_pos)
|
||||||
|
|
||||||
|
if line_selected:
|
||||||
|
self.solv.vertical(line_selected, self.sketch.working_plane)
|
||||||
|
|
||||||
|
if self.solv.solve() == ResultFlag.OKAY:
|
||||||
|
print("Fuck yeah")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE:
|
||||||
|
print("Solve_failed - Converge")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS:
|
||||||
|
print("Solve_failed - Unknowns")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.INCONSISTENT:
|
||||||
|
print("Solve_failed - Incons")
|
||||||
|
|
||||||
|
if event.button() == Qt.LeftButton and self.mouse_mode == "distance":
|
||||||
|
# Depending on selected elemnts either point line or line distance
|
||||||
|
#print("distance")
|
||||||
|
e1 = None
|
||||||
|
e2 = None
|
||||||
|
|
||||||
|
if self.hovered_point:
|
||||||
|
print("buf point")
|
||||||
|
# Get the point as UI point as buffer
|
||||||
|
self.main_buffer[0] = self.hovered_point
|
||||||
|
|
||||||
|
elif self.selected_line:
|
||||||
|
# Get the point as UI point as buffer
|
||||||
|
self.main_buffer[1] = local_event_pos
|
||||||
|
|
||||||
|
if self.main_buffer[0] and self.main_buffer[1]:
|
||||||
|
# Define point line combination
|
||||||
|
e1 = self.get_handle_from_ui_point(self.main_buffer[0])
|
||||||
|
e2 = self.get_line_handle_from_ui_point(self.main_buffer[1])
|
||||||
|
|
||||||
|
elif not self.main_buffer[0]:
|
||||||
|
# Define only line selection
|
||||||
|
e1, e2 = self.get_point_line_handles_from_ui_point(local_event_pos)
|
||||||
|
|
||||||
|
if e1 and e2:
|
||||||
|
# Ask fo the dimension and solve if both elements are present
|
||||||
|
length, ok = QInputDialog.getDouble(self, 'Distance', 'Enter a mm value:', value=100, decimals=2)
|
||||||
|
self.solv.distance(e1, e2, length, self.sketch.working_plane)
|
||||||
|
|
||||||
|
if self.solv.solve() == ResultFlag.OKAY:
|
||||||
|
print("Fuck yeah")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.DIDNT_CONVERGE:
|
||||||
|
print("Solve_failed - Converge")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.TOO_MANY_UNKNOWNS:
|
||||||
|
print("Solve_failed - Unknowns")
|
||||||
|
|
||||||
|
elif self.solv.solve() == ResultFlag.INCONSISTENT:
|
||||||
|
print("Solve_failed - Incons")
|
||||||
|
|
||||||
|
self.constrain_done.emit()
|
||||||
|
self.main_buffer = [None, None]
|
||||||
|
|
||||||
|
# Update the main point list with the new elements and draw them
|
||||||
|
points_need_update = self.check_all_points()
|
||||||
|
self.update_ui_points(points_need_update)
|
||||||
|
self.check_all_lines_and_update(points_need_update)
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
local_event_pos = self.viewport_to_local_coord(event.pos())
|
||||||
|
|
||||||
|
closest_point = None
|
||||||
|
min_distance = float('inf')
|
||||||
|
threshold = 10 # Distance threshold for highlighting
|
||||||
|
|
||||||
|
if self.sketch:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 dic in self.sketch.slv_lines:
|
||||||
|
p1 = dic['ui_points'][0]
|
||||||
|
p2 = dic['ui_points'][1]
|
||||||
|
|
||||||
|
if self.is_point_on_line(local_event_pos, p1, p2):
|
||||||
|
self.selected_line = p1, p2
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.selected_line = None
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def mouseDoubleClickEvent(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def drawBackgroundGrid(self, painter):
|
||||||
|
"""Draw a background grid."""
|
||||||
|
grid_spacing = 50
|
||||||
|
pen = QPen(QColor(200, 200, 200), 1, Qt.SolidLine)
|
||||||
|
painter.setPen(pen)
|
||||||
|
|
||||||
|
# Draw vertical grid lines
|
||||||
|
for x in range(-self.width() // 2, self.width() // 2, grid_spacing):
|
||||||
|
painter.drawLine(x, -self.height() // 2, x, self.height() // 2)
|
||||||
|
|
||||||
|
# Draw horizontal grid lines
|
||||||
|
for y in range(-self.height() // 2, self.height() // 2, grid_spacing):
|
||||||
|
painter.drawLine(-self.width() // 2, y, self.width() // 2, y)
|
||||||
|
|
||||||
|
def drawAxes(self, painter):
|
||||||
|
painter.setRenderHint(QPainter.Antialiasing)
|
||||||
|
|
||||||
|
# Set up pen for dashed lines
|
||||||
|
pen = QPen(Qt.gray, 1, Qt.DashLine)
|
||||||
|
painter.setPen(pen)
|
||||||
|
|
||||||
|
middle_x = self.width() // 2
|
||||||
|
middle_y = self.height() // 2
|
||||||
|
|
||||||
|
# Draw X axis as dashed line
|
||||||
|
painter.drawLine(0, middle_y, self.width(), middle_y)
|
||||||
|
|
||||||
|
# Draw Y axis as dashed line
|
||||||
|
painter.drawLine(middle_x, 0, middle_x, self.height())
|
||||||
|
|
||||||
|
# Draw tick marks
|
||||||
|
tick_length = int(10 * self.zoom)
|
||||||
|
tick_spacing = int(50 * self.zoom)
|
||||||
|
|
||||||
|
pen = QPen(Qt.gray, 1, Qt.SolidLine)
|
||||||
|
painter.setPen(pen)
|
||||||
|
|
||||||
|
# Draw tick marks on the X axis to the right and left from the middle point
|
||||||
|
for x in range(0, self.width() // 2, tick_spacing):
|
||||||
|
painter.drawLine(middle_x + x, middle_y - tick_length // 2, middle_x + x, middle_y + tick_length // 2)
|
||||||
|
painter.drawLine(middle_x - x, middle_y - tick_length // 2, middle_x - x, middle_y + tick_length // 2)
|
||||||
|
|
||||||
|
# Draw tick marks on the Y axis upwards and downwards from the middle point
|
||||||
|
for y in range(0, self.height() // 2, tick_spacing):
|
||||||
|
painter.drawLine(middle_x - tick_length // 2, middle_y + y, middle_x + tick_length // 2, middle_y + y)
|
||||||
|
painter.drawLine(middle_x - tick_length // 2, middle_y - y, middle_x + tick_length // 2, middle_y - y)
|
||||||
|
|
||||||
|
# Draw the origin point in red
|
||||||
|
painter.setPen(QPen(Qt.red, 4))
|
||||||
|
painter.drawPoint(middle_x, middle_y)
|
||||||
|
|
||||||
|
def draw_cross(self, painter, pos: QPoint, size=10):
|
||||||
|
# Set up the pen
|
||||||
|
pen = QPen(QColor('green')) # You can change the color as needed
|
||||||
|
pen.setWidth(int(2 / self.zoom)) # Set the line widt)h
|
||||||
|
painter.setPen(pen)
|
||||||
|
x = pos.x()
|
||||||
|
y = pos.y()
|
||||||
|
|
||||||
|
# Calculate the endpoints of the cross
|
||||||
|
half_size = size // 2
|
||||||
|
|
||||||
|
# Draw the horizontal line
|
||||||
|
painter.drawLine(x - half_size, y, x + half_size, y)
|
||||||
|
|
||||||
|
# Draw the vertical line
|
||||||
|
painter.drawLine(x, y - half_size, x, y + half_size)
|
||||||
|
|
||||||
|
def to_quadrant_coords(self, point):
|
||||||
|
"""Translate linear coordinates to quadrant coordinates."""
|
||||||
|
center_x = self.width() // 2
|
||||||
|
center_y = self.height() // 2
|
||||||
|
quadrant_x = point.x() - center_x
|
||||||
|
quadrant_y = center_y - point.y() # Note the change here
|
||||||
|
return QPoint(quadrant_x, quadrant_y) / self.zoom
|
||||||
|
|
||||||
|
def from_quadrant_coords(self, point: QPoint):
|
||||||
|
"""Translate quadrant coordinates to linear coordinates."""
|
||||||
|
center_x = self.width() // 2
|
||||||
|
center_y = self.height() // 2
|
||||||
|
widget_x = center_x + point.x() * self.zoom
|
||||||
|
widget_y = center_y - point.y() * self.zoom # Note the subtraction here
|
||||||
|
|
||||||
|
return QPoint(int(widget_x), int(widget_y))
|
||||||
|
|
||||||
|
def from_quadrant_coords_no_center(self, point):
|
||||||
|
"""Invert Y Coordinate for mesh"""
|
||||||
|
center_x = 0
|
||||||
|
center_y = 0
|
||||||
|
widget_x = point.x()
|
||||||
|
widget_y = -point.y()
|
||||||
|
return QPoint(int(widget_x), int(widget_y))
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
painter = QPainter(self)
|
||||||
|
painter.setRenderHint(QPainter.Antialiasing)
|
||||||
|
|
||||||
|
self.drawAxes(painter)
|
||||||
|
|
||||||
|
# Create a QTransform object
|
||||||
|
transform = QTransform()
|
||||||
|
|
||||||
|
# Translate the origin to the center of the widget
|
||||||
|
center = QPointF(self.width() / 2, self.height() / 2)
|
||||||
|
transform.translate(center.x(), center.y())
|
||||||
|
|
||||||
|
# Apply the zoom factor
|
||||||
|
transform.scale(self.zoom, -self.zoom) # Negative y-scale to invert y-axis
|
||||||
|
|
||||||
|
# Set the transform to the painter
|
||||||
|
painter.setTransform(transform)
|
||||||
|
|
||||||
|
pen = QPen(Qt.gray)
|
||||||
|
pen.setWidthF(2 / self.zoom)
|
||||||
|
painter.setPen(pen)
|
||||||
|
|
||||||
|
# Draw points
|
||||||
|
if self.sketch:
|
||||||
|
for point in self.sketch.slv_points:
|
||||||
|
painter.drawEllipse(point['ui_point'], 3 / self.zoom, 3 / self.zoom)
|
||||||
|
|
||||||
|
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)))
|
||||||
|
|
||||||
|
pen = QPen(Qt.green)
|
||||||
|
pen.setWidthF(2 / self.zoom)
|
||||||
|
painter.setPen(pen)
|
||||||
|
|
||||||
|
if self.solv.entity_len():
|
||||||
|
for i in range(self.solv.entity_len()):
|
||||||
|
entity = self.solv.entity(i)
|
||||||
|
if entity.is_point_2d() and self.solv.params(entity.params):
|
||||||
|
x, y = self.solv.params(entity.params)
|
||||||
|
point = QPointF(x, y)
|
||||||
|
painter.drawEllipse(point, 6 / self.zoom, 6 / self.zoom)
|
||||||
|
|
||||||
|
# Highlight point hovered
|
||||||
|
if self.hovered_point:
|
||||||
|
highlight_pen = QPen(QColor(255, 0, 0))
|
||||||
|
highlight_pen.setWidthF(2 / self.zoom)
|
||||||
|
painter.setPen(highlight_pen)
|
||||||
|
painter.drawEllipse(self.hovered_point, 5 / self.zoom, 5 / self.zoom)
|
||||||
|
|
||||||
|
# Highlight line hovered
|
||||||
|
if self.selected_line and not self.hovered_point:
|
||||||
|
p1, p2 = self.selected_line
|
||||||
|
painter.setPen(QPen(Qt.red, 2 / self.zoom))
|
||||||
|
painter.drawLine(p1, p2)
|
||||||
|
|
||||||
|
for cross in self.sketch.proj_points:
|
||||||
|
self.draw_cross(painter, cross, 10 / self.zoom)
|
||||||
|
|
||||||
|
for selected in self.sketch.proj_lines:
|
||||||
|
pen = QPen(Qt.white, 1, Qt.DashLine)
|
||||||
|
painter.setPen(pen)
|
||||||
|
painter.drawLine(selected)
|
||||||
|
|
||||||
|
painter.end()
|
||||||
|
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
delta = event.angleDelta().y()
|
||||||
|
self.zoom += (delta / 200) * 0.1
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def aspect_ratio(self):
|
||||||
|
return self.width() / self.height() * (1.0 / abs(self.zoom))
|
||||||
|
|
||||||
|
|
||||||
|
class Point2D:
|
||||||
|
"""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
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = SketchWidget()
|
||||||
|
window.setWindowTitle("Snap Line Widget")
|
||||||
|
window.resize(800, 600)
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec())
|
897
drawing_modules/draw_widget_solve.py
Normal file
897
drawing_modules/draw_widget_solve.py
Normal file
@ -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())
|
504
drawing_modules/gl_widget.py
Normal file
504
drawing_modules/gl_widget.py
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
import sys
|
||||||
|
import numpy as np
|
||||||
|
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
||||||
|
from PySide6.QtOpenGLWidgets import QOpenGLWidget
|
||||||
|
from PySide6.QtCore import Qt, QPoint
|
||||||
|
from OpenGL.GL import *
|
||||||
|
from OpenGL.GLU import *
|
||||||
|
|
||||||
|
##testing
|
||||||
|
|
||||||
|
def create_cube(scale=1):
|
||||||
|
vertices = np.array([
|
||||||
|
[0, 0, 0],
|
||||||
|
[2, 0, 0],
|
||||||
|
[2, 2, 0],
|
||||||
|
[0, 2, 0],
|
||||||
|
[0, 0, 2],
|
||||||
|
[2, 0, 2],
|
||||||
|
[2, 2, 2],
|
||||||
|
[0, 2, 2]
|
||||||
|
]) * scale
|
||||||
|
|
||||||
|
faces = np.array([
|
||||||
|
[0, 1, 2],
|
||||||
|
[2, 3, 0],
|
||||||
|
[4, 5, 6],
|
||||||
|
[6, 7, 4],
|
||||||
|
[0, 1, 5],
|
||||||
|
[5, 4, 0],
|
||||||
|
[2, 3, 7],
|
||||||
|
[7, 6, 2],
|
||||||
|
[0, 3, 7],
|
||||||
|
[7, 4, 0],
|
||||||
|
[1, 2, 6],
|
||||||
|
[6, 5, 1]
|
||||||
|
])
|
||||||
|
|
||||||
|
return vertices, faces
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("OpenGL Cube Viewer")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
|
||||||
|
self.opengl_widget = OpenGLWidget()
|
||||||
|
|
||||||
|
central_widget = QWidget()
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.opengl_widget)
|
||||||
|
central_widget.setLayout(layout)
|
||||||
|
self.setCentralWidget(central_widget)
|
||||||
|
|
||||||
|
# Load cube data
|
||||||
|
vertices, faces = create_cube()
|
||||||
|
self.opengl_widget.load_interactor_mesh((vertices, faces))
|
||||||
|
|
||||||
|
|
||||||
|
class OpenGLWidget(QOpenGLWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.vertices = None
|
||||||
|
self.faces = None
|
||||||
|
self.selected_face = -1
|
||||||
|
self.scale_factor = 1
|
||||||
|
self.mesh_loaded = None
|
||||||
|
self.interactor_loaded = None
|
||||||
|
self.centroid = None
|
||||||
|
self.stl_file = "out.stl" # Replace with your STL file path
|
||||||
|
self.lastPos = QPoint()
|
||||||
|
self.startPos = None
|
||||||
|
self.endPos = None
|
||||||
|
self.xRot = 180
|
||||||
|
self.yRot = 0
|
||||||
|
self.zoom = -2
|
||||||
|
self.sketch = []
|
||||||
|
self.gl_width = self.width()
|
||||||
|
self.gl_height = self.height()
|
||||||
|
|
||||||
|
def map_value_to_range(self, value, value_min=0, value_max=1920, range_min=-1, range_max=1):
|
||||||
|
value = max(value_min, min(value_max, value))
|
||||||
|
mapped_value = ((value - value_min) / (value_max - value_min)) * (range_max - range_min) + range_min
|
||||||
|
|
||||||
|
return mapped_value
|
||||||
|
|
||||||
|
def load_stl(self, filename: str) -> object:
|
||||||
|
try:
|
||||||
|
stl_mesh = mesh.Mesh.from_file(filename)
|
||||||
|
|
||||||
|
# Extract vertices
|
||||||
|
vertices = np.concatenate([stl_mesh.v0, stl_mesh.v1, stl_mesh.v2])
|
||||||
|
|
||||||
|
# Calculate bounding box
|
||||||
|
min_x, min_y, min_z = vertices.min(axis=0)
|
||||||
|
max_x, max_y, max_z = vertices.max(axis=0)
|
||||||
|
|
||||||
|
# Calculate centroid
|
||||||
|
centroid_x = (min_x + max_x) / 2.0
|
||||||
|
centroid_y = (min_y + max_y) / 2.0
|
||||||
|
centroid_z = (min_z + max_z) / 2.0
|
||||||
|
|
||||||
|
self.mesh_loaded = stl_mesh.vectors
|
||||||
|
self.centroid = (centroid_x, centroid_y, centroid_z)
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: File {filename} not found.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading {filename}: {e}")
|
||||||
|
|
||||||
|
return None, (0, 0, 0)
|
||||||
|
|
||||||
|
def load_interactor_mesh(self, simp_mesh):
|
||||||
|
self.interactor_loaded = simp_mesh
|
||||||
|
# Calculate centroid based on the average position of vertices
|
||||||
|
centroid = np.mean(simp_mesh[0], axis=0)
|
||||||
|
|
||||||
|
self.centroid = tuple(centroid)
|
||||||
|
print(f"Centroid: {self.centroid}")
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def load_mesh_direct(self, mesh):
|
||||||
|
try:
|
||||||
|
stl_mesh = mesh
|
||||||
|
|
||||||
|
# Extract vertices
|
||||||
|
vertices = np.array(stl_mesh)
|
||||||
|
|
||||||
|
# Calculate centroid based on the average position of vertices
|
||||||
|
centroid = np.mean(vertices, axis=0)
|
||||||
|
|
||||||
|
self.mesh_loaded = vertices
|
||||||
|
self.centroid = tuple(centroid)
|
||||||
|
print(f"Centroid: {self.centroid}")
|
||||||
|
self.update()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def clear_mesh(self):
|
||||||
|
self.mesh_loaded = None
|
||||||
|
|
||||||
|
def initializeGL(self):
|
||||||
|
glClearColor(0, 0, 0, 1)
|
||||||
|
glEnable(GL_DEPTH_TEST)
|
||||||
|
|
||||||
|
def resizeGL(self, width, height):
|
||||||
|
glViewport(0, 0, width, height)
|
||||||
|
glMatrixMode(GL_PROJECTION)
|
||||||
|
glLoadIdentity()
|
||||||
|
|
||||||
|
aspect = width / float(height)
|
||||||
|
|
||||||
|
self.gl_width = self.width()
|
||||||
|
self.gl_height = self.height()
|
||||||
|
|
||||||
|
gluPerspective(45.0, aspect, 0.01, 1000.0)
|
||||||
|
glMatrixMode(GL_MODELVIEW)
|
||||||
|
|
||||||
|
def unproject(self, x, y, z, modelview, projection, viewport):
|
||||||
|
mvp = np.dot(projection, modelview)
|
||||||
|
mvp_inv = np.linalg.inv(mvp)
|
||||||
|
|
||||||
|
ndc = np.array([(x - viewport[0]) / viewport[2] * 2 - 1,
|
||||||
|
(y - viewport[1]) / viewport[3] * 2 - 1,
|
||||||
|
2 * z - 1,
|
||||||
|
1])
|
||||||
|
|
||||||
|
world = np.dot(mvp_inv, ndc)
|
||||||
|
print("world undproj", world)
|
||||||
|
return world[:3] / world[3]
|
||||||
|
|
||||||
|
def draw_ray(self, ray_start, ray_end):
|
||||||
|
glColor3f(1.0, 0.0, 0.0) # Set the color of the ray (red)
|
||||||
|
glBegin(GL_LINES)
|
||||||
|
glVertex3f(*ray_start)
|
||||||
|
glVertex3f(*ray_end)
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
if event.buttons() & Qt.RightButton:
|
||||||
|
self.select_face(event)
|
||||||
|
|
||||||
|
def select_face(self, event):
|
||||||
|
x = event.position().x()
|
||||||
|
y = event.position().y()
|
||||||
|
|
||||||
|
modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
|
||||||
|
projection = glGetDoublev(GL_PROJECTION_MATRIX)
|
||||||
|
viewport = glGetIntegerv(GL_VIEWPORT)
|
||||||
|
|
||||||
|
# Unproject near and far points in world space
|
||||||
|
ray_start = gluUnProject(x, y, 0.0, modelview, projection, viewport)
|
||||||
|
ray_end = gluUnProject(x, y, 1.0, modelview, projection, viewport)
|
||||||
|
|
||||||
|
ray_start = np.array(ray_start)
|
||||||
|
ray_end = np.array(ray_end)
|
||||||
|
ray_direction = ray_end - ray_start
|
||||||
|
ray_direction /= np.linalg.norm(ray_direction)
|
||||||
|
|
||||||
|
print(f"Ray start: {ray_start}")
|
||||||
|
print(f"Ray end: {ray_end}")
|
||||||
|
print(f"Ray direction: {ray_direction}")
|
||||||
|
|
||||||
|
self.selected_face = self.check_intersection(ray_start, ray_end)
|
||||||
|
print(f"Selected face: {self.selected_face}")
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def ray_box_intersection(self, ray_origin, ray_direction, box_min, box_max):
|
||||||
|
inv_direction = 1 / (ray_direction + 1e-7) # Add small value to avoid division by zero
|
||||||
|
t1 = (box_min - ray_origin) * inv_direction
|
||||||
|
t2 = (box_max - ray_origin) * inv_direction
|
||||||
|
|
||||||
|
t_min = np.max(np.minimum(t1, t2))
|
||||||
|
t_max = np.min(np.maximum(t1, t2))
|
||||||
|
|
||||||
|
print(f"min: {t_min}, max: {t_max}" )
|
||||||
|
|
||||||
|
return t_max >= t_min and t_max > 0
|
||||||
|
|
||||||
|
def check_intersection(self, ray_start, ray_end):
|
||||||
|
# Get the current modelview matrix
|
||||||
|
modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
|
||||||
|
|
||||||
|
# Transform vertices to camera space
|
||||||
|
vertices_cam = [np.dot(modelview, np.append(v, 1))[:3] for v in self.interactor_loaded[0]]
|
||||||
|
|
||||||
|
ray_direction = ray_end - ray_start
|
||||||
|
ray_direction /= np.linalg.norm(ray_direction)
|
||||||
|
|
||||||
|
print(f"Checking intersection with {len(self.interactor_loaded[1])} faces")
|
||||||
|
for face_idx, face in enumerate(self.interactor_loaded[1]):
|
||||||
|
v0, v1, v2 = [vertices_cam[i] for i in face]
|
||||||
|
intersection = self.moller_trumbore(ray_start, ray_direction, v0, v1, v2)
|
||||||
|
if intersection is not None:
|
||||||
|
print(f"Intersection found with face {face_idx}")
|
||||||
|
return face_idx
|
||||||
|
|
||||||
|
print("No intersection found")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def moller_trumbore(self, ray_origin, ray_direction, v0, v1, v2):
|
||||||
|
epsilon = 1e-6
|
||||||
|
# Find vectors for two edges sharing v0
|
||||||
|
edge1 = v1 - v0
|
||||||
|
edge2 = v2 - v0
|
||||||
|
pvec = np.cross(ray_direction, edge2)
|
||||||
|
|
||||||
|
det = np.dot(edge1, pvec)
|
||||||
|
print(det)
|
||||||
|
|
||||||
|
"""if det < epsilon:
|
||||||
|
return None"""
|
||||||
|
|
||||||
|
inv_det = 1.0 / det
|
||||||
|
tvec = ray_origin - v0
|
||||||
|
u = np.dot(tvec, pvec) * inv_det
|
||||||
|
|
||||||
|
print("u", u )
|
||||||
|
|
||||||
|
if u < 0.0 or u > 1.0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
qvec = np.cross(tvec, edge1)
|
||||||
|
|
||||||
|
# Calculate v parameter and test bounds
|
||||||
|
v = np.dot(ray_direction, qvec) * inv_det
|
||||||
|
print("v", v)
|
||||||
|
|
||||||
|
if v < 0.0 or u + v > 1.0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Calculate t, ray intersects triangle
|
||||||
|
t = np.dot(edge2, qvec) * inv_det
|
||||||
|
print("t",t)
|
||||||
|
|
||||||
|
if t > epsilon:
|
||||||
|
return ray_origin + t * ray_direction
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ray_triangle_intersection(self, ray_origin, ray_direction, v0, v1, v2):
|
||||||
|
epsilon = 1e-5
|
||||||
|
edge1 = v1 - v0
|
||||||
|
edge2 = v2 - v0
|
||||||
|
h = np.cross(ray_direction, edge2)
|
||||||
|
a = np.dot(edge1, h)
|
||||||
|
|
||||||
|
print(f"Triangle vertices: {v0}, {v1}, {v2}")
|
||||||
|
print(f"a: {a}")
|
||||||
|
|
||||||
|
if abs(a) < epsilon:
|
||||||
|
print("Ray is parallel to the triangle")
|
||||||
|
return None # Ray is parallel to the triangle
|
||||||
|
|
||||||
|
f = 1.0 / a
|
||||||
|
s = ray_origin - v0
|
||||||
|
u = f * np.dot(s, h)
|
||||||
|
|
||||||
|
print(f"u: {u}")
|
||||||
|
|
||||||
|
if u < 0.0 or u > 1.0:
|
||||||
|
print("u is out of range")
|
||||||
|
return None
|
||||||
|
|
||||||
|
q = np.cross(s, edge1)
|
||||||
|
v = f * np.dot(ray_direction, q)
|
||||||
|
|
||||||
|
print(f"v: {v}")
|
||||||
|
|
||||||
|
if v < 0.0 or u + v > 1.0:
|
||||||
|
print("v is out of range")
|
||||||
|
return None
|
||||||
|
|
||||||
|
t = f * np.dot(edge2, q)
|
||||||
|
|
||||||
|
print(f"t: {t}")
|
||||||
|
|
||||||
|
if t > epsilon:
|
||||||
|
intersection_point = ray_origin + t * ray_direction
|
||||||
|
print(f"Intersection point: {intersection_point}")
|
||||||
|
return intersection_point
|
||||||
|
|
||||||
|
print("t is too small")
|
||||||
|
return None
|
||||||
|
def paintGL(self):
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||||
|
glMatrixMode(GL_MODELVIEW)
|
||||||
|
glLoadIdentity()
|
||||||
|
|
||||||
|
# Apply camera transformation
|
||||||
|
glTranslatef(0, 0, self.zoom)
|
||||||
|
glRotatef(self.xRot, 1.0, 0.0, 0.0)
|
||||||
|
glRotatef(self.yRot, 0.0, 1.0, 0.0)
|
||||||
|
|
||||||
|
"""# Apply model transformation
|
||||||
|
glTranslatef(self.tx, self.ty, self.tz)
|
||||||
|
glScalef(self.scale, self.scale, self.scale)
|
||||||
|
glRotatef(self.model_xRot, 1.0, 0.0, 0.0)
|
||||||
|
glRotatef(self.model_yRot, 0.0, 1.0, 0.0)
|
||||||
|
glRotatef(self.model_zRot, 0.0, 0.0, 1.0)"""
|
||||||
|
|
||||||
|
glColor3f(0.9, 0.8, 0.8)
|
||||||
|
self.draw_area()
|
||||||
|
|
||||||
|
if self.mesh_loaded is not None:
|
||||||
|
# Adjust the camera for the STL mesh
|
||||||
|
if self.centroid:
|
||||||
|
glPushMatrix() # Save current transformation matrix
|
||||||
|
glScalef(self.scale_factor, self.scale_factor, self.scale_factor) # Apply scaling
|
||||||
|
|
||||||
|
cx, cy, cz = self.centroid
|
||||||
|
gluLookAt(cx, cy, cz + 100, cx, cy, cz, 0, 1, 0)
|
||||||
|
|
||||||
|
self.draw_mesh_direct(self.mesh_loaded)
|
||||||
|
glPopMatrix() # Restore transformation matrix
|
||||||
|
|
||||||
|
if self.interactor_loaded is not None:
|
||||||
|
# Draw interactor mesh
|
||||||
|
glPushMatrix() # Save current transformation matrix
|
||||||
|
glScalef(self.scale_factor, self.scale_factor, self.scale_factor) # Apply scaling
|
||||||
|
|
||||||
|
self.draw_interactor(self.interactor_loaded)
|
||||||
|
glPopMatrix() # Restore transformation matrix
|
||||||
|
|
||||||
|
if self.selected_face is not None:
|
||||||
|
glColor3f(0.0, 1.0, 0.0) # Red color for selected face
|
||||||
|
glBegin(GL_TRIANGLES)
|
||||||
|
for vertex_idx in self.interactor_loaded[1][self.selected_face]:
|
||||||
|
glVertex3fv(self.interactor_loaded[0][vertex_idx])
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
# Flush the OpenGL pipeline and swap buffers
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(self, 'ray_start') and hasattr(self, 'ray_end'):
|
||||||
|
self.draw_ray(self.ray_start, self.ray_end)
|
||||||
|
|
||||||
|
glFlush()
|
||||||
|
|
||||||
|
def draw_stl(self, vertices):
|
||||||
|
glEnable(GL_LIGHTING)
|
||||||
|
glEnable(GL_LIGHT0)
|
||||||
|
glEnable(GL_DEPTH_TEST)
|
||||||
|
glEnable(GL_COLOR_MATERIAL)
|
||||||
|
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
|
||||||
|
|
||||||
|
glLightfv(GL_LIGHT0, GL_POSITION, (0, 1, 1, 0))
|
||||||
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.6, 0.6, 0.6, 1.0))
|
||||||
|
|
||||||
|
glBegin(GL_TRIANGLES)
|
||||||
|
for triangle in vertices:
|
||||||
|
for vertex in triangle:
|
||||||
|
glVertex3fv(vertex)
|
||||||
|
glEnd()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def draw_interactor(self, simp_mesh: tuple):
|
||||||
|
vertices, faces = simp_mesh
|
||||||
|
|
||||||
|
glEnable(GL_LIGHTING)
|
||||||
|
glEnable(GL_LIGHT0)
|
||||||
|
glEnable(GL_DEPTH_TEST)
|
||||||
|
glEnable(GL_COLOR_MATERIAL)
|
||||||
|
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
|
||||||
|
|
||||||
|
glLightfv(GL_LIGHT0, GL_POSITION, (0, 0.6, 0.6, 0))
|
||||||
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.4, 0.4, 0.4, 0.6))
|
||||||
|
|
||||||
|
# Draw the faces
|
||||||
|
glDisable(GL_LIGHTING)
|
||||||
|
glColor3f(0.2, 0.0, 0.0) # Set face color to red (or any color you prefer)
|
||||||
|
|
||||||
|
glBegin(GL_TRIANGLES)
|
||||||
|
for face in faces:
|
||||||
|
for vertex_index in face:
|
||||||
|
glVertex3fv(vertices[vertex_index])
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
# Draw the lines (edges of the triangles)
|
||||||
|
glColor3f(0.0, 1.0, 0.0) # Set line color to green (or any color you prefer)
|
||||||
|
|
||||||
|
glBegin(GL_LINES)
|
||||||
|
for face in faces:
|
||||||
|
for i in range(len(face)):
|
||||||
|
glVertex3fv(vertices[face[i]])
|
||||||
|
glVertex3fv(vertices[face[(i + 1) % len(face)]])
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
glEnable(GL_LIGHTING) # Re-enable lighting if further drawing requires it
|
||||||
|
|
||||||
|
def draw_mesh_direct(self, points):
|
||||||
|
glEnable(GL_LIGHTING)
|
||||||
|
glEnable(GL_LIGHT0)
|
||||||
|
glEnable(GL_DEPTH_TEST)
|
||||||
|
glEnable(GL_COLOR_MATERIAL)
|
||||||
|
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
|
||||||
|
|
||||||
|
glLightfv(GL_LIGHT0, GL_POSITION, (0, 0.6, 0.6, 0))
|
||||||
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.4, 0.4, 0.4, 0.6))
|
||||||
|
|
||||||
|
glDisable(GL_LIGHTING)
|
||||||
|
glBegin(GL_TRIANGLES)
|
||||||
|
for vertex in points:
|
||||||
|
glVertex3fv(vertex)
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
# Draw the lines (edges of the triangles)
|
||||||
|
#glDisable(GL_LIGHTING) # Disable lighting to avoid affecting the line color
|
||||||
|
glColor3f(0.0, 0.0, 0.0) # Set line color to black (or any color you prefer)
|
||||||
|
|
||||||
|
glBegin(GL_LINES)
|
||||||
|
for i in range(0, len(points), 3):
|
||||||
|
glVertex3fv(points[i])
|
||||||
|
glVertex3fv(points[i + 1])
|
||||||
|
|
||||||
|
glVertex3fv(points[i + 1])
|
||||||
|
glVertex3fv(points[i + 2])
|
||||||
|
|
||||||
|
glVertex3fv(points[i + 2])
|
||||||
|
glVertex3fv(points[i])
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
glEnable(GL_LIGHTING) # Re-enable lighting if further drawing requires it
|
||||||
|
|
||||||
|
def draw_area(self):
|
||||||
|
glColor3f(0.5, 0.5, 0.5) # Gray color
|
||||||
|
|
||||||
|
glBegin(GL_LINES)
|
||||||
|
for x in range(0, self.width(), 1):
|
||||||
|
x_ndc = self.map_value_to_range(x, 0, value_max=self.width(), range_min=-self.gl_width, range_max=self.gl_width)
|
||||||
|
glVertex2f(x_ndc, -self.gl_height) # Start from y = -1
|
||||||
|
glVertex2f(x_ndc, self.gl_height) # End at y = 1
|
||||||
|
|
||||||
|
for y in range(0, self.height(), 1):
|
||||||
|
y_ndc = self.map_value_to_range(y, 0, value_max=self.height(), range_min=-self.gl_height, range_max=self.gl_height)
|
||||||
|
glVertex2f(-self.gl_width, y_ndc) # Start from x = -1
|
||||||
|
glVertex2f(self.gl_width, y_ndc) # End at x = 1
|
||||||
|
glEnd()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
dx = event.x() - self.lastPos.x()
|
||||||
|
dy = event.y() - self.lastPos.y()
|
||||||
|
|
||||||
|
if event.buttons() & Qt.MouseButton.LeftButton :
|
||||||
|
self.xRot += 0.5 * dy
|
||||||
|
self.yRot += 0.5 * dx
|
||||||
|
self.lastPos = event.pos()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def wheelEvent(self, event):
|
||||||
|
delta = event.angleDelta().y()
|
||||||
|
self.zoom += delta / 200
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def aspect_ratio(self):
|
||||||
|
return self.width() / self.height() * (1.0 / abs(self.zoom))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = MainWindow()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec())
|
35
drawing_modules/solvespace_example.py
Normal file
35
drawing_modules/solvespace_example.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from python_solvespace import SolverSystem, ResultFlag
|
||||||
|
|
||||||
|
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, 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(p1.params)
|
||||||
|
print(dof)
|
||||||
|
print(x)
|
||||||
|
print(y)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Error!
|
||||||
|
# Get the list of all constraints
|
||||||
|
failures = solv.failures()
|
||||||
|
print(failures)
|
||||||
|
...
|
||||||
|
|
||||||
|
solve_constraint()
|
815
drawing_modules/vtk_widget.py
Normal file
815
drawing_modules/vtk_widget.py
Normal file
@ -0,0 +1,815 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import vtk
|
||||||
|
from PySide6 import QtCore, QtWidgets
|
||||||
|
from PySide6.QtCore import Signal
|
||||||
|
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
|
||||||
|
from vtkmodules.util.numpy_support import vtk_to_numpy, numpy_to_vtk
|
||||||
|
|
||||||
|
|
||||||
|
class VTKWidget(QtWidgets.QWidget):
|
||||||
|
face_data = Signal(dict)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.selected_vtk_line = []
|
||||||
|
self.access_selected_points = []
|
||||||
|
self.selected_normal = None
|
||||||
|
self.centroid = None
|
||||||
|
self.selected_edges = []
|
||||||
|
self.cell_normals = None
|
||||||
|
|
||||||
|
self.local_matrix = None
|
||||||
|
|
||||||
|
self.project_tosketch_points = []
|
||||||
|
self.project_tosketch_lines = []
|
||||||
|
|
||||||
|
self.vtk_widget = QVTKRenderWindowInteractor(self)
|
||||||
|
|
||||||
|
self.picked_edge_actors = []
|
||||||
|
self.displayed_normal_actors = []
|
||||||
|
self.body_actors_orig = []
|
||||||
|
self.projected_mesh_actors = []
|
||||||
|
self.interactor_actors = []
|
||||||
|
|
||||||
|
self.flip_toggle = False
|
||||||
|
|
||||||
|
# Create layout and add VTK widget
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.addWidget(self.vtk_widget)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Create VTK pipeline
|
||||||
|
self.renderer = vtk.vtkRenderer()
|
||||||
|
self.renderer_projections = vtk.vtkRenderer()
|
||||||
|
self.renderer_indicators = vtk.vtkRenderer()
|
||||||
|
|
||||||
|
self.renderer.SetViewport(0, 0, 1, 1) # Full viewport
|
||||||
|
self.renderer_projections.SetViewport(0, 0, 1, 1) # Full viewport, overlays the first
|
||||||
|
self.renderer_indicators.SetViewport(0, 0, 1, 1) # Full viewport, overlays the first
|
||||||
|
|
||||||
|
self.renderer.SetLayer(0)
|
||||||
|
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)
|
||||||
|
render_window.AddRenderer(self.renderer)
|
||||||
|
render_window.AddRenderer(self.renderer_projections)
|
||||||
|
render_window.AddRenderer(self.renderer_indicators)
|
||||||
|
|
||||||
|
self.camera = vtk.vtkCamera()
|
||||||
|
self.camera.SetPosition(5, 5, 1000)
|
||||||
|
self.camera.SetFocalPoint(0, 0, 0)
|
||||||
|
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)
|
||||||
|
|
||||||
|
self.interactor = self.vtk_widget.GetRenderWindow().GetInteractor()
|
||||||
|
|
||||||
|
# Light Setup
|
||||||
|
def add_light(renderer, position, color=(1, 1, 1), intensity=1.0):
|
||||||
|
light = vtk.vtkLight()
|
||||||
|
light.SetPosition(position)
|
||||||
|
light.SetColor(color)
|
||||||
|
light.SetIntensity(intensity)
|
||||||
|
renderer.AddLight(light)
|
||||||
|
|
||||||
|
# Add lights from multiple directions
|
||||||
|
add_light(self.renderer, (1000, 0, 0), intensity=1.5)
|
||||||
|
add_light(self.renderer, (-1000, 0, 0), intensity=1.5)
|
||||||
|
add_light(self.renderer, (0, 1000, 0), intensity=1.5)
|
||||||
|
add_light(self.renderer, (0, -1000, 0), intensity=1.5)
|
||||||
|
add_light(self.renderer, (0, 0, 1000), intensity=1.5)
|
||||||
|
add_light(self.renderer, (0, 0, -1000), intensity=1.5)
|
||||||
|
|
||||||
|
# Set up picking
|
||||||
|
self.picker = vtk.vtkCellPicker()
|
||||||
|
self.picker.SetTolerance(0.005)
|
||||||
|
|
||||||
|
# Create a mapper and actor for picked cells
|
||||||
|
self.picked_mapper = vtk.vtkDataSetMapper()
|
||||||
|
self.picked_actor = vtk.vtkActor()
|
||||||
|
self.picked_actor.SetMapper(self.picked_mapper)
|
||||||
|
self.picked_actor.GetProperty().SetColor(1.0, 0.0, 0.0) # Red color for picked faces
|
||||||
|
self.picked_actor.VisibilityOff() # Initially hide the actor
|
||||||
|
self.renderer.AddActor(self.picked_actor)
|
||||||
|
|
||||||
|
# Create an extract selection filter
|
||||||
|
self.extract_selection = vtk.vtkExtractSelection()
|
||||||
|
|
||||||
|
# Set up interactor style
|
||||||
|
self.style = vtk.vtkInteractorStyleTrackballCamera()
|
||||||
|
self.interactor.SetInteractorStyle(self.style)
|
||||||
|
|
||||||
|
# Add observer for mouse clicks
|
||||||
|
self.interactor.AddObserver("RightButtonPressEvent", self.on_click)
|
||||||
|
|
||||||
|
# Add axis gizmo (smaller size)
|
||||||
|
self.axes = vtk.vtkAxesActor()
|
||||||
|
self.axes.SetTotalLength(0.5, 0.5, 0.5) # Reduced size
|
||||||
|
self.axes.SetShaftType(0)
|
||||||
|
self.axes.SetAxisLabels(1)
|
||||||
|
|
||||||
|
# Create an orientation marker
|
||||||
|
self.axes_widget = vtk.vtkOrientationMarkerWidget()
|
||||||
|
self.axes_widget.SetOrientationMarker(self.axes)
|
||||||
|
self.axes_widget.SetInteractor(self.interactor)
|
||||||
|
self.axes_widget.SetViewport(0.0, 0.0, 0.2, 0.2) # Set position and size
|
||||||
|
self.axes_widget.EnabledOn()
|
||||||
|
self.axes_widget.InteractiveOff()
|
||||||
|
|
||||||
|
# Start the interactor
|
||||||
|
self.interactor.Initialize()
|
||||||
|
self.interactor.Start()
|
||||||
|
|
||||||
|
# Create the grid
|
||||||
|
grid = self.create_grid(size=100, spacing=10)
|
||||||
|
|
||||||
|
# Setup actor and mapper
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(grid)
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetPickable(False)
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(0.5, 0.5, 0.5) # Set grid color to gray
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Create lines
|
||||||
|
lines = vtk.vtkCellArray()
|
||||||
|
|
||||||
|
# Create the grid
|
||||||
|
for i in range(-size, size + 1, spacing):
|
||||||
|
# X-direction line
|
||||||
|
points.InsertNextPoint(i, -size, 0)
|
||||||
|
points.InsertNextPoint(i, size, 0)
|
||||||
|
line = vtk.vtkLine()
|
||||||
|
line.GetPointIds().SetId(0, points.GetNumberOfPoints() - 2)
|
||||||
|
line.GetPointIds().SetId(1, points.GetNumberOfPoints() - 1)
|
||||||
|
lines.InsertNextCell(line)
|
||||||
|
|
||||||
|
# Y-direction line
|
||||||
|
points.InsertNextPoint(-size, i, 0)
|
||||||
|
points.InsertNextPoint(size, i, 0)
|
||||||
|
line = vtk.vtkLine()
|
||||||
|
line.GetPointIds().SetId(0, points.GetNumberOfPoints() - 2)
|
||||||
|
line.GetPointIds().SetId(1, points.GetNumberOfPoints() - 1)
|
||||||
|
lines.InsertNextCell(line)
|
||||||
|
|
||||||
|
# Create a polydata to store everything in
|
||||||
|
grid = vtk.vtkPolyData()
|
||||||
|
|
||||||
|
# Add the points to the dataset
|
||||||
|
grid.SetPoints(points)
|
||||||
|
|
||||||
|
# Add the lines to the dataset
|
||||||
|
grid.SetLines(lines)
|
||||||
|
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def on_receive_command(self, command):
|
||||||
|
"""Calls the individual commands pressed in main"""
|
||||||
|
print("Receive command: ", command)
|
||||||
|
if command == "flip":
|
||||||
|
self.clear_actors_projection()
|
||||||
|
self.flip_toggle = not self.flip_toggle # Toggle the flag
|
||||||
|
self.on_invert_normal()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compute_normal_from_lines(line1, line2):
|
||||||
|
vec1 = line1[1] - line1[0]
|
||||||
|
vec2 = line2[1] - line2[0]
|
||||||
|
normal = np.cross(vec1, vec2)
|
||||||
|
print(normal)
|
||||||
|
normal = normal / np.linalg.norm(normal)
|
||||||
|
return normal
|
||||||
|
|
||||||
|
def load_interactor_mesh(self, edges, off_vector):
|
||||||
|
# Create vtkPoints to store all points
|
||||||
|
points = vtk.vtkPoints()
|
||||||
|
|
||||||
|
# Create vtkCellArray to store the lines
|
||||||
|
lines = vtk.vtkCellArray()
|
||||||
|
|
||||||
|
for edge in edges:
|
||||||
|
# Add points for this edge
|
||||||
|
point_id1 = points.InsertNextPoint(edge[0])
|
||||||
|
point_id2 = points.InsertNextPoint(edge[1])
|
||||||
|
|
||||||
|
# Create a line using the point IDs
|
||||||
|
line = vtk.vtkLine()
|
||||||
|
line.GetPointIds().SetId(0, point_id1)
|
||||||
|
line.GetPointIds().SetId(1, point_id2)
|
||||||
|
|
||||||
|
# Add the line to the cell array
|
||||||
|
lines.InsertNextCell(line)
|
||||||
|
|
||||||
|
# Create vtkPolyData to store the geometry
|
||||||
|
polydata = vtk.vtkPolyData()
|
||||||
|
polydata.SetPoints(points)
|
||||||
|
polydata.SetLines(lines)
|
||||||
|
|
||||||
|
# Create a transform for mirroring across the y-axis
|
||||||
|
matrix_transform = vtk.vtkTransform()
|
||||||
|
|
||||||
|
if self.local_matrix:
|
||||||
|
print(self.local_matrix)
|
||||||
|
matrix = vtk.vtkMatrix4x4()
|
||||||
|
matrix.DeepCopy(self.local_matrix)
|
||||||
|
matrix.Invert()
|
||||||
|
matrix_transform.SetMatrix(matrix)
|
||||||
|
#matrix_transform.Scale(1, 1, 1) # This mirrors across the y-axis
|
||||||
|
|
||||||
|
# Apply the matrix transform
|
||||||
|
transformFilter = vtk.vtkTransformPolyDataFilter()
|
||||||
|
transformFilter.SetInputData(polydata)
|
||||||
|
transformFilter.SetTransform(matrix_transform)
|
||||||
|
transformFilter.Update()
|
||||||
|
|
||||||
|
# Create and apply the offset transform
|
||||||
|
offset_transform = vtk.vtkTransform()
|
||||||
|
offset_transform.Translate(off_vector[0], off_vector[1], off_vector[2])
|
||||||
|
|
||||||
|
offsetFilter = vtk.vtkTransformPolyDataFilter()
|
||||||
|
offsetFilter.SetInputConnection(transformFilter.GetOutputPort())
|
||||||
|
offsetFilter.SetTransform(offset_transform)
|
||||||
|
offsetFilter.Update()
|
||||||
|
|
||||||
|
# Create a mapper and actor
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputConnection(offsetFilter.GetOutputPort())
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(1.0, 1.0, 1.0)
|
||||||
|
actor.GetProperty().SetLineWidth(4) # Set line width
|
||||||
|
|
||||||
|
# Add the actor to the scene
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
self.interactor_actors.append(actor)
|
||||||
|
|
||||||
|
mapper.Update()
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
def render_from_points_direct_with_faces(self, vertices, faces, color=(0.1, 0.2, 0.8), line_width=2, point_size=5):
|
||||||
|
"""Sketch Widget has inverted Y axiis therefore we invert y via scale here until fix"""
|
||||||
|
|
||||||
|
points = vtk.vtkPoints()
|
||||||
|
|
||||||
|
# Use SetData with numpy array
|
||||||
|
vtk_array = numpy_to_vtk(vertices, deep=True)
|
||||||
|
points.SetData(vtk_array)
|
||||||
|
|
||||||
|
# Create a vtkCellArray to store the triangles
|
||||||
|
triangles = vtk.vtkCellArray()
|
||||||
|
for face in faces:
|
||||||
|
triangle = vtk.vtkTriangle()
|
||||||
|
triangle.GetPointIds().SetId(0, face[0])
|
||||||
|
triangle.GetPointIds().SetId(1, face[1])
|
||||||
|
triangle.GetPointIds().SetId(2, face[2])
|
||||||
|
triangles.InsertNextCell(triangle)
|
||||||
|
|
||||||
|
# Create a polydata object
|
||||||
|
polydata = vtk.vtkPolyData()
|
||||||
|
polydata.SetPoints(points)
|
||||||
|
polydata.SetPolys(triangles)
|
||||||
|
|
||||||
|
# Calculate normals
|
||||||
|
normalGenerator = vtk.vtkPolyDataNormals()
|
||||||
|
normalGenerator.SetInputData(polydata)
|
||||||
|
normalGenerator.ComputePointNormalsOn()
|
||||||
|
normalGenerator.ComputeCellNormalsOn()
|
||||||
|
normalGenerator.Update()
|
||||||
|
|
||||||
|
self.cell_normals = vtk_to_numpy(normalGenerator.GetOutput().GetCellData().GetNormals())
|
||||||
|
|
||||||
|
# Create a mapper and actor
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(polydata)
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(color)
|
||||||
|
actor.GetProperty().EdgeVisibilityOff()
|
||||||
|
actor.GetProperty().SetLineWidth(line_width)
|
||||||
|
actor.GetProperty().SetMetallic(1)
|
||||||
|
actor.GetProperty().SetOpacity(0.8)
|
||||||
|
actor.SetPickable(False)
|
||||||
|
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
self.body_actors_orig.append(actor)
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
def clear_body_actors(self):
|
||||||
|
for actor in self.body_actors_orig:
|
||||||
|
self.renderer.RemoveActor(actor)
|
||||||
|
|
||||||
|
def visualize_matrix(self, matrix):
|
||||||
|
points = vtk.vtkPoints()
|
||||||
|
for i in range(4):
|
||||||
|
for j in range(4):
|
||||||
|
points.InsertNextPoint(matrix.GetElement(0, j),
|
||||||
|
matrix.GetElement(1, j),
|
||||||
|
matrix.GetElement(2, j))
|
||||||
|
|
||||||
|
polydata = vtk.vtkPolyData()
|
||||||
|
polydata.SetPoints(points)
|
||||||
|
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(polydata)
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetPointSize(5)
|
||||||
|
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
|
||||||
|
def numpy_to_vtk(self, array, deep=True):
|
||||||
|
"""Convert a numpy array to a vtk array."""
|
||||||
|
vtk_array = vtk.vtkDoubleArray()
|
||||||
|
vtk_array.SetNumberOfComponents(array.shape[1])
|
||||||
|
vtk_array.SetNumberOfTuples(array.shape[0])
|
||||||
|
|
||||||
|
for i in range(array.shape[0]):
|
||||||
|
for j in range(array.shape[1]):
|
||||||
|
vtk_array.SetComponent(i, j, array[i, j])
|
||||||
|
|
||||||
|
return vtk_array
|
||||||
|
|
||||||
|
def get_points_and_edges_from_polydata(self, polydata) -> list:
|
||||||
|
# Extract points
|
||||||
|
points = {}
|
||||||
|
vtk_points = polydata.GetPoints()
|
||||||
|
for i in range(vtk_points.GetNumberOfPoints()):
|
||||||
|
point = vtk_points.GetPoint(i)
|
||||||
|
points[i] = np.array(point)
|
||||||
|
|
||||||
|
# Extract edges
|
||||||
|
edges = []
|
||||||
|
for i in range(polydata.GetNumberOfCells()):
|
||||||
|
cell = polydata.GetCell(i)
|
||||||
|
if cell.GetCellType() == vtk.VTK_LINE:
|
||||||
|
point_ids = cell.GetPointIds()
|
||||||
|
edge = (point_ids.GetId(0), point_ids.GetId(1))
|
||||||
|
edges.append(edge)
|
||||||
|
|
||||||
|
return points, edges
|
||||||
|
|
||||||
|
def project_mesh_to_plane(self, input_mesh, normal, origin):
|
||||||
|
# Create the projector
|
||||||
|
projector = vtk.vtkProjectPointsToPlane()
|
||||||
|
projector.SetInputData(input_mesh)
|
||||||
|
projector.SetProjectionTypeToSpecifiedPlane()
|
||||||
|
|
||||||
|
# Set the normal and origin of the plane
|
||||||
|
projector.SetNormal(normal)
|
||||||
|
projector.SetOrigin(origin)
|
||||||
|
|
||||||
|
# Execute the projection
|
||||||
|
projector.Update()
|
||||||
|
|
||||||
|
# Get the projected mesh
|
||||||
|
projected_mesh = projector.GetOutput()
|
||||||
|
return projected_mesh
|
||||||
|
|
||||||
|
def compute_2d_coordinates(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()
|
||||||
|
|
||||||
|
# Extract 2D coordinates
|
||||||
|
xy_coordinates = []
|
||||||
|
for i in range(points.GetNumberOfPoints()):
|
||||||
|
point = points.GetPoint(i)
|
||||||
|
xy_coordinates.append((point[0], point[1]))
|
||||||
|
|
||||||
|
return xy_coordinates
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
local_matrix = [matrix.GetElement(i, j) for i in range(4) for j in range(4)]
|
||||||
|
|
||||||
|
# Get the polydata from the line source
|
||||||
|
line_source.Update()
|
||||||
|
polydata = line_source.GetOutput()
|
||||||
|
|
||||||
|
# Apply the transform to the polydata
|
||||||
|
transform_filter = vtk.vtkTransformPolyDataFilter()
|
||||||
|
transform_filter.SetInputData(polydata)
|
||||||
|
transform_filter.SetTransform(transform)
|
||||||
|
transform_filter.Update()
|
||||||
|
|
||||||
|
# Get the transformed points
|
||||||
|
transformed_polydata = transform_filter.GetOutput()
|
||||||
|
transformed_points = transformed_polydata.GetPoints()
|
||||||
|
|
||||||
|
# Extract 2D coordinates
|
||||||
|
xy_coordinates = []
|
||||||
|
for i in range(transformed_points.GetNumberOfPoints()):
|
||||||
|
point = transformed_points.GetPoint(i)
|
||||||
|
xy_coordinates.append((point[0], point[1]))
|
||||||
|
|
||||||
|
return xy_coordinates
|
||||||
|
|
||||||
|
def project_2d_to_3d(self, xy_coordinates, normal):
|
||||||
|
# Normalize the normal vector
|
||||||
|
normal = np.array(normal)
|
||||||
|
normal = normal / np.linalg.norm(normal)
|
||||||
|
|
||||||
|
# Create a vtkTransform for the reverse transformation
|
||||||
|
reverse_transform = vtk.vtkTransform()
|
||||||
|
reverse_transform.PostMultiply() # This ensures transforms are applied in the order we specify
|
||||||
|
|
||||||
|
# Compute the rotation axis and angle (same as in compute_2d_coordinates)
|
||||||
|
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
|
||||||
|
# Apply the inverse rotation
|
||||||
|
reverse_transform.RotateWXYZ(-angle, rotation_axis[0], rotation_axis[1], rotation_axis[2])
|
||||||
|
|
||||||
|
# Create vtkPoints to store the 2D points
|
||||||
|
points_2d = vtk.vtkPoints()
|
||||||
|
for x, y in xy_coordinates:
|
||||||
|
points_2d.InsertNextPoint(x, y, 0) # Z-coordinate is 0 for 2D points
|
||||||
|
|
||||||
|
# Create a polydata with the 2D points
|
||||||
|
polydata_2d = vtk.vtkPolyData()
|
||||||
|
polydata_2d.SetPoints(points_2d)
|
||||||
|
|
||||||
|
# Apply the reverse transform to the polydata
|
||||||
|
transform_filter = vtk.vtkTransformPolyDataFilter()
|
||||||
|
transform_filter.SetInputData(polydata_2d)
|
||||||
|
transform_filter.SetTransform(reverse_transform)
|
||||||
|
transform_filter.Update()
|
||||||
|
|
||||||
|
# Get the transformed points (now in 3D)
|
||||||
|
transformed_polydata = transform_filter.GetOutput()
|
||||||
|
transformed_points = transformed_polydata.GetPoints()
|
||||||
|
|
||||||
|
# Extract 3D coordinates
|
||||||
|
xyz_coordinates = []
|
||||||
|
for i in range(transformed_points.GetNumberOfPoints()):
|
||||||
|
point = transformed_points.GetPoint(i)
|
||||||
|
xyz_coordinates.append((point[0], point[1], point[2]))
|
||||||
|
|
||||||
|
return xyz_coordinates
|
||||||
|
|
||||||
|
def add_normal_line(self, origin, normal, length=10.0, color=(1, 0, 0)):
|
||||||
|
# Normalize the normal vector
|
||||||
|
normal = np.array(normal)
|
||||||
|
normal = normal / np.linalg.norm(normal)
|
||||||
|
|
||||||
|
# Calculate the end point
|
||||||
|
end_point = origin + normal * length
|
||||||
|
|
||||||
|
# Create vtkPoints
|
||||||
|
points = vtk.vtkPoints()
|
||||||
|
points.InsertNextPoint(origin)
|
||||||
|
points.InsertNextPoint(end_point)
|
||||||
|
|
||||||
|
# Create a line
|
||||||
|
line = vtk.vtkLine()
|
||||||
|
line.GetPointIds().SetId(0, 0)
|
||||||
|
line.GetPointIds().SetId(1, 1)
|
||||||
|
|
||||||
|
# Create a cell array to store the line
|
||||||
|
lines = vtk.vtkCellArray()
|
||||||
|
lines.InsertNextCell(line)
|
||||||
|
|
||||||
|
# Create a polydata to store everything in
|
||||||
|
polyData = vtk.vtkPolyData()
|
||||||
|
polyData.SetPoints(points)
|
||||||
|
polyData.SetLines(lines)
|
||||||
|
|
||||||
|
# Create mapper and actor
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(polyData)
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(color)
|
||||||
|
actor.GetProperty().SetLineWidth(2) # Adjust line width as needed
|
||||||
|
|
||||||
|
# Add to renderer
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
return actor # Return the actor in case you need to remove or modify it later
|
||||||
|
|
||||||
|
def on_invert_normal(self):
|
||||||
|
# Kippstufe für Normal flip
|
||||||
|
if self.selected_normal is not None:
|
||||||
|
self.clear_actors_normals()
|
||||||
|
self.compute_projection(self.flip_toggle)
|
||||||
|
|
||||||
|
def on_click(self, obj, event):
|
||||||
|
click_pos = self.interactor.GetEventPosition()
|
||||||
|
|
||||||
|
# Perform pick
|
||||||
|
self.picker.Pick(click_pos[0], click_pos[1], 0, self.renderer)
|
||||||
|
|
||||||
|
# Get picked cell ID
|
||||||
|
cell_id = self.picker.GetCellId()
|
||||||
|
|
||||||
|
if cell_id != -1:
|
||||||
|
print(f"Picked cell ID: {cell_id}")
|
||||||
|
|
||||||
|
# Get the polydata and the picked cell
|
||||||
|
polydata = self.picker.GetActor().GetMapper().GetInput()
|
||||||
|
cell = polydata.GetCell(cell_id)
|
||||||
|
|
||||||
|
# Ensure it's a line
|
||||||
|
if cell.GetCellType() == vtk.VTK_LINE:
|
||||||
|
|
||||||
|
# Get the two points of the line
|
||||||
|
point_id1 = cell.GetPointId(0)
|
||||||
|
point_id2 = cell.GetPointId(1)
|
||||||
|
|
||||||
|
proj_point1 = polydata.GetPoint(point_id1)
|
||||||
|
proj_point2 = polydata.GetPoint(point_id2)
|
||||||
|
|
||||||
|
self.access_selected_points.append((proj_point1, proj_point2))
|
||||||
|
|
||||||
|
point1 = np.array(proj_point1)
|
||||||
|
point2 = np.array(proj_point2)
|
||||||
|
|
||||||
|
#print(f"Line starts at: {point1}")
|
||||||
|
#print(f"Line ends at: {point2}")
|
||||||
|
|
||||||
|
# Store this line for later use if needed
|
||||||
|
self.selected_edges.append((point1, point2))
|
||||||
|
|
||||||
|
# Create a new vtkLineSource for the picked edge
|
||||||
|
line_source = vtk.vtkLineSource()
|
||||||
|
line_source.SetPoint1(point1)
|
||||||
|
line_source.SetPoint2(point2)
|
||||||
|
|
||||||
|
self.selected_vtk_line.append(line_source)
|
||||||
|
|
||||||
|
# Create a mapper and actor for the picked edge
|
||||||
|
edge_mapper = vtk.vtkPolyDataMapper()
|
||||||
|
edge_mapper.SetInputConnection(line_source.GetOutputPort())
|
||||||
|
|
||||||
|
edge_actor = vtk.vtkActor()
|
||||||
|
edge_actor.SetMapper(edge_mapper)
|
||||||
|
edge_actor.GetProperty().SetColor(1.0, 0.0, 0.0) # Red color for picked edges
|
||||||
|
edge_actor.GetProperty().SetLineWidth(5) # Make the line thicker
|
||||||
|
|
||||||
|
# Add the actor to the renderer and store it
|
||||||
|
self.renderer_indicators.AddActor(edge_actor)
|
||||||
|
self.picked_edge_actors.append(edge_actor)
|
||||||
|
|
||||||
|
if len(self.selected_edges) == 2:
|
||||||
|
self.compute_projection(False)
|
||||||
|
|
||||||
|
if len(self.selected_edges) > 2:
|
||||||
|
# Clear lists for selection
|
||||||
|
self.selected_vtk_line.clear()
|
||||||
|
self.selected_edges.clear()
|
||||||
|
self.clear_edge_select()
|
||||||
|
|
||||||
|
# Clear Actors from view
|
||||||
|
self.clear_actors_projection()
|
||||||
|
self.clear_actors_sel_edges()
|
||||||
|
self.clear_actors_normals()
|
||||||
|
|
||||||
|
|
||||||
|
def find_origin_vertex(self, edge1, edge2):
|
||||||
|
if edge1[0] == edge2[0]or edge1[0] == edge2[1]:
|
||||||
|
return edge1[0]
|
||||||
|
elif edge1[1] == edge2[0] or edge1[1] == edge2[1]:
|
||||||
|
return edge1[1]
|
||||||
|
else:
|
||||||
|
return None # The edges don't share a vertex
|
||||||
|
|
||||||
|
def clear_edge_select(self ):
|
||||||
|
# Clear selection after projection was succesful
|
||||||
|
self.selected_edges = []
|
||||||
|
self.selected_normal = []
|
||||||
|
|
||||||
|
def clear_actors_projection(self):
|
||||||
|
"""Removes all actors that were used for projection"""
|
||||||
|
for flat_mesh in self.projected_mesh_actors:
|
||||||
|
self.renderer_projections.RemoveActor(flat_mesh)
|
||||||
|
|
||||||
|
def clear_actors_normals(self):
|
||||||
|
for normals in self.displayed_normal_actors:
|
||||||
|
self.renderer_indicators.RemoveActor(normals)
|
||||||
|
|
||||||
|
def clear_actors_sel_edges(self):
|
||||||
|
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 )
|
||||||
|
edge1 = self.selected_edges[0][1] - self.selected_edges[0][0]
|
||||||
|
edge2 = self.selected_edges[1][1] - self.selected_edges[1][0]
|
||||||
|
selected_normal = np.cross(edge1, edge2)
|
||||||
|
selected_normal = selected_normal / np.linalg.norm(selected_normal)
|
||||||
|
#print("Computed normal:", self.selected_normal)
|
||||||
|
|
||||||
|
# Invert the normal in local z if direction_invert is True
|
||||||
|
if direction_invert:
|
||||||
|
self.selected_normal = -selected_normal
|
||||||
|
else:
|
||||||
|
self.selected_normal = selected_normal
|
||||||
|
|
||||||
|
self.centroid = np.mean([point for edge in self.selected_edges for point in edge], axis=0)
|
||||||
|
#self.centroid = self.find_origin_vertex(edge1, edge2)
|
||||||
|
|
||||||
|
# Draw the normal line
|
||||||
|
normal_length = 50 # Adjust this value to change the length of the normal line
|
||||||
|
normal_actor = self.add_normal_line(self.centroid, self.selected_normal, length=normal_length,
|
||||||
|
color=(1, 0, 0))
|
||||||
|
|
||||||
|
polydata = self.picker.GetActor().GetMapper().GetInput()
|
||||||
|
|
||||||
|
projected_polydata = self.project_mesh_to_plane(polydata, self.selected_normal, self.centroid)
|
||||||
|
|
||||||
|
# Extract 2D coordinates
|
||||||
|
self.project_tosketch_points = self.compute_2d_coordinates(projected_polydata, self.selected_normal)
|
||||||
|
|
||||||
|
# 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)"""
|
||||||
|
|
||||||
|
# Create a mapper and actor for the projected data
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(projected_polydata)
|
||||||
|
|
||||||
|
# Projected mesh in green
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
#actor.GetProperty().SetRenderLinesAsTubes(True)
|
||||||
|
actor.GetProperty().SetColor(0.0, 1.0, 0.0) # Set color to green
|
||||||
|
actor.GetProperty().SetLineWidth(4) # Set line width
|
||||||
|
|
||||||
|
self.renderer_indicators.AddActor(normal_actor)
|
||||||
|
self.displayed_normal_actors.append(normal_actor)
|
||||||
|
|
||||||
|
self.renderer_projections.AddActor(actor)
|
||||||
|
self.projected_mesh_actors.append(actor)
|
||||||
|
|
||||||
|
# Render the scene
|
||||||
|
self.update_render()
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.interactor.Initialize()
|
||||||
|
self.interactor.Start()
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.vtk_widget = VTKWidget()
|
||||||
|
self.setCentralWidget(self.vtk_widget)
|
||||||
|
self.setWindowTitle("VTK Mesh Viewer")
|
||||||
|
self.vtk_widget.create_cube_mesh()
|
||||||
|
self.show()
|
||||||
|
self.vtk_widget.start()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
window = MainWindow()
|
||||||
|
sys.exit(app.exec())
|
337
drawing_modules/vtk_widget_alt_methods.py
Normal file
337
drawing_modules/vtk_widget_alt_methods.py
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
def are_coplanar(self, normal1, normal2, point1, point2, tolerance=1e-6):
|
||||||
|
# Check if normals are parallel
|
||||||
|
if np.abs(np.dot(normal1, normal2)) < 1 - tolerance:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if points lie on the same plane
|
||||||
|
diff = point2 - point1
|
||||||
|
return np.abs(np.dot(diff, normal1)) < tolerance
|
||||||
|
|
||||||
|
|
||||||
|
def merge_coplanar_triangles(self, polydata):
|
||||||
|
# Compute normals
|
||||||
|
normalGenerator = vtk.vtkPolyDataNormals()
|
||||||
|
normalGenerator.SetInputData(polydata)
|
||||||
|
normalGenerator.ComputePointNormalsOff()
|
||||||
|
normalGenerator.ComputeCellNormalsOn()
|
||||||
|
normalGenerator.Update()
|
||||||
|
|
||||||
|
mesh = normalGenerator.GetOutput()
|
||||||
|
n_cells = mesh.GetNumberOfCells()
|
||||||
|
|
||||||
|
# Create a map to store merged triangles
|
||||||
|
merged = {}
|
||||||
|
|
||||||
|
for i in range(n_cells):
|
||||||
|
if i in merged:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cell = mesh.GetCell(i)
|
||||||
|
normal = np.array(mesh.GetCellData().GetNormals().GetTuple(i))
|
||||||
|
point = np.array(cell.GetPoints().GetPoint(0))
|
||||||
|
|
||||||
|
merged[i] = [i]
|
||||||
|
|
||||||
|
for j in range(i + 1, n_cells):
|
||||||
|
if j in merged:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cell_j = mesh.GetCell(j)
|
||||||
|
normal_j = np.array(mesh.GetCellData().GetNormals().GetTuple(j))
|
||||||
|
point_j = np.array(cell_j.GetPoints().GetPoint(0))
|
||||||
|
|
||||||
|
if self.are_coplanar(normal, normal_j, point, point_j):
|
||||||
|
merged[i].append(j)
|
||||||
|
|
||||||
|
# Create new polygons
|
||||||
|
new_polygons = vtk.vtkCellArray()
|
||||||
|
for group in merged.values():
|
||||||
|
if len(group) > 1:
|
||||||
|
polygon = vtk.vtkPolygon()
|
||||||
|
points = set()
|
||||||
|
for idx in group:
|
||||||
|
cell = mesh.GetCell(idx)
|
||||||
|
for j in range(3):
|
||||||
|
point_id = cell.GetPointId(j)
|
||||||
|
points.add(point_id)
|
||||||
|
polygon.GetPointIds().SetNumberOfIds(len(points))
|
||||||
|
for j, point_id in enumerate(points):
|
||||||
|
polygon.GetPointIds().SetId(j, point_id)
|
||||||
|
new_polygons.InsertNextCell(polygon)
|
||||||
|
else:
|
||||||
|
new_polygons.InsertNextCell(mesh.GetCell(group[0]))
|
||||||
|
|
||||||
|
# Create new polydata
|
||||||
|
new_polydata = vtk.vtkPolyData()
|
||||||
|
new_polydata.SetPoints(mesh.GetPoints())
|
||||||
|
new_polydata.SetPolys(new_polygons)
|
||||||
|
|
||||||
|
return new_polydata
|
||||||
|
|
||||||
|
|
||||||
|
def create_cube_mesh(self):
|
||||||
|
# cube_source = vtk.vtkSuperquadricSource()
|
||||||
|
|
||||||
|
reader = vtk.vtkSTLReader()
|
||||||
|
reader.SetFileName("case.stl") # Replace with your mesh file path
|
||||||
|
reader.Update()
|
||||||
|
|
||||||
|
featureEdges = vtk.vtkFeatureEdges()
|
||||||
|
featureEdges.SetInputConnection(reader.GetOutputPort())
|
||||||
|
featureEdges.BoundaryEdgesOn()
|
||||||
|
featureEdges.FeatureEdgesOn()
|
||||||
|
featureEdges.ManifoldEdgesOff()
|
||||||
|
featureEdges.NonManifoldEdgesOff()
|
||||||
|
featureEdges.Update()
|
||||||
|
|
||||||
|
# print(cube_source)
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputConnection(reader.GetOutputPort())
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
|
||||||
|
mapper_edge = vtk.vtkPolyDataMapper()
|
||||||
|
mapper_edge.SetInputConnection(featureEdges.GetOutputPort())
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper_edge)
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
|
||||||
|
|
||||||
|
def simplify_mesh(self, input_mesh, target_reduction):
|
||||||
|
# Create the quadric decimation filter
|
||||||
|
decimate = vtk.vtkDecimatePro()
|
||||||
|
decimate.SetInputData(input_mesh)
|
||||||
|
|
||||||
|
# Set the reduction factor (0 to 1, where 1 means maximum reduction)
|
||||||
|
decimate.SetTargetReduction(target_reduction)
|
||||||
|
|
||||||
|
# Optional: Preserve topology (if needed)
|
||||||
|
decimate.PreserveTopologyOn()
|
||||||
|
|
||||||
|
# Perform the decimation
|
||||||
|
decimate.Update()
|
||||||
|
|
||||||
|
return decimate.GetOutput()
|
||||||
|
|
||||||
|
|
||||||
|
def combine_coplanar_faces(self, input_polydata, tolerance=0.001):
|
||||||
|
# Clean the polydata to merge duplicate points
|
||||||
|
clean = vtk.vtkCleanPolyData()
|
||||||
|
clean.SetInputData(input_polydata)
|
||||||
|
clean.SetTolerance(tolerance)
|
||||||
|
clean.Update()
|
||||||
|
|
||||||
|
# Generate normals and merge coplanar polygons
|
||||||
|
normals = vtk.vtkPolyDataNormals()
|
||||||
|
normals.SetInputConnection(clean.GetOutputPort())
|
||||||
|
normals.SplittingOff() # Disable splitting of sharp edges
|
||||||
|
normals.ConsistencyOn() # Ensure consistent polygon ordering
|
||||||
|
normals.AutoOrientNormalsOn() # Automatically orient normals
|
||||||
|
normals.ComputePointNormalsOff() # We only need face normals
|
||||||
|
normals.ComputeCellNormalsOn() # Compute cell normals
|
||||||
|
normals.Update()
|
||||||
|
|
||||||
|
return normals.GetOutput()
|
||||||
|
|
||||||
|
|
||||||
|
def poisson_reconstruction(self, points):
|
||||||
|
# Create a polydata object from points
|
||||||
|
point_polydata = vtk.vtkPolyData()
|
||||||
|
point_polydata.SetPoints(points)
|
||||||
|
|
||||||
|
# Create a surface reconstruction filter
|
||||||
|
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||||
|
surf.SetInputData(point_polydata)
|
||||||
|
surf.Update()
|
||||||
|
|
||||||
|
# Create a contour filter to extract the surface
|
||||||
|
cf = vtk.vtkContourFilter()
|
||||||
|
cf.SetInputConnection(surf.GetOutputPort())
|
||||||
|
cf.SetValue(0, 0.0)
|
||||||
|
cf.Update()
|
||||||
|
|
||||||
|
# Reverse normals
|
||||||
|
reverse = vtk.vtkReverseSense()
|
||||||
|
reverse.SetInputConnection(cf.GetOutputPort())
|
||||||
|
reverse.ReverseCellsOn()
|
||||||
|
reverse.ReverseNormalsOn()
|
||||||
|
reverse.Update()
|
||||||
|
|
||||||
|
return reverse.GetOutput()
|
||||||
|
|
||||||
|
|
||||||
|
def create_simplified_outline(self, polydata):
|
||||||
|
featureEdges = vtk.vtkFeatureEdges()
|
||||||
|
featureEdges.SetInputData(polydata)
|
||||||
|
featureEdges.BoundaryEdgesOn()
|
||||||
|
featureEdges.FeatureEdgesOn()
|
||||||
|
featureEdges.ManifoldEdgesOff()
|
||||||
|
featureEdges.NonManifoldEdgesOff()
|
||||||
|
featureEdges.Update()
|
||||||
|
|
||||||
|
"""# 3. Clean the edges to merge duplicate points
|
||||||
|
cleaner = vtk.vtkCleanPolyData()
|
||||||
|
cleaner.SetInputConnection(feature_edges.GetOutputPort())
|
||||||
|
cleaner.Update()
|
||||||
|
|
||||||
|
# 4. Optional: Smooth the outline
|
||||||
|
smooth = vtk.vtkSmoothPolyDataFilter()
|
||||||
|
smooth.SetInputConnection(cleaner.GetOutputPort())
|
||||||
|
smooth.SetNumberOfIterations(15)
|
||||||
|
smooth.SetRelaxationFactor(0.1)
|
||||||
|
smooth.FeatureEdgeSmoothingOff()
|
||||||
|
smooth.BoundarySmoothingOn()
|
||||||
|
smooth.Update()"""
|
||||||
|
|
||||||
|
return featureEdges
|
||||||
|
|
||||||
|
|
||||||
|
def render_from_points_direct_with_faces(self, vertices, faces):
|
||||||
|
points = vtk.vtkPoints()
|
||||||
|
for i in range(vertices.shape[0]):
|
||||||
|
points.InsertNextPoint(vertices[i])
|
||||||
|
|
||||||
|
# Create a vtkCellArray to store the triangles
|
||||||
|
triangles = vtk.vtkCellArray()
|
||||||
|
for i in range(faces.shape[0]):
|
||||||
|
triangle = vtk.vtkTriangle()
|
||||||
|
triangle.GetPointIds().SetId(0, faces[i, 0])
|
||||||
|
triangle.GetPointIds().SetId(1, faces[i, 1])
|
||||||
|
triangle.GetPointIds().SetId(2, faces[i, 2])
|
||||||
|
triangles.InsertNextCell(triangle)
|
||||||
|
|
||||||
|
"""vtk_points = vtk.vtkPoints()
|
||||||
|
for point in points:
|
||||||
|
vtk_points.InsertNextPoint(point)
|
||||||
|
|
||||||
|
# Create a vtkCellArray to store the triangles
|
||||||
|
triangles = vtk.vtkCellArray()
|
||||||
|
|
||||||
|
# Assuming points are organized as triplets forming triangles
|
||||||
|
for i in range(0, len(points), 3):
|
||||||
|
triangle = vtk.vtkTriangle()
|
||||||
|
triangle.GetPointIds().SetId(0, i)
|
||||||
|
triangle.GetPointIds().SetId(1, i + 1)
|
||||||
|
triangle.GetPointIds().SetId(2, i + 2)
|
||||||
|
triangles.InsertNextCell(triangle)"""
|
||||||
|
|
||||||
|
# Create a polydata object
|
||||||
|
polydata = vtk.vtkPolyData()
|
||||||
|
polydata.SetPoints(points)
|
||||||
|
polydata.SetPolys(triangles)
|
||||||
|
|
||||||
|
# Calculate normals
|
||||||
|
normalGenerator = vtk.vtkPolyDataNormals()
|
||||||
|
normalGenerator.SetInputData(polydata)
|
||||||
|
normalGenerator.ComputePointNormalsOn()
|
||||||
|
normalGenerator.ComputeCellNormalsOn()
|
||||||
|
normalGenerator.Update()
|
||||||
|
|
||||||
|
self.cell_normals = vtk_to_numpy(normalGenerator.GetOutput().GetCellData().GetNormals())
|
||||||
|
|
||||||
|
# merged_polydata = self.merge_coplanar_triangles(polydata)
|
||||||
|
|
||||||
|
# Create a mapper and actor
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(polydata)
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(1, 1, 1) # Set color (white in this case)
|
||||||
|
actor.GetProperty().EdgeVisibilityOn() # Show edges
|
||||||
|
actor.GetProperty().SetLineWidth(2) # Set line width
|
||||||
|
|
||||||
|
feature_edges = self.create_simplified_outline(polydata)
|
||||||
|
|
||||||
|
# Create a mapper for the feature edges
|
||||||
|
edge_mapper = vtk.vtkPolyDataMapper()
|
||||||
|
# Already wiht output
|
||||||
|
edge_mapper.SetInputConnection(feature_edges.GetOutputPort())
|
||||||
|
|
||||||
|
# Create an actor for the feature edges
|
||||||
|
edge_actor = vtk.vtkActor()
|
||||||
|
edge_actor.SetMapper(edge_mapper)
|
||||||
|
|
||||||
|
# Set the properties of the edge actor
|
||||||
|
edge_actor.GetProperty().SetColor(1, 0, 0) # Set color (red in this case)
|
||||||
|
edge_actor.GetProperty().SetLineWidth(2) # Set line width
|
||||||
|
|
||||||
|
# Optionally, if you want to keep the original mesh visible:
|
||||||
|
# (assuming you have the original mesh mapper and actor set up)
|
||||||
|
self.renderer.AddActor(actor) # Add the original mesh actor
|
||||||
|
# Add the edge actor to the renderer
|
||||||
|
self.renderer.AddActor(edge_actor)
|
||||||
|
|
||||||
|
# Force an update of the pipeline
|
||||||
|
mapper.Update()
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
"""# Print statistics
|
||||||
|
print(f"Original points: {len(points)}")
|
||||||
|
print(f"Number of triangles: {triangles.GetNumberOfCells()}")
|
||||||
|
print(f"Final number of points: {normals.GetOutput().GetNumberOfPoints()}")
|
||||||
|
print(f"Final number of cells: {normals.GetOutput().GetNumberOfCells()}")"""
|
||||||
|
|
||||||
|
|
||||||
|
def render_from_points_direct(self, points):
|
||||||
|
### Rendermethod for SDF mesh (output)
|
||||||
|
# Create a vtkPoints object and store the points in it
|
||||||
|
vtk_points = vtk.vtkPoints()
|
||||||
|
for point in points:
|
||||||
|
vtk_points.InsertNextPoint(point)
|
||||||
|
|
||||||
|
# Create a polydata object
|
||||||
|
point_polydata = vtk.vtkPolyData()
|
||||||
|
point_polydata.SetPoints(vtk_points)
|
||||||
|
|
||||||
|
# Surface reconstruction
|
||||||
|
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||||
|
surf.SetInputData(point_polydata)
|
||||||
|
surf.Update()
|
||||||
|
|
||||||
|
# Create a contour filter to extract the surface
|
||||||
|
cf = vtk.vtkContourFilter()
|
||||||
|
cf.SetInputConnection(surf.GetOutputPort())
|
||||||
|
cf.SetValue(0, 0.0)
|
||||||
|
cf.Update()
|
||||||
|
|
||||||
|
# Reverse the normals
|
||||||
|
reverse = vtk.vtkReverseSense()
|
||||||
|
reverse.SetInputConnection(cf.GetOutputPort())
|
||||||
|
reverse.ReverseCellsOn()
|
||||||
|
reverse.ReverseNormalsOn()
|
||||||
|
reverse.Update()
|
||||||
|
|
||||||
|
# Get the reconstructed mesh
|
||||||
|
reconstructed_mesh = reverse.GetOutput()
|
||||||
|
|
||||||
|
"""# Simplify the mesh
|
||||||
|
target_reduction = 1 # Adjust this value as needed
|
||||||
|
simplified_mesh = self.simplify_mesh(reconstructed_mesh, target_reduction)
|
||||||
|
|
||||||
|
combinded_faces = self.combine_coplanar_faces(simplified_mesh, 0.001)"""
|
||||||
|
|
||||||
|
# Create a mapper and actor for the simplified mesh
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(reconstructed_mesh)
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(1, 1, 1) # Set color (white in this case)
|
||||||
|
actor.GetProperty().EdgeVisibilityOn() # Show edges
|
||||||
|
actor.GetProperty().SetLineWidth(2) # Set line width
|
||||||
|
|
||||||
|
# Add the actor to the renderer
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
|
||||||
|
# Force an update of the pipeline
|
||||||
|
# mapper.Update()
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
# Print statistics
|
||||||
|
print(f"Original points: {len(points)}")
|
||||||
|
print(
|
||||||
|
f"Reconstructed mesh: {reconstructed_mesh.GetNumberOfPoints()} points, {reconstructed_mesh.GetNumberOfCells()} cells")
|
||||||
|
"""print(
|
||||||
|
f"Simplified mesh: {simplified_mesh.GetNumberOfPoints()} points, {simplified_mesh.GetNumberOfCells()} cells")"""
|
111
drawing_modules/vysta_widget.py
Normal file
111
drawing_modules/vysta_widget.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pyvista as pv
|
||||||
|
from pyvista.plotting.opts import ElementType
|
||||||
|
from pyvistaqt import QtInteractor
|
||||||
|
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
||||||
|
|
||||||
|
|
||||||
|
class PyVistaWidget(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# Create the PyVista plotter
|
||||||
|
self.plotter = QtInteractor(self)
|
||||||
|
self.plotter.background_color = "darkgray"
|
||||||
|
|
||||||
|
# Create a layout and add the PyVista widget
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.plotter.interactor)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Set up the picker
|
||||||
|
#self.plotter.enable_cell_picking(callback=self.on_cell_pick, show=True)
|
||||||
|
self.plotter.enable_element_picking(callback=self.on_cell_pick, show=True, mode="face", left_clicking=True)
|
||||||
|
|
||||||
|
def on_cell_pick(self, element):
|
||||||
|
if element is not None:
|
||||||
|
mesh = self.plotter.mesh # Get the current mesh
|
||||||
|
print(mesh)
|
||||||
|
print(element)
|
||||||
|
|
||||||
|
"""# Get the face data
|
||||||
|
face = mesh.extract_cells(element)
|
||||||
|
|
||||||
|
# Compute face normal
|
||||||
|
face.compute_normals(cell_normals=True, inplace=True)
|
||||||
|
normal = face.cell_data['Normals'][0]
|
||||||
|
|
||||||
|
# Get the points of the face
|
||||||
|
points = face.points
|
||||||
|
|
||||||
|
print(f"Picked face ID: {face_id}")
|
||||||
|
print(f"Face normal: {normal}")
|
||||||
|
print("Face points:")
|
||||||
|
for point in points:
|
||||||
|
print(point)"""
|
||||||
|
else:
|
||||||
|
print("No face was picked or the picked element is not a face.")
|
||||||
|
def create_simplified_outline(self, mesh, camera):
|
||||||
|
# Project 3D to 2D
|
||||||
|
points_2d = self.plotter.map_to_2d(mesh.points)
|
||||||
|
|
||||||
|
# Detect silhouette edges (simplified approach)
|
||||||
|
edges = mesh.extract_feature_edges(feature_angle=90, boundary_edges=False, non_manifold_edges=False)
|
||||||
|
|
||||||
|
# Project edges to 2D
|
||||||
|
edge_points_2d = self.plotter.map_to_2d(edges.points)
|
||||||
|
|
||||||
|
# Create 2D outline
|
||||||
|
self.plotter.add_lines(edge_points_2d, color='black', width=2)
|
||||||
|
self.plotter.render()
|
||||||
|
|
||||||
|
def mesh_from_points(self, points):
|
||||||
|
# Convert points to numpy array if not already
|
||||||
|
points = np.array(points)
|
||||||
|
|
||||||
|
# Create faces array
|
||||||
|
num_triangles = len(points) // 3
|
||||||
|
faces = np.arange(len(points)).reshape(num_triangles, 3)
|
||||||
|
faces = np.column_stack((np.full(num_triangles, 3), faces)) # Add 3 as first column
|
||||||
|
|
||||||
|
# Create PyVista PolyData
|
||||||
|
mesh = pv.PolyData(points, faces)
|
||||||
|
|
||||||
|
# Optional: Merge duplicate points
|
||||||
|
mesh = mesh.clean()
|
||||||
|
|
||||||
|
# Optional: Compute normals
|
||||||
|
mesh = mesh.compute_normals(point_normals=False, cell_normals=True, consistent_normals=True)
|
||||||
|
edges = mesh.extract_feature_edges(30, non_manifold_edges=False)
|
||||||
|
|
||||||
|
# Clear any existing meshes
|
||||||
|
self.plotter.clear()
|
||||||
|
|
||||||
|
# Add the mesh to the plotter
|
||||||
|
self.plotter.add_mesh(mesh, pickable=True, color='white', show_edges=True, line_width=2, pbr=True, metallic=0.8, roughness=0.1, diffuse=1)
|
||||||
|
self.plotter.add_mesh(edges, color="red", line_width=10)
|
||||||
|
|
||||||
|
# Reset the camera to fit the new mesh
|
||||||
|
self.plotter.reset_camera()
|
||||||
|
|
||||||
|
# Update the render window
|
||||||
|
self.plotter.update()
|
||||||
|
|
||||||
|
# Print statistics
|
||||||
|
print(f"Original points: {len(points)}")
|
||||||
|
print(f"Number of triangles: {num_triangles}")
|
||||||
|
print(f"Final number of points: {mesh.n_points}")
|
||||||
|
print(f"Final number of cells: {mesh.n_cells}")
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("PyVista in PySide6")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
886
main.py
Normal file
886
main.py
Normal file
@ -0,0 +1,886 @@
|
|||||||
|
# nuitka-project: --plugin-enable=pyside6
|
||||||
|
# nuitka-project: --plugin-enable=numpy
|
||||||
|
# nuitka-project: --standalone
|
||||||
|
# nuitka-project: --macos-create-app-bundle
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
import names
|
||||||
|
from PySide6.QtCore import Qt, QPoint, Signal, QSize
|
||||||
|
from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog, QDialog, QVBoxLayout, QHBoxLayout, QLabel, QDoubleSpinBox, QCheckBox, QPushButton, 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_widget_solve import SketchWidget
|
||||||
|
from sdf import *
|
||||||
|
from python_solvespace import SolverSystem, ResultFlag
|
||||||
|
from mesh_modules import simple_mesh, vesta_mesh, interactor_mesh
|
||||||
|
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
|
||||||
|
line.setFixedHeight(1)
|
||||||
|
return line
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# Length input
|
||||||
|
length_layout = QHBoxLayout()
|
||||||
|
length_label = QLabel('Extrude Length (mm):')
|
||||||
|
self.length_input = QDoubleSpinBox()
|
||||||
|
self.length_input.setDecimals(2)
|
||||||
|
self.length_input.setRange(0, 1000) # Adjust range as needed
|
||||||
|
length_layout.addWidget(length_label)
|
||||||
|
length_layout.addWidget(self.length_input)
|
||||||
|
|
||||||
|
# Symmetric checkbox
|
||||||
|
self.symmetric_checkbox = QCheckBox('Symmetric Extrude')
|
||||||
|
self.invert_checkbox = QCheckBox('Invert Extrusion')
|
||||||
|
self.cut_checkbox = QCheckBox('Perform Cut')
|
||||||
|
self.union_checkbox = QCheckBox('Combine')
|
||||||
|
self.rounded_checkbox = QCheckBox('Round Edges')
|
||||||
|
self.seperator = create_hline()
|
||||||
|
|
||||||
|
# OK and Cancel buttons
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
ok_button = QPushButton('OK')
|
||||||
|
cancel_button = QPushButton('Cancel')
|
||||||
|
ok_button.clicked.connect(self.accept)
|
||||||
|
cancel_button.clicked.connect(self.reject)
|
||||||
|
button_layout.addWidget(ok_button)
|
||||||
|
button_layout.addWidget(cancel_button)
|
||||||
|
|
||||||
|
# Add all widgets to main layout
|
||||||
|
layout.addLayout(length_layout)
|
||||||
|
layout.addWidget(self.seperator)
|
||||||
|
layout.addWidget(self.cut_checkbox)
|
||||||
|
layout.addWidget(self.union_checkbox)
|
||||||
|
layout.addWidget(self.seperator)
|
||||||
|
layout.addWidget(self.symmetric_checkbox)
|
||||||
|
layout.addWidget(self.invert_checkbox)
|
||||||
|
layout.addWidget(self.seperator)
|
||||||
|
layout.addWidget(self.rounded_checkbox)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Set up the UI from the generated GUI module
|
||||||
|
self.ui = Ui_fluencyCAD()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
|
||||||
|
self.custom_3D_Widget = VTKWidget()
|
||||||
|
layout = self.ui.gl_box.layout()
|
||||||
|
layout.addWidget(self.custom_3D_Widget)
|
||||||
|
size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||||
|
#self.custom_3D_Widget.setSizePolicy(size_policy)
|
||||||
|
|
||||||
|
self.sketchWidget = SketchWidget()
|
||||||
|
layout2 = self.ui.sketch_tab.layout() # Get the layout of self.ui.gl_canvas
|
||||||
|
layout2.addWidget(self.sketchWidget)
|
||||||
|
size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||||
|
self.sketchWidget.setSizePolicy(size_policy)
|
||||||
|
|
||||||
|
### Main Model -OLD ?
|
||||||
|
"""self.model = {
|
||||||
|
'sketches': {},
|
||||||
|
'operation': {},
|
||||||
|
}"""
|
||||||
|
self.list_selected = []
|
||||||
|
|
||||||
|
#self.ui.pb_apply_code.pressed.connect(self.check_current_tab)
|
||||||
|
self.ui.sketch_list.currentItemChanged.connect(self.on_item_changed)
|
||||||
|
self.ui.sketch_list.itemChanged.connect(self.draw_mesh)
|
||||||
|
|
||||||
|
### Sketches
|
||||||
|
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_to_compo)
|
||||||
|
self.ui.pb_del_sketch.pressed.connect(self.del_sketch)
|
||||||
|
self.ui.pb_edt_sktch.pressed.connect(self.edit_sketch)
|
||||||
|
|
||||||
|
self.ui.pb_flip_face.pressed.connect(self.on_flip_face)
|
||||||
|
|
||||||
|
###Modes
|
||||||
|
self.ui.pb_linetool.pressed.connect(self.act_line_mode)
|
||||||
|
self.ui.pb_con_ptpt.pressed.connect(self.act_constrain_pt_pt_mode)
|
||||||
|
self.ui.pb_con_line.pressed.connect(self.act_constrain_pt_line_mode)
|
||||||
|
self.ui.pb_con_horiz.pressed.connect(self.act_constrain_horiz_line_mode)
|
||||||
|
self.ui.pb_con_vert.pressed.connect(self.act_constrain_vert_line_mode)
|
||||||
|
self.ui.pb_con_dist.pressed.connect(self.act_constrain_distance_mode)
|
||||||
|
self.ui.pb_con_mid.pressed.connect(self.act_constrain_mid_point_mode)
|
||||||
|
|
||||||
|
### Operations
|
||||||
|
self.ui.pb_extrdop.pressed.connect(self.send_extrude)
|
||||||
|
self.ui.pb_cutop.pressed.connect(self.send_cut)
|
||||||
|
self.ui.pb_del_body.pressed.connect(self.del_body)
|
||||||
|
|
||||||
|
self.sketchWidget.constrain_done.connect(self.draw_op_complete)
|
||||||
|
self.setFocusPolicy(Qt.StrongFocus)
|
||||||
|
|
||||||
|
self.send_command.connect(self.custom_3D_Widget.on_receive_command)
|
||||||
|
|
||||||
|
self.ui.actionNew_Project.triggered.connect(self.new_project)
|
||||||
|
|
||||||
|
self.project = Project()
|
||||||
|
self.new_project()
|
||||||
|
|
||||||
|
### 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):
|
||||||
|
name = f"sketches-{str(names.get_first_name())}"
|
||||||
|
sketch = Sketch()
|
||||||
|
sketch.id = name
|
||||||
|
sketch.origin = [0,0,0]
|
||||||
|
|
||||||
|
self.sketchWidget.reset_buffers()
|
||||||
|
self.sketchWidget.create_sketch(sketch)
|
||||||
|
|
||||||
|
def add_new_sketch_wp(self):
|
||||||
|
## Sketch projected from 3d view into 2d
|
||||||
|
name = f"sketches-{str(names.get_first_name())}"
|
||||||
|
sketch = Sketch()
|
||||||
|
sketch.id = name
|
||||||
|
sketch.origin = self.custom_3D_Widget.centroid
|
||||||
|
sketch.normal = self.custom_3D_Widget.selected_normal
|
||||||
|
sketch.slv_points = []
|
||||||
|
sketch.slv_lines = []
|
||||||
|
sketch.proj_points = self.custom_3D_Widget.project_tosketch_points
|
||||||
|
sketch.proj_lines = self.custom_3D_Widget.project_tosketch_lines
|
||||||
|
|
||||||
|
self.sketchWidget.reset_buffers()
|
||||||
|
self.sketchWidget.create_sketch(sketch)
|
||||||
|
self.sketchWidget.create_workplane_projected()
|
||||||
|
|
||||||
|
if not sketch.proj_lines:
|
||||||
|
self.sketchWidget.convert_proj_points(sketch.proj_points)
|
||||||
|
|
||||||
|
self.sketchWidget.convert_proj_lines(sketch.proj_lines)
|
||||||
|
self.sketchWidget.update()
|
||||||
|
|
||||||
|
# CLear all selections after it has been projected
|
||||||
|
self.custom_3D_Widget.project_tosketch_points.clear()
|
||||||
|
self.custom_3D_Widget.project_tosketch_lines.clear()
|
||||||
|
self.custom_3D_Widget.clear_actors_projection()
|
||||||
|
self.custom_3D_Widget.clear_actors_normals()
|
||||||
|
|
||||||
|
def add_sketch_to_compo(self):
|
||||||
|
"""
|
||||||
|
Add sketch to component
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
sketch = Sketch()
|
||||||
|
sketch_from_widget = self.sketchWidget.get_sketch()
|
||||||
|
|
||||||
|
#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():
|
||||||
|
self.sketchWidget.mouse_mode = 'line'
|
||||||
|
else:
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
self.sketchWidget.line_draw_buffer = [None, None]
|
||||||
|
|
||||||
|
def act_constrain_pt_pt_mode(self):
|
||||||
|
if not self.ui.pb_con_ptpt.isChecked():
|
||||||
|
self.sketchWidget.mouse_mode = 'pt_pt'
|
||||||
|
else:
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
|
||||||
|
def act_constrain_pt_line_mode(self):
|
||||||
|
if not self.ui.pb_con_line.isChecked():
|
||||||
|
self.sketchWidget.mouse_mode = 'pt_line'
|
||||||
|
else:
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
|
||||||
|
def act_constrain_horiz_line_mode(self):
|
||||||
|
if not self.ui.pb_con_horiz.isChecked():
|
||||||
|
self.sketchWidget.mouse_mode = 'horiz'
|
||||||
|
else:
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
|
||||||
|
def act_constrain_vert_line_mode(self):
|
||||||
|
if not self.ui.pb_con_vert.isChecked():
|
||||||
|
self.sketchWidget.mouse_mode = 'vert'
|
||||||
|
else:
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
|
||||||
|
def act_constrain_distance_mode(self):
|
||||||
|
if not self.ui.pb_con_dist.isChecked():
|
||||||
|
self.sketchWidget.mouse_mode = 'distance'
|
||||||
|
else:
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
|
||||||
|
def act_constrain_mid_point_mode(self):
|
||||||
|
if not self.ui.pb_con_mid.isChecked():
|
||||||
|
self.sketchWidget.mouse_mode = 'pb_con_mid'
|
||||||
|
else:
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
|
||||||
|
def draw_op_complete(self):
|
||||||
|
# safely disable the line modes
|
||||||
|
self.ui.pb_linetool.setChecked(False)
|
||||||
|
self.ui.pb_con_ptpt.setChecked(False)
|
||||||
|
self.ui.pb_con_line.setChecked(False)
|
||||||
|
self.ui.pb_con_dist.setChecked(False)
|
||||||
|
self.ui.pb_con_mid.setChecked(False)
|
||||||
|
self.ui.pb_con_perp.setChecked(False)
|
||||||
|
|
||||||
|
self.sketchWidget.mouse_mode = None
|
||||||
|
self.sketchWidget.reset_buffers()
|
||||||
|
|
||||||
|
def draw_mesh(self):
|
||||||
|
|
||||||
|
name = self.ui.body_list.currentItem().text()
|
||||||
|
print("selected_for disp", name)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
vertices, faces = model_data
|
||||||
|
vesta.save_mesh_as_stl(vertices, faces, 'test.stl')
|
||||||
|
self.custom_3D_Widget.render_from_points_direct_with_faces(vertices, faces)
|
||||||
|
|
||||||
|
def on_item_changed(self, current_item, previous_item):
|
||||||
|
if current_item:
|
||||||
|
name = current_item.text()
|
||||||
|
#self.view_update()
|
||||||
|
print(f"Selected item: {name}")
|
||||||
|
|
||||||
|
def update_body(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def del_body(self):
|
||||||
|
print("Deleting")
|
||||||
|
name = self.ui.body_list.currentItem() # Get the current item
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
item_name = name.text()
|
||||||
|
print("obj_name", item_name)
|
||||||
|
# Check if the 'operation' key exists in the model dictionary
|
||||||
|
|
||||||
|
if 'operation' in self.model and item_name in self.model['operation']:
|
||||||
|
if self.model['operation'][item_name]['id'] == item_name:
|
||||||
|
row = self.ui.body_list.row(name) # Get the row of the current item
|
||||||
|
self.ui.body_list.takeItem(row) # Remove the item from the list widget
|
||||||
|
self.model['operation'].pop(item_name) # Remove the item from the operation dictionary
|
||||||
|
print(f"Removed operation: {item_name}")
|
||||||
|
self.custom_3D_Widget.clear_mesh()
|
||||||
|
|
||||||
|
def send_extrude(self):
|
||||||
|
# Dialog input
|
||||||
|
is_symmetric = None
|
||||||
|
length = None
|
||||||
|
invert = None
|
||||||
|
|
||||||
|
selected = self.ui.sketch_list.currentItem()
|
||||||
|
name = selected.text()
|
||||||
|
|
||||||
|
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
|
||||||
|
del points[-1]
|
||||||
|
|
||||||
|
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}")
|
||||||
|
else:
|
||||||
|
length = 0
|
||||||
|
#print("Extrude cancelled")
|
||||||
|
|
||||||
|
normal = self.custom_3D_Widget.selected_normal
|
||||||
|
#print("Normie enter", normal)
|
||||||
|
if normal is None:
|
||||||
|
normal = [0, 0, 1]
|
||||||
|
|
||||||
|
centroid = self.custom_3D_Widget.centroid
|
||||||
|
if centroid is None:
|
||||||
|
centroid = [0, 0, 0]
|
||||||
|
else:
|
||||||
|
centroid = list(centroid)
|
||||||
|
#print("This centroid ", centroid)
|
||||||
|
|
||||||
|
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}"
|
||||||
|
|
||||||
|
body = Body()
|
||||||
|
body.sketch = sketch #we add the sketches for reference here
|
||||||
|
body.id = name_op
|
||||||
|
body.sdf_body = f
|
||||||
|
|
||||||
|
### Interactor
|
||||||
|
interactor = Interactor()
|
||||||
|
interactor.add_lines_for_interactor(sketch.interactor_lines)
|
||||||
|
interactor.invert = invert
|
||||||
|
|
||||||
|
if not invert:
|
||||||
|
edges = interactor_mesh.generate_mesh(interactor.lines, 0, length)
|
||||||
|
else:
|
||||||
|
edges = interactor_mesh.generate_mesh(interactor.lines, 0, -length)
|
||||||
|
|
||||||
|
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.ui.body_list.addItem(name_op)
|
||||||
|
items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0]
|
||||||
|
self.ui.body_list.setCurrentItem(items)
|
||||||
|
|
||||||
|
self.draw_mesh()
|
||||||
|
|
||||||
|
def send_cut(self):
|
||||||
|
"""name = self.ui.body_list.currentItem().text()
|
||||||
|
points = self.model['operation'][name]['sdf_object']
|
||||||
|
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:
|
||||||
|
f = difference(self.list_selected[0], self.list_selected[1]) # equivalent
|
||||||
|
|
||||||
|
element = {
|
||||||
|
'id': name,
|
||||||
|
'type': 'cut',
|
||||||
|
'sdf_object': f,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create body element and assign known stuff
|
||||||
|
name_op = f"cut-{name}"
|
||||||
|
|
||||||
|
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:
|
||||||
|
print("mindestens 2!")
|
||||||
|
|
||||||
|
def load_and_render(self, file):
|
||||||
|
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
|
||||||
|
|
||||||
|
def angle_between_normals(self, normal1, normal2):
|
||||||
|
# Ensure the vectors are normalized
|
||||||
|
n1 = normal1 / np.linalg.norm(normal1)
|
||||||
|
n2 = normal2 / np.linalg.norm(normal2)
|
||||||
|
|
||||||
|
# Compute the dot product
|
||||||
|
dot_product = np.dot(n1, n2)
|
||||||
|
|
||||||
|
# Clip the dot product to the valid range [-1, 1]
|
||||||
|
dot_product = np.clip(dot_product, -1.0, 1.0)
|
||||||
|
|
||||||
|
# Compute the angle in radians
|
||||||
|
angle_rad = np.arccos(dot_product)
|
||||||
|
|
||||||
|
# Convert to degrees if needed
|
||||||
|
angle_deg = np.degrees(angle_rad)
|
||||||
|
print("Angle deg", angle_deg)
|
||||||
|
|
||||||
|
return angle_rad
|
||||||
|
|
||||||
|
def offset_syn(self, f, length):
|
||||||
|
f = f.translate((0,0, length / 2))
|
||||||
|
return f
|
||||||
|
|
||||||
|
def distance(self, p1, p2):
|
||||||
|
"""Calculate the distance between two points."""
|
||||||
|
print("p1", p1)
|
||||||
|
print("p2", p2)
|
||||||
|
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
self.sdf_points = points_for_sdf
|
||||||
|
|
||||||
|
def filter_lines_for_interactor(self, lines):
|
||||||
|
### Filter lines that are not meant to be drawn for the interactor like contruction lines
|
||||||
|
filtered_lines = []
|
||||||
|
for line in lines:
|
||||||
|
if not line.is_helper:
|
||||||
|
filtered_lines.append(line)
|
||||||
|
|
||||||
|
self.interactor_lines = filtered_lines
|
||||||
|
|
||||||
|
def extrude(self, height: float, symet: bool = True, invert: bool = False, offset_length: float = None):
|
||||||
|
"""
|
||||||
|
Extrude a 2D shape into 3D, orient it along the normal, and position it relative to the centroid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Normalize the normal vector
|
||||||
|
normal = np.array(self.normal)
|
||||||
|
normal = normal / np.linalg.norm(self.normal)
|
||||||
|
|
||||||
|
# Create the 2D shape
|
||||||
|
f = polygon(self.sdf_points)
|
||||||
|
|
||||||
|
# Extrude the shape along the Z-axis
|
||||||
|
f = f.extrude(height)
|
||||||
|
|
||||||
|
# Center the shape along its extrusion axis
|
||||||
|
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, self.origin, normal)
|
||||||
|
# Adjust the offset vector by subtracting the inset distance along the normal direction
|
||||||
|
adjusted_offset = offset_vector - (normal * height)
|
||||||
|
if invert:
|
||||||
|
# Translate the shape along the adjusted offset vector
|
||||||
|
f = f.translate(adjusted_offset)
|
||||||
|
else:
|
||||||
|
f = f.translate(offset_vector)
|
||||||
|
|
||||||
|
# If offset_length is provided, adjust the offset_vector
|
||||||
|
if offset_length is not None:
|
||||||
|
# Check if offset_vector is not a zero vector
|
||||||
|
offset_vector_magnitude = np.linalg.norm(offset_vector)
|
||||||
|
if offset_vector_magnitude > 1e-10: # Use a small threshold to avoid floating-point issues
|
||||||
|
# Normalize the offset vector
|
||||||
|
offset_vector_norm = offset_vector / offset_vector_magnitude
|
||||||
|
# Scale the normalized vector by the desired length
|
||||||
|
offset_vector = offset_vector_norm * offset_length
|
||||||
|
f = f.translate(offset_vector)
|
||||||
|
else:
|
||||||
|
print("Warning: Offset vector has zero magnitude. Using original vector.")
|
||||||
|
|
||||||
|
# Translate the shape along the adjusted offset vector
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
class Output:
|
||||||
|
def export_mesh(self, sdf_object):
|
||||||
|
"""FINAL EXPORT"""
|
||||||
|
result_points = sdf_object.generate()
|
||||||
|
write_binary_stl('out.stl', result_points)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
class Project:
|
||||||
|
"""Project -> Timeline -> Component -> Sketch -> Body / Interactor -> Connector -> Assembly -> PB Render"""
|
||||||
|
timeline: Timeline = None
|
||||||
|
assembly: Assembly = None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication()
|
||||||
|
window = MainWindow()
|
||||||
|
window.show()
|
||||||
|
app.exec()
|
||||||
|
|
||||||
|
|
43
mesh_modules/interactor_mesh.py
Normal file
43
mesh_modules/interactor_mesh.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Draw simple boundary based on the lines and depth
|
||||||
|
|
||||||
|
def generate_mesh(lines: list, z_origin: float, depth: float, invert: bool = False):
|
||||||
|
|
||||||
|
origin = create_3D(lines, z_origin)
|
||||||
|
|
||||||
|
if invert :
|
||||||
|
extruded = create_3D(lines, z_origin - depth)
|
||||||
|
else:
|
||||||
|
extruded = create_3D(lines, z_origin + depth)
|
||||||
|
|
||||||
|
vert_lines = create_vert_lines(origin, extruded)
|
||||||
|
|
||||||
|
print(f"Result = {origin} / {extruded} / {vert_lines}")
|
||||||
|
|
||||||
|
return origin + vert_lines + extruded
|
||||||
|
|
||||||
|
|
||||||
|
def create_vert_lines(origin, extruded):
|
||||||
|
vert_lines = []
|
||||||
|
for d3_point_o, d3point_e in zip(origin, extruded):
|
||||||
|
for sp3d_1, sp3d_2 in zip(d3_point_o, d3point_e):
|
||||||
|
new_line = sp3d_1, sp3d_2
|
||||||
|
vert_lines.append(new_line)
|
||||||
|
return vert_lines
|
||||||
|
|
||||||
|
|
||||||
|
def create_3D(lines, z_pos):
|
||||||
|
line_loop = []
|
||||||
|
for coordinate2d in lines:
|
||||||
|
start, end = coordinate2d
|
||||||
|
|
||||||
|
xs, ys = start
|
||||||
|
coordinate3d_start_orig = xs, -ys, z_pos
|
||||||
|
|
||||||
|
xe, ye = end
|
||||||
|
coordinate3d_end_orig = xe, -ye, z_pos
|
||||||
|
|
||||||
|
line3d_orig = coordinate3d_start_orig, coordinate3d_end_orig
|
||||||
|
|
||||||
|
line_loop.append(line3d_orig)
|
||||||
|
|
||||||
|
return line_loop
|
213
mesh_modules/simple_mesh.py
Normal file
213
mesh_modules/simple_mesh.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay, ConvexHull
|
||||||
|
from shapely.geometry import Polygon, Point
|
||||||
|
|
||||||
|
|
||||||
|
def alpha_shape(points, alpha):
|
||||||
|
"""
|
||||||
|
Compute the alpha shape (concave hull) of a set of points.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def add_edge(edges, edge_points, points, i, j):
|
||||||
|
"""Add a line between the i-th and j-th points if not in the list already"""
|
||||||
|
if (i, j) in edges or (j, i) in edges:
|
||||||
|
return
|
||||||
|
edges.add((i, j))
|
||||||
|
edge_points.append(points[[i, j]])
|
||||||
|
|
||||||
|
tri = Delaunay(points)
|
||||||
|
edges = set()
|
||||||
|
edge_points = []
|
||||||
|
|
||||||
|
# Loop over triangles:
|
||||||
|
for ia, ib, ic in tri.simplices:
|
||||||
|
pa = points[ia]
|
||||||
|
pb = points[ib]
|
||||||
|
pc = points[ic]
|
||||||
|
# Lengths of sides of triangle
|
||||||
|
a = np.sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
|
||||||
|
b = np.sqrt((pb[0] - pc[0]) ** 2 + (pb[1] - pc[1]) ** 2)
|
||||||
|
c = np.sqrt((pc[0] - pa[0]) ** 2 + (pc[1] - pa[1]) ** 2)
|
||||||
|
# Semiperimeter of triangle
|
||||||
|
s = (a + b + c) / 2.0
|
||||||
|
# Area of triangle by Heron's formula
|
||||||
|
area = np.sqrt(s * (s - a) * (s - b) * (s - c))
|
||||||
|
circum_r = a * b * c / (4.0 * area)
|
||||||
|
# Here's the radius filter.
|
||||||
|
if circum_r < 1.0 / alpha:
|
||||||
|
add_edge(edges, edge_points, points, ia, ib)
|
||||||
|
add_edge(edges, edge_points, points, ib, ic)
|
||||||
|
add_edge(edges, edge_points, points, ic, ia)
|
||||||
|
|
||||||
|
m = np.array(edge_points)
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def generate_mesh(points, depth, alpha=0.1):
|
||||||
|
"""
|
||||||
|
Generate a mesh by extruding a 2D shape along the Z-axis, automatically detecting holes.
|
||||||
|
|
||||||
|
:param points: List of (x, y) tuples representing all points of the 2D shape, including potential holes.
|
||||||
|
:param depth: Extrusion depth along the Z-axis.
|
||||||
|
:param alpha: Alpha value for the alpha shape algorithm (controls the "tightness" of the boundary).
|
||||||
|
:return: Tuple of vertices and faces.
|
||||||
|
"""
|
||||||
|
# Convert points to a numpy array
|
||||||
|
points_2d = np.array(points)
|
||||||
|
|
||||||
|
# Compute the alpha shape (outer boundary)
|
||||||
|
boundary_edges = alpha_shape(points_2d, alpha)
|
||||||
|
|
||||||
|
# Create a Polygon from the boundary
|
||||||
|
boundary_polygon = Polygon(boundary_edges)
|
||||||
|
|
||||||
|
# Separate points into boundary and interior
|
||||||
|
boundary_points = []
|
||||||
|
interior_points = []
|
||||||
|
for point in points:
|
||||||
|
if Point(point).touches(boundary_polygon) or Point(point).within(boundary_polygon):
|
||||||
|
if Point(point).touches(boundary_polygon):
|
||||||
|
boundary_points.append(point)
|
||||||
|
else:
|
||||||
|
interior_points.append(point)
|
||||||
|
|
||||||
|
# Perform Delaunay triangulation on all points
|
||||||
|
tri = Delaunay(points_2d)
|
||||||
|
|
||||||
|
# Generate the top and bottom faces
|
||||||
|
bottom_face = np.hstack((tri.points, np.zeros((tri.points.shape[0], 1))))
|
||||||
|
top_face = np.hstack((tri.points, np.ones((tri.points.shape[0], 1)) * depth))
|
||||||
|
|
||||||
|
# Combine top and bottom vertices
|
||||||
|
vertices_array = np.vstack((bottom_face, top_face))
|
||||||
|
|
||||||
|
# Create faces
|
||||||
|
faces = []
|
||||||
|
|
||||||
|
# Bottom face triangulation
|
||||||
|
for simplex in tri.simplices:
|
||||||
|
faces.append(simplex.tolist())
|
||||||
|
|
||||||
|
# Top face triangulation (with an offset)
|
||||||
|
top_offset = len(tri.points)
|
||||||
|
for simplex in tri.simplices:
|
||||||
|
faces.append([i + top_offset for i in simplex])
|
||||||
|
|
||||||
|
# Side faces for the outer boundary
|
||||||
|
for i in range(len(boundary_points)):
|
||||||
|
next_i = (i + 1) % len(boundary_points)
|
||||||
|
current = points.index(boundary_points[i])
|
||||||
|
next_point = points.index(boundary_points[next_i])
|
||||||
|
faces.append([current, top_offset + current, top_offset + next_point])
|
||||||
|
faces.append([current, top_offset + next_point, next_point])
|
||||||
|
|
||||||
|
# Convert vertices to the desired format: list of tuples
|
||||||
|
vertices = [tuple(vertex) for vertex in vertices_array]
|
||||||
|
|
||||||
|
return vertices, faces
|
||||||
|
|
||||||
|
def generate_mesh_wholes(points, holes, depth):
|
||||||
|
"""
|
||||||
|
Generate a mesh by extruding a 2D shape along the Z-axis, including holes.
|
||||||
|
|
||||||
|
:param points: List of (x, y) tuples representing the outer boundary of the 2D shape.
|
||||||
|
:param holes: List of lists, where each inner list contains (x, y) tuples representing a hole.
|
||||||
|
:param depth: Extrusion depth along the Z-axis.
|
||||||
|
:return: Tuple of vertices and faces.
|
||||||
|
"""
|
||||||
|
# Convert points to a numpy array
|
||||||
|
points_2d = np.array(points)
|
||||||
|
|
||||||
|
# Prepare points for triangulation
|
||||||
|
triangulation_points = points_2d.tolist()
|
||||||
|
for hole in holes:
|
||||||
|
triangulation_points.extend(hole)
|
||||||
|
|
||||||
|
# Perform Delaunay triangulation
|
||||||
|
tri = Delaunay(np.array(triangulation_points))
|
||||||
|
|
||||||
|
# Generate the top and bottom faces
|
||||||
|
bottom_face = np.hstack((tri.points, np.zeros((tri.points.shape[0], 1))))
|
||||||
|
top_face = np.hstack((tri.points, np.ones((tri.points.shape[0], 1)) * depth))
|
||||||
|
|
||||||
|
# Combine top and bottom vertices
|
||||||
|
vertices_array = np.vstack((bottom_face, top_face))
|
||||||
|
|
||||||
|
# Create faces
|
||||||
|
faces = []
|
||||||
|
|
||||||
|
# Bottom face triangulation
|
||||||
|
for simplex in tri.simplices:
|
||||||
|
faces.append(simplex.tolist())
|
||||||
|
|
||||||
|
# Top face triangulation (with an offset)
|
||||||
|
top_offset = len(tri.points)
|
||||||
|
for simplex in tri.simplices:
|
||||||
|
faces.append([i + top_offset for i in simplex])
|
||||||
|
|
||||||
|
# Side faces
|
||||||
|
for i in range(len(points)):
|
||||||
|
next_i = (i + 1) % len(points)
|
||||||
|
faces.append([i, top_offset + i, top_offset + next_i])
|
||||||
|
faces.append([i, top_offset + next_i, next_i])
|
||||||
|
|
||||||
|
# Side faces for holes
|
||||||
|
start_index = len(points)
|
||||||
|
for hole in holes:
|
||||||
|
for i in range(len(hole)):
|
||||||
|
current = start_index + i
|
||||||
|
next_i = start_index + (i + 1) % len(hole)
|
||||||
|
faces.append([current, top_offset + next_i, top_offset + current])
|
||||||
|
faces.append([current, next_i, top_offset + next_i])
|
||||||
|
start_index += len(hole)
|
||||||
|
|
||||||
|
# Convert vertices to the desired format: list of tuples
|
||||||
|
vertices = [tuple(vertex) for vertex in vertices_array]
|
||||||
|
|
||||||
|
return vertices, faces
|
||||||
|
|
||||||
|
def generate_mesh_simple(points, depth):
|
||||||
|
"""
|
||||||
|
Generate a mesh by extruding a 2D shape along the Z-axis.
|
||||||
|
|
||||||
|
:param points: List of (x, y) tuples representing the 2D shape.
|
||||||
|
:param depth: Extrusion depth along the Z-axis.
|
||||||
|
:return: Tuple of vertices and faces.
|
||||||
|
"""
|
||||||
|
# Convert points to a numpy array
|
||||||
|
points_2d = np.array(points)
|
||||||
|
|
||||||
|
# Get the convex hull of the points to ensure they form a proper polygon
|
||||||
|
hull = ConvexHull(points_2d)
|
||||||
|
hull_points = points_2d[hull.vertices]
|
||||||
|
|
||||||
|
# Generate the top and bottom faces
|
||||||
|
bottom_face = np.hstack((hull_points, np.zeros((hull_points.shape[0], 1))))
|
||||||
|
top_face = np.hstack((hull_points, np.ones((hull_points.shape[0], 1)) * depth))
|
||||||
|
|
||||||
|
# Combine top and bottom vertices
|
||||||
|
vertices_array = np.vstack((bottom_face, top_face))
|
||||||
|
|
||||||
|
# Create faces
|
||||||
|
faces = []
|
||||||
|
|
||||||
|
# Bottom face triangulation (counter-clockwise)
|
||||||
|
for i in range(len(hull_points) - 2):
|
||||||
|
faces.append([0, i + 2, i + 1])
|
||||||
|
|
||||||
|
# Top face triangulation (counter-clockwise, with an offset)
|
||||||
|
top_offset = len(hull_points)
|
||||||
|
for i in range(len(hull_points) - 2):
|
||||||
|
faces.append([top_offset, top_offset + i + 1, top_offset + i + 2])
|
||||||
|
|
||||||
|
# Side faces (ensure counter-clockwise order)
|
||||||
|
for i in range(len(hull_points)):
|
||||||
|
next_i = (i + 1) % len(hull_points)
|
||||||
|
faces.append([i, top_offset + i, top_offset + next_i])
|
||||||
|
faces.append([i, top_offset + next_i, next_i])
|
||||||
|
|
||||||
|
# Convert vertices to the desired format: list of tuples
|
||||||
|
vertices = [tuple(vertex) for vertex in vertices_array]
|
||||||
|
|
||||||
|
return vertices, faces
|
||||||
|
|
119
mesh_modules/vesta_mesh.py
Normal file
119
mesh_modules/vesta_mesh.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import numpy as np
|
||||||
|
from skimage import measure
|
||||||
|
import multiprocessing
|
||||||
|
from functools import partial
|
||||||
|
from multiprocessing.pool import ThreadPool
|
||||||
|
import itertools
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def _cartesian_product(*arrays):
|
||||||
|
la = len(arrays)
|
||||||
|
dtype = np.result_type(*arrays)
|
||||||
|
arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)
|
||||||
|
for i, a in enumerate(np.ix_(*arrays)):
|
||||||
|
arr[..., i] = a
|
||||||
|
return arr.reshape(-1, la)
|
||||||
|
|
||||||
|
|
||||||
|
class VESTA:
|
||||||
|
def __init__(self, sdf, bounds=None, resolution=64, threshold=0.0, workers=None):
|
||||||
|
self.sdf = sdf
|
||||||
|
self.bounds = bounds
|
||||||
|
self.resolution = resolution
|
||||||
|
self.threshold = threshold
|
||||||
|
self.workers = workers or multiprocessing.cpu_count()
|
||||||
|
|
||||||
|
def _estimate_bounds(self):
|
||||||
|
s = 16
|
||||||
|
x0 = y0 = z0 = -1e9
|
||||||
|
x1 = y1 = z1 = 1e9
|
||||||
|
prev = None
|
||||||
|
for i in range(32):
|
||||||
|
X = np.linspace(x0, x1, s)
|
||||||
|
Y = np.linspace(y0, y1, s)
|
||||||
|
Z = np.linspace(z0, z1, s)
|
||||||
|
d = np.array([X[1] - X[0], Y[1] - Y[0], Z[1] - Z[0]])
|
||||||
|
threshold = np.linalg.norm(d) / 2
|
||||||
|
if threshold == prev:
|
||||||
|
break
|
||||||
|
prev = threshold
|
||||||
|
P = _cartesian_product(X, Y, Z)
|
||||||
|
volume = self.sdf(P).reshape((len(X), len(Y), len(Z)))
|
||||||
|
where = np.argwhere(np.abs(volume) <= threshold)
|
||||||
|
if where.size == 0:
|
||||||
|
continue
|
||||||
|
x1, y1, z1 = (x0, y0, z0) + where.max(axis=0) * d + d / 2
|
||||||
|
x0, y0, z0 = (x0, y0, z0) + where.min(axis=0) * d - d / 2
|
||||||
|
if prev is None:
|
||||||
|
raise ValueError("Failed to estimate bounds. No points found within any threshold.")
|
||||||
|
return ((x0, y0, z0), (x1, y1, z1))
|
||||||
|
|
||||||
|
def _vesta_worker(self, chunk):
|
||||||
|
x0, x1, y0, y1, z0, z1 = chunk
|
||||||
|
X = np.linspace(x0, x1, self.resolution)
|
||||||
|
Y = np.linspace(y0, y1, self.resolution)
|
||||||
|
Z = np.linspace(z0, z1, self.resolution)
|
||||||
|
P = _cartesian_product(X, Y, Z)
|
||||||
|
V = self.sdf(P).reshape((self.resolution, self.resolution, self.resolution))
|
||||||
|
|
||||||
|
try:
|
||||||
|
verts, faces, _, _ = measure.marching_cubes(V, self.threshold)
|
||||||
|
except RuntimeError:
|
||||||
|
# Return empty arrays if marching_cubes fails
|
||||||
|
return np.array([]), np.array([])
|
||||||
|
|
||||||
|
# Scale and translate vertices to match the chunk's bounds
|
||||||
|
verts = verts / (self.resolution - 1)
|
||||||
|
verts[:, 0] = verts[:, 0] * (x1 - x0) + x0
|
||||||
|
verts[:, 1] = verts[:, 1] * (y1 - y0) + y0
|
||||||
|
verts[:, 2] = verts[:, 2] * (z1 - z0) + z0
|
||||||
|
|
||||||
|
return verts, faces
|
||||||
|
|
||||||
|
def _merge_meshes(self, results):
|
||||||
|
all_verts = []
|
||||||
|
all_faces = []
|
||||||
|
offset = 0
|
||||||
|
for verts, faces in results:
|
||||||
|
if len(verts) > 0 and len(faces) > 0:
|
||||||
|
all_verts.append(verts)
|
||||||
|
all_faces.append(faces + offset)
|
||||||
|
offset += len(verts)
|
||||||
|
if not all_verts or not all_faces:
|
||||||
|
return np.array([]), np.array([])
|
||||||
|
return np.vstack(all_verts), np.vstack(all_faces)
|
||||||
|
|
||||||
|
def generate_mesh(self):
|
||||||
|
if self.bounds is None:
|
||||||
|
self.bounds = self._estimate_bounds()
|
||||||
|
|
||||||
|
(x0, y0, z0), (x1, y1, z1) = self.bounds
|
||||||
|
chunks = [
|
||||||
|
(x0, x1, y0, y1, z0, z1)
|
||||||
|
]
|
||||||
|
|
||||||
|
with ThreadPool(self.workers) as pool:
|
||||||
|
results = pool.map(self._vesta_worker, chunks)
|
||||||
|
|
||||||
|
verts, faces = self._merge_meshes(results)
|
||||||
|
return verts, faces
|
||||||
|
|
||||||
|
|
||||||
|
def generate_mesh_from_sdf(sdf, bounds=None, resolution=64, threshold=0.0, workers=None):
|
||||||
|
vesta = VESTA(sdf, bounds, resolution, threshold, workers)
|
||||||
|
return vesta.generate_mesh()
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to save the mesh as an STL file
|
||||||
|
def save_mesh_as_stl(vertices, faces, filename):
|
||||||
|
from stl import mesh
|
||||||
|
|
||||||
|
# Create the mesh
|
||||||
|
cube = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
|
||||||
|
for i, f in enumerate(faces):
|
||||||
|
for j in range(3):
|
||||||
|
cube.vectors[i][j] = vertices[f[j], :]
|
||||||
|
|
||||||
|
# Write the mesh to file
|
||||||
|
cube.save(filename)
|
5
meshtest.py
Normal file
5
meshtest.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from sdf import *
|
||||||
|
f = box(1).translate((1,1,-0.2))
|
||||||
|
c = hexagon(1).extrude(1).orient([0,0,-1])
|
||||||
|
c = f & c
|
||||||
|
f.save("out.stl")
|
29
qodana.yaml
Normal file
29
qodana.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#-------------------------------------------------------------------------------#
|
||||||
|
# Qodana analysis is configured by qodana.yaml file #
|
||||||
|
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||||
|
#-------------------------------------------------------------------------------#
|
||||||
|
version: "1.0"
|
||||||
|
|
||||||
|
#Specify inspection profile for code analysis
|
||||||
|
profile:
|
||||||
|
name: qodana.starter
|
||||||
|
|
||||||
|
#Enable inspections
|
||||||
|
#include:
|
||||||
|
# - name: <SomeEnabledInspectionId>
|
||||||
|
|
||||||
|
#Disable inspections
|
||||||
|
#exclude:
|
||||||
|
# - name: <SomeDisabledInspectionId>
|
||||||
|
# paths:
|
||||||
|
# - <path/where/not/run/inspection>
|
||||||
|
|
||||||
|
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||||
|
#bootstrap: sh ./prepare-qodana.sh
|
||||||
|
|
||||||
|
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||||
|
#plugins:
|
||||||
|
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||||
|
|
||||||
|
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||||
|
linter: jetbrains/qodana-<linter>:latest
|
66
requirements.txt
Normal file
66
requirements.txt
Normal file
@ -0,0 +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
|
127
untitled.ui
Normal file
127
untitled.ui
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>fluencyCAD</class>
|
||||||
|
<widget class="QMainWindow" name="fluencyCAD">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>600</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>fluencyCAD</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>Drawing</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gl_box">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>4</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Model Viewer</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>1</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Code Editor</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="textEdit"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_4">
|
||||||
|
<property name="title">
|
||||||
|
<string>Code Tools</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Apply Code</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete Code</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Export STL</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save code</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Load Code</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Modify</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>24</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
Loading…
x
Reference in New Issue
Block a user