From b1ac7701e65d05dc2711fd3884193646b80933ce Mon Sep 17 00:00:00 2001 From: turn211 <78619703+turn211@users.noreply.github.com> Date: Wed, 17 Mar 2021 19:25:38 -0700 Subject: [PATCH 01/21] [Stylesheet] ProDark: major bugfix for add-ons https://forum.freecadweb.org/viewtopic.php?f=34&t=55134&start=100 --- src/Gui/Stylesheets/ProDark.qss | 378 +++++++++++++++++++------------- 1 file changed, 226 insertions(+), 152 deletions(-) diff --git a/src/Gui/Stylesheets/ProDark.qss b/src/Gui/Stylesheets/ProDark.qss index 6e2f52f452..3238ff9885 100644 --- a/src/Gui/Stylesheets/ProDark.qss +++ b/src/Gui/Stylesheets/ProDark.qss @@ -24,7 +24,9 @@ INSTALLATION WINDOWS = C:/[INSTALLATION_PATH]/FreeCAD/data/Gui/Stylesheets/ LINUX = /home/[YOUR_USER_NAME]/.FreeCAD/Gui/Stylesheets/ -============================================================================================================ +============================================================================================================ +THESE COLOURS WERE USED AS TEMP SCRATCHPAD FOR DESIGNING. PLEASE DISREGARD! + BACKGROUND (darker to lighter) black #1e1e1e @@ -143,6 +145,10 @@ QToolBox::tab:hover /*================================================================================================== QStatusBar ==================================================================================================*/ +QStatusBar > QLabel { + margin-left: 4px; +} + QStatusBar::item { border: 1px solid #333333; @@ -220,7 +226,7 @@ QMenu QToolButton:pressed, QMenu QPushButton:selected, QMenu QToolButton:selected { color: white; - background-color: #696969; /* same as QMenu::item:selected and QMenu::item:pressed */ + background-color: #557bb6; /* same as QMenu::item:selected and QMenu::item:pressed */ } QMenu QRadioButton:disabled, @@ -286,7 +292,7 @@ Group box QGroupBox { color: #bcbcbc; border:1px solid rgba(255,255,255,20); /* lighter than its own border-color */; - border-radius: 2px; + border-radius: 1px; margin-top: 10px; padding: 6px; background-color: rgba(255,255,255,0); @@ -316,7 +322,7 @@ QToolTip { background-color: #2a2a2a; /*opacity: 90%; doesn't correctly work */ padding: 4px; - border-radius: 2px; /* has no effect */ + border-radius: 1px; /* has no effect */ } @@ -334,15 +340,15 @@ QDockWidget::title { text-align: center; background-color: #2a2a2a; border-bottom: 4px solid #333333; /* fix to simulate margin between this :title and tabs */ /* same as main background color */ - margin-left: 7px; - margin-right: 7px; + margin-left: 6px; + margin-right: 6px; } QDockWidget::close-button, QDockWidget::float-button { border: none; background: transparent; - border-radius: 2px; + border-radius: 1px; subcontrol-origin: padding; subcontrol-position: right center; } @@ -369,7 +375,6 @@ QDockWidget::float-button:pressed { /* fix for Python Console (probably there is a smarter way to arrive to it) */ QDockWidget > QFrame { background-color: #3c3c3c; - border: 6px solid #333333; } @@ -385,12 +390,12 @@ QProgressBar:horizontal { text-align: center; border: 1px solid rgba(0,0,0,140); padding: 1px; - border-radius: 2px; + border-radius: 1px; } QProgressBar::chunk, QProgressBar::chunk:horizontal { background-color: #557BB6; - border-radius: 2px; + border-radius: 1px; } @@ -398,7 +403,7 @@ QProgressBar::chunk:horizontal { Scroll ==================================================================================================*/ QAbstractScrollArea { - border-radius: 2px; + border-radius: 1px; background-color: transparent; } @@ -425,7 +430,7 @@ QScrollBar::handle:horizontal:hover { QScrollBar::handle:horizontal { min-width: 5px; - border-radius: 2px; + border-radius: 1px; margin: 4px 15px; } @@ -477,7 +482,7 @@ QScrollBar:vertical { QScrollBar::handle:vertical { min-height: 24px; - border-radius: 2px; + border-radius: 1px; margin: 15px 4px; } @@ -667,7 +672,7 @@ QDialog#Gui__Dialog__DlgPreferences > QListView { /* unique styles for sections inside Preferences */ QDialog#Gui__Dialog__DlgPreferences > QListView::item { - border-radius: 2px; + border-radius: 1px; } QDialog#Gui__Dialog__DlgPreferences > QListView::item:hover { /* Preference left icons*/ @@ -687,7 +692,7 @@ Tab bar buttons QTabBar::close-button { subcontrol-origin: margin; subcontrol-position: center right; /* only works for Qt 4.6 and newer */; - border-radius: 2px; + border-radius: 1px; background-image: url(qss:images_dark-light/close_light.svg); background-position: center center; background-repeat: none; @@ -790,7 +795,7 @@ QTableView { selection-color: #ffffff; selection-background-color: #557BB6; /* should be similar to QListView::item selected background-color */ show-decoration-selected: 1; /* make the selection span the entire width of the view */ - border-radius: 2px; + border-radius: 1px; } QListView::item:hover, @@ -821,7 +826,7 @@ Gui--PropertyEditor--PropertyEditor > QWidget > QWidget > QLabel:disabled { color: transparent; background-color: transparent; border: none; - border-radius: 2px; + border-radius: 1px; margin: 0px; padding: 0px; } @@ -894,7 +899,7 @@ QTreeView > QWidget > QTimeEdit:down-button, QTreeView > QWidget > QDateEdit:down-button, QTreeView > QWidget > QDateTimeEdit:down-button, QTreeView > QWidget > Gui--ColorButton { - border-radius: 2px; + border-radius: 1px; } /* set focus colors to best viewing the editable fields */ @@ -927,7 +932,7 @@ QTreeView > QWidget > QDateTimeEdit:read-only { /* Fix to correctly (not totally) draw QTextEdit on OSX at Page properties: "Page result", "Template" and "Editable Texts" */ Gui--PropertyEditor--PropertyEditor > QWidget > QWidget > QWidget { min-height: 14px; - border-radius: 2px; /* reset */ + border-radius: 1px; /* reset */ } @@ -937,8 +942,8 @@ Header of tree and list views QHeaderView { color: #d2d2d2; background-color: #2a2a2a; - border-top-left-radius: 2px; /* 1px less than its container */ - border-top-right-radius: 2px; /* 1px less than its container */ + border-top-left-radius: 1px; /* 1px less than its container */ + border-top-right-radius: 1px; /* 1px less than its container */ border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; } @@ -1220,7 +1225,7 @@ QToolBar > Gui--WorkbenchComboBox { selection-color: #ffffff; selection-background-color: #2a2a2a; border: 1px solid #2a2a2a; - border-radius: 2px; + border-radius: 1px; min-width: 50px; /* it ensures the default value is correctly displayed */ min-height: 16px; /* important to be a pair number in order to up/down buttons to be divisible by two (if not set could create a blank line in Ubuntu. Its downside is that it's needed to reset it (min-width: 0px) on following elements that can't have it such as fields inside QToolBar and inside QTreeView (Property editor) */ padding: 1px 2px; /* temporal: could don't be compatible with elements inside Tree/List view */ @@ -1244,9 +1249,9 @@ QDateTimeEdit { color: #f5f5f5; background-color: #494949; /* lineedits and drop-downs */ selection-color: #ffffff; - selection-background-color: #2a2a2a; + selection-background-color: #557bb6; border: 0px solid #2a2a2a; - border-radius: 2px; + border-radius: 1px; min-width: 50px; /* it ensures the default value is correctly displayed */ min-height: 16px; /* important to be a pair number in order to up/down buttons to be divisible by two (if not set could create a blank line in Ubuntu. Its downside is that it's needed to reset it (min-width: 0px) on following elements that can't have it such as fields inside QToolBar and inside QTreeView (Property editor) */ padding: 1px 2px; /* temporal: could don't be compatible with elements inside Tree/List view */ @@ -1293,7 +1298,7 @@ QDateTimeEdit:focus { border-color: #333333; border: 1px; border-right-color: #557BB6; /* same as up/down or drop-down button color */ - background-color: #557bb6; + background-color: #494949; } QComboBox:disabled, @@ -1461,8 +1466,8 @@ QComboBox::drop-down { subcontrol-origin: border; /* important */ subcontrol-position: top right; width: 20px; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; + border-top-right-radius: 1px; + border-bottom-right-radius: 1px; } QComboBox::drop-down:on, @@ -1505,109 +1510,156 @@ QComboBox QAbstractItemView { /*================================================================================================== Push button ==================================================================================================*/ -QPushButton#inspectButton { - background-color: #2a2a2a; - border-bottom: 2px solid #1e1e1e; - min-height: 16px; -} - -QPushButton:focus#inspectButton, -QPushButton:hover#inspectButton { - background-color: #557bb6; - border: -2px solid #557bb6; -} - -QPushButton:checked#inspectButton { - background-color: #557bb6; - border-bottom: solid #557bb6; -} - -QPushButton:pressed#inspectButton { - background-color: #557bb6; - border-bottom: 1px solid #3c3c3c; -} - -QPushButton#NavigationIndicator { - background-color: #557bb6; - min-height: 16px; - border: 2px solid #557bb6; -} - -QPushButton:hover#NavigationIndicator { - border: -2px solid #557bb6; -} - -QPushButton#buttonAddLevel { - margin-left:10px; -} - -QPushButton#buttonRename { - margin-right:10px; -} - -QPushButton#buttonAddLevel, -QPushButton#buttonAddProxy, -QPushButton#buttonDelete, -QPushButton#buttonToggle, -QPushButton#buttonIsolate, -QPushButton#buttonSaveView, -QPushButton#buttonRename { - color: #f5f5f5; - max-width: 100%; - min-width: 16px; - min-height: 24px; - padding: 4px; - background-color: #333333; - border: 1px #557bb6; -} - -QPushButton:hover#buttonAddLevel, -QPushButton:hover#buttonAddProxy, -QPushButton:hover#buttonDelete, -QPushButton:hover#buttonToggle, -QPushButton:hover#buttonIsolate, -QPushButton:hover#buttonSaveView, -QPushButton:hover#buttonRename { - color: #cbd8e6; - background-color: #557BB6; -} - QPushButton { color: #e0e0e0; text-align: center; - min-width: 70px; - background-color: #2a2a2a; /* Middle Mouse Navigation Button and Ok Cancel Apply Help Preferences Buttons */ - border: 2px solid #2a2a2a; - border-bottom-color: #1e1e1e; /* simulates shadow under the button */ - padding: 2px 2px; - margin: 2px 2px; - min-height: 16px; /* same as QTabBar QPushButton min-width */ + background-color: #2a2a2a; + padding: 4px 20px; + border: 1px solid #494949; + margin: 4px 4px; border-radius: 1px; - } QPushButton:hover, QPushButton:focus { - color: #cbd8e6; - border: -2px solid #333333; - background-color: #557BB6; + color: #ffffff; + background-color: #557bb6; + border: 1px solid #f5f5f5; } QPushButton:disabled, QPushButton:disabled:checked { color: #f5f5f5; background-color: #2a2a2a; /* same as enabled color */ - border-color: #2a2a2a; /* same as enabled color */ + border: 1px solid #2a2a2a; /* same as enabled color */ } QPushButton:pressed { - background-color: #557BB6; + color: #ffffff; + background-color: #48699a; border: 1px solid #3c3c3c; } QPushButton:checked { background-color: #557BB6; - border: solid #557BB6; + border: 1px solid #557BB6; +} + +/* Inspect Widgets Addon */ + +QDockWidget#InspectWidgets QPushButton { + background-color: #2a2a2a; + border: 1px solid #3c3c3c; + min-height: 16px; + margin: 0px; +} + +QDockWidget#InspectWidgets QPushButton:hover { + color: #ffffff; + background-color: #557bb6; + border: 1px solid #f5f5f5; + border-bottom: 1px solid #f5f5f5; +} + +QDockWidget#InspectWidgets QPushButton:checked, +QDockWidget#InspectWidgets QPushButton:pressed { + background-color: #557bb6; + border: 1px solid #557bb6; +} + +/* CAD Navigation Style */ + +QPushButton#NavigationIndicator { + background-color: #557bb6; + padding: 2px; + margin: 0px; + border: 1px solid #333333; + border-radius: 1px; + min-width: 90px; + min-height: 24px; +} + +QPushButton:hover#NavigationIndicator { + color: #ffffff; + border: 1px solid #f5f5f5; +} + +QPushButton:pressed#NavigationIndicator { + color: #ffffff; + background-color: #557bb6; + border: 1px solid #557bb6; +} + +/* BIM Views Manager */ + +QWidget#Form QPushButton { + background-color: #333333; + padding: 4px 2px; + border: 1px solid #3c3c3c; + border-radius: 1px; + margin: 2px; + margin-bottom: 8px; + max-width: 100%; + min-width: 16px; + min-height: 24px; +} + +QWidget#Form QPushButton:hover { + border: 1px solid #f5f5f5; + background-color: #557BB6; +} + +QWidget#Form QPushButton:pressed { + border: 1px solid #557bb6; + background-color: #557BB6; +} + +/* Sketcher Manual Update Button */ + +QPushButton#manualUpdate { + padding: 4px; + margin: 0px; + border: 1px solid #494949; +} + +QPushButton:pressed#manualUpdate { + color: #ffffff; + border: 1px solid #3c3c3c; + background-color: #48699a; +} + +/* Addon Manager */ + +QDialog#Dialog QPushButton { + padding: 4px; + margin: 0px; + border: 1px solid #494949; +} + +QDialog#Dialog QPushButton:hover { + color: #ffffff; + border: 1px solid #3c3c3c; + background-color: #48699a; +} + +QPushButton#buttonUninstall { + margin-left: 16px; +} + +QPushButton#buttonClose { + margin-right: 8px; +} + +/* Ok Cancel Apply Help Preferences Buttons */ /* Hack to move Help button left */ + +QDialogButtonBox > QPushButton { + color: #e0e0e0; + text-align: center; + background-color: #2a2a2a; + border: 1px solid #494949; + padding: 4px; + margin-right: 8px; + min-width: 60px; } /* Color Buttons */ @@ -1644,7 +1696,7 @@ Gui--PropertyEditor--PropertyEditor > QWidget > QWidget > QPushButton { background-color: #2a2a2a; border: 1px solid #1e1e1e; min-width: 16px; /* reset it due to larger value on regular QPushButton, same or bigger value as regular QPushButton min-height */ - border-radius: 2px; + border-radius: 1px; margin: 0px; /* reset */ padding: 0px; /* reset */ } @@ -1653,48 +1705,44 @@ Gui--PropertyEditor--PropertyEditor > QWidget > QWidget > QPushButton { Gui--PropertyEditor--PropertyEditor > QWidget > QWidget > QWidget > QWidget > QFrame { background-color: #333333; /* main background color */ border: 1px solid #333333; - border-radius: 2px; + border-radius: 1px; padding: 2px 6px; } -QPushButton:checked { - background-color: #3c3c3c; - border-color: #3c3c3c; -} - - /*================================================================================================== Tool button inside QDialogs that works as QPushButtons ==================================================================================================*/ /* found under Tools -> Customize -> Macros -> Pixmap "..." button */ + QDialog QToolButton { color: #e0e0e0; text-align: center; - background-color: qlineargradient(spread:pad, x1:0, y1:0.3, x2:0, y2:1, stop:0 #2a2a2a, stop:1 #1e1e1e); - border: 1px solid #1e1e1e; - border-bottom-color: black; /* simulates shadow under the button */ + background-color: #2a2a2a; + border: 1px solid #494949; padding: 0px; /* different than regular QPushButton */ - margin: 2px; /* different than regular QPushButton */ + margin: 2px; min-height: 16px; /* same as QTabBar QPushButton min-width */ - border-radius: 2px; + border-radius: 1px; } QDialog QToolButton:hover, QDialog QToolButton:focus { - color: #cbd8e6; - border-color: #557BB6; - background-color: #557BB6; + color: #ffffff; + background-color: #557bb6; + border: 1px solid #f5f5f5; } QDialog QToolButton:disabled, QDialog QToolButton:disabled:checked { - color: #333333; - border-color: #424242; - background-color: #424242; + color: #f5f5f5; + background-color: #2a2a2a; + border: 1px solid #2a2a2a; } QDialog QToolButton:pressed { - background-color: #557BB6; + color: #ffffff; + background-color: #48699a; + border: 1px solid #3c3c3c; } @@ -1711,7 +1759,7 @@ QSint--ActionGroup QFrame[class="content"] QToolButton { padding: 2px 6px; /* different than regular QPushButton */ margin: 2px; /* different than regular QPushButton */ min-height: 16px; /* same as QTabBar QPushButton min-width */ - border-radius: 2px; + border-radius: 1px; } QSint--ActionGroup QFrame[class="content"] QToolButton:hover, @@ -1779,7 +1827,7 @@ QRadioButton:disabled { QRadioButton::indicator { width: 12px; height: 12px; - border-radius: 2px; + border-radius: 1px; } QRadioButton::indicator:pressed { @@ -1944,7 +1992,7 @@ QSlider:vertical { QSlider::groove { background-color: #2a2a2a; border: 2px solid #3c3c3c; - border-radius: 2px; + border-radius: 1px; margin: 4px 0px; } @@ -1968,7 +2016,7 @@ QSlider::handle:vertical { border: 1px solid #2a2a2a; width: 16px; height: 16px; - border-radius: 2px; + border-radius: 1px; } QSlider::handle:horizontal { @@ -2028,35 +2076,44 @@ QToolBar > QPushButton { margin: 0px; /* doesn't work with :left, :right:, :top or :bottom sub-controls */ min-width: 24px; /* could not be larger due to switchable Preferences "Size of toolbar icons" */ min-height: 24px; /* could not be larger due to switchable Preferences "Size of toolbar icons" */ - border-radius: 2px; /* same as regular QPushButton */ + border-radius: 1px; /* same as regular QPushButton */ } QToolBar > QPushButton:checked { - border: 1px solid #333333; + border: 1px solid #3c3c3c; background-color: #557BB6; } +/* Hack to avoid QPushButton text partially hidden under menu-indicator */ +QToolBar > QPushButton::menu-indicator:!checked { + image: none; + width: 0px; +} + QToolBar > QPushButton:!checked { background-color: #333333; /* Current Working Plane and Nudge */ - border: 1px solid #333333; - text-align: left; + padding: 2px 4px; + border: 1px solid #3c3c3c; + margin: 0px 2px; } QToolBar > QPushButton:checked:hover { - border-color: #557BB6; + border: 1px solid #f5f5f5; } QToolBar > QPushButton:!checked:hover { color: #ffffff; background-color: #557BB6; - border-color: #557BB6; + border: 1px solid #f5f5f5; } QToolBar > QPushButton:checked:pressed { + border: 1px solid #557bb6; background-color: solid #557BB6; } QToolBar > QPushButton:!checked:pressed { + border: 1px solid #557bb6; background-color: #557BB6; } @@ -2069,43 +2126,60 @@ QToolBar > QPushButton:!checked:disabled { QToolBar > QToolButton { margin: 2px; padding: 2px; - border-radius: 2px; + border-radius: 1px; + border: 1px solid transparent; } QToolBar > QToolButton:hover { background-color: #557BB6; + border: 1px solid #f5f5f5; } QToolBar > QToolButton:pressed { background-color: #557BB6; + border: 1px solid #557bb6; } /* ToolBar menu buttons (buttons with drop-down menu) */ QToolBar > QToolButton#qt_toolbutton_menubutton { padding-right: 20px; /* Hack to add more width to buttons with menu */ - border: 1px solid transparent; - border-radius: 2px; -} - -QToolBar > QToolButton#qt_toolbutton_menubutton:hover, -QToolBar > QToolButton#qt_toolbutton_menubutton:pressed, -QToolBar > QToolButton#qt_toolbutton_menubutton:open { - border: 1px solid #557BB6; + border: 1px solid #333333; + border-radius: 1px; } QToolBar QToolButton::menu-button, QToolBar > QToolButton#qt_toolbutton_menubutton::menu-button { border: none; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; + border-top-right-radius: 1px; + border-bottom-right-radius: 1px; width: 16px; /* 16px width + 4px for border = 20px allocated above */ outline: none; background-color: transparent; } -QToolBar > QToolButton#qt_toolbutton_menubutton::menu-button:hover, +QToolBar > QToolButton#qt_toolbutton_menubutton::menu-button:hover { + border-top: 1px solid #f5f5f5; + border-bottom: 1px solid #f5f5f5; + border-right: 1px solid #f5f5f5; + background-color: #557BB6; +} + +QToolBar > QToolButton#qt_toolbutton_menubutton:pressed, +QToolBar > QToolButton#qt_toolbutton_menubutton:open { + background-color: #557BB6; + border: 1px solid #557BB6; +} + +QToolBar > QToolButton#qt_toolbutton_menubutton:hover { + background-color: #557BB6; + border: 1px solid #f5f5f5; +} + QToolBar > QToolButton#qt_toolbutton_menubutton::menu-button:pressed, QToolBar > QToolButton#qt_toolbutton_menubutton::menu-button:open { + border-top: 1px solid #557bb6; + border-bottom: 1px solid #557bb6; + border-right: 1px solid #557bb6; background-color: #557BB6; } @@ -2208,7 +2282,7 @@ QTableView > QWidget > QTimeEdit:down-button, QTableView > QWidget > QDateEdit:down-button, QTableView > QWidget > QDateTimeEdit:down-button, QTableView > QWidget > Gui--ColorButton { - border-radius: 2px; + border-radius: 1px; } QTableView > QWidget > QComboBox, @@ -2334,7 +2408,7 @@ QToolBar#Selector QToolButton { border: none; margin: 0px; padding: 2px 6px; - border-radius: 2px; + border-radius: 1px; } /* Active tab */ From 9c8685c20cd70553d85940c2ba93ade69f38171d Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 19 Mar 2021 15:18:25 +0100 Subject: [PATCH 02/21] Import: [skip ci] avoid conflicting class names in global namespace --- src/Mod/Import/App/AppImportPy.cpp | 3 ++- src/Mod/Import/Gui/AppImportGuiPy.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Import/App/AppImportPy.cpp b/src/Mod/Import/App/AppImportPy.cpp index 3f629d102b..f76e59bf15 100644 --- a/src/Mod/Import/App/AppImportPy.cpp +++ b/src/Mod/Import/App/AppImportPy.cpp @@ -82,6 +82,8 @@ #include "ImpExpDxf.h" +namespace Import { + class ImportOCAFExt : public Import::ImportOCAF2 { public: @@ -98,7 +100,6 @@ private: } }; -namespace Import { class Module : public Py::ExtensionModule { public: diff --git a/src/Mod/Import/Gui/AppImportGuiPy.cpp b/src/Mod/Import/Gui/AppImportGuiPy.cpp index e029d2db61..e081764279 100644 --- a/src/Mod/Import/Gui/AppImportGuiPy.cpp +++ b/src/Mod/Import/Gui/AppImportGuiPy.cpp @@ -138,6 +138,7 @@ FC_LOG_LEVEL_INIT("Import", true, true) +namespace ImportGui { class OCAFBrowser { public: @@ -382,7 +383,6 @@ public: } }; -namespace ImportGui { class Module : public Py::ExtensionModule { public: From d759a8e43d607442250b26efae70abda582a1007 Mon Sep 17 00:00:00 2001 From: David Osterberg Date: Wed, 10 Feb 2021 21:25:35 +0100 Subject: [PATCH 03/21] Sketcher: Change constraint-conversion MessageBox to DlgCheckableMessageBox --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index d63db8a157..c39f630414 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -2165,8 +2166,15 @@ void CmdSketcherConstrainCoincident::activated(int iMsg) ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); if(hGrp->GetBool("NotifyConstraintSubstitutions", true)) { - QMessageBox::information(Gui::getMainWindow(), QObject::tr("Constraint Substitution"), - QObject::tr("Endpoint to endpoint tangency was applied instead.")); + auto hChecked = App::GetApplication().GetParameterGroupByPath( QByteArray("User parameter:BaseApp/CheckMessages")); + hChecked->SetBool("Sketcher_Constraint_Substitution", false); + Gui::Dialog::DlgCheckableMessageBox::showMessage( + QObject::tr("Sketcher Constraint Substitution"), + QObject::tr("Endpoint to endpoint tangency was applied instead."), + false, + QObject::tr("Don't tell me again") + ); + hGrp->SetBool("NotifyConstraintSubstitutions", !hChecked->GetBool("Sketcher_Constraint_Substitution", true)); } getSelection().clearSelection(); @@ -4327,8 +4335,15 @@ void CmdSketcherConstrainTangent::activated(int iMsg) ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); if(hGrp->GetBool("NotifyConstraintSubstitutions", true)) { - QMessageBox::information(Gui::getMainWindow(), QObject::tr("Constraint Substitution"), - QObject::tr("Endpoint to endpoint tangency was applied. The coincident constraint was deleted.")); + auto hChecked = App::GetApplication().GetParameterGroupByPath( QByteArray("User parameter:BaseApp/CheckMessages")); + hChecked->SetBool("Sketcher_Constraint_Substitution", false); + Gui::Dialog::DlgCheckableMessageBox::showMessage( + QObject::tr("Sketcher Constraint Substitution"), + QObject::tr("Endpoint to endpoint tangency was applied instead."), + false, + QObject::tr("Don't tell me again") + ); + hGrp->SetBool("NotifyConstraintSubstitutions", !hChecked->GetBool("Sketcher_Constraint_Substitution", true)); } getSelection().clearSelection(); return; From 4bf6b1984971547372b994b99371ca2a4351e138 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 20 Mar 2021 15:54:47 +0100 Subject: [PATCH 04/21] Gui: Extend DlgCheckableMessageBox to use specific preference path and parameter --- src/Gui/DlgCheckableMessageBox.cpp | 35 +++++++++++++++++++++++++----- src/Gui/DlgCheckableMessageBox.h | 14 ++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/Gui/DlgCheckableMessageBox.cpp b/src/Gui/DlgCheckableMessageBox.cpp index 6920619569..3a001ecb59 100644 --- a/src/Gui/DlgCheckableMessageBox.cpp +++ b/src/Gui/DlgCheckableMessageBox.cpp @@ -38,7 +38,7 @@ #include #include -#include "MainWindow.h" +#include "MainWindow.h" #include //#include @@ -54,7 +54,7 @@ QByteArray toParamEntry(QString name) void DlgCheckableMessageBox::showMessage(const QString& header, const QString& message, bool check, const QString& checkText) { - bool checked = App::GetApplication().GetParameterGroupByPath( QByteArray("User parameter:BaseApp/CheckMessages"))->GetBool(toParamEntry(header)); + bool checked = App::GetApplication().GetParameterGroupByPath(QByteArray("User parameter:BaseApp/CheckMessages"))->GetBool(toParamEntry(header)); if (!checked) { DlgCheckableMessageBox *mb = new DlgCheckableMessageBox(Gui::getMainWindow()); @@ -69,7 +69,25 @@ void DlgCheckableMessageBox::showMessage(const QString& header, const QString& m mb->show(); } } +void DlgCheckableMessageBox::showMessage(const QString& header, const QString& message, const QString& prefPath, const QString& paramEntry, + bool entryDefault, bool check, const QString& checkText) +{ + bool checked = App::GetApplication().GetParameterGroupByPath(prefPath.toLatin1())->GetBool(paramEntry.toLatin1(), entryDefault); + if(checked == entryDefault) { + auto mb = new Gui::Dialog::DlgCheckableMessageBox(Gui::getMainWindow()); + mb->setWindowTitle(header); + mb->setIconPixmap(QMessageBox::standardIcon(QMessageBox::Warning)); + mb->setText(message); + mb->setPrefPath(prefPath); + mb->setPrefEntry(paramEntry); + mb->setCheckBoxText(checkText); + mb->setChecked(check); + mb->setStandardButtons(QDialogButtonBox::Ok); + mb->setDefaultButton(QDialogButtonBox::Ok); + mb->show(); + } +} struct DlgCheckableMessageBoxPrivate { DlgCheckableMessageBoxPrivate() : clickedButton(0) {} @@ -80,7 +98,8 @@ struct DlgCheckableMessageBoxPrivate { DlgCheckableMessageBox::DlgCheckableMessageBox(QWidget *parent) : QDialog(parent), - m_d(new DlgCheckableMessageBoxPrivate) + m_d(new DlgCheckableMessageBoxPrivate), + prefPath(QLatin1String("User parameter:BaseApp/CheckMessages")) { setModal(true); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -99,10 +118,14 @@ DlgCheckableMessageBox::~DlgCheckableMessageBox() void DlgCheckableMessageBox::setPrefEntry(const QString& entry) { paramEntry = toParamEntry(entry); - bool checked = App::GetApplication().GetParameterGroupByPath(QByteArray("User parameter:BaseApp/CheckMessages"))->GetBool(paramEntry); + bool checked = App::GetApplication().GetParameterGroupByPath(prefPath.toLatin1())->GetBool(paramEntry); setChecked(checked); } +void DlgCheckableMessageBox::setPrefPath(const QString& path) +{ + prefPath = path; +} void DlgCheckableMessageBox::slotClicked(QAbstractButton *b) { @@ -198,14 +221,14 @@ void DlgCheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s void DlgCheckableMessageBox::accept() { if(!paramEntry.isEmpty()) - App::GetApplication().GetParameterGroupByPath( QByteArray("User parameter:BaseApp/CheckMessages"))->SetBool(paramEntry,isChecked()); + App::GetApplication().GetParameterGroupByPath(prefPath.toLatin1())->SetBool(paramEntry,isChecked()); QDialog::accept(); } void DlgCheckableMessageBox::reject() { if(!paramEntry.isEmpty()) - App::GetApplication().GetParameterGroupByPath( QByteArray("User parameter:BaseApp/CheckMessages"))->SetBool(paramEntry,isChecked()); + App::GetApplication().GetParameterGroupByPath(prefPath.toLatin1())->SetBool(paramEntry,isChecked()); QDialog::reject(); } diff --git a/src/Gui/DlgCheckableMessageBox.h b/src/Gui/DlgCheckableMessageBox.h index 62419dc7fa..7f750586e7 100644 --- a/src/Gui/DlgCheckableMessageBox.h +++ b/src/Gui/DlgCheckableMessageBox.h @@ -73,6 +73,8 @@ public: void setPrefEntry(const QString& entry); + void setPrefPath(const QString& path); + virtual void accept(); virtual void reject(); @@ -99,8 +101,15 @@ public: // Conversion convenience static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); - // convenient show method - static void showMessage(const QString& header, const QString& message, bool check=false, const QString& checkText = QString::fromLatin1("Don't show me again")); + /// convenient show method + /// It shows a dialog with header and message provided and a checkbox in check state with the message provided. + /// It uses a parameter in path "User parameter:BaseApp/CheckMessages" derived from the header test, defaulting to false, + /// to store the status of the checkbox, when the user exits the modal dialog. + static void showMessage(const QString& header, const QString& message, bool check = false, const QString& checkText = QString::fromLatin1("Don't show me again")); + + /// Same as showMessage above, but it checks the specific preference path and parameter provided, defaulting to entryDefault value if the parameter is not present. + static void showMessage(const QString& header, const QString& message, const QString& prefPath, const QString& paramEntry, bool entryDefault = false, + bool check = false, const QString& checkText = QString::fromLatin1("Don't show me again")); private Q_SLOTS: void slotClicked(QAbstractButton *b); @@ -108,6 +117,7 @@ private Q_SLOTS: private: DlgCheckableMessageBoxPrivate *m_d; QByteArray paramEntry; + QString prefPath; }; } // namespace Dialog From c08a19626fa637dc411abb30b392d70d817994d7 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 20 Mar 2021 15:56:21 +0100 Subject: [PATCH 05/21] Sketcher: Use old NotifyConstraintSubstitution parameter in DlgCheackableMessageBox --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 38 +++++++-------------- src/Mod/Sketcher/Gui/CommandConstraints.h | 3 ++ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index c39f630414..64b2f46163 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -664,6 +664,17 @@ void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj, Gui::Selection GeoId1,PosId1,GeoId2,PosId2); } +void SketcherGui::notifyConstraintSubstitutions(const QString & message) +{ + Gui::Dialog::DlgCheckableMessageBox::showMessage( QObject::tr("Sketcher Constraint Substitution"), + message, + QLatin1String("User parameter:BaseApp/Preferences/Mod/Sketcher/General"), + QLatin1String("NotifyConstraintSubstitutions"), + true, // Default ParamEntry + true, // checkbox state + QObject::tr("Keep notifying me of constraint substitutions")); +} + namespace SketcherGui { @@ -2163,19 +2174,7 @@ void CmdSketcherConstrainCoincident::activated(int iMsg) Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. tryAutoRecomputeIfNotSolve(Obj); - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - - if(hGrp->GetBool("NotifyConstraintSubstitutions", true)) { - auto hChecked = App::GetApplication().GetParameterGroupByPath( QByteArray("User parameter:BaseApp/CheckMessages")); - hChecked->SetBool("Sketcher_Constraint_Substitution", false); - Gui::Dialog::DlgCheckableMessageBox::showMessage( - QObject::tr("Sketcher Constraint Substitution"), - QObject::tr("Endpoint to endpoint tangency was applied instead."), - false, - QObject::tr("Don't tell me again") - ); - hGrp->SetBool("NotifyConstraintSubstitutions", !hChecked->GetBool("Sketcher_Constraint_Substitution", true)); - } + notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); getSelection().clearSelection(); return; @@ -4332,19 +4331,8 @@ void CmdSketcherConstrainTangent::activated(int iMsg) Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. tryAutoRecomputeIfNotSolve(Obj); - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/General"); + notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); - if(hGrp->GetBool("NotifyConstraintSubstitutions", true)) { - auto hChecked = App::GetApplication().GetParameterGroupByPath( QByteArray("User parameter:BaseApp/CheckMessages")); - hChecked->SetBool("Sketcher_Constraint_Substitution", false); - Gui::Dialog::DlgCheckableMessageBox::showMessage( - QObject::tr("Sketcher Constraint Substitution"), - QObject::tr("Endpoint to endpoint tangency was applied instead."), - false, - QObject::tr("Don't tell me again") - ); - hGrp->SetBool("NotifyConstraintSubstitutions", !hChecked->GetBool("Sketcher_Constraint_Substitution", true)); - } getSelection().clearSelection(); return; } diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.h b/src/Mod/Sketcher/Gui/CommandConstraints.h index d1e90de378..ee589805b6 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.h +++ b/src/Mod/Sketcher/Gui/CommandConstraints.h @@ -138,6 +138,9 @@ bool checkConstraint(const std::vector< Sketcher::Constraint * > &vals, Sketcher /// Does an endpoint-to-endpoint tangency void doEndpointTangency(Sketcher::SketchObject* Obj, Gui::SelectionObject &selection, int GeoId1, int GeoId2, Sketcher::PointPos PosId1, Sketcher::PointPos PosId2); + +/// shows constraint substitution information dialog box, enabling the user to forgo further notifications +void notifyConstraintSubstitutions(const QString & message); } #endif // SKETCHERGUI_DrawSketchHandler_H From 477e0d3982ac1edce2480df37f41355182856dbc Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 20 Mar 2021 16:13:56 +0100 Subject: [PATCH 06/21] Gui: [skip ci] avoid using const reference as member for several action classes to avoid memory corruption --- src/Gui/SoFCSelectionAction.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Gui/SoFCSelectionAction.h b/src/Gui/SoFCSelectionAction.h index 4f1c36b51d..e3e8331bd0 100644 --- a/src/Gui/SoFCSelectionAction.h +++ b/src/Gui/SoFCSelectionAction.h @@ -25,9 +25,11 @@ #define _SoFCSelectionAction_h //#include +#include #include #include -#include +#include +#include #include #include @@ -101,7 +103,7 @@ public: SoFCEnableSelectionAction (const SbBool& sel); ~SoFCEnableSelectionAction(); - const SbBool& selection; + SbBool selection; static void initClass(); static void finish(void); @@ -126,7 +128,7 @@ public: SoFCEnableHighlightAction (const SbBool& sel); ~SoFCEnableHighlightAction(); - const SbBool& highlight; + SbBool highlight; static void initClass(); static void finish(void); @@ -151,7 +153,7 @@ public: SoFCSelectionColorAction (const SoSFColor& col); ~SoFCSelectionColorAction(); - const SoSFColor& selectionColor; + SoSFColor selectionColor; static void initClass(); static void finish(void); @@ -176,7 +178,7 @@ public: SoFCHighlightColorAction (const SoSFColor& col); ~SoFCHighlightColorAction(); - const SoSFColor& highlightColor; + SoSFColor highlightColor; static void initClass(); static void finish(void); @@ -201,7 +203,7 @@ public: SoFCDocumentAction (const SoSFString& docName); ~SoFCDocumentAction(); - const SoSFString& documentName; + SoSFString documentName; static void initClass(); static void finish(void); From 3a656d8597187dcc0e5d3db483cd5f5dbf7c2b43 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 3 Feb 2021 06:27:34 +0100 Subject: [PATCH 07/21] Sketcher: UI Constraint Creation - PointOnObject + Tangency on edge constraint substitution ============================================================================================ On creation of a constraint from the UI (toolbar/menu): 1. if a PointOnObject constraint preexisted the addition of an edge-to-edge tangency, substitute it with a point-to-edge tangency. 2. if an edge-to-edge tangency preexisted, addition of a PointOnObject results in a substitution of the edge-to-edge tangency with an edge-to-curve tangency. Bonus: - Refactor of this with preexisting coincident+tangent substitution. - Activate both substitutions in continuous constraint addition mode. --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 253 ++++++++++++++------ src/Mod/Sketcher/Gui/CommandConstraints.h | 6 +- 2 files changed, 190 insertions(+), 69 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 64b2f46163..77826c7247 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -642,9 +642,9 @@ bool SketcherGui::checkConstraint(const std::vector< Sketcher::Constraint * > &v return false; } - -void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj, Gui::SelectionObject &selection, - int GeoId1, int GeoId2, PointPos PosId1, PointPos PosId2){ +void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj, + int GeoId1, int GeoId2, PointPos PosId1, PointPos PosId2) +{ // This code supports simple B-spline endpoint tangency to any other geometric curve const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); @@ -660,10 +660,16 @@ void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj, Gui::Selection // GeoId1 is the B-spline now } // end of code supports simple B-spline endpoint tangency - Gui::cmdAppObjectArgs(selection.getObject(), "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", + Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", GeoId1,PosId1,GeoId2,PosId2); } +void SketcherGui::doEndpointToEdgeTangency( Sketcher::SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2) +{ + Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d)) ", + GeoId1,PosId1,GeoId2); +} + void SketcherGui::notifyConstraintSubstitutions(const QString & message) { Gui::Dialog::DlgCheckableMessageBox::showMessage( QObject::tr("Sketcher Constraint Substitution"), @@ -2066,6 +2072,10 @@ public: protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); + // returns true if a substitution took place + bool substituteConstraintCombinations(SketchObject * Obj, + int GeoId1, PointPos PosId1, + int GeoId2, PointPos PosId2); }; CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident() @@ -2084,6 +2094,48 @@ CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident() allowedSelSequences = {{SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}}; } +bool CmdSketcherConstrainCoincident::substituteConstraintCombinations(SketchObject * Obj, + int GeoId1, PointPos PosId1, + int GeoId2, PointPos PosId2) +{ + // checks for direct and indirect coincidence constraints + bool constraintExists = Obj->arePointsCoincident(GeoId1,PosId1,GeoId2,PosId2); + + const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); + + int j=0; + for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it,++j) { + if( (*it)->Type == Sketcher::Tangent && + (*it)->FirstPos == Sketcher::none && (*it)->SecondPos == Sketcher::none && + (*it)->Third == Constraint::GeoUndef && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap edge tangency with ptp tangency")); + + if( constraintExists ) { + // try to remove any pre-existing direct coincident constraints + Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", GeoId1, PosId1); + } + + Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", j); + + doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2); + + commitCommand(); + Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. + tryAutoRecomputeIfNotSolve(Obj); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); + + getSelection().clearSelection(); + return true; + } + } + + return false; +} + void CmdSketcherConstrainCoincident::activated(int iMsg) { Q_UNUSED(iMsg); @@ -2145,42 +2197,16 @@ void CmdSketcherConstrainCoincident::activated(int iMsg) return; } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, PosId1,GeoId2, PosId2)) + return; + // check if this coincidence is already enforced (even indirectly) bool constraintExists=Obj->arePointsCoincident(GeoId1,PosId1,GeoId2,PosId2); - // check for a preexisting edge-to-edge tangency - const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); - - int j=0; - for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it,++j) { - if( (*it)->Type == Sketcher::Tangent && - (*it)->FirstPos == Sketcher::none && (*it)->SecondPos == Sketcher::none && - (*it)->Third == Constraint::GeoUndef && - (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || - ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { - - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap edge tangency with ptp tangency")); - - if(constraintExists) { - // try to remove any pre-existing direct coincident constraints - Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", GeoId1, PosId1); - } - - Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", j); - - doEndpointTangency(Obj, selection[0], GeoId1, GeoId2, PosId1, PosId2); - - commitCommand(); - Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. - tryAutoRecomputeIfNotSolve(Obj); - - notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); - - getSelection().clearSelection(); - return; - } - } - if (!constraintExists) { constraintsAdded = true; Gui::cmdAppObjectArgs(selection[0].getObject(), "addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", @@ -2218,6 +2244,13 @@ void CmdSketcherConstrainCoincident::applyConstraint(std::vector &sel return; } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, PosId1,GeoId2, PosId2)) + return; + // undo command open Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add coincident constraint")); @@ -2595,10 +2628,11 @@ public: protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); + // returns true if a substitution took place + bool substituteConstraintCombinations(SketchObject * Obj, + int GeoId1, PointPos PosId1, int GeoId2); }; -//DEF_STD_CMD_A(CmdSketcherConstrainPointOnObject); - CmdSketcherConstrainPointOnObject::CmdSketcherConstrainPointOnObject() :CmdSketcherConstraint("Sketcher_ConstrainPointOnObject") { @@ -2619,6 +2653,36 @@ CmdSketcherConstrainPointOnObject::CmdSketcherConstrainPointOnObject() } +bool CmdSketcherConstrainPointOnObject::substituteConstraintCombinations( SketchObject * Obj, + int GeoId1, PointPos PosId1, int GeoId2) +{ + const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); + + int cid = 0; + for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it, ++cid) { + if( (*it)->Type == Sketcher::Tangent && + (*it)->FirstPos == Sketcher::none && (*it)->SecondPos == Sketcher::none && + (*it)->Third == Constraint::GeoUndef && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + // NOTE: This function does not either open or commit a command as it is used for group addition + // it relies on such infrastructure being provided by the caller. + + Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", cid); + + doEndpointToEdgeTangency(Obj, GeoId1, PosId1, GeoId2); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to edge tangency was applied instead.")); + + getSelection().clearSelection(); + return true; + } + } + + return false; +} + void CmdSketcherConstrainPointOnObject::activated(int iMsg) { Q_UNUSED(iMsg); @@ -2689,6 +2753,11 @@ void CmdSketcherConstrainPointOnObject::activated(int iMsg) continue; } + if(substituteConstraintCombinations(Obj, points[iPnt].GeoId, points[iPnt].PosId, curves[iCrv].GeoId)) { + cnt++; + continue; + } + cnt++; Gui::cmdAppObjectArgs(selection[0].getObject(),"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", points[iPnt].GeoId, points[iPnt].PosId, curves[iCrv].GeoId); @@ -2773,6 +2842,12 @@ void CmdSketcherConstrainPointOnObject::applyConstraint(std::vector & return; } + if(substituteConstraintCombinations(Obj, GeoIdVt, PosIdVt, GeoIdCrv)) { + commitCommand(); + tryAutoRecompute(Obj); + return; + } + if (allOK) { Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", GeoIdVt, PosIdVt, GeoIdCrv); @@ -4090,6 +4165,8 @@ public: protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); + // returns true if a substitution took place + bool substituteConstraintCombinations(SketchObject * Obj, int GeoId1, int GeoId2); }; CmdSketcherConstrainTangent::CmdSketcherConstrainTangent() @@ -4118,6 +4195,62 @@ CmdSketcherConstrainTangent::CmdSketcherConstrainTangent() {SelVertexOrRoot, SelVertex} /*Two Endpoints*/ /*No Place for One Endpoint and One Curve*/}; } +bool CmdSketcherConstrainTangent::substituteConstraintCombinations(SketchObject * Obj, int GeoId1, int GeoId2) +{ + const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); + + int cid = 0; + for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it, ++cid) { + if( (*it)->Type == Sketcher::Coincident && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + // save values because 'doEndpointTangency' changes the + // constraint property and thus invalidates this iterator + int first = (*it)->First; + int firstpos = static_cast((*it)->FirstPos); + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap coincident+tangency with ptp tangency")); + + doEndpointTangency(Obj, (*it)->First, (*it)->Second, (*it)->FirstPos, (*it)->SecondPos); + + Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", first, firstpos); + + commitCommand(); + Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. + tryAutoRecomputeIfNotSolve(Obj); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied. The coincident constraint was deleted.")); + + getSelection().clearSelection(); + return true; + } + else if( (*it)->Type == Sketcher::PointOnObject && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap PointOnObject+tangency with point to curve tangency")); + + doEndpointToEdgeTangency(Obj, (*it)->First, (*it)->FirstPos, (*it)->Second); + + Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", cid); // remove the preexisting point on object constraint. + + commitCommand(); + + // A substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. However, + // delConstraint includes such solve() internally. So at this point it is already solved. + tryAutoRecomputeIfNotSolve(Obj); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to edge tangency was applied. The point on object constraint was deleted.")); + + getSelection().clearSelection(); + return true; + } + } + + return false; +} + void CmdSketcherConstrainTangent::activated(int iMsg) { Q_UNUSED(iMsg); @@ -4244,7 +4377,7 @@ void CmdSketcherConstrainTangent::activated(int iMsg) } openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint")); - doEndpointTangency(Obj, selection[0], GeoId1, GeoId2, PosId1, PosId2); + doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2); commitCommand(); tryAutoRecompute(Obj); @@ -4308,35 +4441,13 @@ void CmdSketcherConstrainTangent::activated(int iMsg) QObject::tr("Select an edge that is not a B-spline weight")); return; } - // check if there is a coincidence constraint on GeoId1, GeoId2 - const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); - for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it) { - if( (*it)->Type == Sketcher::Coincident && - (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || - ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { - - // save values because 'doEndpointTangency' changes the - // constraint property and thus invalidates this iterator - int first = (*it)->First; - int firstpos = static_cast((*it)->FirstPos); - - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap coincident+tangency with ptp tangency")); - - doEndpointTangency(Obj, selection[0], (*it)->First, (*it)->Second, (*it)->FirstPos, (*it)->SecondPos); - - Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", first, firstpos); - - commitCommand(); - Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. - tryAutoRecomputeIfNotSolve(Obj); - - notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); - - getSelection().clearSelection(); - return; - } - } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, GeoId2)) + return; if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || @@ -4529,6 +4640,12 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector &selSeq return; } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, GeoId2)) + return; if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.h b/src/Mod/Sketcher/Gui/CommandConstraints.h index ee589805b6..14f1aa8cd3 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.h +++ b/src/Mod/Sketcher/Gui/CommandConstraints.h @@ -137,10 +137,14 @@ void tryAutoRecomputeIfNotSolve(Sketcher::SketchObject* obj); bool checkConstraint(const std::vector< Sketcher::Constraint * > &vals, Sketcher::ConstraintType type, int geoid, Sketcher::PointPos pos); /// Does an endpoint-to-endpoint tangency -void doEndpointTangency(Sketcher::SketchObject* Obj, Gui::SelectionObject &selection, int GeoId1, int GeoId2, Sketcher::PointPos PosId1, Sketcher::PointPos PosId2); +void doEndpointTangency(Sketcher::SketchObject* Obj, int GeoId1, int GeoId2, Sketcher::PointPos PosId1, Sketcher::PointPos PosId2); + +/// Does an endpoint-edge tangency +void doEndpointToEdgeTangency( Sketcher::SketchObject* Obj, int GeoId1, Sketcher::PointPos PosId1, int GeoId2); /// shows constraint substitution information dialog box, enabling the user to forgo further notifications void notifyConstraintSubstitutions(const QString & message); + } #endif // SKETCHERGUI_DrawSketchHandler_H From 182952a8811a82c0cb2da6dee7b716ef7a17a2c7 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 20 Mar 2021 19:19:30 +0100 Subject: [PATCH 08/21] Mesh: [skip ci] do not make lower case read lines of OBJ file as otherwise it may fail to load the MTL file --- src/Mod/Mesh/App/Core/MeshIO.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Mod/Mesh/App/Core/MeshIO.cpp b/src/Mod/Mesh/App/Core/MeshIO.cpp index 1c49e1205c..5184bff722 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.cpp +++ b/src/Mod/Mesh/App/Core/MeshIO.cpp @@ -341,11 +341,6 @@ bool MeshInput::LoadOBJ (std::istream &rstrIn) unsigned long countMaterialFacets = 0; while (std::getline(rstrIn, line)) { - // when a group name comes don't make it lower case - if (!line.empty() && line[0] != 'g') { - for (std::string::iterator it = line.begin(); it != line.end(); ++it) - *it = tolower(*it); - } if (boost::regex_match(line.c_str(), what, rx_p)) { fX = (float)std::atof(what[1].first); fY = (float)std::atof(what[4].first); @@ -1285,8 +1280,7 @@ bool MeshInput::LoadMeshNode (std::istream &rstrIn) return false; while (std::getline(rstrIn, line)) { - for (std::string::iterator it = line.begin(); it != line.end(); ++it) - *it = tolower(*it); + boost::algorithm::to_lower(line); if (boost::regex_match(line.c_str(), what, rx_p)) { fX = (float)std::atof(what[1].first); fY = (float)std::atof(what[4].first); From f4e3e8abd61cf1ac605d2c915efd95069e32804b Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 20 Mar 2021 21:54:30 +0100 Subject: [PATCH 09/21] Mesh: [skip ci] refactor Mesh open/import functions --- src/Mod/Mesh/App/AppMeshPy.cpp | 156 +++----------------------------- src/Mod/Mesh/App/CMakeLists.txt | 2 + src/Mod/Mesh/App/Importer.cpp | 123 +++++++++++++++++++++++++ src/Mod/Mesh/App/Importer.h | 63 +++++++++++++ 4 files changed, 199 insertions(+), 145 deletions(-) create mode 100644 src/Mod/Mesh/App/Importer.cpp create mode 100644 src/Mod/Mesh/App/Importer.h diff --git a/src/Mod/Mesh/App/AppMeshPy.cpp b/src/Mod/Mesh/App/AppMeshPy.cpp index 305fbbcdd9..524e84f40c 100644 --- a/src/Mod/Mesh/App/AppMeshPy.cpp +++ b/src/Mod/Mesh/App/AppMeshPy.cpp @@ -52,6 +52,7 @@ #include "Mesh.h" #include "Exporter.h" +#include "Importer.h" #include "FeatureMeshImport.h" #include @@ -173,79 +174,11 @@ private: std::string EncodedName = std::string(Name); PyMem_Free(Name); - MeshObject mesh; - MeshCore::Material mat; - if (mesh.load(EncodedName.c_str(), &mat)) { - Base::FileInfo file(EncodedName.c_str()); - // create new document and add Import feature - App::Document *pcDoc = App::GetApplication().newDocument("Unnamed"); - unsigned long segmct = mesh.countSegments(); - if (segmct > 1) { - for (unsigned long i=0; i segm(mesh.meshFromSegment(group.getIndices())); - Mesh::Feature *pcFeature = static_cast - (pcDoc->addObject("Mesh::Feature", groupName.c_str())); - pcFeature->Label.setValue(groupName.c_str()); - pcFeature->Mesh.swapMesh(*segm); - - // if colors are set per face - if (mat.binding == MeshCore::MeshIO::PER_FACE && - mat.diffuseColor.size() == mesh.countFacets()) { - App::PropertyColorList* prop = static_cast - (pcFeature->addDynamicProperty("App::PropertyColorList", "VertexColors")); - if (prop) { - std::vector diffuseColor; - diffuseColor.reserve(group.getIndices().size()); - for (const auto& it : group.getIndices()) { - diffuseColor.push_back(mat.diffuseColor[it]); - } - prop->setValues(diffuseColor); - } - } - pcFeature->purgeTouched(); - } - } - else if (mat.binding == MeshCore::MeshIO::PER_VERTEX && - mat.diffuseColor.size() == mesh.countPoints()) { - FeatureCustom *pcFeature = new FeatureCustom(); - pcFeature->Label.setValue(file.fileNamePure().c_str()); - pcFeature->Mesh.swapMesh(mesh); - App::PropertyColorList* prop = static_cast - (pcFeature->addDynamicProperty("App::PropertyColorList", "VertexColors")); - if (prop) { - prop->setValues(mat.diffuseColor); - } - pcFeature->purgeTouched(); - - pcDoc->addObject(pcFeature, file.fileNamePure().c_str()); - } - else if (mat.binding == MeshCore::MeshIO::PER_FACE && - mat.diffuseColor.size() == mesh.countFacets()) { - FeatureCustom *pcFeature = new FeatureCustom(); - pcFeature->Label.setValue(file.fileNamePure().c_str()); - pcFeature->Mesh.swapMesh(mesh); - App::PropertyColorList* prop = static_cast - (pcFeature->addDynamicProperty("App::PropertyColorList", "FaceColors")); - if (prop) { - prop->setValues(mat.diffuseColor); - } - pcFeature->purgeTouched(); - - pcDoc->addObject(pcFeature, file.fileNamePure().c_str()); - } - else { - Mesh::Feature *pcFeature = static_cast - (pcDoc->addObject("Mesh::Feature", file.fileNamePure().c_str())); - pcFeature->Label.setValue(file.fileNamePure().c_str()); - pcFeature->Mesh.swapMesh(mesh); - pcFeature->purgeTouched(); - } - } + Mesh::Importer import(pcDoc); + import.load(EncodedName); return Py::None(); } @@ -260,86 +193,19 @@ private: PyMem_Free(Name); App::Document *pcDoc = 0; - if (DocName) + if (DocName) { pcDoc = App::GetApplication().getDocument(DocName); - else + } + else { pcDoc = App::GetApplication().getActiveDocument(); + } if (!pcDoc) { pcDoc = App::GetApplication().newDocument(DocName); } - MeshObject mesh; - MeshCore::Material mat; - if (mesh.load(EncodedName.c_str(), &mat)) { - Base::FileInfo file(EncodedName.c_str()); - unsigned long segmct = mesh.countSegments(); - if (segmct > 1) { - for (unsigned long i=0; i segm(mesh.meshFromSegment(group.getIndices())); - Mesh::Feature *pcFeature = static_cast - (pcDoc->addObject("Mesh::Feature", groupName.c_str())); - pcFeature->Label.setValue(groupName.c_str()); - pcFeature->Mesh.swapMesh(*segm); - - // if colors are set per face - if (mat.binding == MeshCore::MeshIO::PER_FACE && - mat.diffuseColor.size() == mesh.countFacets()) { - App::PropertyColorList* prop = static_cast - (pcFeature->addDynamicProperty("App::PropertyColorList", "VertexColors")); - if (prop) { - std::vector diffuseColor; - diffuseColor.reserve(group.getIndices().size()); - for (const auto& it : group.getIndices()) { - diffuseColor.push_back(mat.diffuseColor[it]); - } - prop->setValues(diffuseColor); - } - } - pcFeature->purgeTouched(); - } - } - else if (mat.binding == MeshCore::MeshIO::PER_VERTEX && - mat.diffuseColor.size() == mesh.countPoints()) { - FeatureCustom *pcFeature = new FeatureCustom(); - pcFeature->Label.setValue(file.fileNamePure().c_str()); - pcFeature->Mesh.swapMesh(mesh); - App::PropertyColorList* prop = static_cast - (pcFeature->addDynamicProperty("App::PropertyColorList", "VertexColors")); - if (prop) { - prop->setValues(mat.diffuseColor); - } - pcFeature->purgeTouched(); - - pcDoc->addObject(pcFeature, file.fileNamePure().c_str()); - } - else if (mat.binding == MeshCore::MeshIO::PER_FACE && - mat.diffuseColor.size() == mesh.countFacets()) { - FeatureCustom *pcFeature = new FeatureCustom(); - pcFeature->Label.setValue(file.fileNamePure().c_str()); - pcFeature->Mesh.swapMesh(mesh); - App::PropertyColorList* prop = static_cast - (pcFeature->addDynamicProperty("App::PropertyColorList", "FaceColors")); - if (prop) { - prop->setValues(mat.diffuseColor); - } - pcFeature->purgeTouched(); - - pcDoc->addObject(pcFeature, file.fileNamePure().c_str()); - } - else { - Mesh::Feature *pcFeature = static_cast - (pcDoc->addObject("Mesh::Feature", file.fileNamePure().c_str())); - pcFeature->Label.setValue(file.fileNamePure().c_str()); - pcFeature->Mesh.swapMesh(mesh); - pcFeature->purgeTouched(); - } - } + Mesh::Importer import(pcDoc); + import.load(EncodedName); return Py::None(); } diff --git a/src/Mod/Mesh/App/CMakeLists.txt b/src/Mod/Mesh/App/CMakeLists.txt index 2ecfb6084d..187b2115de 100644 --- a/src/Mod/Mesh/App/CMakeLists.txt +++ b/src/Mod/Mesh/App/CMakeLists.txt @@ -329,6 +329,8 @@ SET(Mesh_SRCS AppMeshPy.cpp Exporter.cpp Exporter.h + Importer.cpp + Importer.h Facet.cpp Facet.h FacetPyImp.cpp diff --git a/src/Mod/Mesh/App/Importer.cpp b/src/Mod/Mesh/App/Importer.cpp new file mode 100644 index 0000000000..06ac6c3ea0 --- /dev/null +++ b/src/Mod/Mesh/App/Importer.cpp @@ -0,0 +1,123 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif + +#include "Importer.h" +#include "MeshFeature.h" +#include + +using namespace Mesh; + + +Importer::Importer(App::Document* doc) + : document(doc) +{ +} + +void Importer::load(const std::string& fileName) +{ + MeshObject mesh; + MeshCore::Material mat; + + if (mesh.load(fileName.c_str(), &mat)) { + Base::FileInfo file(fileName.c_str()); + unsigned long segmct = mesh.countSegments(); + if (segmct > 1) { + createMeshFromSegments(file.fileNamePure(), mat, mesh); + } + else if (mat.binding == MeshCore::MeshIO::PER_VERTEX && + mat.diffuseColor.size() == mesh.countPoints()) { + Feature* feature = createMesh(file.fileNamePure(), mesh); + addVertexColors(feature, mat.diffuseColor); + feature->purgeTouched(); + } + else if (mat.binding == MeshCore::MeshIO::PER_FACE && + mat.diffuseColor.size() == mesh.countFacets()) { + Feature* feature = createMesh(file.fileNamePure(), mesh); + addFaceColors(feature, mat.diffuseColor); + feature->purgeTouched(); + } + else { + Feature* feature = createMesh(file.fileNamePure(), mesh); + feature->purgeTouched(); + } + } +} + +void Importer::addVertexColors(Feature* feature, const std::vector& colors) +{ + addColors(feature, "VertexColors", colors); +} + +void Importer::addFaceColors(Feature* feature, const std::vector& colors) +{ + addColors(feature, "FaceColors", colors); +} + +void Importer::addColors(Feature* feature, const std::string& property, const std::vector& colors) +{ + App::PropertyColorList* prop = static_cast + (feature->addDynamicProperty("App::PropertyColorList", property.c_str())); + if (prop) { + prop->setValues(colors); + } +} + +void Importer::createMeshFromSegments(const std::string& name, MeshCore::Material& mat, MeshObject& mesh) +{ + unsigned long segmct = mesh.countSegments(); + for (unsigned long i=0; i segm(mesh.meshFromSegment(group.getIndices())); + Feature* feature = createMesh(groupName, *segm); + + // if colors are set per face + if (mat.binding == MeshCore::MeshIO::PER_FACE && + mat.diffuseColor.size() == mesh.countFacets()) { + + std::vector diffuseColor; + diffuseColor.reserve(group.getIndices().size()); + for (const auto& it : group.getIndices()) { + diffuseColor.push_back(mat.diffuseColor[it]); + } + + addFaceColors(feature, diffuseColor); + } + feature->purgeTouched(); + } +} + +Feature* Importer::createMesh(const std::string& name, MeshObject& mesh) +{ + Mesh::Feature *pcFeature = static_cast + (document->addObject("Mesh::Feature", name.c_str())); + pcFeature->Label.setValue(name); + pcFeature->Mesh.swapMesh(mesh); + return pcFeature; +} diff --git a/src/Mod/Mesh/App/Importer.h b/src/Mod/Mesh/App/Importer.h new file mode 100644 index 0000000000..e40afd79c9 --- /dev/null +++ b/src/Mod/Mesh/App/Importer.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef MESH_IMPORTER_H +#define MESH_IMPORTER_H + +#include +#include + +namespace App { +class Document; +class Color; +} + +namespace MeshCore { +struct Material; +} +namespace Mesh +{ +class MeshObject; +class Feature; + +class Importer +{ +public: + Importer(App::Document*); + ~Importer() = default; + + void load(const std::string& fileName); + +private: + void addVertexColors(Feature*, const std::vector&); + void addFaceColors(Feature*, const std::vector&); + void addColors(Feature*, const std::string& property, const std::vector&); + Feature* createMesh(const std::string& name, MeshObject&); + void createMeshFromSegments(const std::string& name, MeshCore::Material& mat, MeshObject& mesh); + +private: + App::Document* document; +}; + +} // namespace Mesh + +#endif // MESH_IMPORTER_H From 595fc118e38cb3addffedf55207c8d5b81be0d11 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Thu, 14 Jan 2021 07:15:37 +0100 Subject: [PATCH 10/21] Part: Part2DObject::seekTrimPoints rename parameters ==================================================== This function takes parameters named GeoId, which evoke the Sketcher GeoId and which indeed are used by the sketcher. However, these indices are just indices of the provided geometry list. Therefore: 1. The are never negative (no matter if a corresponding Sketcher GeoId is external or not). 2. They do not have reserved indices for axes. 3. Most importantly, not found GeoId1 and GeoId2 are coded as -1, which for the Sketcher has a well defined meaning (axis), whereas the Sketcher uses Constraint::GeoUndef as undefined or not present/ not found index, which currently is -2000. For these reasons, it was judged appropriate to rename the parameters and documentation to emphasise that this function is unrelated and dettached from any Sketcher knowledge, and that indices are just normal indices of a vector. --- src/Mod/Part/App/Part2DObject.cpp | 34 +++++++++++++++---------------- src/Mod/Part/App/Part2DObject.h | 14 +++++++------ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Mod/Part/App/Part2DObject.cpp b/src/Mod/Part/App/Part2DObject.cpp index 597503823e..e26414b592 100644 --- a/src/Mod/Part/App/Part2DObject.cpp +++ b/src/Mod/Part/App/Part2DObject.cpp @@ -108,11 +108,11 @@ Base::Axis Part2DObject::getAxis(int axId) const } bool Part2DObject::seekTrimPoints(const std::vector &geomlist, - int GeoId, const Base::Vector3d &point, - int &GeoId1, Base::Vector3d &intersect1, - int &GeoId2, Base::Vector3d &intersect2) + int geometryIndex, const Base::Vector3d &point, + int &geometryIndex1, Base::Vector3d &intersect1, + int &geometryIndex2, Base::Vector3d &intersect2) { - if (GeoId >= int(geomlist.size())) + if ( geometryIndex >= int(geomlist.size())) return false; gp_Pln plane(gp_Pnt(0,0,0),gp_Dir(0,0,1)); @@ -120,7 +120,7 @@ bool Part2DObject::seekTrimPoints(const std::vector &geomlist, Standard_Boolean periodic=Standard_False; double period = 0; Handle(Geom2d_Curve) primaryCurve; - Handle(Geom_Geometry) geom = (geomlist[GeoId])->handle(); + Handle(Geom_Geometry) geom = (geomlist[geometryIndex])->handle(); Handle(Geom_Curve) curve3d = Handle(Geom_Curve)::DownCast(geom); if (curve3d.IsNull()) @@ -141,14 +141,14 @@ bool Part2DObject::seekTrimPoints(const std::vector &geomlist, double pickedParam = Projector.LowerDistanceParameter(); // find intersection points - GeoId1 = -1; - GeoId2 = -1; + geometryIndex1 = -1; + geometryIndex2 = -1; double param1=-1e10,param2=1e10; gp_Pnt2d p1,p2; Handle(Geom2d_Curve) secondaryCurve; for (int id=0; id < int(geomlist.size()); id++) { // #0000624: Trim tool doesn't work with construction lines - if (id != GeoId/* && !geomlist[id]->Construction*/) { + if (id != geometryIndex/* && !geomlist[id]->Construction*/) { geom = (geomlist[id])->handle(); curve3d = Handle(Geom_Curve)::DownCast(geom); if (!curve3d.IsNull()) { @@ -205,24 +205,24 @@ bool Part2DObject::seekTrimPoints(const std::vector &geomlist, if (param > param1) { param1 = param; p1 = p; - GeoId1 = id; + geometryIndex1 = id; } param -= period; // transfer param into the interval (pickedParam pickedParam+period] if (param < param2) { param2 = param; p2 = p; - GeoId2 = id; + geometryIndex2 = id; } } else if (param < pickedParam && param > param1) { param1 = param; p1 = p; - GeoId1 = id; + geometryIndex1 = id; } else if (param > pickedParam && param < param2) { param2 = param; p2 = p; - GeoId2 = id; + geometryIndex2 = id; } } } @@ -233,18 +233,18 @@ bool Part2DObject::seekTrimPoints(const std::vector &geomlist, // in case both points coincide, cancel the selection of one of both if (fabs(param2-param1-period) < 1e-10) { if (param2 - pickedParam >= pickedParam - param1) - GeoId2 = -1; + geometryIndex2 = -1; else - GeoId1 = -1; + geometryIndex1 = -1; } } - if (GeoId1 < 0 && GeoId2 < 0) + if ( geometryIndex1 < 0 && geometryIndex2 < 0) return false; - if (GeoId1 >= 0) + if ( geometryIndex1 >= 0) intersect1 = Base::Vector3d(p1.X(),p1.Y(),0.f); - if (GeoId2 >= 0) + if ( geometryIndex2 >= 0) intersect2 = Base::Vector3d(p2.X(),p2.Y(),0.f); return true; } diff --git a/src/Mod/Part/App/Part2DObject.h b/src/Mod/Part/App/Part2DObject.h index 356e487540..972da40cd0 100644 --- a/src/Mod/Part/App/Part2DObject.h +++ b/src/Mod/Part/App/Part2DObject.h @@ -66,17 +66,19 @@ public: /// verify and accept the assigned geometry virtual void acceptGeometry(); - /** calculate the points where a curve with index GeoId should be trimmed + /** calculate the points where a curve with index geometryIndex should be trimmed * with respect to the rest of the curves contained in the list geomlist * and a picked point. The outputs intersect1 and intersect2 specify the * tightest boundaries for trimming around the picked point and the - * indexes GeoId1 and GeoId2 specify the corresponding curves that intersect - * the curve GeoId. + * indexes geometryIndex1 and geometryIndex2 specify the corresponding curves that intersect + * the curve geometryIndex. + * + * If intersection is found, the associated geometryIndex1 or geometryIndex2 retuns -1. */ static bool seekTrimPoints(const std::vector &geomlist, - int GeoId, const Base::Vector3d &point, - int &GeoId1, Base::Vector3d &intersect1, - int &GeoId2, Base::Vector3d &intersect2); + int geometryIndex, const Base::Vector3d &point, + int &geometryIndex1, Base::Vector3d &intersect1, + int &geometryIndex2, Base::Vector3d &intersect2); static const int H_Axis; static const int V_Axis; From 010a2cbd4196f48022e11c59b1ccb3fad811d222 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Thu, 14 Jan 2021 08:34:39 +0100 Subject: [PATCH 11/21] Sketcher: ViewProvider - add marker information layer ===================================================== New Coin Marker layer intended to temporarely highlight sections of the 3D View for information purposes. Independent from the actual redrawing of the geometry and constraints. --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 72 +++++++++++++++++++++ src/Mod/Sketcher/Gui/ViewProviderSketch.h | 3 + 2 files changed, 75 insertions(+) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index a8d6e12fba..5dfec0e8e8 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -196,13 +196,16 @@ struct EditData { CurvesMaterials(0), RootCrossMaterials(0), EditCurvesMaterials(0), + EditMarkersMaterials(0), PointsCoordinate(0), CurvesCoordinate(0), RootCrossCoordinate(0), EditCurvesCoordinate(0), + EditMarkersCoordinate(0), CurveSet(0), RootCrossSet(0), EditCurveSet(0), + EditMarkerSet(0), PointSet(0), textX(0), textPos(0), @@ -213,6 +216,7 @@ struct EditData { CurvesDrawStyle(0), RootCrossDrawStyle(0), EditCurvesDrawStyle(0), + EditMarkersDrawStyle(0), ConstraintDrawStyle(0), InformationDrawStyle(0) {} @@ -267,13 +271,16 @@ struct EditData { SoMaterial *CurvesMaterials; SoMaterial *RootCrossMaterials; SoMaterial *EditCurvesMaterials; + SoMaterial *EditMarkersMaterials; SoCoordinate3 *PointsCoordinate; SoCoordinate3 *CurvesCoordinate; SoCoordinate3 *RootCrossCoordinate; SoCoordinate3 *EditCurvesCoordinate; + SoCoordinate3 *EditMarkersCoordinate; SoLineSet *CurveSet; SoLineSet *RootCrossSet; SoLineSet *EditCurveSet; + SoMarkerSet *EditMarkerSet; SoMarkerSet *PointSet; SoText2 *textX; @@ -287,6 +294,7 @@ struct EditData { SoDrawStyle * CurvesDrawStyle; SoDrawStyle * RootCrossDrawStyle; SoDrawStyle * EditCurvesDrawStyle; + SoDrawStyle * EditMarkersDrawStyle; SoDrawStyle * ConstraintDrawStyle; SoDrawStyle * InformationDrawStyle; }; @@ -3837,6 +3845,8 @@ void ViewProviderSketch::updateInventorNodeSizes() edit->CurvesDrawStyle->lineWidth = 3 * edit->pixelScalingFactor; edit->RootCrossDrawStyle->lineWidth = 2 * edit->pixelScalingFactor; edit->EditCurvesDrawStyle->lineWidth = 3 * edit->pixelScalingFactor; + edit->EditMarkersDrawStyle->pointSize = 8 * edit->pixelScalingFactor; + edit->EditMarkerSet->markerIndex = Gui::Inventor::MarkerBitmaps::getMarkerIndex("CIRCLE_LINE", edit->MarkerSize); edit->ConstraintDrawStyle->lineWidth = 1 * edit->pixelScalingFactor; edit->InformationDrawStyle->lineWidth = 1 * edit->pixelScalingFactor; } @@ -6144,6 +6154,47 @@ void ViewProviderSketch::drawEdit(const std::vector &EditCurve) index[0] = EditCurve.size(); edit->EditCurvesCoordinate->point.finishEditing(); edit->EditCurveSet->numVertices.finishEditing(); + edit->EditCurvesMaterials->diffuseColor.finishEditing(); +} + +void ViewProviderSketch::drawEditMarkers(const std::vector &EditMarkers, unsigned int augmentationlevel) +{ + assert(edit); + + // determine marker size + int augmentedmarkersize = edit->MarkerSize; + + auto supportedsizes = Gui::Inventor::MarkerBitmaps::getSupportedSizes("CIRCLE_LINE"); + + auto defaultmarker = std::find(supportedsizes.begin(), supportedsizes.end(), edit->MarkerSize); + + if(defaultmarker != supportedsizes.end()) { + auto validAugmentationLevels = std::distance(defaultmarker,supportedsizes.end()); + + if(augmentationlevel >= validAugmentationLevels) + augmentationlevel = validAugmentationLevels - 1; + + augmentedmarkersize = *std::next(defaultmarker, augmentationlevel); + } + + edit->EditMarkerSet->markerIndex.startEditing(); + edit->EditMarkerSet->markerIndex = Gui::Inventor::MarkerBitmaps::getMarkerIndex("CIRCLE_LINE", augmentedmarkersize); + + // add the points to set + edit->EditMarkersCoordinate->point.setNum(EditMarkers.size()); + edit->EditMarkersMaterials->diffuseColor.setNum(EditMarkers.size()); + SbVec3f *verts = edit->EditMarkersCoordinate->point.startEditing(); + SbColor *color = edit->EditMarkersMaterials->diffuseColor.startEditing(); + + int i=0; // setting up the line set + for (std::vector::const_iterator it = EditMarkers.begin(); it != EditMarkers.end(); ++it,i++) { + verts[i].setValue(it->x,it->y,zEdit); + color[i] = InformationColor; + } + + edit->EditMarkersCoordinate->point.finishEditing(); + edit->EditMarkersMaterials->diffuseColor.finishEditing(); + edit->EditMarkerSet->markerIndex.finishEditing(); } void ViewProviderSketch::updateData(const App::Property *prop) @@ -6686,6 +6737,27 @@ void ViewProviderSketch::createEditInventorNodes(void) SbColor cursorTextColor(0,0,1); cursorTextColor.setPackedValue((uint32_t)hGrp->GetUnsigned("CursorTextColor", cursorTextColor.getPackedValue()), transparency); + // stuff for the EditMarkers +++++++++++++++++++++++++++++++++++++++ + SoSeparator* editMarkersRoot = new SoSeparator; + edit->EditRoot->addChild(editMarkersRoot); + edit->EditMarkersMaterials = new SoMaterial; + edit->EditMarkersMaterials->setName("EditMarkersMaterials"); + editCurvesRoot->addChild(edit->EditMarkersMaterials); + + edit->EditMarkersCoordinate = new SoCoordinate3; + edit->EditMarkersCoordinate->setName("EditMarkersCoordinate"); + editCurvesRoot->addChild(edit->EditMarkersCoordinate); + + edit->EditMarkersDrawStyle = new SoDrawStyle; + edit->EditMarkersDrawStyle->setName("EditMarkersDrawStyle"); + edit->EditMarkersDrawStyle->pointSize = 8 * edit->pixelScalingFactor; + editCurvesRoot->addChild(edit->EditMarkersDrawStyle); + + edit->EditMarkerSet = new SoMarkerSet; + edit->EditMarkerSet->setName("EditMarkerSet"); + edit->EditMarkerSet->markerIndex = Gui::Inventor::MarkerBitmaps::getMarkerIndex("CIRCLE_LINE", edit->MarkerSize); + editCurvesRoot->addChild(edit->EditMarkerSet); + // stuff for the edit coordinates ++++++++++++++++++++++++++++++++++++++ SoSeparator *Coordsep = new SoSeparator(); SoPickStyle* ps = new SoPickStyle(); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 8cc79abf0a..6f66d6507f 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -128,6 +128,9 @@ public: /// draw the edit curve void drawEdit(const std::vector &EditCurve); + /// draw the edit markers + void drawEditMarkers(const std::vector &EditMarkers, unsigned int augmentationlevel = 0); + /// Is the view provider selectable bool isSelectable(void) const override; /// Observer message from the Selection From 8be50486af956baf871867347b20f7b80767569b Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 17 Jan 2021 05:17:34 +0100 Subject: [PATCH 12/21] Sketcher: reset Marker Edit layer when exiting DrawSketchHandler --- src/Mod/Sketcher/Gui/DrawSketchHandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index 16d32274bd..7c057cd74e 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -72,6 +72,7 @@ void DrawSketchHandler::quit(void) { assert(sketchgui); sketchgui->drawEdit(std::vector()); + sketchgui->drawEditMarkers(std::vector()); resetPositionText(); Gui::Selection().rmvSelectionGate(); From 2afcbff0d6374fe9ca5eb2efff458d5ca2e20205 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 27 Jan 2021 07:51:16 +0100 Subject: [PATCH 13/21] Part: Geometry - Add function to trim a B-Spline ================================================ Supports both non-periodic and periodic case --- src/Mod/Part/App/Geometry.cpp | 31 +++++++++++++++++++++++++++++++ src/Mod/Part/App/Geometry.h | 2 ++ 2 files changed, 33 insertions(+) diff --git a/src/Mod/Part/App/Geometry.cpp b/src/Mod/Part/App/Geometry.cpp index 2f86b87185..e2dc02e7bb 100644 --- a/src/Mod/Part/App/Geometry.cpp +++ b/src/Mod/Part/App/Geometry.cpp @@ -1539,6 +1539,37 @@ bool GeomBSplineCurve::removeKnot(int index, int multiplicity, double tolerance) } } +void GeomBSplineCurve::Trim(double u, double v) +{ + auto splitUnwrappedBSpline = [this](double u, double v) { + // it makes a copy internally (checked in the source code of OCCT) + auto handle = GeomConvert::SplitBSplineCurve ( myCurve, + u, + v, + Precision::Confusion() + ); + setHandle(handle); + }; + + try { + if(!isPeriodic()) { + splitUnwrappedBSpline(u, v); + } + else { // periodic + if( v < u ) { // wraps over origin + v = v + 1.0; // v needs one extra lap (1.0) + + splitUnwrappedBSpline(u, v); + } + else { + splitUnwrappedBSpline(u, v); + } + } + } + catch (Standard_Failure& e) { + THROWM(Base::CADKernelError,e.GetMessageString()) + } +} // Persistence implementer unsigned int GeomBSplineCurve::getMemSize (void) const diff --git a/src/Mod/Part/App/Geometry.h b/src/Mod/Part/App/Geometry.h index b1f9568298..fea71fbbbc 100644 --- a/src/Mod/Part/App/Geometry.h +++ b/src/Mod/Part/App/Geometry.h @@ -307,6 +307,8 @@ public: void increaseMultiplicity(int index, int multiplicity); bool removeKnot(int index, int multiplicity, double tolerance = Precision::PConfusion()); + void Trim(double u, double v); + // Persistence implementer --------------------- virtual unsigned int getMemSize(void) const; virtual void Save(Base::Writer &/*writer*/) const; From 2d817128d0fa21c2015193716e06bbd97908c867 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 17 Jan 2021 05:22:23 +0100 Subject: [PATCH 14/21] Sketcher: SketchObject - Trim refactor ====================================== * Refactor the code for all GeomTrimmedCurve and non-periodic BSplines in a single block, adding support for arcs of hyperbola, parabola and B-Splines. * Refactor the code for periodic curves (circle, ellipse) in a single block, adding support for periodic B-Splines. * Add support for trimming limited by external geometry * Trim deletes a geometry if intersections are detected and nothing would be left after trimming * Trim deletes a geometry if no intersection are detected Bonus: * Function addConstraint moving the constraint instead of cloning it again * SketchObject::seekTrimPoints as a wrapper of Part2DObject::seekTrimPoints providing a correct handling of GeoId indices. * Helper functions addConstraint/createconstraint to create new constraints and move them into the Constraint property * New getGeometry with templated return type defaulting to Part::Geometry. --- src/Mod/Sketcher/App/SketchObject.cpp | 1396 ++++++++++--------------- src/Mod/Sketcher/App/SketchObject.h | 62 +- 2 files changed, 609 insertions(+), 849 deletions(-) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 0c15240bd6..db96c20248 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -1274,6 +1274,13 @@ int SketchObject::addCopyOfConstraints(const SketchObject &orig) } int SketchObject::addConstraint(const Constraint *constraint) +{ + auto constraint_ptr = std::unique_ptr(constraint->clone()); + + return addConstraint(std::move(constraint_ptr)); +} + +int SketchObject::addConstraint(std::unique_ptr constraint) { Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. @@ -1281,7 +1288,7 @@ int SketchObject::addConstraint(const Constraint *constraint) std::vector< Constraint * > newVals(vals); - Constraint *constNew = constraint->clone(); + Constraint *constNew = constraint.release(); if (constNew->Type == Tangent || constNew->Type == Perpendicular) AutoLockTangencyAndPerpty(constNew); @@ -2320,44 +2327,91 @@ int SketchObject::extend(int GeoId, double increment, int endpoint) { return retcode; } +std::unique_ptr SketchObject::createConstraint( Sketcher::ConstraintType constrType, + int firstGeoId, + Sketcher::PointPos firstPos, + int secondGeoId, + Sketcher::PointPos secondPos, + int thirdGeoId, + Sketcher::PointPos thirdPos) +{ + auto newConstr = std::make_unique(); + + newConstr->Type = constrType; + newConstr->First = firstGeoId; + newConstr->FirstPos = firstPos; + newConstr->Second = secondGeoId; + newConstr->SecondPos = secondPos; + newConstr->Third = thirdGeoId; + newConstr->ThirdPos = thirdPos; + return newConstr; +} + +void SketchObject::addConstraint(Sketcher::ConstraintType constrType, + int firstGeoId, + Sketcher::PointPos firstPos, + int secondGeoId, + Sketcher::PointPos secondPos, + int thirdGeoId, + Sketcher::PointPos thirdPos) +{ + auto newConstr = createConstraint(constrType, firstGeoId, firstPos, secondGeoId, secondPos, thirdGeoId, thirdPos); + + this->addConstraint(std::move(newConstr)); +} + +bool SketchObject::seekTrimPoints(int GeoId, const Base::Vector3d &point, + int &GeoId1, Base::Vector3d &intersect1, + int &GeoId2, Base::Vector3d &intersect2) +{ + if (GeoId < 0 || GeoId > getHighestCurveIndex()) + return false; + + auto geos = getCompleteGeometry(); // this includes the axes too + + geos.resize(geos.size()-2); // remove the axes to avoid intersections with the axes + + int localindex1, localindex2; + + // Not found in will be returned as -1, not as GeoUndef, Part WB is agnostic to the concept of GeoUndef + if(!Part2DObject::seekTrimPoints(geos, GeoId, point, localindex1, intersect1, localindex2, intersect2)) + return false; + + GeoId1 = getGeoIdFromCompleteGeometryIndex(localindex1); // invalid complete geometry indices are mapped to GeoUndef + GeoId2 = getGeoIdFromCompleteGeometryIndex(localindex2); + + return true; +} + int SketchObject::trim(int GeoId, const Base::Vector3d& point) { Base::StateLocker lock(managedoperation, true); // no need to check input data validity as this is an sketchobject managed operation. + //******************* Basic checks rejecting the operation ****************************************// if (GeoId < 0 || GeoId > getHighestCurveIndex()) return -1; - const std::vector &geomlist = getInternalGeometry(); + auto geo = getGeometry(GeoId); - int GeoId1=Constraint::GeoUndef, GeoId2=Constraint::GeoUndef; - Base::Vector3d point1, point2; - Part2DObject::seekTrimPoints(geomlist, GeoId, point, GeoId1, point1, GeoId2, point2); - if (GeoId1 < 0 && GeoId2 >= 0) { - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - } + if (!GeometryFacade::isInternalType(geo,InternalType::None)) + return -1; // internal alignment geometry is not trimmable - auto handlemultipleintersection = [this] (Constraint * constr, int GeoId, PointPos pos, PointPos & secondPos) { - - Base::Vector3d cp = getPoint(constr->First,constr->FirstPos); - - Base::Vector3d ee = getPoint(GeoId,pos); - - if( (ee-cp).Length() < Precision::Confusion() ) { - secondPos = constr->FirstPos; - } - }; - - auto isPointAtPosition = [this] (int GeoId1, PointPos pos1, Base::Vector3d point) { - - Base::Vector3d pp = getPoint(GeoId1,pos1); + //******************* Lambdas - common functions for different intersections ****************************************// + // returns true if the point defined by (GeoId1, pos1) can be considered to be coincident with point. + auto arePointsWithinPrecision = [] (Base::Vector3d point1, Base::Vector3d point2) { // From testing: 500x (or 0.000050) is needed in order to not falsely distinguish points calculated with seekTrimPoints - if( (point-pp).Length() < 500*Precision::Confusion()) + if( (point1-point2).Length() < 500*Precision::Confusion()) return true; return false; + }; + auto isPointAtPosition = [this, arePointsWithinPrecision] (int GeoId1, PointPos pos1, Base::Vector3d point) { + + Base::Vector3d pp = getPoint(GeoId1,pos1); + + return arePointsWithinPrecision(point, pp); }; // Helper function to remove Equal constraints from a chosen edge (e.g Line segment). @@ -2367,7 +2421,7 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point) int index = 0; const std::vector &constraints = this->Constraints.getValues(); for (std::vector::const_iterator it=constraints.begin(); - it != constraints.end(); ++it) { + it != constraints.end(); ++it, ++index) { Constraint *constr = *(it); if (constr->First == GeoId && constr->Type == Sketcher::Equal) { delete_list.push_back(index); @@ -2375,27 +2429,27 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point) if (constr->Second == GeoId && constr->Type == Sketcher::Equal) { delete_list.push_back(index); } - index++; } - delConstraints(delete_list); + delConstraints(delete_list, false); }; - auto creategeometryundopoint = [this, geomlist]() { - Geometry.setValues(geomlist); - }; - - auto getTransformParamsAndDeleteConstraints = [this, isPointAtPosition, handlemultipleintersection] - (int GeoId, int GeoId1, Base::Vector3d point1, ConstraintType &constrType1, PointPos &secondPos1) { + // Checks whether preexisting constraints must be converted to new constraints. + // Preexisting point on object constraints get converted to coincidents, unless an end-to-end tangency is more relevant. + // returns by reference: + // - The type of constraint that should be used to constraint GeoId1 and GeoId + // - The element of GeoId1 to which the constraint should be applied. + auto transformPreexistingConstraints = [this, isPointAtPosition] ( int GeoId, int GeoId1, Base::Vector3d point1, + ConstraintType &constrType, PointPos &secondPos ) { const std::vector &constraints = this->Constraints.getValues(); int constrId=0; std::vector delete_list; for (std::vector::const_iterator it=constraints.begin(); it != constraints.end(); ++it) { Constraint *constr = *(it); + // There is a preexisting PointOnObject constraint, see if it must be converted to a coincident if (constr->Type == Sketcher::PointOnObject && constr->First == GeoId1 && constr->Second == GeoId) { - if (isPointAtPosition(GeoId1, constr->FirstPos, point1)) { - constrType1 = Sketcher::Coincident; - secondPos1 = constr->FirstPos; - handlemultipleintersection(constr, GeoId, start, secondPos1); + if (isPointAtPosition(constr->First, constr->FirstPos, point1)) { + constrType = Sketcher::Coincident; + secondPos = constr->FirstPos; delete_list.push_back(constrId); } } @@ -2410,867 +2464,500 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point) Constraint *constr = *(it); if (constr->Type == Sketcher::Tangent) { if (constr->First == GeoId1 && constr->Second == GeoId) { - constrType1 = constr->Type; - if (secondPos1 == Sketcher::none) - secondPos1 = constr->FirstPos; + constrType = Sketcher::Tangent; + if (secondPos == Sketcher::none) + secondPos = constr->FirstPos; delete_list.push_back(constrId); } else if (constr->First == GeoId && constr->Second == GeoId1) { - constrType1 = constr->Type; - if (secondPos1 == Sketcher::none) - secondPos1 = constr->SecondPos; + constrType = Sketcher::Tangent; + if (secondPos == Sketcher::none) + secondPos = constr->SecondPos; delete_list.push_back(constrId); } } + if (constr->Type == Sketcher::Perpendicular) { + if (constr->First == GeoId1 && constr->Second == GeoId) { + constrType = Sketcher::Perpendicular; + if (secondPos == Sketcher::none) + secondPos = constr->FirstPos; + delete_list.push_back(constrId); + } else if (constr->First == GeoId && constr->Second == GeoId1) { + constrType = Sketcher::Perpendicular; + if (secondPos == Sketcher::none) + secondPos = constr->SecondPos; + delete_list.push_back(constrId); + } + } + constrId++; } - delConstraints(delete_list); + delConstraints(delete_list, false); }; - Part::Geometry *geo = geomlist[GeoId]; - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg = static_cast(geo); - Base::Vector3d startPnt = lineSeg->getStartPoint(); - Base::Vector3d endPnt = lineSeg->getEndPoint(); - Base::Vector3d dir = (endPnt - startPnt).Normalize(); - double length = (endPnt - startPnt)*dir; - double x0 = (point - startPnt)*dir; - if (GeoId1 >= 0 && GeoId2 >= 0) { - double x1 = (point1 - startPnt)*dir; - double x2 = (point2 - startPnt)*dir; - if (x1 > x2) { - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - std::swap(x1,x2); - } - if (x1 >= 0.001*length && x2 <= 0.999*length) { - if (x1 < x0 && x2 > x0) { // trim away a segment in the middle - int newGeoId = addGeometry(geo); - // go through all constraints and replace the point (GeoId,end) with (newGeoId,end) - transferConstraints(GeoId, end, newGeoId, end); - delEqualConstraintsOnGeoId(GeoId); - delEqualConstraintsOnGeoId(newGeoId); - movePoint(GeoId, end, point1,false,true); - movePoint(newGeoId, start, point2,false,true); + // makes an equality constraint between GeoId1 and GeoId2 + auto constrainAsEqual = [this] (int GeoId1, int GeoId2) { + auto newConstr = std::make_unique(); + // Build Constraints associated with new pair of arcs + newConstr->Type = Sketcher::Equal; + newConstr->First = GeoId1; + newConstr->FirstPos = Sketcher::none; + newConstr->Second = GeoId2; + newConstr->SecondPos = Sketcher::none; + addConstraint(std::move(newConstr)); + }; - PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; - ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; + // Adds a Point on ObjectConstraint except if the object is a B-Spline + auto addConstraintIfNoPointOnBSpline = [this](ConstraintType constrType, int GeoId1, PointPos pos1, int GeoId2, PointPos pos2) + { + if (constrType == PointOnObject && getGeometry(GeoId2)->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) + return; // There is no support for PointOnObject in BSpline - getTransformParamsAndDeleteConstraints(GeoId, GeoId1, point1, constrType1, secondPos1); + addConstraint(constrType, GeoId1, pos1, GeoId2, pos2); + }; - // constrain the trimming points on the corresponding geometries - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId1; - newConstr->SecondPos = secondPos1; + // Removes all internal geometry of a BSplineCurve and updates the GeoId index after removal + auto ifBSplineRemoveInternalAlignmentGeometry = [this](int &GeoId) + { + const Part::Geometry* geo = getGeometry(GeoId); + if(geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) { + // We need to remove the internal geometry of the BSpline, as BSplines change in number of poles and knots + // We save the tags of the relevant geometry to retrieve the new GeoIds later on. + boost::uuids::uuid GeoIdTag; - addConstraint(newConstr); + GeoIdTag = geo->getTag(); - getTransformParamsAndDeleteConstraints(GeoId, GeoId2, point2, constrType2, secondPos2); + deleteUnusedInternalGeometry(GeoId); - newConstr->Type = constrType2; - newConstr->First = newGeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId2; - newConstr->SecondPos = secondPos2; + auto vals = getCompleteGeometry(); - addConstraint(newConstr); - - // new line segments colinear - newConstr->Type = Sketcher::Tangent; - newConstr->First = GeoId; - newConstr->FirstPos = none; - newConstr->Second = newGeoId; - newConstr->SecondPos = Sketcher::none; - - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; + for(size_t i = 0; i < vals.size(); i++) { + if(vals[i]->getTag() == GeoIdTag) { + GeoId = getGeoIdFromCompleteGeometryIndex(i); + break; } - } else if (x1 < 0.001*length) { // drop the first intersection point - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - } else if (x2 > 0.999*length) { // drop the second intersection point + } + } + }; + + // given a geometry and tree points, returns the corresponding parameters of the geometry points closest to them + auto getIntersectionParameters = [](const Part::Geometry * geo, + const Base::Vector3d point, double &pointParam, + const Base::Vector3d point1, double &point1Param, + const Base::Vector3d point2, double &point2Param) { + + auto curve = static_cast(geo); + + try { + curve->closestParameter(point, pointParam); + curve->closestParameter(point1, point1Param); + curve->closestParameter(point2, point2Param); + } + catch(Base::CADKernelError & e) { + e.ReportException(); + return false; + } + + return true; + }; + + //******************* Step A => Detection of intersection - Common to all Geometries ****************************************// + int GeoId1=Constraint::GeoUndef, GeoId2=Constraint::GeoUndef; + Base::Vector3d point1, point2; + // Using SketchObject wrapper, as Part2DObject version returns GeoId = -1 when intersection not found, which is wrong for a GeoId (axis). + // seekTrimPoints returns: + // - For a parameter associated with "point" between an intersection and the end point (non-periodic case) GeoId1 != GeoUndef and GeoId2 == GeoUndef + // - For a parameter associated with "point" between the start point and an intersection (non-periodic case) GeoId2 != GeoUndef and GeoId1 == GeoUndef + // - For a parameter associated with "point" between two intersection points, GeoId1 != GeoUndef and GeoId2 != GeoUndef + // + // FirstParam < point1param < point2param < LastParam + if(!SketchObject::seekTrimPoints(GeoId, point, GeoId1, point1, GeoId2, point2)) { + // If no suitable trim points are found, then trim defaults to deleting the geometry + delGeometry(GeoId); + return 0; + } + + //******************* Preparation of BSplines ****************************************// + // Trimmed B-Spline internal geometry cannot be reused + geo = getGeometry(GeoId); + + auto isBSpline = geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId(); + auto isPeriodicBSpline = isBSpline && static_cast(geo)->isPeriodic(); + auto isNonPeriodicBSpline = isBSpline && !static_cast(geo)->isPeriodic(); + auto isLineSegment = geo->getTypeId() == Part::GeomLineSegment::getClassTypeId(); + auto isDerivedFromTrimmedCurve = geo->isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()); + auto isCircle = geo->getTypeId() == Part::GeomCircle::getClassTypeId(); + auto isEllipse = geo->getTypeId() == Part::GeomEllipse::getClassTypeId(); + + if(isBSpline) { + + // Two options, it is a periodic bspline and we need two intersections or + // it is a non-periodic bspline and one intersection is enough. + auto bspline = static_cast(geo); + + if(bspline->isPeriodic() && (GeoId1 == Constraint::GeoUndef || GeoId2 == Constraint::GeoUndef)) + return -1; + + ifBSplineRemoveInternalAlignmentGeometry(GeoId); // GeoId gets updated here + + // When internal alignment geometry is removed from a bspline, it moves slightly + // this causes that small segments are detected near the endpoints. + // + // The alternative to this re-detection, is to remove the internal alignment geometry + // before the detection. However, that would cause the lost of the internal alignment geometry + // in a case where trimming does not succeed because seekTrimPoints fails to find (suitable) intersection(s) + if(!SketchObject::seekTrimPoints(GeoId, point, GeoId1, point1, GeoId2, point2)) { + // If no suitable trim points are found, then trim defaults to deleting the geometry + delGeometry(GeoId); + return 0; + } + + geo = getGeometry(GeoId); + } + + if( GeoId1 != Constraint::GeoUndef && + GeoId2 != Constraint::GeoUndef && + arePointsWithinPrecision(point1, point2) ) { // If both points are detected and are coincident, deletion is the only option. + delGeometry(GeoId); + + return 0; + } + + //******************* Step B.1 => Trimming for GeomTrimmedCurves (line segment and arcs) ****************************************// + if (isDerivedFromTrimmedCurve || isNonPeriodicBSpline ) { + + if(geo->isDerivedFrom(Part::GeomConic::getClassTypeId())) { + auto *tc = static_cast(geo); + if(tc->isReversed()) { + const_cast(tc)->reverse(); // reversing does not change the curve as seen by the sketcher. + } + } + + //****** Step B.1 (1) => Determine intersection parameters ******// + // Now LastParam > FirstParam + double firstParam, lastParam; + if (isDerivedFromTrimmedCurve) { + auto aoc = static_cast(geo); + aoc->getRange(firstParam, lastParam); + } + else if(isNonPeriodicBSpline) { + auto bsp = static_cast(geo); + firstParam = bsp->getFirstParameter(); + lastParam = bsp->getLastParameter(); + } + else + return -1; + + double pointParam, point1Param, point2Param; + if(!getIntersectionParameters(geo, point, pointParam, point1, point1Param, point2, point2Param)) + return -1; + +#ifdef DEBUG + Base::Console().Log("Trim sought: GeoId1=%d (%f), GeoId2=%d (%f)\n",GeoId1, point1Param, GeoId2, point2Param); +#endif + + // seekTrimPoints enforces that firstParam < point1Param < point2Param < lastParam + auto paramDistance = [](double param1, double param2) { + double distance = fabs(param1 - param2); + + if(distance < Precision::Confusion()) + return 0.; else - return -1; - } - if (GeoId1 >= 0) { - double x1 = (point1 - startPnt)*dir; - if (x1 >= 0.001*length && x1 <= 0.999*length) { - creategeometryundopoint(); // for when geometry will change, but no new geometry will be committed. + return distance; + }; - ConstraintType constrType1 = Sketcher::PointOnObject; - PointPos secondPos1 = Sketcher::none; + //****** Step B.1 (2) => Determine trimmable sections and trim operation ******// - getTransformParamsAndDeleteConstraints(GeoId, GeoId1, point1, constrType1, secondPos1); + // Determine if there is something trimmable + double startDistance = GeoId1 != Constraint::GeoUndef?paramDistance(firstParam, point1Param):paramDistance(firstParam, point2Param); + double endDistance = GeoId2 != Constraint::GeoUndef?paramDistance(lastParam, point2Param):paramDistance(lastParam, point1Param); + double middleDistance = (GeoId1 != Constraint::GeoUndef && GeoId2 != Constraint::GeoUndef)?paramDistance(point1Param, point2Param):0.0; - if (x1 > x0) { // trim line start - delConstraintOnPoint(GeoId, start, false); - delEqualConstraintsOnGeoId(GeoId); - movePoint(GeoId, start, point1,false,true); + bool trimmableStart = startDistance > 0.; + bool trimmableMiddle = middleDistance > 0.; + bool trimmableEnd = endDistance > 0.; - // constrain the trimming point on the corresponding geometry - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId1; - newConstr->SecondPos = secondPos1; + struct Operation { + Operation():Type(trim_none), actingParam(0.), intersectingGeoId(Constraint::GeoUndef){} + enum { + trim_none, + trim_start, + trim_middle, + trim_end, + trim_delete + } Type; - addConstraint(newConstr); + double actingParam; + Base::Vector3d actingPoint; + int intersectingGeoId; + }; - delete newConstr; + Operation op; - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); + if ( GeoId1!=Constraint::GeoUndef && + GeoId2!=Constraint::GeoUndef && + pointParam > point1Param && pointParam < point2Param ) { // Trim Point between intersection points - return 0; - } - else if (x1 < x0) { // trim line end - delConstraintOnPoint(GeoId, end, false); - delEqualConstraintsOnGeoId(GeoId); - movePoint(GeoId, end, point1,false,true); - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId1; - newConstr->SecondPos = secondPos1; - - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; - } + if( (!trimmableStart && !trimmableEnd) || !trimmableMiddle) { // if after trimming nothing would be left or if there is nothing to trim + op.Type = Operation::trim_delete; + } + else if(trimmableStart && trimmableEnd) { + op.Type = Operation::trim_middle; // trim between point1Param and point2Param + } + else if (trimmableStart /*&&!trimmableEnd*/) { + op.Type = Operation::trim_end; + op.actingParam = point1Param; // trim from point1Param until lastParam + op.actingPoint = point1; + op.intersectingGeoId = GeoId1; + } + else { // !trimmableStart && trimmableEnd + op.Type = Operation::trim_start; + op.actingParam = point2Param; // trim from firstParam until point2Param + op.actingPoint = point2; + op.intersectingGeoId = GeoId2; } } - } else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) { - const Part::GeomCircle *circle = static_cast(geo); - Base::Vector3d center = circle->getCenter(); - double theta0 = Base::fmod(atan2(point.y - center.y,point.x - center.x), 2.f*M_PI); - if (GeoId1 >= 0 && GeoId2 >= 0) { - double theta1 = Base::fmod(atan2(point1.y - center.y, point1.x - center.x), 2.f*M_PI); - double theta2 = Base::fmod(atan2(point2.y - center.y, point2.x - center.x), 2.f*M_PI); - if (Base::fmod(theta1 - theta0, 2.f*M_PI) > Base::fmod(theta2 - theta0, 2.f*M_PI)) { - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - std::swap(theta1,theta2); - } - if (theta1 == theta0 || theta1 == theta2) - return -1; - else if (theta1 > theta2) - theta2 += 2.f*M_PI; - - // Trim Point between intersection points - - // Create a new arc to substitute Circle in geometry list and set parameters - Part::GeomArcOfCircle *geoNew = new Part::GeomArcOfCircle(); - geoNew->setCenter(center); - geoNew->setRadius(circle->getRadius()); - geoNew->setRange(theta1, theta2,/*emulateCCW=*/true); - - std::vector< Part::Geometry * > newVals(geomlist); - newVals[GeoId] = geoNew; - // This is a special case, we need the geometry and the vertexindex updated here (via onChanged() ) - // this is not a transaction. if the vertexindex is not rebuild the code below will fail. - Geometry.setValues(std::move(newVals)); - - PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; - ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; - - // check first if start and end points are within a confusion tolerance - if(isPointAtPosition(GeoId1, Sketcher::start, point1)) { - constrType1 = Sketcher::Coincident; - secondPos1 = Sketcher::start; - } - else if(isPointAtPosition(GeoId1, Sketcher::end, point1)) { - constrType1 = Sketcher::Coincident; - secondPos1 = Sketcher::end; - } - - if(isPointAtPosition(GeoId2, Sketcher::start, point2)) { - constrType2 = Sketcher::Coincident; - secondPos2 = Sketcher::start; - } - else if(isPointAtPosition(GeoId2, Sketcher::end, point2)) { - constrType2 = Sketcher::Coincident; - secondPos2 = Sketcher::end; - } - - getTransformParamsAndDeleteConstraints(GeoId, GeoId1, point1, constrType1, secondPos1); - getTransformParamsAndDeleteConstraints(GeoId, GeoId2, point2, constrType2, secondPos2); - - if( (constrType1 == Sketcher::Coincident && secondPos1 == Sketcher::none) || - (constrType2 == Sketcher::Coincident && secondPos2 == Sketcher::none)) - THROWM(ValueError,"Invalid position Sketcher::none when creating a Coincident constraint") - - // constrain the trimming points on the corresponding geometries - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId1; - newConstr->SecondPos = secondPos1; - - - addConstraint(newConstr); - - // Add Second Constraint - newConstr->Type = constrType2; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId2; - newConstr->SecondPos = secondPos2; - - - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; - } - } else if (geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) { - const std::vector &constraints = this->Constraints.getValues(); - const Part::GeomEllipse *ellipse = static_cast(geo); - Base::Vector3d center = ellipse->getCenter(); - double theta0; - ellipse->closestParameter(point,theta0); - theta0 = Base::fmod(theta0, 2.f*M_PI); - if (GeoId1 >= 0 && GeoId2 >= 0) { - double theta1; - ellipse->closestParameter(point1,theta1); - theta1 = Base::fmod(theta1, 2.f*M_PI); - double theta2; - ellipse->closestParameter(point2,theta2); - theta2 = Base::fmod(theta2, 2.f*M_PI); - if (Base::fmod(theta1 - theta0, 2.f*M_PI) > Base::fmod(theta2 - theta0, 2.f*M_PI)) { - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - std::swap(theta1,theta2); - } - if (theta1 == theta0 || theta1 == theta2) - return -1; - else if (theta1 > theta2) - theta2 += 2.f*M_PI; - - // Trim Point between intersection points - - // Create a new arc to substitute Circle in geometry list and set parameters - Part::GeomArcOfEllipse *geoNew = new Part::GeomArcOfEllipse(); - geoNew->setCenter(center); - geoNew->setMajorRadius(ellipse->getMajorRadius()); - geoNew->setMinorRadius(ellipse->getMinorRadius()); - geoNew->setMajorAxisDir(ellipse->getMajorAxisDir()); - geoNew->setRange(theta1, theta2, /*emulateCCW=*/true); - - std::vector< Part::Geometry * > newVals(geomlist); - newVals[GeoId] = geoNew; - // This is a special case, we need the geometry and the vertexindex updated here (via onChanged() ) - // this is not a transaction. if the vertexindex is not rebuild the code below will fail. - Geometry.setValues(std::move(newVals)); - - auto handleinternalalignment = [this] (Constraint * constr, int GeoId, PointPos & secondPos) { - if( constr->Type == Sketcher::InternalAlignment && - ( constr->AlignmentType == Sketcher::EllipseMajorDiameter || - constr->AlignmentType == Sketcher::EllipseMinorDiameter ) ) { - - Base::Vector3d sp = getPoint(constr->First,start); - Base::Vector3d ep = getPoint(constr->First,end); - - Base::Vector3d ee = getPoint(GeoId,start); - - if( (ee-sp).Length() < (ee-ep).Length() ) { - secondPos = Sketcher::start; - } - else { - secondPos = Sketcher::end; - } - } - }; - - PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; - ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; - for (std::vector::const_iterator it=constraints.begin(); - it != constraints.end(); ++it) { - Constraint *constr = *(it); - if (secondPos1 == Sketcher::none && (constr->First == GeoId1 && constr->Second == GeoId)) { - constrType1= Sketcher::Coincident; - if(constr->FirstPos == Sketcher::none){ - handleinternalalignment(constr, GeoId, secondPos1); - } - else { - handlemultipleintersection(constr, GeoId, start, secondPos1); - } - - } else if(secondPos2 == Sketcher::none && (constr->First == GeoId2 && constr->Second == GeoId)) { - constrType2 = Sketcher::Coincident; - - if(constr->FirstPos == Sketcher::none){ - handleinternalalignment(constr, GeoId, secondPos2); - } - else { - handlemultipleintersection(constr, GeoId, end, secondPos2); - } - } - } - - if( (constrType1 == Sketcher::Coincident && secondPos1 == Sketcher::none) || - (constrType2 == Sketcher::Coincident && secondPos2 == Sketcher::none)) - THROWM(ValueError,"Invalid position Sketcher::none when creating a Coincident constraint") - - // constrain the trimming points on the corresponding geometries - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId1; - - if (constrType1 == Sketcher::Coincident) { - newConstr->SecondPos = secondPos1; - delConstraintOnPoint(GeoId1, secondPos1, false); - } - - addConstraint(newConstr); - - // Reset secondpos in case it was set previously - newConstr->SecondPos = Sketcher::none; - - // Add Second Constraint - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId2; - - if (constrType2 == Sketcher::Coincident) { - newConstr->SecondPos = secondPos2; - delConstraintOnPoint(GeoId2, secondPos2, false); - } - - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; - } - } else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - const Part::GeomArcOfCircle *aoc = static_cast(geo); - Base::Vector3d center = aoc->getCenter(); - double startAngle, endAngle; - aoc->getRange(startAngle, endAngle, /*emulateCCW=*/true); - double dir = (startAngle < endAngle) ? 1 : -1; // this is always == 1 - double arcLength = (endAngle - startAngle)*dir; - double theta0 = Base::fmod(atan2(point.y - center.y, point.x - center.x) - startAngle, 2.f*M_PI); // x0, the angle of the "click" in the trim operation. - if (GeoId1 >= 0 && GeoId2 >= 0) { - double theta1 = Base::fmod(atan2(point1.y - center.y, point1.x - center.x) - startAngle, 2.f*M_PI) * dir; // x1 - double theta2 = Base::fmod(atan2(point2.y - center.y, point2.x - center.x) - startAngle, 2.f*M_PI) * dir; // x2 - if (theta1 > theta2) { - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - std::swap(theta1,theta2); - } - if (theta1 >= 0.001*arcLength && theta2 <= 0.999*arcLength) { - // Trim Point between intersection points - if (theta1 < theta0 && theta2 > theta0) { - int newGeoId = addGeometry(geo); - // go through all constraints and replace the point (GeoId,end) with (newGeoId,end) - transferConstraints(GeoId, end, newGeoId, end); - Part::GeomArcOfCircle *aoc1 = static_cast(geomlist[GeoId]); - Part::GeomArcOfCircle *aoc2 = static_cast(geomlist[newGeoId]); - aoc1->setRange(startAngle, startAngle + theta1, /*emulateCCW=*/true); - aoc2->setRange(startAngle + theta2, endAngle, /*emulateCCW=*/true); - - // constrain the trimming points on the corresponding geometries - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - - // Build Constraints associated with new pair of arcs - newConstr->Type = Sketcher::Equal; - newConstr->First = GeoId; - newConstr->FirstPos = Sketcher::none; - newConstr->Second = newGeoId; - newConstr->SecondPos = Sketcher::none; - addConstraint(newConstr); - - - PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; - ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; - - getTransformParamsAndDeleteConstraints(GeoId, GeoId1, point1, constrType1, secondPos1); - - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = Sketcher::end; - newConstr->Second = GeoId1; - newConstr->SecondPos = Sketcher::none; - newConstr->SecondPos = secondPos1; - - addConstraint(newConstr); - - getTransformParamsAndDeleteConstraints(GeoId, GeoId2, point2, constrType2, secondPos2); - - newConstr->Type = constrType2; - newConstr->First = newGeoId; - newConstr->FirstPos = Sketcher::start; - newConstr->Second = GeoId2; - newConstr->SecondPos = secondPos2; - - addConstraint(newConstr); - - newConstr->Type = Sketcher::Coincident; - newConstr->First = GeoId; - newConstr->FirstPos = Sketcher::mid; - newConstr->Second = newGeoId; - newConstr->SecondPos = Sketcher::mid; - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; - } else { - return -1; - } - } else if (theta1 < 0.001*arcLength) { // drop the second intersection point - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - } else if (theta2 > 0.999*arcLength) { - // Do nothing here + else if (GeoId2!=Constraint::GeoUndef && pointParam < point2Param) { + if(trimmableEnd) { + op.Type = Operation::trim_start; + op.actingParam = point2Param; // trim from firstParam until point2Param + op.actingPoint = point2; + op.intersectingGeoId = GeoId2; } else { - return -1; + op.Type = Operation::trim_delete; } } - if (GeoId1 >= 0) { - creategeometryundopoint(); // for when geometry will change, but no new geometry will be committed. - ConstraintType constrType1 = Sketcher::PointOnObject; // So this is the fallback constraint type here. - PointPos secondPos1 = Sketcher::none; - - getTransformParamsAndDeleteConstraints(GeoId, GeoId1, point1, constrType1, secondPos1); - - double theta1 = Base::fmod(atan2(point1.y - center.y, point1.x - center.x) - startAngle, 2.f*M_PI) * dir; // x1 - - if (theta1 >= 0.001*arcLength && theta1 <= 0.999*arcLength) { - if (theta1 > theta0) { // trim arc start - delConstraintOnPoint(GeoId, start, false); - Part::GeomArcOfCircle *aoc1 = static_cast(geomlist[GeoId]); - aoc1->setRange(startAngle + theta1, endAngle, /*emulateCCW=*/true); - // constrain the trimming point on the corresponding geometry - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId1; - newConstr->SecondPos = secondPos1; - - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; - } - else { // trim arc end - delConstraintOnPoint(GeoId, end, false); - - Part::GeomArcOfCircle *aoc1 = static_cast(geomlist[GeoId]); - aoc1->setRange(startAngle, startAngle + theta1, /*emulateCCW=*/true); - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId1; - newConstr->SecondPos = secondPos1; - - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; - } + else if (GeoId1!=Constraint::GeoUndef && pointParam > point1Param) { + if(trimmableStart) { + op.Type = Operation::trim_end; + op.actingParam = point1Param; // trim from point1Param until lastParam + op.actingPoint = point1; + op.intersectingGeoId = GeoId1; + } + else { + op.Type = Operation::trim_delete; } } - } else if (geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) { - const std::vector &constraints = this->Constraints.getValues(); - const Part::GeomArcOfEllipse *aoe = static_cast(geo); - Base::Vector3d center = aoe->getCenter(); - double startAngle, endAngle; - aoe->getRange(startAngle, endAngle,/*emulateCCW=*/true); - double dir = (startAngle < endAngle) ? 1 : -1; // this is always == 1 - double arcLength = (endAngle - startAngle)*dir; - double theta0 = Base::fmod( - atan2(-aoe->getMajorRadius()*((point.x-center.x)*aoe->getMajorAxisDir().y-(point.y-center.y)*aoe->getMajorAxisDir().x), - aoe->getMinorRadius()*((point.x-center.x)*aoe->getMajorAxisDir().x+(point.y-center.y)*aoe->getMajorAxisDir().y) - )- startAngle, 2.f*M_PI); // x0 - if (GeoId1 >= 0 && GeoId2 >= 0) { - double theta1 = Base::fmod( - atan2(-aoe->getMajorRadius()*((point1.x-center.x)*aoe->getMajorAxisDir().y-(point1.y-center.y)*aoe->getMajorAxisDir().x), - aoe->getMinorRadius()*((point1.x-center.x)*aoe->getMajorAxisDir().x+(point1.y-center.y)*aoe->getMajorAxisDir().y) - )- startAngle, 2.f*M_PI) * dir; // x1 - double theta2 = Base::fmod( - atan2(-aoe->getMajorRadius()*((point2.x-center.x)*aoe->getMajorAxisDir().y-(point2.y-center.y)*aoe->getMajorAxisDir().x), - aoe->getMinorRadius()*((point2.x-center.x)*aoe->getMajorAxisDir().x+(point2.y-center.y)*aoe->getMajorAxisDir().y) - )- startAngle, 2.f*M_PI) * dir; // x2 - - if (theta1 > theta2) { - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - std::swap(theta1,theta2); - } - if (theta1 >= 0.001*arcLength && theta2 <= 0.999*arcLength) { - // Trim Point between intersection points - if (theta1 < theta0 && theta2 > theta0) { - int newGeoId = addGeometry(geo); - // go through all constraints and replace the point (GeoId,end) with (newGeoId,end) - transferConstraints(GeoId, end, newGeoId, end); - - Part::GeomArcOfEllipse *aoe1 = static_cast(geomlist[GeoId]); - Part::GeomArcOfEllipse *aoe2 = static_cast(geomlist[newGeoId]); - aoe1->setRange(startAngle, startAngle + theta1, /*emulateCCW=*/true); - aoe2->setRange(startAngle + theta2, endAngle, /*emulateCCW=*/true); - - // constrain the trimming points on the corresponding geometries - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - - // Build Constraints associated with new pair of arcs - newConstr->Type = Sketcher::Equal; - newConstr->First = GeoId; - newConstr->Second = newGeoId; - addConstraint(newConstr); - - PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; - ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; - - for (std::vector::const_iterator it=constraints.begin(); - it != constraints.end(); ++it) { - Constraint *constr = *(it); - if (secondPos1 == Sketcher::none && - (constr->First == GeoId1 && constr->Second == GeoId)) { - constrType1= Sketcher::Coincident; - secondPos1 = constr->FirstPos; - } else if (secondPos2 == Sketcher::none && - (constr->First == GeoId2 && constr->Second == GeoId)) { - constrType2 = Sketcher::Coincident; - secondPos2 = constr->FirstPos; - } - } - - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId1; - - if (constrType1 == Sketcher::Coincident) { - newConstr->SecondPos = secondPos1; - delConstraintOnPoint(GeoId1, secondPos1, false); - } - - addConstraint(newConstr); - - // Reset secondpos in case it was set previously - newConstr->SecondPos = Sketcher::none; - - newConstr->Type = constrType2; - newConstr->First = newGeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId2; - - if (constrType2 == Sketcher::Coincident) { - newConstr->SecondPos = secondPos2; - delConstraintOnPoint(GeoId2, secondPos2, false); - } - - addConstraint(newConstr); - - newConstr->Type = Sketcher::Coincident; - newConstr->First = GeoId; - newConstr->FirstPos = Sketcher::mid; - newConstr->Second = newGeoId; - newConstr->SecondPos = Sketcher::mid; - addConstraint(newConstr); - - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - - return 0; - } else { - return -1; - } - } else if (theta1 < 0.001*arcLength) { // drop the second intersection point - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - } else if (theta2 > 0.999*arcLength) { - // Do nothing here - } else { - return -1; - } + else { + return -1; } - if (GeoId1 >= 0) { - double theta1 = Base::fmod( - atan2(-aoe->getMajorRadius()*((point1.x-center.x)*aoe->getMajorAxisDir().y-(point1.y-center.y)*aoe->getMajorAxisDir().x), - aoe->getMinorRadius()*((point1.x-center.x)*aoe->getMajorAxisDir().x+(point1.y-center.y)*aoe->getMajorAxisDir().y) - )- startAngle, 2.f*M_PI) * dir; // x1 + //****** Step B.1 (3) => Execute Trimming operation ******// - creategeometryundopoint(); // for when geometry will change, but no new geometry will be committed. - ConstraintType constrType = Sketcher::PointOnObject; + if( op.Type == Operation::trim_delete) { + delGeometry(GeoId); + + return 0; + } + else if ( op.Type == Operation::trim_middle ) { + // We need to create new curve, this new curve will represent the segment comprising the end + auto vals = getInternalGeometry(); + auto newVals(vals); + newVals[GeoId] = newVals[GeoId]->clone(); + newVals.push_back(newVals[GeoId]->clone()); + int newGeoId = newVals.size() - 1; + + if (isDerivedFromTrimmedCurve) { + static_cast(newVals[GeoId])->setRange(firstParam, point1Param); + static_cast(newVals.back())->setRange(point2Param, lastParam); + } + else if(isNonPeriodicBSpline) { + static_cast(newVals[GeoId])->Trim( firstParam, point1Param); + static_cast(newVals.back())->Trim( point2Param, lastParam); + } + + Geometry.setValues(std::move(newVals)); + + // go through all constraints and replace the point (GeoId,end) with (newGeoId,end) + transferConstraints(GeoId, end, newGeoId, end); + + // For a trimmed line segment, if it had an equality constraint, it must be removed as the segment length is not equal + // For the rest of trimmed curves, the proportion shall be constrain to be equal. + if(isLineSegment) { + delEqualConstraintsOnGeoId(GeoId); + delEqualConstraintsOnGeoId(newGeoId); + } + + if(!isLineSegment && !isNonPeriodicBSpline) { + constrainAsEqual(GeoId, newGeoId); + } + + //****** Step B.1 (4) => Constraint end points of trim sections ******// + + // constrain the trimming points on the corresponding geometries + PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; + ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; + + // Segment comprising the start + transformPreexistingConstraints (GeoId, GeoId1, point1, constrType1, secondPos1); + + addConstraintIfNoPointOnBSpline (constrType1, GeoId, Sketcher::end, GeoId1, secondPos1); + + // Segment comprising the end + transformPreexistingConstraints (GeoId, GeoId2, point2, constrType2, secondPos2); + + addConstraintIfNoPointOnBSpline (constrType2, newGeoId, Sketcher::start, GeoId2, secondPos2); + + // Both segments have a coincident center + if(!isLineSegment && !isBSpline) { + addConstraint(Sketcher::Coincident, GeoId, Sketcher::mid, newGeoId, Sketcher::mid); + } + + if (isNonPeriodicBSpline) + exposeInternalGeometry(GeoId); + + if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver + solve(); + + return 0; + } + else if ( op.Type == Operation::trim_start || op.Type == Operation::trim_end ) { // drop the second/first intersection point + geo = getGeometry(GeoId); + if (isDerivedFromTrimmedCurve) { + auto newGeo = std::unique_ptr(static_cast( geo->clone())); + + if (op.Type == Operation::trim_start) + newGeo->setRange( op.actingParam, lastParam); + else if (op.Type == Operation::trim_end) + newGeo->setRange( firstParam, op.actingParam); + + Geometry.set1Value(GeoId, std::move(newGeo)); + } + else if(isNonPeriodicBSpline) { + auto newGeo = std::unique_ptr(static_cast( geo->clone())); + + if (op.Type == Operation::trim_start) + newGeo->Trim( op.actingParam, lastParam); + else if (op.Type == Operation::trim_end) + newGeo->Trim( firstParam, op.actingParam); + + Geometry.set1Value(GeoId, std::move(newGeo)); + } + + // After trimming it, a line segment won't have the same length + if( isLineSegment) { + delEqualConstraintsOnGeoId(GeoId); + } + + //****** Step B.1 (4) => Constraint end points ******// + ConstraintType constrType = Sketcher::PointOnObject; // So this is the fallback constraint type here. PointPos secondPos = Sketcher::none; - for (std::vector::const_iterator it=constraints.begin(); - it != constraints.end(); ++it) { - Constraint *constr = *(it); - if ((constr->First == GeoId1 && constr->Second == GeoId)) { - constrType = Sketcher::Coincident; - secondPos = constr->FirstPos; - delConstraintOnPoint(GeoId1, constr->FirstPos, false); - break; - } + + transformPreexistingConstraints (GeoId, op.intersectingGeoId, op.actingPoint, constrType, secondPos); + + if (op.Type == Operation::trim_start) { + delConstraintOnPoint(GeoId, start, false); + // constrain the trimming point on the corresponding geometry + addConstraintIfNoPointOnBSpline (constrType, GeoId, start, op.intersectingGeoId, secondPos); + } + else if (op.Type == Operation::trim_end) { + delConstraintOnPoint(GeoId, end, false); + // constrain the trimming point on the corresponding geometry + addConstraintIfNoPointOnBSpline (constrType, GeoId, end, op.intersectingGeoId, secondPos); } - if (theta1 >= 0.001*arcLength && theta1 <= 0.999*arcLength) { - if (theta1 > theta0) { // trim arc start - delConstraintOnPoint(GeoId, start, false); - Part::GeomArcOfEllipse *aoe1 = static_cast(geomlist[GeoId]); - aoe1->setRange(startAngle + theta1, endAngle, /*emulateCCW=*/true); - // constrain the trimming point on the corresponding geometry - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType; - newConstr->First = GeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId1; + if (isNonPeriodicBSpline) + exposeInternalGeometry(GeoId); - if (constrType == Sketcher::Coincident) - newConstr->SecondPos = secondPos; + if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver + solve(); - addConstraint(newConstr); - delete newConstr; + return 0; + } + else { + return -1; + } + } + //******************* Step B.2 => Trimming for unbounded periodic geometries ****************************************// + else if ( isCircle || isEllipse || isPeriodicBSpline) { + //****** STEP A(2) => Common tests *****// + if( GeoId1==Constraint::GeoUndef || GeoId2==Constraint::GeoUndef) + return -1; - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); + //****** Step B.2 (1) => Determine intersection parameters ******// + double pointParam, point1Param, point2Param; + if(!getIntersectionParameters(geo, point, pointParam, point1, point1Param, point2, point2Param)) + return -1; - return 0; - } - else { // trim arc end - delConstraintOnPoint(GeoId, end, false); - Part::GeomArcOfEllipse *aoe1 = static_cast(geomlist[GeoId]); - aoe1->setRange(startAngle, startAngle + theta1, /*emulateCCW=*/true); +#ifdef DEBUG + Base::Console().Log("Trim sought: GeoId1=%d (%f), GeoId2=%d (%f)\n",GeoId1, point1Param, GeoId2, point2Param); +#endif - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId1; + //****** Step B.2 (3) => Execute Trimming operation ******// + // Two intersection points detected + std::unique_ptr geoNew; - if (constrType == Sketcher::Coincident) - newConstr->SecondPos = secondPos; - - addConstraint(newConstr); - delete newConstr; - - if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver - solve(); - return 0; - } - } + if (isCircle) { + auto circle = static_cast(geo); + auto aoc = std::make_unique(); + aoc->setCenter(circle->getCenter()); + aoc->setRadius(circle->getRadius()); + aoc->setRange(point2Param, point1Param,/*emulateCCW=*/false); + geoNew = std::move(aoc); + } + else if (isEllipse) { + auto ellipse = static_cast(geo); + auto aoe = std::make_unique(); + aoe->setCenter(ellipse->getCenter()); + aoe->setMajorRadius(ellipse->getMajorRadius()); + aoe->setMinorRadius(ellipse->getMinorRadius()); + aoe->setMajorAxisDir(ellipse->getMajorAxisDir()); + aoe->setRange(point2Param, point1Param,/*emulateCCW=*/false); // CCW curve goes from point2 (start) to point1 (end) + geoNew = std::move( aoe ); + } + else if (isPeriodicBSpline) { + auto bspline = std::unique_ptr(static_cast(geo->clone())); + bspline->Trim(point2Param, point1Param); + geoNew = std::move( bspline ); } - } else if (geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) { - const std::vector &constraints = this->Constraints.getValues(); - const Part::GeomArcOfHyperbola *aoh = static_cast(geo); - Base::Vector3d center = aoh->getCenter(); - double startAngle, endAngle; - aoh->getRange(startAngle, endAngle, /*emulateCCW=*/true); - double dir = (startAngle < endAngle) ? 1 : -1; // this is always == 1 - double arcLength = (endAngle - startAngle)*dir; - double theta0 = Base::fmod( - atan2(-aoh->getMajorRadius()*((point.x-center.x)*sin(aoh->getAngleXU())-(point.y-center.y)*cos(aoh->getAngleXU())), - aoh->getMinorRadius()*((point.x-center.x)*cos(aoh->getAngleXU())+(point.y-center.y)*sin(aoh->getAngleXU())) - )- startAngle, 2.f*M_PI); // x0 - if (GeoId1 >= 0 && GeoId2 >= 0) { - double theta1 = Base::fmod( - atan2(-aoh->getMajorRadius()*((point1.x-center.x)*sin(aoh->getAngleXU())-(point1.y-center.y)*cos(aoh->getAngleXU())), - aoh->getMinorRadius()*((point1.x-center.x)*cos(aoh->getAngleXU())+(point1.y-center.y)*sin(aoh->getAngleXU())) - )- startAngle, 2.f*M_PI) * dir; // x1 - double theta2 = Base::fmod( - atan2(-aoh->getMajorRadius()*((point2.x-center.x)*sin(aoh->getAngleXU())-(point2.y-center.y)*cos(aoh->getAngleXU())), - aoh->getMinorRadius()*((point2.x-center.x)*cos(aoh->getAngleXU())+(point2.y-center.y)*sin(aoh->getAngleXU())) - )- startAngle, 2.f*M_PI) * dir; // x2 + this->Geometry.set1Value(GeoId, std::move(geoNew)); - if (theta1 > theta2) { - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - std::swap(theta1,theta2); - } - if (theta1 >= 0.001*arcLength && theta2 <= 0.999*arcLength) { - // Trim Point between intersection points - if (theta1 < theta0 && theta2 > theta0) { - int newGeoId = addGeometry(geo); - // go through all constraints and replace the point (GeoId,end) with (newGeoId,end) - transferConstraints(GeoId, end, newGeoId, end); + //****** Step B.2 (4) => Constraint end points ******// - Part::GeomArcOfHyperbola *aoh1 = static_cast(geomlist[GeoId]); - Part::GeomArcOfHyperbola *aoh2 = static_cast(geomlist[newGeoId]); - aoh1->setRange(startAngle, startAngle + theta1, /*emulateCCW=*/true); - aoh2->setRange(startAngle + theta2, endAngle, /*emulateCCW=*/true); + PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; + ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; - // constrain the trimming points on the corresponding geometries - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - - // Build Constraints associated with new pair of arcs - newConstr->Type = Sketcher::Equal; - newConstr->First = GeoId; - newConstr->Second = newGeoId; - addConstraint(newConstr); - - PointPos secondPos1 = Sketcher::none, secondPos2 = Sketcher::none; - ConstraintType constrType1 = Sketcher::PointOnObject, constrType2 = Sketcher::PointOnObject; - - for (std::vector::const_iterator it=constraints.begin(); - it != constraints.end(); ++it) { - Constraint *constr = *(it); - if (secondPos1 == Sketcher::none && - (constr->First == GeoId1 && constr->Second == GeoId)) { - constrType1= Sketcher::Coincident; - secondPos1 = constr->FirstPos; - } else if (secondPos2 == Sketcher::none && - (constr->First == GeoId2 && constr->Second == GeoId)) { - constrType2 = Sketcher::Coincident; - secondPos2 = constr->FirstPos; - } - } - - newConstr->Type = constrType1; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId1; - - if (constrType1 == Sketcher::Coincident) { - newConstr->SecondPos = secondPos1; - delConstraintOnPoint(GeoId1, secondPos1, false); - } - - addConstraint(newConstr); - - // Reset secondpos in case it was set previously - newConstr->SecondPos = Sketcher::none; - - newConstr->Type = constrType2; - newConstr->First = newGeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId2; - - if (constrType2 == Sketcher::Coincident) { - newConstr->SecondPos = secondPos2; - delConstraintOnPoint(GeoId2, secondPos2, false); - } - - addConstraint(newConstr); - - newConstr->Type = Sketcher::Coincident; - newConstr->First = GeoId; - newConstr->FirstPos = Sketcher::mid; - newConstr->Second = newGeoId; - newConstr->SecondPos = Sketcher::mid; - addConstraint(newConstr); - - delete newConstr; - - return 0; - } else { - return -1; - } - } else if (theta1 < 0.001*arcLength) { // drop the second intersection point - std::swap(GeoId1,GeoId2); - std::swap(point1,point2); - } else if (theta2 > 0.999*arcLength) { - // Do nothing here - } else { - return -1; - } + // check first if start and end points are within a confusion tolerance + if(isPointAtPosition(GeoId1, Sketcher::start, point1)) { + constrType1 = Sketcher::Coincident; + secondPos1 = Sketcher::start; + } + else if(isPointAtPosition(GeoId1, Sketcher::end, point1)) { + constrType1 = Sketcher::Coincident; + secondPos1 = Sketcher::end; } - if (GeoId1 >= 0) { - - double theta1 = Base::fmod( - atan2(-aoh->getMajorRadius()*((point1.x-center.x)*sin(aoh->getAngleXU())-(point1.y-center.y)*cos(aoh->getAngleXU())), - aoh->getMinorRadius()*((point1.x-center.x)*cos(aoh->getAngleXU())+(point1.y-center.y)*sin(aoh->getAngleXU())) - )- startAngle, 2.f*M_PI) * dir; // x1 - - creategeometryundopoint(); // for when geometry will change, but no new geometry will be committed. - ConstraintType constrType = Sketcher::PointOnObject; - PointPos secondPos = Sketcher::none; - for (std::vector::const_iterator it=constraints.begin(); - it != constraints.end(); ++it) { - Constraint *constr = *(it); - if ((constr->First == GeoId1 && constr->Second == GeoId)) { - constrType = Sketcher::Coincident; - secondPos = constr->FirstPos; - delConstraintOnPoint(GeoId1, constr->FirstPos, false); - break; - } - } - - if (theta1 >= 0.001*arcLength && theta1 <= 0.999*arcLength) { - if (theta1 > theta0) { // trim arc start - delConstraintOnPoint(GeoId, start, false); - Part::GeomArcOfHyperbola *aoe1 = static_cast(geomlist[GeoId]); - aoe1->setRange(startAngle + theta1, endAngle, /*emulateCCW=*/true); - // constrain the trimming point on the corresponding geometry - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType; - newConstr->First = GeoId; - newConstr->FirstPos = start; - newConstr->Second = GeoId1; - - if (constrType == Sketcher::Coincident) - newConstr->SecondPos = secondPos; - - addConstraint(newConstr); - delete newConstr; - return 0; - } - else { // trim arc end - delConstraintOnPoint(GeoId, end, false); - Part::GeomArcOfHyperbola *aoe1 = static_cast(geomlist[GeoId]); - aoe1->setRange(startAngle, startAngle + theta1, /*emulateCCW=*/true); - Sketcher::Constraint *newConstr = new Sketcher::Constraint(); - newConstr->Type = constrType; - newConstr->First = GeoId; - newConstr->FirstPos = end; - newConstr->Second = GeoId1; - - if (constrType == Sketcher::Coincident) - newConstr->SecondPos = secondPos; - - addConstraint(newConstr); - delete newConstr; - return 0; - } - } + if(isPointAtPosition(GeoId2, Sketcher::start, point2)) { + constrType2 = Sketcher::Coincident; + secondPos2 = Sketcher::start; } + else if(isPointAtPosition(GeoId2, Sketcher::end, point2)) { + constrType2 = Sketcher::Coincident; + secondPos2 = Sketcher::end; + } + + transformPreexistingConstraints (GeoId, GeoId1, point1, constrType1, secondPos1); + transformPreexistingConstraints (GeoId, GeoId2, point2, constrType2, secondPos2); + + if( (constrType1 == Sketcher::Coincident && secondPos1 == Sketcher::none) || + (constrType2 == Sketcher::Coincident && secondPos2 == Sketcher::none)) + THROWM(ValueError,"Invalid position Sketcher::none when creating a Coincident constraint") + + // constrain the trimming points on the corresponding geometries + addConstraintIfNoPointOnBSpline (constrType1, GeoId, end, GeoId1, secondPos1); + + addConstraintIfNoPointOnBSpline (constrType2, GeoId, start, GeoId2, secondPos2); + + if (isBSpline) + exposeInternalGeometry(GeoId); + + if(noRecomputes) // if we do not have a recompute, the sketch must be solved to update the DoF of the solver + solve(); + + return 0; } return -1; @@ -6116,17 +5803,30 @@ int SketchObject::delConstraintsToExternal() return 0; } -const Part::Geometry* SketchObject::getGeometry(int GeoId) const +int SketchObject::getCompleteGeometryIndex(int GeoId) const { if (GeoId >= 0) { - const std::vector &geomlist = getInternalGeometry(); - if (GeoId < int(geomlist.size())) - return geomlist[GeoId]; + if (GeoId < int(Geometry.getSize())) + return GeoId; } else if (-GeoId <= int(ExternalGeo.size())) - return ExternalGeo[-GeoId-1]; + return -GeoId-1; - return nullptr; + return Constraint::GeoUndef; +} + +int SketchObject::getGeoIdFromCompleteGeometryIndex(int completeGeometryIndex) const +{ + int completeGeometryCount = int(Geometry.getSize()+ExternalGeo.size()); + + if(completeGeometryIndex < 0 || + completeGeometryIndex >= completeGeometryCount) + return Constraint::GeoUndef; + + if(completeGeometryIndex < Geometry.getSize()) + return completeGeometryIndex; + else + return (completeGeometryIndex - completeGeometryCount); } std::unique_ptr SketchObject::getGeometryFacade(int GeoId) const diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index 90759eaf48..1c7cdda6d3 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -140,8 +140,17 @@ public: int addCopyOfConstraints(const SketchObject &orig); /// add constraint int addConstraint(const Constraint *constraint); + /// add constraint + int addConstraint(std::unique_ptr constraint); /// delete constraint int delConstraint(int ConstrId); + /** deletes a group of constraints at once, if norecomputes is active, the default behaviour is that + * it will solve the sketch. + * + * If updating the Geometry property as a consequence of a (sucessful) solve() is not wanted, updategeometry=false, + * prevents the update. This allows to update the solve status (e.g. dof), without updating the geometry (i.e. make it + * move to fulfil the constraints). + */ int delConstraints(std::vector ConstrIds, bool updategeometry=true); int delConstraintOnPoint(int GeoId, PointPos PosId, bool onlyCoincident=true); int delConstraintOnPoint(int VertexId, bool onlyCoincident=true); @@ -168,7 +177,12 @@ public: * id==-2 for the vertical sketch axis * id<=-3 for user defined projected external geometries, */ - const Part::Geometry* getGeometry(int GeoId) const; + template < typename GeometryT = Part::Geometry, + typename = typename std::enable_if< + std::is_base_of::type>::value + >::type + > + const GeometryT * getGeometry(int GeoId) const; std::unique_ptr getGeometryFacade(int GeoId) const; @@ -184,6 +198,11 @@ public: /// retrieves a vector containing both normal and external Geometry (including the sketch axes) std::vector getCompleteGeometry(void) const; + /// converts a GeoId index into an index of the CompleteGeometry vector + int getCompleteGeometryIndex(int GeoId) const; + + int getGeoIdFromCompleteGeometryIndex(int completeGeometryIndex) const; + /// returns non zero if the sketch contains conflicting constraints int hasConflicts(void) const; /** @@ -458,6 +477,14 @@ public: bool isCarbonCopyAllowed(App::Document *pDoc, App::DocumentObject *pObj, bool & xinv, bool & yinv, eReasonList* rsn = 0) const; bool isPerformingInternalTransaction() const {return internaltransaction;}; + + /** retrieves intersection points of this curve with the closest two curves around a point of this curve. + * - it includes internal and external intersecting geometry. + * - it returns Constraint::GeoUndef if no intersection is found. + */ + bool seekTrimPoints(int GeoId, const Base::Vector3d &point, + int &GeoId1, Base::Vector3d &intersect1, + int &GeoId2, Base::Vector3d &intersect2); public: // Analyser functions int autoConstraint(double precision = Precision::Confusion() * 1000, double angleprecision = M_PI/20, bool includeconstruction = true); @@ -556,6 +583,24 @@ protected: // and corrects the state if not matching. void synchroniseGeometryState(); + // helper function to create a new constraint and move it to the Constraint Property + void addConstraint( Sketcher::ConstraintType constrType, + int firstGeoId, + Sketcher::PointPos firstPos, + int secondGeoId = Constraint::GeoUndef, + Sketcher::PointPos secondPos = Sketcher::none, + int thirdGeoId = Constraint::GeoUndef, + Sketcher::PointPos thirdPos = Sketcher::none); + + // creates a new constraint + std::unique_ptr createConstraint( Sketcher::ConstraintType constrType, + int firstGeoId, + Sketcher::PointPos firstPos, + int secondGeoId = Constraint::GeoUndef, + Sketcher::PointPos secondPos = Sketcher::none, + int thirdGeoId = Constraint::GeoUndef, + Sketcher::PointPos thirdPos = Sketcher::none); + private: /// Flag to allow external geometry from other bodies than the one this sketch belongs to bool allowOtherBody; @@ -631,6 +676,21 @@ inline int SketchObject::moveTemporaryPoint(int geoId, PointPos pos, Base::Vecto return solvedSketch.movePoint(geoId, pos, toPoint, relative); } +template < typename GeometryT, + typename > +const GeometryT * SketchObject::getGeometry(int GeoId) const +{ + if (GeoId >= 0) { + const std::vector &geomlist = getInternalGeometry(); + if (GeoId < int(geomlist.size())) + return static_cast(geomlist[GeoId]); + } + else if (-GeoId <= int(ExternalGeo.size())) + return static_cast(ExternalGeo[-GeoId-1]); + + return nullptr; +} + typedef App::FeaturePythonT SketchObjectPython; } //namespace Sketcher From 28e40ffbee3a3e91071b0db14183100edd657829 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 17 Jan 2021 05:27:36 +0100 Subject: [PATCH 15/21] Sketcher:: Trim creator UI ========================== * Enable to select any GeomTrimmedCurve (any arc supported by the Sketcher), as well as B-Splines. * Use Edit Markers to provide visualisation of the cutting position and cut segment end point before trimming. * Reject trimming Internal aligned geometry (internals of complex geometries such as ellipses or hyperbolas) --- src/Mod/Sketcher/Gui/CommandCreateGeo.cpp | 65 ++++++++++++++++++----- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index a491efc692..25a6be8ae6 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include "ViewProviderSketch.h" #include "DrawSketchHandler.h" @@ -5450,13 +5451,15 @@ namespace SketcherGui { int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1; Sketcher::SketchObject *Sketch = static_cast(object); const Part::Geometry *geom = Sketch->getGeometry(GeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geom->getTypeId() == Part::GeomCircle::getClassTypeId()|| - geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()|| - geom->getTypeId() == Part::GeomEllipse::getClassTypeId()|| - geom->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() - ) - return true; + if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) || + geom->getTypeId() == Part::GeomCircle::getClassTypeId() || + geom->getTypeId() == Part::GeomEllipse::getClassTypeId() || + geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() + ) { + // We do not trim internal geometry of complex geometries + if( Sketcher::GeometryFacade::isInternalType(geom, Sketcher::InternalType::None)) + return true; + } } return false; } @@ -5483,6 +5486,40 @@ public: virtual void mouseMove(Base::Vector2d onSketchPos) { Q_UNUSED(onSketchPos); + + int GeoId = sketchgui->getPreselectCurve(); + + if (GeoId > -1) { + auto sk = static_cast(sketchgui->getObject()); + int GeoId1, GeoId2; + Base::Vector3d intersect1, intersect2; + if(sk->seekTrimPoints(GeoId, Base::Vector3d(onSketchPos.x,onSketchPos.y,0), + GeoId1, intersect1, + GeoId2, intersect2)) { + + EditMarkers.resize(0); + + if(GeoId1 != Sketcher::Constraint::GeoUndef) + EditMarkers.emplace_back(intersect1.x, intersect1.y); + else { + auto start = sk->getPoint(GeoId, Sketcher::start); + EditMarkers.emplace_back(start.x, start.y); + } + + if(GeoId2 != Sketcher::Constraint::GeoUndef) + EditMarkers.emplace_back(intersect2.x, intersect2.y); + else { + auto end = sk->getPoint(GeoId, Sketcher::end); + EditMarkers.emplace_back( end.x, end.y); + } + + sketchgui->drawEditMarkers(EditMarkers, 2); // maker augmented by two sizes (see supported marker sizes) + } + } + else { + EditMarkers.resize(0); + sketchgui->drawEditMarkers(EditMarkers, 2); + } } virtual bool pressButton(Base::Vector2d onSketchPos) @@ -5496,11 +5533,10 @@ public: int GeoId = sketchgui->getPreselectCurve(); if (GeoId > -1) { const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() || - geom->getTypeId() == Part::GeomCircle::getClassTypeId() || - geom->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geom->getTypeId() == Part::GeomEllipse::getClassTypeId()) { + if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) || + geom->getTypeId() == Part::GeomCircle::getClassTypeId() || + geom->getTypeId() == Part::GeomEllipse::getClassTypeId() || + geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ) { try { Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Trim edge")); Gui::cmdAppObjectArgs(sketchgui->getObject(), "trim(%d,App.Vector(%f,%f,0))", @@ -5513,12 +5549,17 @@ public: Gui::Command::abortCommand(); } } + + EditMarkers.resize(0); + sketchgui->drawEditMarkers(EditMarkers); } else // exit the trimming tool if the user clicked on empty space sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider return true; } +private: + std::vector EditMarkers; }; DEF_STD_CMD_A(CmdSketcherTrimming) From 011cef1e7babd89f02844fa2fbf4ab31859405e4 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sun, 21 Mar 2021 23:18:44 +0100 Subject: [PATCH 16/21] FEM: material task panel, fix unuseable Gui if material editor was canceled and material accepted --- src/Mod/Fem/femtaskpanels/task_material_common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Mod/Fem/femtaskpanels/task_material_common.py b/src/Mod/Fem/femtaskpanels/task_material_common.py index 2855d73408..a11c36b7fa 100644 --- a/src/Mod/Fem/femtaskpanels/task_material_common.py +++ b/src/Mod/Fem/femtaskpanels/task_material_common.py @@ -204,6 +204,10 @@ class _TaskPanel: # leave task panel *************************************************************************** def accept(self): # print(self.material) + if self.material == {}: # happens if material editor was canceled + FreeCAD.Console.PrintError("Empty material dictionary, nothing was changed.\n") + self.recompute_and_set_back_all() + return True if self.selectionWidget.has_equal_references_shape_types(): self.do_not_set_thermal_zeros() from materialtools.cardutils import check_mat_units as checkunits From 804830d77225a0afb789787119a99c364ea7206d Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 22 Mar 2021 03:19:47 +0100 Subject: [PATCH 17/21] [FEM] fix specific heat of air - also adjust values slightly according to various sources Since a factor 1000 is a big mistake and air is the most common gas, please commit this also for FC 0.19 --- src/Mod/Material/FluidMaterial/Air.FCMat | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Material/FluidMaterial/Air.FCMat b/src/Mod/Material/FluidMaterial/Air.FCMat index aed9a29a10..a23277eb7b 100644 --- a/src/Mod/Material/FluidMaterial/Air.FCMat +++ b/src/Mod/Material/FluidMaterial/Air.FCMat @@ -1,19 +1,19 @@ [General] Name = Air -Description = Standard air properties at 20 Degrees Celsius and 1 atm +Description = Dry air properties at 20 Degrees Celsius and 1 atm MolarMass = 28.965 Father = Gas [Fluidic] -Density = 1.20 kg/m^3 +Density = 1.204 kg/m^3 DynamicViscosity = 1.80e-5 kg/m/s KinematicViscosity = 1.511e-5 m^2/s ; PrandtlNumber is a nondimension number for CFD simulation PrandtlNumber = 0.7 [Thermal] -SpecificHeat = 1.005 J/kg/K -ThermalConductivity = 0.0257 W/m/K +SpecificHeat = 1.01 kJ/kg/K +ThermalConductivity = 0.02587 W/m/K ; volumetric expansion coeff of ideal gas depends on temperature and pressure VolumetricThermalExpansionCoefficient = 3.43e-3 m/m/K From 5e90c07375cdcd11e356a40357c44a8f65d95868 Mon Sep 17 00:00:00 2001 From: donovaly Date: Mon, 22 Mar 2021 03:13:04 +0100 Subject: [PATCH 18/21] [FEM] add material cards for gases also add electrical conductivity for air and water --- src/Mod/Material/CMakeLists.txt | 2 ++ src/Mod/Material/FluidMaterial/Air.FCMat | 1 + src/Mod/Material/FluidMaterial/Argon.FCMat | 23 +++++++++++++++++++ src/Mod/Material/FluidMaterial/Nitrogen.FCMat | 23 +++++++++++++++++++ src/Mod/Material/FluidMaterial/Water.FCMat | 1 + 5 files changed, 50 insertions(+) create mode 100644 src/Mod/Material/FluidMaterial/Argon.FCMat create mode 100644 src/Mod/Material/FluidMaterial/Nitrogen.FCMat diff --git a/src/Mod/Material/CMakeLists.txt b/src/Mod/Material/CMakeLists.txt index 54daf1c795..8c544aedde 100644 --- a/src/Mod/Material/CMakeLists.txt +++ b/src/Mod/Material/CMakeLists.txt @@ -44,6 +44,8 @@ SOURCE_GROUP("MatLib" FILES ${MaterialLib_Files}) SET (FluidMaterial_Files FluidMaterial/None.FCMat FluidMaterial/Air.FCMat + FluidMaterial/Argon.FCMat + FluidMaterial/Nitrogen.FCMat FluidMaterial/Water.FCMat FluidMaterial/Readme.md ) diff --git a/src/Mod/Material/FluidMaterial/Air.FCMat b/src/Mod/Material/FluidMaterial/Air.FCMat index a23277eb7b..2ecdf911ff 100644 --- a/src/Mod/Material/FluidMaterial/Air.FCMat +++ b/src/Mod/Material/FluidMaterial/Air.FCMat @@ -20,3 +20,4 @@ VolumetricThermalExpansionCoefficient = 3.43e-3 m/m/K [Electrical] RelativePermittivity = 1.00059 ; at 18°C and 50Hz +ElectricalConductivity = 1e-12 S/m diff --git a/src/Mod/Material/FluidMaterial/Argon.FCMat b/src/Mod/Material/FluidMaterial/Argon.FCMat new file mode 100644 index 0000000000..6bd8193cce --- /dev/null +++ b/src/Mod/Material/FluidMaterial/Argon.FCMat @@ -0,0 +1,23 @@ +[General] +Name = Argon +Description = Argon properties at 20 Degrees Celsius and 1 atm +MolarMass = 39.95 +Father = Gas + +[Fluidic] +Density = 1.641 kg/m^3 +DynamicViscosity = 22.3e-6 kg/m/s +; Kinematic Viscosity = Dynamic Viscosity / Density +KinematicViscosity = 13.59-6 m^2/s +; PrandtlNumber is a nondimension number for CFD simulation +PrandtlNumber = 0.7 + +[Thermal] +SpecificHeat = 0.520 kJ/kg/K +ThermalConductivity = 0.018 W/m/K +; volumetric expansion coeff of ideal gas depends on temperature and pressure +VolumetricThermalExpansionCoefficient = 3.43e-3 1/K + +[Electrical] +RelativePermittivity = 1.000513 +ElectricalConductivity = 1e-15 S/m diff --git a/src/Mod/Material/FluidMaterial/Nitrogen.FCMat b/src/Mod/Material/FluidMaterial/Nitrogen.FCMat new file mode 100644 index 0000000000..9362414372 --- /dev/null +++ b/src/Mod/Material/FluidMaterial/Nitrogen.FCMat @@ -0,0 +1,23 @@ +[General] +Name = Nitrogen +Description = Nitrogen properties at 20 Degrees Celsius and 1 atm +MolarMass = 14.007 +Father = Gas + +[Fluidic] +Density = 1.2506 kg/m^3 +DynamicViscosity = 17.58e-6 kg/m/s +; Kinematic Viscosity = Dynamic Viscosity / Density +KinematicViscosity = 14.06e-6 m^2/s +; PrandtlNumber is a nondimension number for CFD simulation +PrandtlNumber = 0.7 + +[Thermal] +SpecificHeat = 1.04 kJ/kg/K +ThermalConductivity = 25.83e−3 W/m/K +; volumetric expansion coeff of ideal gas depends on temperature and pressure +VolumetricThermalExpansionCoefficient = 3.43e-3 1/K + +[Electrical] +RelativePermittivity = 1.00058 +ElectricalConductivity = 1e-12 S/m diff --git a/src/Mod/Material/FluidMaterial/Water.FCMat b/src/Mod/Material/FluidMaterial/Water.FCMat index c88709d60f..6a854d4757 100644 --- a/src/Mod/Material/FluidMaterial/Water.FCMat +++ b/src/Mod/Material/FluidMaterial/Water.FCMat @@ -22,3 +22,4 @@ VolumetricThermalExpansionCoefficient = 2.07e-4 m/m/K [Electrical] RelativePermittivity = 80.0 ; at 20°C and 50Hz +ElectricalConductivity = 5.5e-6 S/m From cf11f3887523d429164e6edc521846e19899906d Mon Sep 17 00:00:00 2001 From: donovaly Date: Thu, 24 Sep 2020 01:56:24 +0200 Subject: [PATCH 19/21] [PD] add along length option for pad - as discussed here: https://forum.freecadweb.org/viewtopic.php?f=17&t=50466&start=10#p433327 this PR is the first step for new Pad features. It implements 3 features requested by users: 1. an option to measure the pad length along its direction if there is a custom direction 2. when there are two lengths, the reverse feature should be active 3. the pad dialog shows now always the used pad direction, no matter if it is a custom one or the sketch's normal - Furthermore there are some code improvements (avoid rounding and blocking signals) - Also adapt TestPad.py because its test used the case two lengths + reversed but this was not possible before this PR - the reverse option was not take into account for two lengths in FeatureSketchBased.cpp) Now it is and therefore the test must be updated: In the test the second sketch has the normal vector 0, -1, 0. As the reverse option is set the pad direction is 0, 1, 0. So in y direction is the length (1mm) and in -y direction is length2 (2mm). This gives together with the other pad of volume 1, a total volume of 4. - fix UI issue: either reversed or midplane As noticed by @chennes, when the pad uses symmetric, reversed is not sensible and vice versa. This commit fixes the missing Gui side for the case midplane is checked and the App side. - the PR also fix two typos --- src/Mod/PartDesign/App/FeaturePad.cpp | 29 ++- src/Mod/PartDesign/App/FeaturePad.h | 1 + src/Mod/PartDesign/App/FeatureSketchBased.cpp | 5 +- src/Mod/PartDesign/Gui/TaskPadParameters.cpp | 108 +++++++--- src/Mod/PartDesign/Gui/TaskPadParameters.h | 2 + src/Mod/PartDesign/Gui/TaskPadParameters.ui | 203 ++++++++++-------- src/Mod/PartDesign/PartDesignTests/TestPad.py | 4 +- 7 files changed, 215 insertions(+), 137 deletions(-) diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index b2b700de92..dc2edbe054 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -56,7 +56,7 @@ using namespace PartDesign; -const char* Pad::TypeEnums[]= {"Length","UpToLast","UpToFirst","UpToFace","TwoLengths",NULL}; +const char* Pad::TypeEnums[]= {"Length", "UpToLast", "UpToFirst", "UpToFace", "TwoLengths", NULL}; PROPERTY_SOURCE(PartDesign::Pad, PartDesign::ProfileBased) @@ -70,6 +70,7 @@ Pad::Pad() ADD_PROPERTY_TYPE(Length2, (100.0), "Pad", App::Prop_None,"Second Pad length"); ADD_PROPERTY_TYPE(UseCustomVector, (0), "Pad", App::Prop_None, "Use custom vector for pad direction"); ADD_PROPERTY_TYPE(Direction, (Base::Vector3d(1.0, 1.0, 1.0)), "Pad", App::Prop_None, "Pad direction vector"); + ADD_PROPERTY_TYPE(AlongCustomVector, (1), "Pad", App::Prop_None, "Measure length along custom direction vector"); ADD_PROPERTY_TYPE(UpToFace, (0), "Pad", App::Prop_None, "Face where pad will end"); ADD_PROPERTY_TYPE(Offset, (0.0), "Pad", App::Prop_None, "Offset from face in which pad will end"); static const App::PropertyQuantityConstraint::Constraints signedLengthConstraint = {-DBL_MAX, DBL_MAX, 1.0}; @@ -88,6 +89,7 @@ short Pad::mustExecute() const Length2.isTouched() || UseCustomVector.isTouched() || Direction.isTouched() || + AlongCustomVector.isTouched() || Offset.isTouched() || UpToFace.isTouched()) return 1; @@ -104,6 +106,12 @@ App::DocumentObjectExecReturn *Pad::execute(void) if ((std::string(Type.getValueAsString()) == "TwoLengths") && (L < Precision::Confusion())) return new App::DocumentObjectExecReturn("Second length of pad too small"); + // if midplane is true, disable reversed and vice versa + bool hasMidplane = Midplane.getValue(); + bool hasReversed = Reversed.getValue(); + Midplane.setReadOnly(hasReversed); + Reversed.setReadOnly(hasMidplane); + Part::Feature* obj = 0; TopoDS_Shape sketchshape; try { @@ -133,12 +141,13 @@ App::DocumentObjectExecReturn *Pad::execute(void) base.Move(invObjLoc); Base::Vector3d paddingDirection; - - // use the given vector if necessary + if (!UseCustomVector.getValue()) { + // use sketch's normal vector for direction paddingDirection = SketchVector; } else { + // use the given vector // if null vector, use SketchVector if ( (fabs(Direction.getValue().x) < Precision::Confusion()) && (fabs(Direction.getValue().y) < Precision::Confusion()) @@ -168,9 +177,15 @@ App::DocumentObjectExecReturn *Pad::execute(void) if (factor < Precision::Confusion()) return new App::DocumentObjectExecReturn("Pad: Creation failed because direction is orthogonal to sketch's normal vector"); - // perform the length correction - L = L / factor; - L2 = L2 / factor; + // perform the length correction if not along custom vector + if (AlongCustomVector.getValue()) { + L = L / factor; + L2 = L2 / factor; + } + + // explicitly set the Direction so that the dialog shows also the used direction + // if the sketch's normal vector was used + Direction.setValue(paddingDirection); dir.Transform(invObjLoc.Transformation()); @@ -306,7 +321,7 @@ App::DocumentObjectExecReturn *Pad::execute(void) } } else { generatePrism(prism, sketchshape, method, dir, L, L2, - Midplane.getValue(), Reversed.getValue()); + hasMidplane, hasReversed); } if (prism.IsNull()) diff --git a/src/Mod/PartDesign/App/FeaturePad.h b/src/Mod/PartDesign/App/FeaturePad.h index a4d1172865..9c646d2c27 100644 --- a/src/Mod/PartDesign/App/FeaturePad.h +++ b/src/Mod/PartDesign/App/FeaturePad.h @@ -45,6 +45,7 @@ public: App::PropertyLength Length2; App::PropertyBool UseCustomVector; App::PropertyVector Direction; + App::PropertyBool AlongCustomVector; App::PropertyLength Offset; /** @name methods override feature */ diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 01141a2a80..bc6169fa3d 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -587,8 +587,11 @@ void ProfileBased::generatePrism(TopoDS_Shape& prism, if (method == "TwoLengths") { // midplane makes no sense here - Loffset = -L2; Ltotal += L2; + if (reversed) + Loffset = -L; + else + Loffset = -L2; } else if (midplane) Loffset = -Ltotal/2; diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp index 2604b60aad..f2e927fe47 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp @@ -34,15 +34,15 @@ #include "TaskPadParameters.h" #include #include +#include #include #include -#include #include +#include +#include +#include #include #include -#include -#include -#include #include #include #include "TaskSketchBasedParameters.h" @@ -75,6 +75,7 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, PartDesign::Pad* pcPad = static_cast(vp->getObject()); Base::Quantity l = pcPad->Length.getQuantityValue(); Base::Quantity l2 = pcPad->Length2.getQuantityValue(); + bool alongCustom = pcPad->AlongCustomVector.getValue(); bool useCustom = pcPad->UseCustomVector.getValue(); double xs = pcPad->Direction.getValue().x; double ys = pcPad->Direction.getValue().y; @@ -93,10 +94,18 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, faceId = std::atoi(&upToFace[4]); } + // set decimals for the direction edits + // do this here before the edits are filed to avoid rounding mistakes + int UserDecimals = Base::UnitsApi::getDecimals(); + ui->XDirectionEdit->setDecimals(UserDecimals); + ui->YDirectionEdit->setDecimals(UserDecimals); + ui->ZDirectionEdit->setDecimals(UserDecimals); + // Fill data into dialog elements ui->lengthEdit->setValue(l); ui->lengthEdit2->setValue(l2); ui->groupBoxDirection->setChecked(useCustom); + ui->checkBoxAlongDirection->setChecked(alongCustom); ui->XDirectionEdit->setValue(xs); ui->YDirectionEdit->setValue(ys); ui->ZDirectionEdit->setValue(zs); @@ -105,23 +114,16 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, // Bind input fields to properties ui->lengthEdit->bind(pcPad->Length); ui->lengthEdit2->bind(pcPad->Length2); - ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(pcPad, std::string("Direction.x"))); ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(pcPad, std::string("Direction.y"))); ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(pcPad, std::string("Direction.z"))); - ui->offsetEdit->bind(pcPad->Offset); + ui->checkBoxMidplane->setChecked(midplane); // According to bug #0000521 the reversed option // shouldn't be de-activated if the pad has a support face ui->checkBoxReversed->setChecked(reversed); - // set decimals for the direction edits - int UserDecimals = Base::UnitsApi::getDecimals(); - ui->XDirectionEdit->setDecimals(UserDecimals); - ui->YDirectionEdit->setDecimals(UserDecimals); - ui->ZDirectionEdit->setDecimals(UserDecimals); - // Set object labels if (obj && PartDesign::Feature::isDatum(obj)) { ui->lineFaceName->setText(QString::fromUtf8(obj->Label.getValue())); @@ -138,7 +140,6 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, ui->lineFaceName->clear(); ui->lineFaceName->setProperty("FeatureName", QVariant()); } - ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace.c_str())); ui->changeMode->clear(); @@ -155,6 +156,8 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, this, SLOT(onLengthChanged(double))); connect(ui->lengthEdit2, SIGNAL(valueChanged(double)), this, SLOT(onLength2Changed(double))); + connect(ui->checkBoxAlongDirection, SIGNAL(toggled(bool)), + this, SLOT(onCBAlongDirectionChanged(bool))); connect(ui->groupBoxDirection, SIGNAL(toggled(bool)), this, SLOT(onGBDirectionChanged(bool))); connect(ui->XDirectionEdit, SIGNAL(valueChanged(double)), @@ -197,16 +200,17 @@ void TaskPadParameters::updateUI(int index) { // disable/hide everything unless we are sure we don't need it // exception: the direction parameters are in any case visible - bool isLengthEditVisable = false; - bool isLengthEdit2Visable = false; - bool isOffsetEditVisable = false; + bool isLengthEditVisible = false; + bool isLengthEdit2Visible = false; + bool isOffsetEditVisible = false; bool isMidplateEnabled = false; - bool isReversedEnabled = false; + bool isReversedEnabled = true; + bool isReversedVisible = true; bool isFaceEditEnabled = false; // dimension if (index == 0) { - isLengthEditVisable = true; + isLengthEditVisible = true; ui->lengthEdit->selectNumber(); // Make sure that the spin box has the focus to get key events // Calling setFocus() directly doesn't work because the spin box is not @@ -218,13 +222,16 @@ void TaskPadParameters::updateUI(int index) } // up to first/last else if (index == 1 || index == 2) { - isOffsetEditVisable = true; - isReversedEnabled = true; + isOffsetEditVisible = true; + isReversedEnabled = false; + isReversedVisible = false; } // up to face else if (index == 3) { - isOffsetEditVisable = true; - isFaceEditEnabled = true; + isOffsetEditVisible = true; + isFaceEditEnabled = true; + isReversedEnabled = false; + isReversedVisible = false; QMetaObject::invokeMethod(ui->lineFaceName, "setFocus", Qt::QueuedConnection); // Go into reference selection mode if no face has been selected yet if (ui->lineFaceName->property("FeatureName").isNull()) @@ -232,25 +239,27 @@ void TaskPadParameters::updateUI(int index) } // two dimensions else { - isLengthEditVisable = true; - isLengthEdit2Visable = true; + isLengthEditVisible = true; + isLengthEdit2Visible = true; } - ui->lengthEdit->setVisible( isLengthEditVisable ); - ui->lengthEdit->setEnabled( isLengthEditVisable ); - ui->labelLength->setVisible( isLengthEditVisable ); + ui->lengthEdit->setVisible( isLengthEditVisible ); + ui->lengthEdit->setEnabled( isLengthEditVisible ); + ui->labelLength->setVisible( isLengthEditVisible ); + ui->checkBoxAlongDirection->setVisible( isLengthEditVisible ); - ui->offsetEdit->setVisible( isOffsetEditVisable ); - ui->offsetEdit->setEnabled( isOffsetEditVisable ); - ui->labelOffset->setVisible( isOffsetEditVisable ); + ui->offsetEdit->setVisible( isOffsetEditVisible ); + ui->offsetEdit->setEnabled( isOffsetEditVisible ); + ui->labelOffset->setVisible( isOffsetEditVisible ); ui->checkBoxMidplane->setEnabled( isMidplateEnabled ); ui->checkBoxReversed->setEnabled( isReversedEnabled ); + ui->checkBoxReversed->setVisible( isReversedVisible ); - ui->lengthEdit2->setVisible( isLengthEdit2Visable ); - ui->lengthEdit2->setEnabled( isLengthEdit2Visable ); - ui->labelLength2->setVisible( isLengthEdit2Visable ); + ui->lengthEdit2->setVisible( isLengthEdit2Visible ); + ui->lengthEdit2->setEnabled( isLengthEdit2Visible ); + ui->labelLength2->setVisible( isLengthEdit2Visible ); ui->buttonFace->setEnabled( isFaceEditEnabled ); ui->lineFaceName->setEnabled( isFaceEditEnabled ); @@ -301,11 +310,26 @@ void TaskPadParameters::onLength2Changed(double len) recomputeFeature(); } +void TaskPadParameters::onCBAlongDirectionChanged(bool on) +{ + PartDesign::Pad* pcPad = static_cast(vp->getObject()); + pcPad->AlongCustomVector.setValue(on); + recomputeFeature(); +} + void TaskPadParameters::onGBDirectionChanged(bool on) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); pcPad->UseCustomVector.setValue(on); + // dis/enable length direction + ui->checkBoxAlongDirection->setEnabled(on); + if (!on) + ui->checkBoxAlongDirection->setChecked(!on); recomputeFeature(); + // the calculation of the sketch's normal vector is done in FeaturePad.cpp + // if this vector was used for the recomputation we must fill the direction + // vector edit fields. Therefore update + updateDirectionEdits(); } void TaskPadParameters::onXDirectionEditChanged(double len) @@ -319,7 +343,6 @@ void TaskPadParameters::onXDirectionEditChanged(double len) updateDirectionEdits(); } - void TaskPadParameters::onYDirectionEditChanged(double len) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); @@ -339,9 +362,16 @@ void TaskPadParameters::onZDirectionEditChanged(double len) void TaskPadParameters::updateDirectionEdits(void) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); + // we don't want to execute the onChanged edits, but just update their contents + ui->XDirectionEdit->blockSignals(true); + ui->YDirectionEdit->blockSignals(true); + ui->ZDirectionEdit->blockSignals(true); ui->XDirectionEdit->setValue(pcPad->Direction.getValue().x); ui->YDirectionEdit->setValue(pcPad->Direction.getValue().y); ui->ZDirectionEdit->setValue(pcPad->Direction.getValue().z); + ui->XDirectionEdit->blockSignals(false); + ui->YDirectionEdit->blockSignals(false); + ui->ZDirectionEdit->blockSignals(false); } void TaskPadParameters::onOffsetChanged(double len) @@ -355,6 +385,7 @@ void TaskPadParameters::onMidplaneChanged(bool on) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); pcPad->Midplane.setValue(on); + // reversed is not sensible when midplane ui->checkBoxReversed->setEnabled(!on); recomputeFeature(); } @@ -363,6 +394,8 @@ void TaskPadParameters::onReversedChanged(bool on) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); pcPad->Reversed.setValue(on); + // midplane is not sensible when reversed + ui->checkBoxMidplane->setEnabled(!on); recomputeFeature(); } @@ -391,6 +424,7 @@ void TaskPadParameters::onButtonFace(const bool pressed) { this->blockConnection(!pressed); + // only faces are allowed TaskSketchBasedParameters::onSelectReference(pressed, false, true, false); // Update button if onButtonFace() is called explicitly @@ -432,6 +466,11 @@ double TaskPadParameters::getLength2(void) const return ui->lengthEdit2->value().getValue(); } +bool TaskPadParameters::getAlongCustom(void) const +{ + return ui->checkBoxAlongDirection->isChecked(); +} + bool TaskPadParameters::getCustom(void) const { return ui->groupBoxDirection->isChecked(); @@ -563,6 +602,7 @@ void TaskPadParameters::apply() FCMD_OBJ_CMD(obj, "UseCustomVector = " << (getCustom() ? 1 : 0)); FCMD_OBJ_CMD(obj, "Direction = (" << getXDirection() << ", " << getYDirection() << ", " << getZDirection() << ")"); + FCMD_OBJ_CMD(obj, "AlongCustomVector = " << (getAlongCustom() ? 1 : 0)); FCMD_OBJ_CMD(obj,"Type = " << getMode()); QString facename = getFaceName(); FCMD_OBJ_CMD(obj,"UpToFace = " << facename.toLatin1().data()); diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.h b/src/Mod/PartDesign/Gui/TaskPadParameters.h index 17172344ed..ede9b7278c 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.h @@ -58,6 +58,7 @@ public: private Q_SLOTS: void onLengthChanged(double); void onLength2Changed(double); + void onCBAlongDirectionChanged(bool); void onGBDirectionChanged(bool); void onXDirectionEditChanged(double); void onYDirectionEditChanged(double); @@ -75,6 +76,7 @@ protected: private: double getLength(void) const; double getLength2(void) const; + bool getAlongCustom(void) const; bool getCustom(void) const; double getXDirection(void) const; double getYDirection(void) const; diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.ui b/src/Mod/PartDesign/Gui/TaskPadParameters.ui index 5a040a832d..a2c73db1c2 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.ui @@ -7,13 +7,13 @@ 0 0 280 - 350 + 373 Form - + @@ -66,97 +66,114 @@ the sketch plane's normal vector will be used true - - - - - x - - - - - - - x-component of direction vector - - - false - - - -100.000000000000000 - - - 100.000000000000000 - - - 0.100000000000000 - - - - - - - - - - y - - - - - - - y-component of direction vector - - - false - - - -100.000000000000000 - - - 100.000000000000000 - - - 0.100000000000000 - - - - - - - - - - z - - - - - - - z-component of direction vector - - - false - - - -100.000000000000000 - - - 100.000000000000000 - - - 0.100000000000000 - - - 1.000000000000000 - - - - - - + + + + + x + + + + + + + x-component of direction vector + + + false + + + -100.000000000000000 + + + 100.000000000000000 + + + 0.100000000000000 + + + + + + + + + + y + + + + + + + y-component of direction vector + + + false + + + -100.000000000000000 + + + 100.000000000000000 + + + 0.100000000000000 + + + + + + + + + + z + + + + + + + z-component of direction vector + + + false + + + -100.000000000000000 + + + 100.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + + + + true + + + If unchecked, the length will be +measured along the specified direction + + + Length along sketch normal + + + true + + + diff --git a/src/Mod/PartDesign/PartDesignTests/TestPad.py b/src/Mod/PartDesign/PartDesignTests/TestPad.py index 28f6e9ba6c..16d0f16345 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestPad.py +++ b/src/Mod/PartDesign/PartDesignTests/TestPad.py @@ -159,8 +159,8 @@ class TestPad(unittest.TestCase): self.Body.addObject(self.Pad1) self.Pad1.Profile = self.PadSketch1 self.Pad1.Type = 4 - self.Pad1.Length = 2.0 - self.Pad1.Length2 = 1.0 + self.Pad1.Length = 1.0 + self.Pad1.Length2 = 2.0 self.Pad1.Reversed = 1 self.Doc.recompute() self.assertAlmostEqual(self.Pad1.Shape.Volume, 4.0) From b4b1cbedb13929f81f50e2f1cc9b7a8b182e2958 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 22 Mar 2021 16:40:15 +0100 Subject: [PATCH 20/21] PD: fix several issues in pad: * pass boolean instead of int to setValue of PropertyBool * support of symmetric option for two-length mode * fix bug in TaskPadParameters::updateUI() * remove strange acronyms from method names --- src/Mod/PartDesign/App/FeaturePad.cpp | 2 +- src/Mod/PartDesign/App/FeatureSketchBased.cpp | 2 ++ src/Mod/PartDesign/Gui/TaskPadParameters.cpp | 22 +++++++++---------- src/Mod/PartDesign/Gui/TaskPadParameters.h | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index dc2edbe054..6265075f01 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -70,7 +70,7 @@ Pad::Pad() ADD_PROPERTY_TYPE(Length2, (100.0), "Pad", App::Prop_None,"Second Pad length"); ADD_PROPERTY_TYPE(UseCustomVector, (0), "Pad", App::Prop_None, "Use custom vector for pad direction"); ADD_PROPERTY_TYPE(Direction, (Base::Vector3d(1.0, 1.0, 1.0)), "Pad", App::Prop_None, "Pad direction vector"); - ADD_PROPERTY_TYPE(AlongCustomVector, (1), "Pad", App::Prop_None, "Measure length along custom direction vector"); + ADD_PROPERTY_TYPE(AlongCustomVector, (true), "Pad", App::Prop_None, "Measure length along custom direction vector"); ADD_PROPERTY_TYPE(UpToFace, (0), "Pad", App::Prop_None, "Face where pad will end"); ADD_PROPERTY_TYPE(Offset, (0.0), "Pad", App::Prop_None, "Offset from face in which pad will end"); static const App::PropertyQuantityConstraint::Constraints signedLengthConstraint = {-DBL_MAX, DBL_MAX, 1.0}; diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index bc6169fa3d..bee0e7242b 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -590,6 +590,8 @@ void ProfileBased::generatePrism(TopoDS_Shape& prism, Ltotal += L2; if (reversed) Loffset = -L; + else if (midplane) + Loffset = -0.5 * (L2 + L); else Loffset = -L2; } else if (midplane) diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp index f2e927fe47..f5dc799f9e 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp @@ -157,9 +157,9 @@ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, connect(ui->lengthEdit2, SIGNAL(valueChanged(double)), this, SLOT(onLength2Changed(double))); connect(ui->checkBoxAlongDirection, SIGNAL(toggled(bool)), - this, SLOT(onCBAlongDirectionChanged(bool))); + this, SLOT(onAlongDirectionChanged(bool))); connect(ui->groupBoxDirection, SIGNAL(toggled(bool)), - this, SLOT(onGBDirectionChanged(bool))); + this, SLOT(onDirectionToggled(bool))); connect(ui->XDirectionEdit, SIGNAL(valueChanged(double)), this, SLOT(onXDirectionEditChanged(double))); connect(ui->YDirectionEdit, SIGNAL(valueChanged(double)), @@ -204,8 +204,8 @@ void TaskPadParameters::updateUI(int index) bool isLengthEdit2Visible = false; bool isOffsetEditVisible = false; bool isMidplateEnabled = false; - bool isReversedEnabled = true; - bool isReversedVisible = true; + bool isReversedEnabled = false; + bool isReversedVisible = false; bool isFaceEditEnabled = false; // dimension @@ -216,22 +216,19 @@ void TaskPadParameters::updateUI(int index) // Calling setFocus() directly doesn't work because the spin box is not // yet visible. QMetaObject::invokeMethod(ui->lengthEdit, "setFocus", Qt::QueuedConnection); - isMidplateEnabled = true; + isMidplateEnabled = !ui->checkBoxReversed->isChecked(); // Reverse only makes sense if Midplane is not true isReversedEnabled = !ui->checkBoxMidplane->isChecked(); + isReversedVisible = true; } // up to first/last else if (index == 1 || index == 2) { isOffsetEditVisible = true; - isReversedEnabled = false; - isReversedVisible = false; } // up to face else if (index == 3) { isOffsetEditVisible = true; isFaceEditEnabled = true; - isReversedEnabled = false; - isReversedVisible = false; QMetaObject::invokeMethod(ui->lineFaceName, "setFocus", Qt::QueuedConnection); // Go into reference selection mode if no face has been selected yet if (ui->lineFaceName->property("FeatureName").isNull()) @@ -241,6 +238,9 @@ void TaskPadParameters::updateUI(int index) else { isLengthEditVisible = true; isLengthEdit2Visible = true; + isMidplateEnabled = !ui->checkBoxReversed->isChecked(); + isReversedEnabled = !ui->checkBoxMidplane->isChecked(); + isReversedVisible = true; } ui->lengthEdit->setVisible( isLengthEditVisible ); @@ -310,14 +310,14 @@ void TaskPadParameters::onLength2Changed(double len) recomputeFeature(); } -void TaskPadParameters::onCBAlongDirectionChanged(bool on) +void TaskPadParameters::onAlongDirectionChanged(bool on) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); pcPad->AlongCustomVector.setValue(on); recomputeFeature(); } -void TaskPadParameters::onGBDirectionChanged(bool on) +void TaskPadParameters::onDirectionToggled(bool on) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); pcPad->UseCustomVector.setValue(on); diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.h b/src/Mod/PartDesign/Gui/TaskPadParameters.h index ede9b7278c..f639496331 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.h @@ -58,8 +58,8 @@ public: private Q_SLOTS: void onLengthChanged(double); void onLength2Changed(double); - void onCBAlongDirectionChanged(bool); - void onGBDirectionChanged(bool); + void onAlongDirectionChanged(bool); + void onDirectionToggled(bool); void onXDirectionEditChanged(double); void onYDirectionEditChanged(double); void onZDirectionEditChanged(double); From 1ac35d2b423df1e9b70242bed8d5e37dafbc0904 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 22 Mar 2021 17:13:56 +0100 Subject: [PATCH 21/21] Import: [skip ci] handle utf-8 filepath when writing to dxf --- src/Mod/Import/App/dxf.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Import/App/dxf.cpp b/src/Mod/Import/App/dxf.cpp index 19e116beac..65d96bfb57 100644 --- a/src/Mod/Import/App/dxf.cpp +++ b/src/Mod/Import/App/dxf.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "dxf.h" @@ -47,7 +48,8 @@ m_layerName("none") // start the file m_fail = false; m_version = 12; - m_ofs = new ofstream(filepath, ios::out); + Base::FileInfo fi(filepath); + m_ofs = new Base::ofstream(fi, ios::out); m_ssBlock = new std::ostringstream(); m_ssBlkRecord = new std::ostringstream(); m_ssEntity = new std::ostringstream();