diff --git a/.github/workflows/issue-metrics.yml b/.github/workflows/issue-metrics.yml index 76c947c4f2..e080fbf405 100644 --- a/.github/workflows/issue-metrics.yml +++ b/.github/workflows/issue-metrics.yml @@ -12,6 +12,7 @@ jobs: build: name: issue metrics runs-on: ubuntu-latest + if: github.repository_owner == 'FreeCAD' steps: @@ -19,7 +20,7 @@ jobs: shell: bash run: | # Calculate the first day of the previous month - first_day=$(date -d "last month" +%Y-%m-01) + first_day=$(date -d "last month" +%Y-%m-15) # Calculate the last day of the previous month last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d) @@ -32,7 +33,7 @@ jobs: uses: github/issue-metrics@v2 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SEARCH_QUERY: 'repo:FreeCAD/FreeCAD is:issue created:${{ env.last_month }} -reason:"not planned"' + SEARCH_QUERY: 'repo:FreeCAD/FreeCAD is:issue created:${{ env.last_month }}' - name: Create issue uses: peter-evans/create-issue-from-file@v4 @@ -40,4 +41,4 @@ jobs: title: Monthly issue metrics report token: ${{ secrets.GITHUB_TOKEN }} content-filepath: ./issue_metrics.md - assignees: maxwxyz \ No newline at end of file + assignees: maxwxyz diff --git a/conda/conda-env.yaml b/conda/conda-env.yaml index 1bd63b1952..85f441dae3 100644 --- a/conda/conda-env.yaml +++ b/conda/conda-env.yaml @@ -3,6 +3,5 @@ channels: - conda-forge dependencies: - conda-devenv -- mamba==1.4.9 # NOTE: Pin to highest version supported by the devenv -- python==3.11.* # dependencies. If a higher version is installed, a crash - # occurs during the downgrade process. +- mamba +- python==3.11.* diff --git a/conda/environment.devenv.yml b/conda/environment.devenv.yml index fa434281b4..a2504b69e6 100644 --- a/conda/environment.devenv.yml +++ b/conda/environment.devenv.yml @@ -73,7 +73,7 @@ dependencies: - graphviz - hdf5 - libcxx -- mamba==1.4.9 +- mamba - matplotlib - ninja - numpy diff --git a/src/Gui/PreferencePages/DlgSettings3DView.ui b/src/Gui/PreferencePages/DlgSettings3DView.ui index 3a85779209..0854dd66a5 100644 --- a/src/Gui/PreferencePages/DlgSettings3DView.ui +++ b/src/Gui/PreferencePages/DlgSettings3DView.ui @@ -281,31 +281,6 @@ but slower response to any scene changes. View - - - None - - - - - Line Smoothing - - - - - MSAA 2x - - - - - MSAA 4x - - - - - MSAA 8x - - diff --git a/src/Gui/PreferencePages/DlgSettings3DViewImp.cpp b/src/Gui/PreferencePages/DlgSettings3DViewImp.cpp index 49f175bb2a..9fc095a637 100644 --- a/src/Gui/PreferencePages/DlgSettings3DViewImp.cpp +++ b/src/Gui/PreferencePages/DlgSettings3DViewImp.cpp @@ -25,6 +25,9 @@ #ifndef _PreComp_ # include # include +# include +# include +# include #endif #include @@ -40,42 +43,23 @@ using namespace Gui::Dialog; /* TRANSLATOR Gui::Dialog::DlgSettings3DViewImp */ -bool DlgSettings3DViewImp::showMsg = true; - -/** - * Constructs a DlgSettings3DViewImp which is a child of 'parent', with the - * name 'name' and widget flags set to 'f' - */ DlgSettings3DViewImp::DlgSettings3DViewImp(QWidget* parent) : PreferencePage( parent ) , ui(new Ui_DlgSettings3DView) { ui->setupUi(this); + addAntiAliasing(); } -/** - * Destroys the object and frees any allocated resources - */ DlgSettings3DViewImp::~DlgSettings3DViewImp() = default; void DlgSettings3DViewImp::saveSettings() { - // must be done as very first because we create a new instance of NavigatorStyle - // where we set some attributes afterwards - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath - ("User parameter:BaseApp/Preferences/View"); - - int index = ui->comboAliasing->currentIndex(); - hGrp->SetInt("AntiAliasing", index); - - index = ui->renderCache->currentIndex(); - hGrp->SetInt("RenderCache", index); + saveAntiAliasing(); + saveRenderCache(); + saveMarkerSize(); ui->comboTransparentRender->onSave(); - - QVariant const &vBoxMarkerSize = ui->boxMarkerSize->itemData(ui->boxMarkerSize->currentIndex()); - hGrp->SetInt("MarkerSize", vBoxMarkerSize.toInt()); - ui->CheckBox_CornerCoordSystem->onSave(); ui->SpinBox_CornerCoordSystemSize->onSave(); ui->CheckBox_ShowAxisCross->onSave(); @@ -106,23 +90,139 @@ void DlgSettings3DViewImp::loadSettings() ui->sliderIntensity->onRestore(); ui->radioPerspective->onRestore(); ui->radioOrthographic->onRestore(); + ui->comboTransparentRender->onRestore(); + loadAntiAliasing(); + loadRenderCache(); + loadMarkerSize(); +} + +namespace { +class GLFormatCheck { +public: + GLFormatCheck() { + context.setFormat(format); + context.create(); + offscreen.setFormat(format); + offscreen.create(); + context.makeCurrent(&offscreen); + } + + bool testSamples(int num) { + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::Depth); + fboFormat.setSamples(num); + QOpenGLFramebufferObject fbo(100, 100, fboFormat); // NOLINT + return fbo.format().samples() == num; + } + +private: + QSurfaceFormat format; + QOpenGLContext context; + QOffscreenSurface offscreen; +}; +} + +void DlgSettings3DViewImp::addAntiAliasing() +{ + QString none = QCoreApplication::translate("Gui::Dialog::DlgSettings3DView", "None"); + QString line = QCoreApplication::translate("Gui::Dialog::DlgSettings3DView", "Line Smoothing"); + QString msaa2x = QCoreApplication::translate("Gui::Dialog::DlgSettings3DView", "MSAA 2x"); + QString msaa4x = QCoreApplication::translate("Gui::Dialog::DlgSettings3DView", "MSAA 4x"); + QString msaa6x = QCoreApplication::translate("Gui::Dialog::DlgSettings3DView", "MSAA 6x"); + QString msaa8x = QCoreApplication::translate("Gui::Dialog::DlgSettings3DView", "MSAA 8x"); + ui->comboAliasing->clear(); + ui->comboAliasing->addItem(none, int(Gui::View3DInventorViewer::None)); + ui->comboAliasing->addItem(line, int(Gui::View3DInventorViewer::Smoothing)); + + // Do the samples checks only once + static std::vector> modes; + static bool formatCheck = true; + if (formatCheck) { + formatCheck = false; + + GLFormatCheck check; + // NOLINTBEGIN + if (check.testSamples(2)) { + modes.emplace_back(msaa2x, int(Gui::View3DInventorViewer::MSAA2x)); + } + if (check.testSamples(4)) { + modes.emplace_back(msaa4x, int(Gui::View3DInventorViewer::MSAA4x)); + } + if (check.testSamples(6)) { + modes.emplace_back(msaa6x, int(Gui::View3DInventorViewer::MSAA6x)); + } + if (check.testSamples(8)) { + modes.emplace_back(msaa8x, int(Gui::View3DInventorViewer::MSAA8x)); + } + // NOLINTEND + } + + for (const auto& it : modes) { + ui->comboAliasing->addItem(it.first, it.second); + } +} + +void DlgSettings3DViewImp::saveAntiAliasing() +{ ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/View"); - int index = hGrp->GetInt("AntiAliasing", int(Gui::View3DInventorViewer::None)); - index = Base::clamp(index, 0, ui->comboAliasing->count()-1); - ui->comboAliasing->setCurrentIndex(index); + int index = ui->comboAliasing->currentIndex(); + int aliasing = ui->comboAliasing->itemData(index).toInt(); + hGrp->SetInt("AntiAliasing", aliasing); +} + +void DlgSettings3DViewImp::loadAntiAliasing() +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); + + int aliasing = int(hGrp->GetInt("AntiAliasing", int(Gui::View3DInventorViewer::None))); + int index = ui->comboAliasing->findData(aliasing); + if (index != -1) { + ui->comboAliasing->setCurrentIndex(index); + } + // connect after setting current item of the combo box connect(ui->comboAliasing, qOverload(&QComboBox::currentIndexChanged), this, &DlgSettings3DViewImp::onAliasingChanged); +} - index = hGrp->GetInt("RenderCache", 0); - ui->renderCache->setCurrentIndex(index); +void DlgSettings3DViewImp::saveRenderCache() +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); - ui->comboTransparentRender->onRestore(); + int cache = ui->renderCache->currentIndex(); + hGrp->SetInt("RenderCache", cache); +} - int const current = hGrp->GetInt("MarkerSize", 9L); +void DlgSettings3DViewImp::loadRenderCache() +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); + + long cache = hGrp->GetInt("RenderCache", 0); + ui->renderCache->setCurrentIndex(int(cache)); +} + +void DlgSettings3DViewImp::saveMarkerSize() +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); + + QVariant const &vBoxMarkerSize = ui->boxMarkerSize->itemData(ui->boxMarkerSize->currentIndex()); + hGrp->SetInt("MarkerSize", vBoxMarkerSize.toInt()); +} + +void DlgSettings3DViewImp::loadMarkerSize() +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/View"); + + // NOLINTBEGIN + int marker = hGrp->GetInt("MarkerSize", 9L); ui->boxMarkerSize->addItem(tr("5px"), QVariant(5)); ui->boxMarkerSize->addItem(tr("7px"), QVariant(7)); ui->boxMarkerSize->addItem(tr("9px"), QVariant(9)); @@ -132,9 +232,12 @@ void DlgSettings3DViewImp::loadSettings() ui->boxMarkerSize->addItem(tr("20px"), QVariant(20)); ui->boxMarkerSize->addItem(tr("25px"), QVariant(25)); ui->boxMarkerSize->addItem(tr("30px"), QVariant(30)); - index = ui->boxMarkerSize->findData(QVariant(current)); - if (index < 0) index = 2; - ui->boxMarkerSize->setCurrentIndex(index); + marker = ui->boxMarkerSize->findData(QVariant(marker)); + if (marker < 0) { + marker = 2; + } + ui->boxMarkerSize->setCurrentIndex(marker); + // NOLINTEND } void DlgSettings3DViewImp::resetSettingsToDefaults() @@ -161,24 +264,29 @@ void DlgSettings3DViewImp::changeEvent(QEvent *e) ui->comboAliasing->blockSignals(true); int aliasing = ui->comboAliasing->currentIndex(); ui->retranslateUi(this); + addAntiAliasing(); ui->comboAliasing->setCurrentIndex(aliasing); ui->comboAliasing->blockSignals(false); } else { - QWidget::changeEvent(e); + PreferencePage::changeEvent(e); } } void DlgSettings3DViewImp::onAliasingChanged(int index) { - if (index < 0 || !isVisible()) + if (index < 0 || !isVisible()) { return; + } + // Show this message only once per application session to reduce // annoyance when showing it too often. + static bool showMsg = true; if (showMsg) { showMsg = false; QMessageBox::information(this, tr("Anti-aliasing"), - tr("Open a new viewer or restart %1 to apply anti-aliasing changes.").arg(qApp->applicationName())); + tr("Open a new viewer or restart %1 to apply anti-aliasing changes.") + .arg(qApp->applicationName())); } } diff --git a/src/Gui/PreferencePages/DlgSettings3DViewImp.h b/src/Gui/PreferencePages/DlgSettings3DViewImp.h index 86af22838a..5d047f1152 100644 --- a/src/Gui/PreferencePages/DlgSettings3DViewImp.h +++ b/src/Gui/PreferencePages/DlgSettings3DViewImp.h @@ -56,9 +56,19 @@ private Q_SLOTS: protected: void changeEvent(QEvent *e) override; +private: + void addAntiAliasing(); + void saveAntiAliasing(); + void loadAntiAliasing(); + void saveRenderCache(); + void loadRenderCache(); + void saveMarkerSize(); + void loadMarkerSize(); + private: std::unique_ptr ui; - static bool showMsg; + + Q_DISABLE_COPY_MOVE(DlgSettings3DViewImp) }; } // namespace Dialog diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index 62322b8683..671e6ee5b7 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -1963,6 +1963,8 @@ int View3DInventorViewer::getNumSamples() return 2; case View3DInventorViewer::MSAA4x: return 4; + case View3DInventorViewer::MSAA6x: + return 6; case View3DInventorViewer::MSAA8x: return 8; case View3DInventorViewer::Smoothing: diff --git a/src/Gui/View3DInventorViewer.h b/src/Gui/View3DInventorViewer.h index 221ad4ba95..eca7f186b0 100644 --- a/src/Gui/View3DInventorViewer.h +++ b/src/Gui/View3DInventorViewer.h @@ -117,11 +117,12 @@ public: */ //@{ enum AntiAliasing { - None, - Smoothing, - MSAA2x, - MSAA4x, - MSAA8x + None = 0, + Smoothing = 1, + MSAA2x = 2, + MSAA4x = 3, + MSAA6x = 5, + MSAA8x = 4 }; //@} diff --git a/src/Gui/resource.cpp b/src/Gui/resource.cpp index a5746990a6..05304998a6 100644 --- a/src/Gui/resource.cpp +++ b/src/Gui/resource.cpp @@ -124,5 +124,6 @@ WidgetFactorySupplier::WidgetFactorySupplier() new WidgetProducer; new WidgetProducer; new WidgetProducer; + new WidgetProducer; } // clang-format on diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index d940b7c249..e168c3bf93 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -265,7 +265,12 @@ def export(exportList, filename, colors=None, preferences=None): ifcfile = ifcopenshell.open(templatefile) ifcfile = exportIFCHelper.writeUnits(ifcfile,preferences["IFC_UNIT"]) - history = ifcfile.by_type("IfcOwnerHistory")[0] + history = ifcfile.by_type("IfcOwnerHistory") + if history: + history = history[0] + else: + # IFC4 allows to not write any history + history = None objectslist = Draft.get_group_contents(exportList, walls=True, addgroups=True) @@ -296,7 +301,9 @@ def export(exportList, filename, colors=None, preferences=None): if existing_file: project = ifcfile.by_type("IfcProject")[0] - context = ifcfile.by_type("IFcGeometricRepresentationContext")[-1] + body_contexts = [c for c in ifcfile.by_type("IfcGeometricRepresentationSubContext") if c.ContextIdentifier in ["Body", "Facetation"]] + body_contexts.extend([c for c in ifcfile.by_type("IfcGeometricRepresentationContext", include_subtypes=False) if c.ContextType == "Model"]) + context = body_contexts[0] # we take the first one (subcontext if existing, or context if not) else: contextCreator = exportIFCHelper.ContextCreator(ifcfile, objectslist) context = contextCreator.model_view_subcontext diff --git a/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui b/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui index 46537effc4..f09608cf7f 100644 --- a/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui +++ b/src/Mod/Fem/Gui/DlgSettingsFemCcx.ui @@ -393,14 +393,14 @@ - + Time incrementation control parameter - + Use non ccx defaults @@ -416,6 +416,38 @@ + + + + Maximum number of iterations + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 10000000 + + + 10 + + + 2000 + + + AnalysisMaxIterations + + + Mod/Fem/Ccx + + + @@ -445,10 +477,10 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 3 + 9 - 0.010000000000000 + 0.000000001000000 0.010000000000000 @@ -465,7 +497,7 @@ - + s @@ -484,10 +516,10 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 3 + 9 - 0.010000000000000 + 0.000000001000000 0.010000000000000 @@ -504,20 +536,130 @@ + + + s + + + + + + + Time Minimum Step + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::DefaultContextMenu + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 9 + + + 0.000000001000000 + + + 0.010000000000000 + + + 0.000010000000000 + + + AnalysisTimeMinimumStep + + + Mod/Fem/Ccx + + + + + + + s + + + + + + + Time Maximum Step + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::DefaultContextMenu + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 9 + + + 0.000000001000000 + + + 1.000000000000000 + + + 1.000000000000000 + + + AnalysisTimeMaximumStep + + + Mod/Fem/Ccx + + + + s - + Beam, shell element 3D output format - + 3D Output, unchecked for 2D @@ -569,35 +711,6 @@ - - - - Maximum number of iterations - - - - - - - 1 - - - 10000000 - - - 10 - - - 2000 - - - AnalysisMaxIterations - - - Mod/Fem/Ccx - - - @@ -695,7 +808,7 @@ - + Hz diff --git a/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp b/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp index a5030ffd0a..bea9df709f 100644 --- a/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp +++ b/src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp @@ -73,6 +73,8 @@ void DlgSettingsFemCcxImp::saveSettings() ui->sb_ccx_max_iterations->onSave(); // Max number of iterations ui->dsb_ccx_initial_time_step->onSave(); // Initial time step ui->dsb_ccx_analysis_time->onSave(); // Analysis time + ui->dsb_ccx_minimum_time_step->onSave(); // Minimum time step + ui->dsb_ccx_maximum_time_step->onSave(); // Maximum time step ui->cb_analysis_type->onSave(); ui->cb_BeamShellOutput->onSave(); // Beam shell output 3d or 2d @@ -98,6 +100,8 @@ void DlgSettingsFemCcxImp::loadSettings() ui->sb_ccx_max_iterations->onRestore(); // Max number of iterations ui->dsb_ccx_initial_time_step->onRestore(); // Initial time step ui->dsb_ccx_analysis_time->onRestore(); // Analysis time + ui->dsb_ccx_minimum_time_step->onRestore(); // Minimum time step + ui->dsb_ccx_maximum_time_step->onRestore(); // Maximum time step ui->cb_analysis_type->onRestore(); ui->cb_BeamShellOutput->onRestore(); // Beam shell output 3d or 2d diff --git a/src/Mod/Fem/femexamples/thermomech_bimetall.py b/src/Mod/Fem/femexamples/thermomech_bimetall.py index a34763d9c3..8368eb703a 100644 --- a/src/Mod/Fem/femexamples/thermomech_bimetall.py +++ b/src/Mod/Fem/femexamples/thermomech_bimetall.py @@ -145,7 +145,7 @@ def setup(doc=None, solvertype="ccxtools"): # solver_obj.MatrixSolverType = "default" solver_obj.MatrixSolverType = "spooles" # thomas solver_obj.SplitInputWriter = False - solver_obj.IterationsThermoMechMaximum = 2000 + solver_obj.IterationsMaximum = 2000 # solver_obj.IterationsControlParameterTimeUse = True # thermomech spine analysis.addObject(solver_obj) diff --git a/src/Mod/Fem/femsolver/calculix/solver.py b/src/Mod/Fem/femsolver/calculix/solver.py index fb5d18aeaf..8d60061ed8 100644 --- a/src/Mod/Fem/femsolver/calculix/solver.py +++ b/src/Mod/Fem/femsolver/calculix/solver.py @@ -198,19 +198,23 @@ def add_attributes(obj, ccx_prefs): ehl = ccx_prefs.GetFloat("EigenmodeHighLimit", 1000000.0) obj.EigenmodeHighLimit = (ehl, 0.0, 1000000.0, 10000.0) - if not hasattr(obj, "IterationsThermoMechMaximum"): - help_string_IterationsThermoMechMaximum = ( - "Maximum Number of thermo mechanical iterations " + if not hasattr(obj, "IterationsMaximum"): + help_string_IterationsMaximum = ( + "Maximum Number of iterations " "in each time step before stopping jobs" ) obj.addProperty( "App::PropertyIntegerConstraint", - "IterationsThermoMechMaximum", + "IterationsMaximum", "Fem", - help_string_IterationsThermoMechMaximum + help_string_IterationsMaximum ) niter = ccx_prefs.GetInt("AnalysisMaxIterations", 200) - obj.IterationsThermoMechMaximum = niter + obj.IterationsMaximum = niter + + if hasattr(obj, "IterationsThermoMechMaximum"): + obj.IterationsMaximum = obj.IterationsThermoMechMaximum + obj.removeProperty("IterationsThermoMechMaximum") if not hasattr(obj, "BucklingFactors"): obj.addProperty( @@ -242,6 +246,26 @@ def add_attributes(obj, ccx_prefs): eni = ccx_prefs.GetFloat("AnalysisTime", 1.0) obj.TimeEnd = eni + if not hasattr(obj, "TimeMinimumStep"): + obj.addProperty( + "App::PropertyFloatConstraint", + "TimeMinimumStep", + "Fem", + "Minimum time step" + ) + mini = ccx_prefs.GetFloat("AnalysisTimeMinimumStep", 0.00001) + obj.TimeMinimumStep = mini + + if not hasattr(obj, "TimeMaximumStep"): + obj.addProperty( + "App::PropertyFloatConstraint", + "TimeMaximumStep", + "Fem", + "Maximum time step" + ) + maxi = ccx_prefs.GetFloat("AnalysisTimeMaximumStep", 1.0) + obj.TimeMaximumStep = maxi + if not hasattr(obj, "ThermoMechSteadyState"): obj.addProperty( "App::PropertyBool", @@ -332,7 +356,7 @@ def add_attributes(obj, ccx_prefs): if not hasattr(obj, "IterationsUserDefinedTimeStepLength"): help_string_IterationsUserDefinedTimeStepLength = ( "Set to True to use the user defined time steps. " - "The time steps are set with TimeInitialStep and TimeEnd" + "They are set with TimeInitialStep, TimeEnd, TimeMinimum and TimeMaximum" ) obj.addProperty( "App::PropertyBool", diff --git a/src/Mod/Fem/femsolver/calculix/write_step_equation.py b/src/Mod/Fem/femsolver/calculix/write_step_equation.py index e4beef37c2..02a06c1f1f 100644 --- a/src/Mod/Fem/femsolver/calculix/write_step_equation.py +++ b/src/Mod/Fem/femsolver/calculix/write_step_equation.py @@ -45,12 +45,11 @@ def write_step_equation(f, ccxwriter): "Analysis type frequency and geometrical nonlinear " "analysis are not allowed together, linear is used instead!\n" ) - if ccxwriter.solver_obj.IterationsThermoMechMaximum: - if ccxwriter.analysis_type == "thermomech": - step += ", INC={}".format(ccxwriter.solver_obj.IterationsThermoMechMaximum) + if ccxwriter.solver_obj.IterationsMaximum: + if ccxwriter.analysis_type == "thermomech" or ccxwriter.analysis_type == "static": + step += ", INC={}".format(ccxwriter.solver_obj.IterationsMaximum) elif ( - ccxwriter.analysis_type == "static" - or ccxwriter.analysis_type == "frequency" + ccxwriter.analysis_type == "frequency" or ccxwriter.analysis_type == "buckling" ): # parameter is for thermomechanical analysis only, see ccx manual *STEP @@ -124,9 +123,11 @@ def write_step_equation(f, ccxwriter): if ccxwriter.analysis_type == "static" or ccxwriter.analysis_type == "check": if ccxwriter.solver_obj.IterationsUserDefinedIncrementations is True \ or ccxwriter.solver_obj.IterationsUserDefinedTimeStepLength is True: - analysis_parameter = "{},{}".format( + analysis_parameter = "{},{},{},{}".format( ccxwriter.solver_obj.TimeInitialStep, - ccxwriter.solver_obj.TimeEnd + ccxwriter.solver_obj.TimeEnd, + ccxwriter.solver_obj.TimeMinimumStep, + ccxwriter.solver_obj.TimeMaximumStep ) elif ccxwriter.analysis_type == "frequency": if ccxwriter.solver_obj.EigenmodeLowLimit == 0.0 \ @@ -140,9 +141,11 @@ def write_step_equation(f, ccxwriter): ) elif ccxwriter.analysis_type == "thermomech": # OvG: 1.0 increment, total time 1 for steady state will cut back automatically - analysis_parameter = "{},{}".format( + analysis_parameter = "{},{},{},{}".format( ccxwriter.solver_obj.TimeInitialStep, - ccxwriter.solver_obj.TimeEnd + ccxwriter.solver_obj.TimeEnd, + ccxwriter.solver_obj.TimeMinimumStep, + ccxwriter.solver_obj.TimeMaximumStep ) elif ccxwriter.analysis_type == "buckling": analysis_parameter = "{}\n".format(ccxwriter.solver_obj.BucklingFactors) diff --git a/src/Mod/Fem/femtest/data/calculix/box_static.inp b/src/Mod/Fem/femtest/data/calculix/box_static.inp index b3380bd960..2357b51200 100644 --- a/src/Mod/Fem/femtest/data/calculix/box_static.inp +++ b/src/Mod/Fem/femtest/data/calculix/box_static.inp @@ -489,7 +489,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp index 31bcd23b49..c61d0b81d6 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp @@ -62,7 +62,7 @@ Eedges *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp index 3b00eee774..b2366b0f18 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp @@ -62,7 +62,7 @@ Eedges *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp index 6d3eb29682..158145c70a 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp @@ -62,7 +62,7 @@ Eedges *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp index 3060c64375..bd7c47159e 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp @@ -385,7 +385,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp index 4b9576c2bd..7b7cd41ff8 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp @@ -86,7 +86,7 @@ Efaces *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp index 111fdf49b7..a201a9c3a2 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp @@ -74,7 +74,7 @@ Efaces *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp index 73b099e0ef..fb0a022008 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp @@ -204,7 +204,7 @@ Eedges *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp index 49fb62c6b9..72dd4db87c 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp @@ -62,7 +62,7 @@ Eedges *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp index 7c062296e5..59da919b13 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp @@ -1562,7 +1562,7 @@ Efaces *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp index 5c0e12187f..04dda8c146 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp @@ -292,7 +292,7 @@ Efaces *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp index 3a6ab24119..03f0e80bd7 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp @@ -359,7 +359,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp index 107253f1d5..9198e5c3a4 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp @@ -359,7 +359,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp index 47cc8a4cc8..d4a67e562b 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp @@ -377,7 +377,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp b/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp index c39beee71f..1a6bc45f52 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp @@ -38373,7 +38373,7 @@ DEPConstraintContact, INDConstraintContact *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp b/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp index 2c83229bb6..501355f029 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp @@ -3401,7 +3401,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp b/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp index e27d347cf4..6eb8cf3780 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp @@ -2153,7 +2153,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp index c00b1d5d73..cc73eb5c7f 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp @@ -18612,7 +18612,7 @@ TIE_DEPConstraintTie, TIE_INDConstraintTie *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp b/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp index f20ff9d23f..23715d316d 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp @@ -3639,7 +3639,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp b/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp index dd776985b2..7489f67648 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp @@ -10980,7 +10980,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp index 517770b828..5ec1a3d3e7 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp @@ -27634,7 +27634,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp index 08f88f3935..9ac894a8c5 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp @@ -2548,7 +2548,7 @@ Efaces *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp b/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp index 193f9e780d..d378596781 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp @@ -1231,7 +1231,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp b/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp index e0e16e90ed..c2cb7c0505 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp @@ -20004,7 +20004,7 @@ Evolumes *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP, NLGEOM +*STEP, NLGEOM, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp index 4bc8f67606..a70ab0a392 100644 --- a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp +++ b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp @@ -2560,7 +2560,7 @@ Efaces *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp index 6aa03980b3..6e68876fff 100644 --- a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp +++ b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp @@ -2560,7 +2560,7 @@ Efaces *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD -*STEP +*STEP, INC=200 *STATIC diff --git a/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp b/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp index 3d95c77f3d..90fce0f689 100644 --- a/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp +++ b/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp @@ -7081,7 +7081,7 @@ Nall,273.0 ** At least one step is needed to run an CalculiX analysis of FreeCAD *STEP, INC=2000 *COUPLED TEMPERATURE-DISPLACEMENT, SOLVER=SPOOLES, STEADY STATE -1.0,1.0 +1.0,1.0,1e-05,1.0 *********************************************************** ** Fixed Constraints diff --git a/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp b/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp index c45a9841e4..1baf8ccb54 100644 --- a/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp +++ b/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp @@ -22,25 +22,25 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif #include @@ -60,81 +60,79 @@ using namespace PartGui; ////////////////////////////////////////////////////////////////////////// -class DlgProjectionOnSurface::EdgeSelection : public Gui::SelectionFilterGate +class DlgProjectionOnSurface::EdgeSelection: public Gui::SelectionFilterGate { public: - bool canSelect; + bool canSelect = false; - EdgeSelection() - : Gui::SelectionFilterGate(nullPointer()) - { - canSelect = false; - } - ~EdgeSelection() override = default; + EdgeSelection() + : Gui::SelectionFilterGate(nullPointer()) + {} - bool allow(App::Document* /*pDoc*/, App::DocumentObject* iPObj, const char* sSubName) override - { - Part::Feature* aPart = dynamic_cast(iPObj); - if (!aPart) - return false; - if (!sSubName) - return false; - std::string subName(sSubName); - if (subName.empty()) - return false; + bool allow(App::Document* /*pDoc*/, App::DocumentObject* iPObj, const char* sSubName) override + { + auto aPart = dynamic_cast(iPObj); + if (!aPart) { + return false; + } + if (!sSubName) { + return false; + } + std::string subName(sSubName); + if (subName.empty()) { + return false; + } - auto subShape = aPart->Shape.getShape().getSubShape(sSubName); - if (subShape.IsNull()) - return false; - auto type = subShape.ShapeType(); - if (type != TopAbs_EDGE) - return false; - return true; - } + auto subShape = aPart->Shape.getShape().getSubShape(sSubName); + if (subShape.IsNull()) { + return false; + } + auto type = subShape.ShapeType(); + return (type == TopAbs_EDGE); + } }; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -class DlgProjectionOnSurface::FaceSelection : public Gui::SelectionFilterGate +class DlgProjectionOnSurface::FaceSelection: public Gui::SelectionFilterGate { public: - bool canSelect; + bool canSelect = false; - FaceSelection() - : Gui::SelectionFilterGate(nullPointer()) - { - canSelect = false; - } - ~FaceSelection() override = default; + FaceSelection() + : Gui::SelectionFilterGate(nullPointer()) + {} - bool allow(App::Document* /*pDoc*/, App::DocumentObject* iPObj, const char* sSubName) override - { - Part::Feature* aPart = dynamic_cast(iPObj); - if (!aPart) - return false; - if (!sSubName) - return false; - std::string subName(sSubName); - if (subName.empty()) - return false; + bool allow(App::Document* /*pDoc*/, App::DocumentObject* iPObj, const char* sSubName) override + { + auto aPart = dynamic_cast(iPObj); + if (!aPart) { + return false; + } + if (!sSubName) { + return false; + } + std::string subName(sSubName); + if (subName.empty()) { + return false; + } - auto subShape = aPart->Shape.getShape().getSubShape(sSubName, true); - if (subShape.IsNull()) - return false; - auto type = subShape.ShapeType(); - if (type != TopAbs_FACE) - return false; - return true; - } + auto subShape = aPart->Shape.getShape().getSubShape(sSubName, true); + if (subShape.IsNull()) { + return false; + } + auto type = subShape.ShapeType(); + return (type == TopAbs_FACE); + } }; ////////////////////////////////////////////////////////////////////////// -DlgProjectionOnSurface::DlgProjectionOnSurface(QWidget *parent) - : QWidget(parent) - , ui(new Ui::DlgProjectionOnSurface) - , m_projectionObjectName(tr("Projection Object")) - , filterEdge(nullptr) - , filterFace(nullptr) +DlgProjectionOnSurface::DlgProjectionOnSurface(QWidget* parent) + : QWidget(parent) + , ui(new Ui::DlgProjectionOnSurface) + , m_projectionObjectName(tr("Projection Object")) + , filterEdge(nullptr) + , filterFace(nullptr) { ui->setupUi(this); setupConnections(); @@ -160,16 +158,15 @@ DlgProjectionOnSurface::DlgProjectionOnSurface(QWidget *parent) disable_ui_elements(m_guiObjectVec, ui->pushButtonAddProjFace); m_partDocument = App::GetApplication().getActiveDocument(); - if (!m_partDocument) - { - throw Base::ValueError(QString(tr("Have no active document!!!")).toUtf8()); + if (!m_partDocument) { + throw Base::ValueError(QString(tr("Have no active document!!!")).toUtf8()); } this->attachDocument(m_partDocument); m_partDocument->openTransaction("Project on surface"); - m_projectionObject = dynamic_cast(m_partDocument->addObject("Part::Feature", "Projection Object")); - if (!m_projectionObject) - { - throw Base::ValueError(QString(tr("Can not create a projection object!!!")).toUtf8()); + m_projectionObject = dynamic_cast( + m_partDocument->addObject("Part::Feature", "Projection Object")); + if (!m_projectionObject) { + throw Base::ValueError(QString(tr("Can not create a projection object!!!")).toUtf8()); } m_projectionObject->Label.setValue(std::string(m_projectionObjectName.toUtf8()).c_str()); onRadioButtonShowAllClicked(); @@ -178,965 +175,1013 @@ DlgProjectionOnSurface::DlgProjectionOnSurface(QWidget *parent) DlgProjectionOnSurface::~DlgProjectionOnSurface() { - delete ui; - for (const auto& it : m_projectionSurfaceVec) - { - try { - higlight_object(it.partFeature, it.partName, false, 0); + delete ui; + for (const auto& it : m_projectionSurfaceVec) { + try { + higlight_object(it.partFeature, it.partName, false, 0); + } + catch (Standard_NoSuchObject& e) { + Base::Console().Warning("DlgProjectionOnSurface::~DlgProjectionOnSurface: %s", + e.GetMessageString()); + } + auto vp = dynamic_cast( + Gui::Application::Instance->getViewProvider(it.partFeature)); + if (vp) { + vp->Selectable.setValue(it.is_selectable); + vp->Transparency.setValue(it.transparency); + } } - catch (Standard_NoSuchObject& e) { - Base::Console().Warning("DlgProjectionOnSurface::~DlgProjectionOnSurface: %s", e.GetMessageString()); + for (const auto& it : m_shapeVec) { + try { + higlight_object(it.partFeature, it.partName, false, 0); + } + catch (Standard_NoSuchObject& e) { + Base::Console().Warning("DlgProjectionOnSurface::~DlgProjectionOnSurface: %s", + e.GetMessageString()); + } } - PartGui::ViewProviderPartExt* vp = dynamic_cast(Gui::Application::Instance->getViewProvider(it.partFeature)); - if (vp) - { - vp->Selectable.setValue(it.is_selectable); - vp->Transparency.setValue(it.transparency); - } - } - for (const auto& it : m_shapeVec) - { - try { - higlight_object(it.partFeature, it.partName, false, 0); - } - catch (Standard_NoSuchObject& e) { - Base::Console().Warning("DlgProjectionOnSurface::~DlgProjectionOnSurface: %s", e.GetMessageString()); - } - } - Gui::Selection().rmvSelectionGate(); + Gui::Selection().rmvSelectionGate(); } void PartGui::DlgProjectionOnSurface::setupConnections() { - connect(ui->pushButtonAddFace, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonAddFaceClicked); - connect(ui->pushButtonAddEdge, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonAddEdgeClicked); - connect(ui->pushButtonGetCurrentCamDir, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonGetCurrentCamDirClicked); - connect(ui->pushButtonDirX, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonDirXClicked); - connect(ui->pushButtonDirY, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonDirYClicked); - connect(ui->pushButtonDirZ, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonDirZClicked); - connect(ui->pushButtonAddProjFace, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonAddProjFaceClicked); - connect(ui->radioButtonShowAll, &QRadioButton::clicked, - this, &DlgProjectionOnSurface::onRadioButtonShowAllClicked); - connect(ui->radioButtonFaces, &QRadioButton::clicked, - this, &DlgProjectionOnSurface::onRadioButtonFacesClicked); - connect(ui->radioButtonEdges, &QRadioButton::clicked, - this, &DlgProjectionOnSurface::onRadioButtonEdgesClicked); - connect(ui->doubleSpinBoxExtrudeHeight, qOverload(&QDoubleSpinBox::valueChanged), - this, &DlgProjectionOnSurface::onDoubleSpinBoxExtrudeHeightValueChanged); - connect(ui->pushButtonAddWire, &QPushButton::clicked, - this, &DlgProjectionOnSurface::onPushButtonAddWireClicked); - connect(ui->doubleSpinBoxSolidDepth, qOverload(&QDoubleSpinBox::valueChanged), - this, &DlgProjectionOnSurface::onDoubleSpinBoxSolidDepthValueChanged); + connect(ui->pushButtonAddFace, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonAddFaceClicked); + connect(ui->pushButtonAddEdge, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonAddEdgeClicked); + connect(ui->pushButtonGetCurrentCamDir, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonGetCurrentCamDirClicked); + connect(ui->pushButtonDirX, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonDirXClicked); + connect(ui->pushButtonDirY, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonDirYClicked); + connect(ui->pushButtonDirZ, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonDirZClicked); + connect(ui->pushButtonAddProjFace, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonAddProjFaceClicked); + connect(ui->radioButtonShowAll, + &QRadioButton::clicked, + this, + &DlgProjectionOnSurface::onRadioButtonShowAllClicked); + connect(ui->radioButtonFaces, + &QRadioButton::clicked, + this, + &DlgProjectionOnSurface::onRadioButtonFacesClicked); + connect(ui->radioButtonEdges, + &QRadioButton::clicked, + this, + &DlgProjectionOnSurface::onRadioButtonEdgesClicked); + connect(ui->doubleSpinBoxExtrudeHeight, + qOverload(&QDoubleSpinBox::valueChanged), + this, + &DlgProjectionOnSurface::onDoubleSpinBoxExtrudeHeightValueChanged); + connect(ui->pushButtonAddWire, + &QPushButton::clicked, + this, + &DlgProjectionOnSurface::onPushButtonAddWireClicked); + connect(ui->doubleSpinBoxSolidDepth, + qOverload(&QDoubleSpinBox::valueChanged), + this, + &DlgProjectionOnSurface::onDoubleSpinBoxSolidDepthValueChanged); } void PartGui::DlgProjectionOnSurface::slotDeletedDocument(const App::Document& Doc) { - if (m_partDocument == &Doc) { - m_partDocument = nullptr; - m_projectionObject = nullptr; - } + if (m_partDocument == &Doc) { + m_partDocument = nullptr; + m_projectionObject = nullptr; + } } void PartGui::DlgProjectionOnSurface::slotDeletedObject(const App::DocumentObject& Obj) { - if (m_projectionObject == &Obj) { - m_projectionObject = nullptr; - } + if (m_projectionObject == &Obj) { + m_projectionObject = nullptr; + } } void PartGui::DlgProjectionOnSurface::apply() { - if (m_partDocument) - m_partDocument->commitTransaction(); + if (m_partDocument) { + m_partDocument->commitTransaction(); + } } void PartGui::DlgProjectionOnSurface::reject() { - if (m_partDocument) - m_partDocument->abortTransaction(); + if (m_partDocument) { + m_partDocument->abortTransaction(); + } } void PartGui::DlgProjectionOnSurface::onPushButtonAddFaceClicked() { - if ( ui->pushButtonAddFace->isChecked() ) - { - m_currentSelection = "add_face"; - disable_ui_elements(m_guiObjectVec, ui->pushButtonAddFace); - if (!filterFace) - { - filterFace = new FaceSelection(); - Gui::Selection().addSelectionGate(filterFace); + if (ui->pushButtonAddFace->isChecked()) { + m_currentSelection = "add_face"; + disable_ui_elements(m_guiObjectVec, ui->pushButtonAddFace); + if (!filterFace) { + filterFace = new FaceSelection(); + Gui::Selection().addSelectionGate(filterFace); + } + } + else { + m_currentSelection = ""; + enable_ui_elements(m_guiObjectVec, nullptr); + Gui::Selection().rmvSelectionGate(); + filterFace = nullptr; } - } - else - { - m_currentSelection = ""; - enable_ui_elements(m_guiObjectVec, nullptr); - Gui::Selection().rmvSelectionGate(); - filterFace = nullptr; - } } void PartGui::DlgProjectionOnSurface::onPushButtonAddEdgeClicked() { - if (ui->pushButtonAddEdge->isChecked()) - { - m_currentSelection = "add_edge"; - disable_ui_elements(m_guiObjectVec, ui->pushButtonAddEdge); - if (!filterEdge) - { - filterEdge = new EdgeSelection(); - Gui::Selection().addSelectionGate(filterEdge); + if (ui->pushButtonAddEdge->isChecked()) { + m_currentSelection = "add_edge"; + disable_ui_elements(m_guiObjectVec, ui->pushButtonAddEdge); + if (!filterEdge) { + filterEdge = new EdgeSelection(); + Gui::Selection().addSelectionGate(filterEdge); + } + ui->radioButtonEdges->setChecked(true); + onRadioButtonEdgesClicked(); + } + else { + m_currentSelection = ""; + enable_ui_elements(m_guiObjectVec, nullptr); + Gui::Selection().rmvSelectionGate(); + filterEdge = nullptr; } - ui->radioButtonEdges->setChecked(true); - onRadioButtonEdgesClicked(); - } - else - { - m_currentSelection = ""; - enable_ui_elements(m_guiObjectVec, nullptr); - Gui::Selection().rmvSelectionGate(); - filterEdge = nullptr; - } } void PartGui::DlgProjectionOnSurface::onPushButtonGetCurrentCamDirClicked() { - get_camera_direction(); + get_camera_direction(); } void PartGui::DlgProjectionOnSurface::onPushButtonDirXClicked() { - set_xyz_dir_spinbox(ui->doubleSpinBoxDirX); + set_xyz_dir_spinbox(ui->doubleSpinBoxDirX); } void PartGui::DlgProjectionOnSurface::onPushButtonDirYClicked() { - set_xyz_dir_spinbox(ui->doubleSpinBoxDirY); + set_xyz_dir_spinbox(ui->doubleSpinBoxDirY); } void PartGui::DlgProjectionOnSurface::onPushButtonDirZClicked() { - set_xyz_dir_spinbox(ui->doubleSpinBoxDirZ); + set_xyz_dir_spinbox(ui->doubleSpinBoxDirZ); } void PartGui::DlgProjectionOnSurface::onSelectionChanged(const Gui::SelectionChanges& msg) { - if (msg.Type == Gui::SelectionChanges::AddSelection) - { - if ( m_currentSelection == "add_face" || m_currentSelection == "add_edge" || m_currentSelection == "add_wire") - { - store_current_selected_parts(m_shapeVec, 0xff00ff00); - create_projection_wire(m_shapeVec); - create_projection_face_from_wire(m_shapeVec); - create_face_extrude(m_shapeVec); - show_projected_shapes(m_shapeVec); - } - else if (m_currentSelection == "add_projection_surface") - { - m_projectionSurfaceVec.clear(); - store_current_selected_parts(m_projectionSurfaceVec, 0xffff0000); - if (!m_projectionSurfaceVec.empty()) - { - PartGui::ViewProviderPartExt* vp = dynamic_cast(Gui::Application::Instance->getViewProvider(m_projectionSurfaceVec.back().partFeature)); - if (vp) - { - vp->Selectable.setValue(false); - vp->Transparency.setValue(90); + if (msg.Type == Gui::SelectionChanges::AddSelection) { + if (m_currentSelection == "add_face" || m_currentSelection == "add_edge" + || m_currentSelection == "add_wire") { + store_current_selected_parts(m_shapeVec, 0xff00ff00); + create_projection_wire(m_shapeVec); + create_projection_face_from_wire(m_shapeVec); + create_face_extrude(m_shapeVec); + show_projected_shapes(m_shapeVec); } - } + else if (m_currentSelection == "add_projection_surface") { + m_projectionSurfaceVec.clear(); + store_current_selected_parts(m_projectionSurfaceVec, 0xffff0000); + if (!m_projectionSurfaceVec.empty()) { + auto vp = dynamic_cast( + Gui::Application::Instance->getViewProvider( + m_projectionSurfaceVec.back().partFeature)); + if (vp) { + vp->Selectable.setValue(false); + vp->Transparency.setValue(90); + } + } - ui->pushButtonAddProjFace->setChecked(false); - onPushButtonAddProjFaceClicked(); + ui->pushButtonAddProjFace->setChecked(false); + onPushButtonAddProjFaceClicked(); + } } - } } void PartGui::DlgProjectionOnSurface::get_camera_direction() { - auto mainWindow = Gui::getMainWindow(); + auto mainWindow = Gui::getMainWindow(); - auto mdiObject = dynamic_cast(mainWindow->activeWindow()); - if (!mdiObject) - return; - auto camerRotation = mdiObject->getViewer()->getCameraOrientation(); - - SbVec3f lookAt(0, 0, -1); - camerRotation.multVec(lookAt, lookAt); - - float valX, valY, valZ; - lookAt.getValue(valX, valY, valZ); - - ui->doubleSpinBoxDirX->setValue(valX); - ui->doubleSpinBoxDirY->setValue(valY); - ui->doubleSpinBoxDirZ->setValue(valZ); -} - -void PartGui::DlgProjectionOnSurface::store_current_selected_parts(std::vector& iStoreVec, const unsigned int iColor) -{ - if (!m_partDocument) - return; - std::vector selObj = Gui::Selection().getSelectionEx(); - if (!selObj.empty()) - { - for (auto it = selObj.begin(); it != selObj.end(); ++it) - { - auto aPart = dynamic_cast(it->getObject()); - if (!aPart) continue; - - if (aPart) - { - SShapeStore currentShapeStore; - currentShapeStore.inputShape = aPart->Shape.getShape().getShape(); - currentShapeStore.partFeature = aPart; - currentShapeStore.partName = aPart->getNameInDocument(); - - PartGui::ViewProviderPartExt* vp = dynamic_cast(Gui::Application::Instance->getViewProvider(aPart)); - if (vp) - { - currentShapeStore.is_selectable = vp->Selectable.getValue(); - currentShapeStore.transparency = vp->Transparency.getValue(); - } - if (!it->getSubNames().empty() ) - { - auto parentShape = currentShapeStore.inputShape; - for (const auto & itName : selObj.front().getSubNames()) - { - auto currentShape = aPart->Shape.getShape().getSubShape(itName.c_str()); - - transform_shape_to_global_position(currentShape, aPart); - - currentShapeStore.inputShape = currentShape; - currentShapeStore.partName = itName; - auto store = store_part_in_vector(currentShapeStore, iStoreVec); - higlight_object(aPart, itName, store, iColor); - store_wire_in_vector(currentShapeStore, parentShape, iStoreVec, iColor); - } - } - else - { - transform_shape_to_global_position(currentShapeStore.inputShape,currentShapeStore.partFeature); - auto store = store_part_in_vector(currentShapeStore, iStoreVec); - higlight_object(aPart, aPart->Shape.getName(), store, iColor); - } - Gui::Selection().clearSelection(m_partDocument->getName()); - Gui::Selection().rmvPreselect(); - } - } - } -} - -bool PartGui::DlgProjectionOnSurface::store_part_in_vector(SShapeStore& iCurrentShape, std::vector& iStoreVec) -{ - if (iCurrentShape.inputShape.IsNull()) - return false; - auto currentType = iCurrentShape.inputShape.ShapeType(); - for ( auto it = iStoreVec.begin(); it != iStoreVec.end(); ++it) - { - if ( currentType == TopAbs_FACE ) - { - if (it->aFace.IsSame(iCurrentShape.inputShape)) - { - iStoreVec.erase(it); - return false; - } - } - else if ( currentType == TopAbs_EDGE ) - { - if (it->aEdge.IsSame(iCurrentShape.inputShape)) - { - iStoreVec.erase(it); - return false; - } - } - } - - if (currentType == TopAbs_FACE) - { - iCurrentShape.aFace = TopoDS::Face(iCurrentShape.inputShape); - } - else if (currentType == TopAbs_EDGE) - { - iCurrentShape.aEdge = TopoDS::Edge(iCurrentShape.inputShape); - } - - auto valX = ui->doubleSpinBoxDirX->value(); - auto valY = ui->doubleSpinBoxDirY->value(); - auto valZ = ui->doubleSpinBoxDirZ->value(); - - iCurrentShape.aProjectionDir = gp_Dir(valX, valY, valZ); - if ( !m_projectionSurfaceVec.empty() ) - { - iCurrentShape.surfaceToProject = m_projectionSurfaceVec.front().aFace; - } - iStoreVec.push_back(iCurrentShape); - return true; -} - -void PartGui::DlgProjectionOnSurface::create_projection_wire(std::vector& iCurrentShape) -{ - try - { - if (iCurrentShape.empty()) + auto mdiObject = dynamic_cast(mainWindow->activeWindow()); + if (!mdiObject) { return; - for ( auto &itCurrentShape : iCurrentShape ) - { - if (m_projectionSurfaceVec.empty()) continue;; - if (!itCurrentShape.aProjectedEdgeVec.empty()) continue;; - if (!itCurrentShape.aProjectedFace.IsNull()) continue;; - if (!itCurrentShape.aProjectedWireVec.empty()) continue;; - - if (!itCurrentShape.aFace.IsNull()) - { - get_all_wire_from_face(itCurrentShape); - for (const auto& itWire : itCurrentShape.aWireVec) - { - BRepProj_Projection aProjection(itWire, itCurrentShape.surfaceToProject, itCurrentShape.aProjectionDir); - double minDistance = std::numeric_limits::max(); - TopoDS_Wire wireToTake; - for ( ; aProjection.More(); aProjection.Next() ) - { - auto it = aProjection.Current(); - BRepExtrema_DistShapeShape distanceMeasure(it, itCurrentShape.aFace); - distanceMeasure.Perform(); - auto currentDistance = distanceMeasure.Value(); - if ( currentDistance > minDistance ) continue; - wireToTake = it; - minDistance = currentDistance; - } - auto aWire = sort_and_heal_wire(wireToTake, itCurrentShape.surfaceToProject); - itCurrentShape.aProjectedWireVec.push_back(aWire); - } - } - else if (!itCurrentShape.aEdge.IsNull()) - { - BRepProj_Projection aProjection(itCurrentShape.aEdge, itCurrentShape.surfaceToProject, itCurrentShape.aProjectionDir); - double minDistance = std::numeric_limits::max(); - TopoDS_Wire wireToTake; - for (; aProjection.More(); aProjection.Next()) - { - auto it = aProjection.Current(); - BRepExtrema_DistShapeShape distanceMeasure(it, itCurrentShape.aEdge); - distanceMeasure.Perform(); - auto currentDistance = distanceMeasure.Value(); - if (currentDistance > minDistance) continue; - wireToTake = it; - minDistance = currentDistance; - } - for (TopExp_Explorer aExplorer(wireToTake, TopAbs_EDGE); aExplorer.More(); aExplorer.Next()) - { - itCurrentShape.aProjectedEdgeVec.push_back(TopoDS::Edge(aExplorer.Current())); - } - } - } - } - catch (const Standard_Failure& error) - { - std::stringstream ssOcc; - error.Print(ssOcc); - throw Base::ValueError(ssOcc.str().c_str()); - } + auto camerRotation = mdiObject->getViewer()->getCameraOrientation(); + + SbVec3f lookAt(0, 0, -1); + camerRotation.multVec(lookAt, lookAt); + + float valX {}; + float valY {}; + float valZ {}; + lookAt.getValue(valX, valY, valZ); + + ui->doubleSpinBoxDirX->setValue(valX); + ui->doubleSpinBoxDirY->setValue(valY); + ui->doubleSpinBoxDirZ->setValue(valZ); } -TopoDS_Shape PartGui::DlgProjectionOnSurface::create_compound(const std::vector& iShapeVec) +void PartGui::DlgProjectionOnSurface::store_current_selected_parts( + std::vector& iStoreVec, + unsigned int iColor) { - if (iShapeVec.empty()) - return {}; - - TopoDS_Compound aCompound; - TopoDS_Builder aBuilder; - aBuilder.MakeCompound(aCompound); - - for (const auto& it : iShapeVec) - { - if ( m_currentShowType == "edges" ) - { - for (const auto& it2 : it.aProjectedEdgeVec) - { - aBuilder.Add(aCompound, it2); - } - for (const auto& it2 : it.aProjectedWireVec) - { - aBuilder.Add(aCompound, it2); - } - continue; - } - else if ( m_currentShowType == "faces" ) - { - if (it.aProjectedFace.IsNull()) - { - for (const auto& it2 : it.aProjectedWireVec) - { - if (!it2.IsNull()) - { - aBuilder.Add(aCompound, it2); - } - } - } - else aBuilder.Add(aCompound, it.aProjectedFace); - continue; - } - else if ( m_currentShowType == "all" ) - { - if (!it.aProjectedSolid.IsNull()) - { - aBuilder.Add(aCompound, it.aProjectedSolid); - } - else if ( !it.aProjectedFace.IsNull() ) - { - aBuilder.Add(aCompound, it.aProjectedFace); - } - else if (!it.aProjectedWireVec.empty()) - { - for (const auto& itWire : it.aProjectedWireVec ) - { - if ( itWire.IsNull() ) continue; - aBuilder.Add(aCompound, itWire); - } - } - else if (!it.aProjectedEdgeVec.empty()) - { - for (const auto& itEdge : it.aProjectedEdgeVec) - { - if (itEdge.IsNull()) continue; - aBuilder.Add(aCompound, itEdge); - } - } - } - } - return TopoDS_Shape(std::move(aCompound)); -} - -void PartGui::DlgProjectionOnSurface::show_projected_shapes(const std::vector& iShapeStoreVec) -{ - if (!m_projectionObject) - return; - auto aCompound = create_compound(iShapeStoreVec); - if ( aCompound.IsNull() ) - { - if (!m_partDocument) + if (!m_partDocument) { return; - m_projectionObject->Shape.setValue(TopoDS_Shape()); - return; - } - auto currentPlacement = m_projectionObject->Placement.getValue(); - m_projectionObject->Shape.setValue(aCompound); - m_projectionObject->Placement.setValue(currentPlacement); + } + std::vector selObj = Gui::Selection().getSelectionEx(); + if (!selObj.empty()) { + for (auto it = selObj.begin(); it != selObj.end(); ++it) { + auto aPart = dynamic_cast(it->getObject()); + if (!aPart) { + continue; + } - //set color - PartGui::ViewProviderPartExt* vp = dynamic_cast(Gui::Application::Instance->getViewProvider(m_projectionObject)); - if (vp) - { - vp->LineColor.setValue(0x8ae23400); - vp->ShapeColor.setValue(0x8ae23400); - vp->PointColor.setValue(0x8ae23400); - vp->Transparency.setValue(0); - } + if (aPart) { + SShapeStore currentShapeStore; + currentShapeStore.inputShape = aPart->Shape.getShape().getShape(); + currentShapeStore.partFeature = aPart; + currentShapeStore.partName = aPart->getNameInDocument(); + + auto vp = dynamic_cast( + Gui::Application::Instance->getViewProvider(aPart)); + if (vp) { + currentShapeStore.is_selectable = vp->Selectable.getValue(); + currentShapeStore.transparency = vp->Transparency.getValue(); + } + if (!it->getSubNames().empty()) { + auto parentShape = currentShapeStore.inputShape; + for (const auto& itName : selObj.front().getSubNames()) { + auto currentShape = aPart->Shape.getShape().getSubShape(itName.c_str()); + + transform_shape_to_global_position(currentShape, aPart); + + currentShapeStore.inputShape = currentShape; + currentShapeStore.partName = itName; + auto store = store_part_in_vector(currentShapeStore, iStoreVec); + higlight_object(aPart, itName, store, iColor); + store_wire_in_vector(currentShapeStore, parentShape, iStoreVec, iColor); + } + } + else { + transform_shape_to_global_position(currentShapeStore.inputShape, + currentShapeStore.partFeature); + auto store = store_part_in_vector(currentShapeStore, iStoreVec); + higlight_object(aPart, aPart->Shape.getName(), store, iColor); + } + Gui::Selection().clearSelection(m_partDocument->getName()); + Gui::Selection().rmvPreselect(); + } + } + } } -void PartGui::DlgProjectionOnSurface::disable_ui_elements(const std::vector& iObjectVec, QWidget* iExceptThis) +bool PartGui::DlgProjectionOnSurface::store_part_in_vector(SShapeStore& iCurrentShape, + std::vector& iStoreVec) { - for ( auto it : iObjectVec ) - { - if ( !it ) continue; - if ( it == iExceptThis ) continue; - it->setDisabled(true); - } + if (iCurrentShape.inputShape.IsNull()) { + return false; + } + auto currentType = iCurrentShape.inputShape.ShapeType(); + for (auto it = iStoreVec.begin(); it != iStoreVec.end(); ++it) { + if (currentType == TopAbs_FACE) { + if (it->aFace.IsSame(iCurrentShape.inputShape)) { + iStoreVec.erase(it); + return false; + } + } + else if (currentType == TopAbs_EDGE) { + if (it->aEdge.IsSame(iCurrentShape.inputShape)) { + iStoreVec.erase(it); + return false; + } + } + } + + if (currentType == TopAbs_FACE) { + iCurrentShape.aFace = TopoDS::Face(iCurrentShape.inputShape); + } + else if (currentType == TopAbs_EDGE) { + iCurrentShape.aEdge = TopoDS::Edge(iCurrentShape.inputShape); + } + + auto valX = ui->doubleSpinBoxDirX->value(); + auto valY = ui->doubleSpinBoxDirY->value(); + auto valZ = ui->doubleSpinBoxDirZ->value(); + + iCurrentShape.aProjectionDir = gp_Dir(valX, valY, valZ); + if (!m_projectionSurfaceVec.empty()) { + iCurrentShape.surfaceToProject = m_projectionSurfaceVec.front().aFace; + } + iStoreVec.push_back(iCurrentShape); + return true; } -void PartGui::DlgProjectionOnSurface::enable_ui_elements(const std::vector& iObjectVec, QWidget* iExceptThis) +void PartGui::DlgProjectionOnSurface::create_projection_wire( + std::vector& iCurrentShape) { - for (auto it : iObjectVec) - { - if (!it) continue; - if (it == iExceptThis) continue; - it->setEnabled(true); - } + try { + if (iCurrentShape.empty()) { + return; + } + for (auto& itCurrentShape : iCurrentShape) { + if (m_projectionSurfaceVec.empty()) { + continue; + }; + if (!itCurrentShape.aProjectedEdgeVec.empty()) { + continue; + }; + if (!itCurrentShape.aProjectedFace.IsNull()) { + continue; + }; + if (!itCurrentShape.aProjectedWireVec.empty()) { + continue; + }; + + if (!itCurrentShape.aFace.IsNull()) { + get_all_wire_from_face(itCurrentShape); + for (const auto& itWire : itCurrentShape.aWireVec) { + BRepProj_Projection aProjection(itWire, + itCurrentShape.surfaceToProject, + itCurrentShape.aProjectionDir); + double minDistance = std::numeric_limits::max(); + TopoDS_Wire wireToTake; + for (; aProjection.More(); aProjection.Next()) { + auto it = aProjection.Current(); + BRepExtrema_DistShapeShape distanceMeasure(it, itCurrentShape.aFace); + distanceMeasure.Perform(); + auto currentDistance = distanceMeasure.Value(); + if (currentDistance > minDistance) { + continue; + } + wireToTake = it; + minDistance = currentDistance; + } + auto aWire = sort_and_heal_wire(wireToTake, itCurrentShape.surfaceToProject); + itCurrentShape.aProjectedWireVec.push_back(aWire); + } + } + else if (!itCurrentShape.aEdge.IsNull()) { + BRepProj_Projection aProjection(itCurrentShape.aEdge, + itCurrentShape.surfaceToProject, + itCurrentShape.aProjectionDir); + double minDistance = std::numeric_limits::max(); + TopoDS_Wire wireToTake; + for (; aProjection.More(); aProjection.Next()) { + auto it = aProjection.Current(); + BRepExtrema_DistShapeShape distanceMeasure(it, itCurrentShape.aEdge); + distanceMeasure.Perform(); + auto currentDistance = distanceMeasure.Value(); + if (currentDistance > minDistance) { + continue; + } + wireToTake = it; + minDistance = currentDistance; + } + for (TopExp_Explorer aExplorer(wireToTake, TopAbs_EDGE); aExplorer.More(); + aExplorer.Next()) { + itCurrentShape.aProjectedEdgeVec.push_back(TopoDS::Edge(aExplorer.Current())); + } + } + } + } + catch (const Standard_Failure& error) { + std::stringstream ssOcc; + error.Print(ssOcc); + throw Base::ValueError(ssOcc.str().c_str()); + } } -void PartGui::DlgProjectionOnSurface::higlight_object(Part::Feature* iCurrentObject, const std::string& iShapeName, bool iHighlight, const unsigned int iColor) +TopoDS_Shape +PartGui::DlgProjectionOnSurface::create_compound(const std::vector& iShapeVec) { - if (!iCurrentObject) - return; - auto partenShape = iCurrentObject->Shape.getShape().getShape(); - auto subShape = iCurrentObject->Shape.getShape().getSubShape(iShapeName.c_str(), true); - - TopoDS_Shape currentShape = subShape; - if (subShape.IsNull()) currentShape = partenShape; - - auto currentShapeType = currentShape.ShapeType(); - TopTools_IndexedMapOfShape anIndices; - TopExp::MapShapes(partenShape, currentShapeType, anIndices); - if (anIndices.IsEmpty()) - return; - if (!anIndices.Contains(currentShape)) - return; - auto index = anIndices.FindIndex(currentShape); - - //set color - PartGui::ViewProviderPartExt* vp = dynamic_cast(Gui::Application::Instance->getViewProvider(iCurrentObject)); - if (vp) - { - std::vector colors; - App::Color defaultColor; - if (currentShapeType == TopAbs_FACE) - { - colors = vp->DiffuseColor.getValues(); - defaultColor = vp->ShapeColor.getValue(); - } - else if ( currentShapeType == TopAbs_EDGE ) - { - colors = vp->LineColorArray.getValues(); - defaultColor = vp->LineColor.getValue(); + if (iShapeVec.empty()) { + return {}; } - if ( static_cast(colors.size()) != anIndices.Extent() ) - { - colors.resize(anIndices.Extent(), defaultColor); + TopoDS_Compound aCompound; + TopoDS_Builder aBuilder; + aBuilder.MakeCompound(aCompound); + + for (const auto& it : iShapeVec) { + if (m_currentShowType == "edges") { + for (const auto& it2 : it.aProjectedEdgeVec) { + aBuilder.Add(aCompound, it2); + } + for (const auto& it2 : it.aProjectedWireVec) { + aBuilder.Add(aCompound, it2); + } + } + else if (m_currentShowType == "faces") { + if (it.aProjectedFace.IsNull()) { + for (const auto& it2 : it.aProjectedWireVec) { + if (!it2.IsNull()) { + aBuilder.Add(aCompound, it2); + } + } + } + else { + aBuilder.Add(aCompound, it.aProjectedFace); + } + } + else if (m_currentShowType == "all") { + if (!it.aProjectedSolid.IsNull()) { + aBuilder.Add(aCompound, it.aProjectedSolid); + } + else if (!it.aProjectedFace.IsNull()) { + aBuilder.Add(aCompound, it.aProjectedFace); + } + else if (!it.aProjectedWireVec.empty()) { + for (const auto& itWire : it.aProjectedWireVec) { + if (itWire.IsNull()) { + continue; + } + aBuilder.Add(aCompound, itWire); + } + } + else if (!it.aProjectedEdgeVec.empty()) { + for (const auto& itEdge : it.aProjectedEdgeVec) { + if (itEdge.IsNull()) { + continue; + } + aBuilder.Add(aCompound, itEdge); + } + } + } + } + return {std::move(aCompound)}; +} + +void PartGui::DlgProjectionOnSurface::show_projected_shapes( + const std::vector& iShapeStoreVec) +{ + if (!m_projectionObject) { + return; + } + auto aCompound = create_compound(iShapeStoreVec); + if (aCompound.IsNull()) { + if (!m_partDocument) { + return; + } + m_projectionObject->Shape.setValue(TopoDS_Shape()); + return; + } + auto currentPlacement = m_projectionObject->Placement.getValue(); + m_projectionObject->Shape.setValue(aCompound); + m_projectionObject->Placement.setValue(currentPlacement); + + // set color + auto vp = dynamic_cast( + Gui::Application::Instance->getViewProvider(m_projectionObject)); + if (vp) { + const unsigned int color = 0x8ae23400; + vp->LineColor.setValue(color); + vp->ShapeColor.setValue(color); + vp->PointColor.setValue(color); + vp->Transparency.setValue(0); + } +} + +void PartGui::DlgProjectionOnSurface::disable_ui_elements(const std::vector& iObjectVec, + QWidget* iExceptThis) +{ + for (auto it : iObjectVec) { + if (!it) { + continue; + } + if (it == iExceptThis) { + continue; + } + it->setDisabled(true); + } +} + +void PartGui::DlgProjectionOnSurface::enable_ui_elements(const std::vector& iObjectVec, + QWidget* iExceptThis) +{ + for (auto it : iObjectVec) { + if (!it) { + continue; + } + if (it == iExceptThis) { + continue; + } + it->setEnabled(true); + } +} + +void PartGui::DlgProjectionOnSurface::higlight_object(Part::Feature* iCurrentObject, + const std::string& iShapeName, + bool iHighlight, + unsigned int iColor) +{ + if (!iCurrentObject) { + return; + } + auto partenShape = iCurrentObject->Shape.getShape().getShape(); + auto subShape = iCurrentObject->Shape.getShape().getSubShape(iShapeName.c_str(), true); + + TopoDS_Shape currentShape = subShape; + if (subShape.IsNull()) { + currentShape = partenShape; } - if ( iHighlight ) - { - App::Color aColor; - aColor.setPackedValue(iColor); - colors.at(index - 1) = aColor; + auto currentShapeType = currentShape.ShapeType(); + TopTools_IndexedMapOfShape anIndices; + TopExp::MapShapes(partenShape, currentShapeType, anIndices); + if (anIndices.IsEmpty()) { + return; } - else - { - colors.at(index - 1) = defaultColor; + if (!anIndices.Contains(currentShape)) { + return; } - if (currentShapeType == TopAbs_FACE) - { - vp->DiffuseColor.setValues(colors); + auto index = anIndices.FindIndex(currentShape); + + // set color + auto vp = dynamic_cast( + Gui::Application::Instance->getViewProvider(iCurrentObject)); + if (vp) { + std::vector colors; + App::Color defaultColor; + if (currentShapeType == TopAbs_FACE) { + colors = vp->DiffuseColor.getValues(); + defaultColor = vp->ShapeColor.getValue(); + } + else if (currentShapeType == TopAbs_EDGE) { + colors = vp->LineColorArray.getValues(); + defaultColor = vp->LineColor.getValue(); + } + + if (static_cast(colors.size()) != anIndices.Extent()) { + colors.resize(anIndices.Extent(), defaultColor); + } + + if (iHighlight) { + App::Color aColor; + aColor.setPackedValue(iColor); + colors.at(index - 1) = aColor; + } + else { + colors.at(index - 1) = defaultColor; + } + if (currentShapeType == TopAbs_FACE) { + vp->DiffuseColor.setValues(colors); + } + else if (currentShapeType == TopAbs_EDGE) { + vp->LineColorArray.setValues(colors); + } } - else if (currentShapeType == TopAbs_EDGE) - { - vp->LineColorArray.setValues(colors); - } - } } void PartGui::DlgProjectionOnSurface::get_all_wire_from_face(SShapeStore& ioCurrentSahpe) { - auto outerWire = ShapeAnalysis::OuterWire(ioCurrentSahpe.aFace); - ioCurrentSahpe.aWireVec.push_back(outerWire); - for (TopExp_Explorer aExplorer(ioCurrentSahpe.aFace, TopAbs_WIRE); aExplorer.More(); aExplorer.Next()) - { - auto currentWire = TopoDS::Wire(aExplorer.Current()); - if (currentWire.IsSame(outerWire)) continue; - ioCurrentSahpe.aWireVec.push_back(currentWire); - } -} - -void PartGui::DlgProjectionOnSurface::create_projection_face_from_wire(std::vector& iCurrentShape) -{ - try - { - if (iCurrentShape.empty()) - return; - - for ( auto &itCurrentShape : iCurrentShape ) - { - if (itCurrentShape.aFace.IsNull()) continue;; - if (itCurrentShape.aProjectedWireVec.empty()) continue;; - if (!itCurrentShape.aProjectedFace.IsNull()) continue;; - - auto surface = BRep_Tool::Surface(itCurrentShape.surfaceToProject); - - //create a wire of all edges in parametric space on the surface of the face to projected - // --> otherwise BRepBuilderAPI_MakeFace can not make a face from the wire! - for (const auto& itWireVec : itCurrentShape.aProjectedWireVec) - { - std::vector edgeVec; - for (TopExp_Explorer aExplorer(itWireVec, TopAbs_EDGE); aExplorer.More(); aExplorer.Next()) - { - auto currentEdge = TopoDS::Edge(aExplorer.Current()); - edgeVec.push_back(currentEdge); + auto outerWire = ShapeAnalysis::OuterWire(ioCurrentSahpe.aFace); + ioCurrentSahpe.aWireVec.push_back(outerWire); + for (TopExp_Explorer aExplorer(ioCurrentSahpe.aFace, TopAbs_WIRE); aExplorer.More(); + aExplorer.Next()) { + auto currentWire = TopoDS::Wire(aExplorer.Current()); + if (currentWire.IsSame(outerWire)) { + continue; } - if (edgeVec.empty()) continue; - - std::vector edgeInParametricSpaceVec; - for (auto itEdge : edgeVec) - { - Standard_Real first, last; - auto currentCurve = BRep_Tool::CurveOnSurface(TopoDS::Edge(itEdge), itCurrentShape.surfaceToProject, first, last); - if (!currentCurve) continue; - auto edgeInParametricSpace = BRepBuilderAPI_MakeEdge(currentCurve, surface, first,last).Edge(); - edgeInParametricSpaceVec.push_back(edgeInParametricSpace); - } - auto aWire = sort_and_heal_wire(edgeInParametricSpaceVec, itCurrentShape.surfaceToProject); - itCurrentShape.aProjectedWireInParametricSpaceVec.push_back(aWire); - } - - // try to create a face from the wires - // the first wire is the otherwise - // the following wires are the inside wires - BRepBuilderAPI_MakeFace faceMaker; - bool first = true; - for (auto itWireVec : itCurrentShape.aProjectedWireInParametricSpaceVec) - { - if (first) - { - first = false; - // change the wire direction, otherwise no face is created - auto currentWire = TopoDS::Wire(itWireVec.Reversed()); - if (itCurrentShape.surfaceToProject.Orientation() == TopAbs_REVERSED) currentWire = itWireVec; - faceMaker = BRepBuilderAPI_MakeFace(surface, currentWire); - ShapeFix_Face fix(faceMaker.Face()); - fix.Perform(); - auto aFace = fix.Face(); - BRepCheck_Analyzer aChecker(aFace); - if (!aChecker.IsValid()) - { - faceMaker = BRepBuilderAPI_MakeFace(surface, TopoDS::Wire(currentWire.Reversed())); - } - } - else - { - // make a copy of the current face maker - // if the face fails just try again with the copy - TopoDS_Face tempCopy = BRepBuilderAPI_MakeFace(faceMaker.Face()).Face(); - faceMaker.Add(TopoDS::Wire(itWireVec.Reversed())); - ShapeFix_Face fix(faceMaker.Face()); - fix.Perform(); - auto aFace = fix.Face(); - BRepCheck_Analyzer aChecker(aFace); - if (!aChecker.IsValid()) - { - faceMaker = BRepBuilderAPI_MakeFace(tempCopy); - faceMaker.Add(TopoDS::Wire(itWireVec)); - } - } - } - //auto doneFlag = faceMaker.IsDone(); - //auto error = faceMaker.Error(); - itCurrentShape.aProjectedFace = faceMaker.Face(); + ioCurrentSahpe.aWireVec.push_back(currentWire); } - } - catch (const Standard_Failure& error) - { - std::stringstream ssOcc; - error.Print(ssOcc); - throw Base::ValueError(ssOcc.str().c_str()); - } } -TopoDS_Wire PartGui::DlgProjectionOnSurface::sort_and_heal_wire(const TopoDS_Shape& iShape, const TopoDS_Face& iFaceToProject) +void PartGui::DlgProjectionOnSurface::create_projection_face_from_wire( + std::vector& iCurrentShape) { - std::vector aEdgeVec; - for (TopExp_Explorer aExplorer(iShape, TopAbs_EDGE); aExplorer.More(); aExplorer.Next()) - { - auto anEdge = TopoDS::Edge(aExplorer.Current()); - aEdgeVec.push_back(anEdge); - } - return sort_and_heal_wire(aEdgeVec, iFaceToProject); + try { + if (iCurrentShape.empty()) { + return; + } + + for (auto& itCurrentShape : iCurrentShape) { + if (itCurrentShape.aFace.IsNull()) { + continue; + }; + if (itCurrentShape.aProjectedWireVec.empty()) { + continue; + }; + if (!itCurrentShape.aProjectedFace.IsNull()) { + continue; + }; + + auto surface = BRep_Tool::Surface(itCurrentShape.surfaceToProject); + + // create a wire of all edges in parametric space on the surface of the face to + // projected + // --> otherwise BRepBuilderAPI_MakeFace can not make a face from the wire! + for (const auto& itWireVec : itCurrentShape.aProjectedWireVec) { + std::vector edgeVec; + for (TopExp_Explorer aExplorer(itWireVec, TopAbs_EDGE); aExplorer.More(); + aExplorer.Next()) { + auto currentEdge = TopoDS::Edge(aExplorer.Current()); + edgeVec.push_back(currentEdge); + } + if (edgeVec.empty()) { + continue; + } + + std::vector edgeInParametricSpaceVec; + for (auto itEdge : edgeVec) { + Standard_Real first {}; + Standard_Real last {}; + auto currentCurve = BRep_Tool::CurveOnSurface(TopoDS::Edge(itEdge), + itCurrentShape.surfaceToProject, + first, + last); + if (!currentCurve) { + continue; + } + auto edgeInParametricSpace = + BRepBuilderAPI_MakeEdge(currentCurve, surface, first, last).Edge(); + edgeInParametricSpaceVec.push_back(edgeInParametricSpace); + } + auto aWire = + sort_and_heal_wire(edgeInParametricSpaceVec, itCurrentShape.surfaceToProject); + itCurrentShape.aProjectedWireInParametricSpaceVec.push_back(aWire); + } + + // try to create a face from the wires + // the first wire is the otherwise + // the following wires are the inside wires + BRepBuilderAPI_MakeFace faceMaker; + bool first = true; + for (auto itWireVec : itCurrentShape.aProjectedWireInParametricSpaceVec) { + if (first) { + first = false; + // change the wire direction, otherwise no face is created + auto currentWire = TopoDS::Wire(itWireVec.Reversed()); + if (itCurrentShape.surfaceToProject.Orientation() == TopAbs_REVERSED) { + currentWire = itWireVec; + } + faceMaker = BRepBuilderAPI_MakeFace(surface, currentWire); + ShapeFix_Face fix(faceMaker.Face()); + fix.Perform(); + auto aFace = fix.Face(); + BRepCheck_Analyzer aChecker(aFace); + if (!aChecker.IsValid()) { + faceMaker = + BRepBuilderAPI_MakeFace(surface, TopoDS::Wire(currentWire.Reversed())); + } + } + else { + // make a copy of the current face maker + // if the face fails just try again with the copy + TopoDS_Face tempCopy = BRepBuilderAPI_MakeFace(faceMaker.Face()).Face(); + faceMaker.Add(TopoDS::Wire(itWireVec.Reversed())); + ShapeFix_Face fix(faceMaker.Face()); + fix.Perform(); + auto aFace = fix.Face(); + BRepCheck_Analyzer aChecker(aFace); + if (!aChecker.IsValid()) { + faceMaker = BRepBuilderAPI_MakeFace(tempCopy); + faceMaker.Add(TopoDS::Wire(itWireVec)); + } + } + } + // auto doneFlag = faceMaker.IsDone(); + // auto error = faceMaker.Error(); + itCurrentShape.aProjectedFace = faceMaker.Face(); + } + } + catch (const Standard_Failure& error) { + std::stringstream ssOcc; + error.Print(ssOcc); + throw Base::ValueError(ssOcc.str().c_str()); + } } -TopoDS_Wire PartGui::DlgProjectionOnSurface::sort_and_heal_wire(const std::vector& iEdgeVec, const TopoDS_Face& iFaceToProject) +TopoDS_Wire PartGui::DlgProjectionOnSurface::sort_and_heal_wire(const TopoDS_Shape& iShape, + const TopoDS_Face& iFaceToProject) { - // try to sort and heal all wires -// if the wires are not clean making a face will fail! - ShapeAnalysis_FreeBounds shapeAnalyzer; - Handle(TopTools_HSequenceOfShape) shapeList = new TopTools_HSequenceOfShape; - Handle(TopTools_HSequenceOfShape) aWireHandle; - Handle(TopTools_HSequenceOfShape) aWireWireHandle; + std::vector aEdgeVec; + for (TopExp_Explorer aExplorer(iShape, TopAbs_EDGE); aExplorer.More(); aExplorer.Next()) { + auto anEdge = TopoDS::Edge(aExplorer.Current()); + aEdgeVec.push_back(anEdge); + } + return sort_and_heal_wire(aEdgeVec, iFaceToProject); +} - for (const auto& it : iEdgeVec) - { - shapeList->Append(it); - } +TopoDS_Wire +PartGui::DlgProjectionOnSurface::sort_and_heal_wire(const std::vector& iEdgeVec, + const TopoDS_Face& iFaceToProject) +{ + // try to sort and heal all wires + // if the wires are not clean making a face will fail! + ShapeAnalysis_FreeBounds shapeAnalyzer; + Handle(TopTools_HSequenceOfShape) shapeList = new TopTools_HSequenceOfShape; + Handle(TopTools_HSequenceOfShape) aWireHandle; + Handle(TopTools_HSequenceOfShape) aWireWireHandle; - shapeAnalyzer.ConnectEdgesToWires(shapeList, 0.0001, false, aWireHandle); - shapeAnalyzer.ConnectWiresToWires(aWireHandle, 0.0001, false, aWireWireHandle); - if (!aWireWireHandle) - return {}; - for (auto it = 1; it <= aWireWireHandle->Length(); ++it) - { - auto aShape = TopoDS::Wire(aWireWireHandle->Value(it)); - ShapeFix_Wire aWireRepair(aShape, iFaceToProject, 0.0001); - aWireRepair.FixAddCurve3dMode() = 1; - aWireRepair.FixAddPCurveMode() = 1; - aWireRepair.Perform(); - //return aWireRepair.Wire(); - ShapeFix_Wireframe aWireFramFix(aWireRepair.Wire()); - auto retVal = aWireFramFix.FixWireGaps(); - retVal = aWireFramFix.FixSmallEdges(); - Q_UNUSED(retVal); - return TopoDS::Wire(aWireFramFix.Shape()); - } - return {}; + for (const auto& it : iEdgeVec) { + shapeList->Append(it); + } + + const double tolerance = 0.0001; + ShapeAnalysis_FreeBounds::ConnectEdgesToWires(shapeList, tolerance, false, aWireHandle); + ShapeAnalysis_FreeBounds::ConnectWiresToWires(aWireHandle, tolerance, false, aWireWireHandle); + if (!aWireWireHandle) { + return {}; + } + for (auto it = 1; it <= aWireWireHandle->Length(); ++it) { + auto aShape = TopoDS::Wire(aWireWireHandle->Value(it)); + ShapeFix_Wire aWireRepair(aShape, iFaceToProject, tolerance); + aWireRepair.FixAddCurve3dMode() = 1; + aWireRepair.FixAddPCurveMode() = 1; + aWireRepair.Perform(); + // return aWireRepair.Wire(); + ShapeFix_Wireframe aWireFramFix(aWireRepair.Wire()); + aWireFramFix.FixWireGaps(); + aWireFramFix.FixSmallEdges(); + return TopoDS::Wire(aWireFramFix.Shape()); + } + return {}; } void PartGui::DlgProjectionOnSurface::create_face_extrude(std::vector& iCurrentShape) { - try - { - if (iCurrentShape.empty()) - return; + try { + if (iCurrentShape.empty()) { + return; + } - auto height = ui->doubleSpinBoxExtrudeHeight->value(); + auto height = ui->doubleSpinBoxExtrudeHeight->value(); - for ( auto &itCurrentShape : iCurrentShape ) - { - if (itCurrentShape.aProjectedFace.IsNull()) continue;; - if (itCurrentShape.extrudeValue == height) continue;; + for (auto& itCurrentShape : iCurrentShape) { + if (itCurrentShape.aProjectedFace.IsNull()) { + continue; + } + if (itCurrentShape.extrudeValue == height) { + continue; + } - itCurrentShape.extrudeValue = height; - if (height == 0) - { - itCurrentShape.aProjectedSolid.Nullify(); - } - else - { - gp_Vec directionToExtrude(itCurrentShape.aProjectionDir.XYZ()); - directionToExtrude.Reverse(); - directionToExtrude.Multiply(height); - BRepPrimAPI_MakePrism extrude(itCurrentShape.aProjectedFace, directionToExtrude); - itCurrentShape.aProjectedSolid = extrude.Shape(); - } + itCurrentShape.extrudeValue = height; + if (height == 0) { + itCurrentShape.aProjectedSolid.Nullify(); + } + else { + gp_Vec directionToExtrude(itCurrentShape.aProjectionDir.XYZ()); + directionToExtrude.Reverse(); + directionToExtrude.Multiply(height); + BRepPrimAPI_MakePrism extrude(itCurrentShape.aProjectedFace, directionToExtrude); + itCurrentShape.aProjectedSolid = extrude.Shape(); + } + } + } + catch (const Standard_Failure& error) { + std::stringstream ssOcc; + error.Print(ssOcc); + throw Base::ValueError(ssOcc.str().c_str()); } - } - catch (const Standard_Failure& error) - { - std::stringstream ssOcc; - error.Print(ssOcc); - throw Base::ValueError(ssOcc.str().c_str()); - } } -void PartGui::DlgProjectionOnSurface::store_wire_in_vector(const SShapeStore& iCurrentShape, const TopoDS_Shape& iParentShape, std::vector& iStoreVec, const unsigned int iColor) +void PartGui::DlgProjectionOnSurface::store_wire_in_vector(const SShapeStore& iCurrentShape, + const TopoDS_Shape& iParentShape, + std::vector& iStoreVec, + unsigned int iColor) { - if (m_currentSelection != "add_wire") - return; - if (iParentShape.IsNull()) - return; - if (iCurrentShape.inputShape.IsNull()) - return; - auto currentType = iCurrentShape.inputShape.ShapeType(); - if (currentType != TopAbs_EDGE) - return; - - std::vector aWireVec; - for (TopExp_Explorer aExplorer(iParentShape, TopAbs_WIRE); aExplorer.More(); aExplorer.Next()) - { - aWireVec.push_back(TopoDS::Wire(aExplorer.Current())); - } - - std::vector edgeVec; - for (const auto& it : aWireVec ) - { - bool edgeExists = false; - for (TopExp_Explorer aExplorer(it, TopAbs_EDGE); aExplorer.More(); aExplorer.Next()) - { - auto currentEdge = TopoDS::Edge(aExplorer.Current()); - edgeVec.push_back(currentEdge); - if (currentEdge.IsSame(iCurrentShape.inputShape)) edgeExists = true; - } - if (edgeExists) break; - edgeVec.clear(); - } - - if (edgeVec.empty()) - return; - TopTools_IndexedMapOfShape indexMap; - TopExp::MapShapes(iParentShape, TopAbs_EDGE, indexMap); - if (indexMap.IsEmpty()) - return; - - for (const auto& it : edgeVec ) - { - if ( it.IsSame(iCurrentShape.inputShape)) continue; - if (!indexMap.Contains(it)) + if (m_currentSelection != "add_wire") { return; - auto index = indexMap.FindIndex(it); - auto newEdgeObject = iCurrentShape; - newEdgeObject.inputShape = it; - newEdgeObject.partName = "Edge" + std::to_string(index); + } + if (iParentShape.IsNull()) { + return; + } + if (iCurrentShape.inputShape.IsNull()) { + return; + } + auto currentType = iCurrentShape.inputShape.ShapeType(); + if (currentType != TopAbs_EDGE) { + return; + } - auto store = store_part_in_vector(newEdgeObject, iStoreVec); - higlight_object(newEdgeObject.partFeature, newEdgeObject.partName, store, iColor); - } + std::vector aWireVec; + for (TopExp_Explorer aExplorer(iParentShape, TopAbs_WIRE); aExplorer.More(); aExplorer.Next()) { + aWireVec.push_back(TopoDS::Wire(aExplorer.Current())); + } + + std::vector edgeVec; + for (const auto& it : aWireVec) { + bool edgeExists = false; + for (TopExp_Explorer aExplorer(it, TopAbs_EDGE); aExplorer.More(); aExplorer.Next()) { + auto currentEdge = TopoDS::Edge(aExplorer.Current()); + edgeVec.push_back(currentEdge); + if (currentEdge.IsSame(iCurrentShape.inputShape)) { + edgeExists = true; + } + } + if (edgeExists) { + break; + } + edgeVec.clear(); + } + + if (edgeVec.empty()) { + return; + } + TopTools_IndexedMapOfShape indexMap; + TopExp::MapShapes(iParentShape, TopAbs_EDGE, indexMap); + if (indexMap.IsEmpty()) { + return; + } + + for (const auto& it : edgeVec) { + if (it.IsSame(iCurrentShape.inputShape)) { + continue; + } + if (!indexMap.Contains(it)) { + return; + } + auto index = indexMap.FindIndex(it); + auto newEdgeObject = iCurrentShape; + newEdgeObject.inputShape = it; + newEdgeObject.partName = "Edge" + std::to_string(index); + + auto store = store_part_in_vector(newEdgeObject, iStoreVec); + higlight_object(newEdgeObject.partFeature, newEdgeObject.partName, store, iColor); + } } void PartGui::DlgProjectionOnSurface::set_xyz_dir_spinbox(QDoubleSpinBox* icurrentSpinBox) { - auto currentVal = icurrentSpinBox->value(); - auto newVal = 0.0; - if (currentVal != 1.0 && currentVal != -1.0) - { - newVal = -1; - } - else if (currentVal == 1.0) - { - newVal = -1; - } - else if (currentVal == -1.0) - { - newVal = 1; - } - ui->doubleSpinBoxDirX->setValue(0); - ui->doubleSpinBoxDirY->setValue(0); - ui->doubleSpinBoxDirZ->setValue(0); - icurrentSpinBox->setValue(newVal); + auto currentVal = icurrentSpinBox->value(); + auto newVal = 0.0; + if (currentVal != 1.0 && currentVal != -1.0) { + newVal = -1; + } + else if (currentVal == 1.0) { + newVal = -1; + } + else if (currentVal == -1.0) { + newVal = 1; + } + ui->doubleSpinBoxDirX->setValue(0); + ui->doubleSpinBoxDirY->setValue(0); + ui->doubleSpinBoxDirZ->setValue(0); + icurrentSpinBox->setValue(newVal); } -void PartGui::DlgProjectionOnSurface::transform_shape_to_global_position(TopoDS_Shape& ioShape, Part::Feature* iPart) +void PartGui::DlgProjectionOnSurface::transform_shape_to_global_position(TopoDS_Shape& ioShape, + Part::Feature* iPart) { - auto currentPos = iPart->Placement.getValue().getPosition(); - auto currentRotation = iPart->Placement.getValue().getRotation(); - auto globalPlacement = iPart->globalPlacement(); - auto globalPosition = globalPlacement.getPosition(); - auto globalRotation = globalPlacement.getRotation(); + auto currentPos = iPart->Placement.getValue().getPosition(); + auto currentRotation = iPart->Placement.getValue().getRotation(); + auto globalPlacement = iPart->globalPlacement(); + auto globalPosition = globalPlacement.getPosition(); + auto globalRotation = globalPlacement.getRotation(); - if (currentRotation != globalRotation) - { - auto newRotation = globalRotation; - newRotation *= currentRotation.invert(); + if (currentRotation != globalRotation) { + auto newRotation = globalRotation; + newRotation *= currentRotation.invert(); - gp_Trsf aAngleTransform; - Base::Vector3d rotationAxes; - double rotationAngle; - newRotation.getRawValue(rotationAxes, rotationAngle); - aAngleTransform.SetRotation(gp_Ax1(gp_Pnt(currentPos.x, currentPos.y, currentPos.z), gp_Dir(rotationAxes.x, rotationAxes.y, rotationAxes.z)), rotationAngle); - ioShape = BRepBuilderAPI_Transform(ioShape, aAngleTransform, true).Shape(); - } + gp_Trsf aAngleTransform; + Base::Vector3d rotationAxes; + double rotationAngle {}; + newRotation.getRawValue(rotationAxes, rotationAngle); + aAngleTransform.SetRotation(gp_Ax1(gp_Pnt(currentPos.x, currentPos.y, currentPos.z), + gp_Dir(rotationAxes.x, rotationAxes.y, rotationAxes.z)), + rotationAngle); + ioShape = BRepBuilderAPI_Transform(ioShape, aAngleTransform, true).Shape(); + } - if (currentPos != globalPosition) - { - gp_Trsf aPosTransform; - aPosTransform.SetTranslation(gp_Pnt(currentPos.x, currentPos.y, currentPos.z), gp_Pnt(globalPosition.x, globalPosition.y, globalPosition.z)); - ioShape = BRepBuilderAPI_Transform(ioShape, aPosTransform, true).Shape(); - } + if (currentPos != globalPosition) { + gp_Trsf aPosTransform; + aPosTransform.SetTranslation(gp_Pnt(currentPos.x, currentPos.y, currentPos.z), + gp_Pnt(globalPosition.x, globalPosition.y, globalPosition.z)); + ioShape = BRepBuilderAPI_Transform(ioShape, aPosTransform, true).Shape(); + } } void PartGui::DlgProjectionOnSurface::onPushButtonAddProjFaceClicked() { - if (ui->pushButtonAddProjFace->isChecked()) - { - m_currentSelection = "add_projection_surface"; - disable_ui_elements(m_guiObjectVec, ui->pushButtonAddProjFace); - if (!filterFace) - { - filterFace = new FaceSelection(); - Gui::Selection().addSelectionGate(filterFace); + if (ui->pushButtonAddProjFace->isChecked()) { + m_currentSelection = "add_projection_surface"; + disable_ui_elements(m_guiObjectVec, ui->pushButtonAddProjFace); + if (!filterFace) { + filterFace = new FaceSelection(); + Gui::Selection().addSelectionGate(filterFace); + } + } + else { + m_currentSelection = ""; + enable_ui_elements(m_guiObjectVec, nullptr); + Gui::Selection().rmvSelectionGate(); + filterFace = nullptr; } - } - else - { - m_currentSelection = ""; - enable_ui_elements(m_guiObjectVec, nullptr); - Gui::Selection().rmvSelectionGate(); - filterFace = nullptr; - } } void PartGui::DlgProjectionOnSurface::onRadioButtonShowAllClicked() { - m_currentShowType = "all"; - show_projected_shapes(m_shapeVec); + m_currentShowType = "all"; + show_projected_shapes(m_shapeVec); } void PartGui::DlgProjectionOnSurface::onRadioButtonFacesClicked() { - m_currentShowType = "faces"; - show_projected_shapes(m_shapeVec); + m_currentShowType = "faces"; + show_projected_shapes(m_shapeVec); } void PartGui::DlgProjectionOnSurface::onRadioButtonEdgesClicked() { - m_currentShowType = "edges"; - show_projected_shapes(m_shapeVec); + m_currentShowType = "edges"; + show_projected_shapes(m_shapeVec); } void PartGui::DlgProjectionOnSurface::onDoubleSpinBoxExtrudeHeightValueChanged(double arg1) { - Q_UNUSED(arg1); - create_face_extrude(m_shapeVec); - show_projected_shapes(m_shapeVec); + Q_UNUSED(arg1); + create_face_extrude(m_shapeVec); + show_projected_shapes(m_shapeVec); } void PartGui::DlgProjectionOnSurface::onPushButtonAddWireClicked() { - if (ui->pushButtonAddWire->isChecked()) - { - m_currentSelection = "add_wire"; - disable_ui_elements(m_guiObjectVec, ui->pushButtonAddWire); - if (!filterEdge) - { - filterEdge = new EdgeSelection(); - Gui::Selection().addSelectionGate(filterEdge); + if (ui->pushButtonAddWire->isChecked()) { + m_currentSelection = "add_wire"; + disable_ui_elements(m_guiObjectVec, ui->pushButtonAddWire); + if (!filterEdge) { + filterEdge = new EdgeSelection(); + Gui::Selection().addSelectionGate(filterEdge); + } + ui->radioButtonEdges->setChecked(true); + onRadioButtonEdgesClicked(); + } + else { + m_currentSelection = ""; + enable_ui_elements(m_guiObjectVec, nullptr); + Gui::Selection().rmvSelectionGate(); + filterEdge = nullptr; } - ui->radioButtonEdges->setChecked(true); - onRadioButtonEdgesClicked(); - } - else - { - m_currentSelection = ""; - enable_ui_elements(m_guiObjectVec, nullptr); - Gui::Selection().rmvSelectionGate(); - filterEdge = nullptr; - } } void PartGui::DlgProjectionOnSurface::onDoubleSpinBoxSolidDepthValueChanged(double arg1) { - auto valX = ui->doubleSpinBoxDirX->value(); - auto valY = ui->doubleSpinBoxDirY->value(); - auto valZ = ui->doubleSpinBoxDirZ->value(); + auto valX = ui->doubleSpinBoxDirX->value(); + auto valY = ui->doubleSpinBoxDirY->value(); + auto valZ = ui->doubleSpinBoxDirZ->value(); - auto valueToMove = arg1 - m_lastDepthVal; - Base::Vector3d vectorToMove(valX, valY, valZ); - vectorToMove *= valueToMove; + auto valueToMove = arg1 - m_lastDepthVal; + Base::Vector3d vectorToMove(valX, valY, valZ); + vectorToMove *= valueToMove; - auto placment = m_projectionObject->Placement.getValue(); - placment.move(vectorToMove); - m_projectionObject->Placement.setValue(placment); + auto placment = m_projectionObject->Placement.getValue(); + placment.move(vectorToMove); + m_projectionObject->Placement.setValue(placment); - m_lastDepthVal = ui->doubleSpinBoxSolidDepth->value(); + m_lastDepthVal = ui->doubleSpinBoxSolidDepth->value(); } // --------------------------------------- TaskProjectionOnSurface::TaskProjectionOnSurface() + : widget(new DlgProjectionOnSurface()) + , taskbox(new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("Part_ProjectionOnSurface"), + widget->windowTitle(), + true, + nullptr)) { - widget = new DlgProjectionOnSurface(); - taskbox = new Gui::TaskView::TaskBox( - Gui::BitmapFactory().pixmap("Part_ProjectionOnSurface"), - widget->windowTitle(), true, nullptr); - taskbox->groupLayout()->addWidget(widget); - Content.push_back(taskbox); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); } bool TaskProjectionOnSurface::accept() { - widget->apply(); - return true; - //return (widget->result() == QDialog::Accepted); + widget->apply(); + return true; } bool TaskProjectionOnSurface::reject() { - widget->reject(); - return true; + widget->reject(); + return true; } void TaskProjectionOnSurface::clicked(int id) { - if (id == QDialogButtonBox::Apply) { - try { - widget->apply(); + if (id == QDialogButtonBox::Apply) { + try { + widget->apply(); + } + catch (Base::AbortException&) { + } } - catch (Base::AbortException&) { - - }; - } } #include "moc_DlgProjectionOnSurface.cpp" diff --git a/src/Mod/Part/Gui/DlgProjectionOnSurface.h b/src/Mod/Part/Gui/DlgProjectionOnSurface.h index ec1cd285b0..4a4f6c10bc 100644 --- a/src/Mod/Part/Gui/DlgProjectionOnSurface.h +++ b/src/Mod/Part/Gui/DlgProjectionOnSurface.h @@ -36,22 +36,24 @@ #include -namespace PartGui { +namespace PartGui +{ - class Ui_DlgProjectionOnSurface; +class Ui_DlgProjectionOnSurface; - namespace Ui { - class DlgProjectionOnSurface; - } +namespace Ui +{ +class DlgProjectionOnSurface; +} -class DlgProjectionOnSurface : public QWidget, - public Gui::SelectionObserver, - public App::DocumentObserver +class DlgProjectionOnSurface: public QWidget, + public Gui::SelectionObserver, + public App::DocumentObserver { Q_OBJECT public: - explicit DlgProjectionOnSurface(QWidget *parent = nullptr); + explicit DlgProjectionOnSurface(QWidget* parent = nullptr); ~DlgProjectionOnSurface() override; void apply(); @@ -74,57 +76,64 @@ private: void onDoubleSpinBoxSolidDepthValueChanged(double arg1); private: + struct SShapeStore + { + TopoDS_Shape inputShape; + TopoDS_Face surfaceToProject; + gp_Dir aProjectionDir; + TopoDS_Face aFace; + TopoDS_Edge aEdge; + std::vector aWireVec; + std::vector aProjectedWireVec; + std::vector aProjectedEdgeVec; + std::vector aProjectedWireInParametricSpaceVec; + TopoDS_Face aProjectedFace; + TopoDS_Shape aProjectedSolid; + Part::Feature* partFeature = nullptr; + std::string partName; + bool is_selectable = false; + long transparency = 0; + double extrudeValue = 0.0; + }; - struct SShapeStore - { - TopoDS_Shape inputShape; - TopoDS_Face surfaceToProject; - gp_Dir aProjectionDir; - TopoDS_Face aFace; - TopoDS_Edge aEdge; - std::vector aWireVec; - std::vector aProjectedWireVec; - std::vector aProjectedEdgeVec; - std::vector aProjectedWireInParametricSpaceVec; - TopoDS_Face aProjectedFace; - TopoDS_Shape aProjectedSolid; - Part::Feature* partFeature = nullptr; - std::string partName; - bool is_selectable = false; - long transparency = 0; - float extrudeValue = 0.0f; - }; - - //from Gui::SelectionObserver - void onSelectionChanged(const Gui::SelectionChanges& msg) override; + // from Gui::SelectionObserver + void onSelectionChanged(const Gui::SelectionChanges& msg) override; - void get_camera_direction(); - void store_current_selected_parts(std::vector& iStoreVec, const unsigned int iColor); - bool store_part_in_vector(SShapeStore& iCurrentShape, std::vector& iStoreVec); - void create_projection_wire(std::vector& iCurrentShape); - TopoDS_Shape create_compound(const std::vector& iShapeVec); - void show_projected_shapes(const std::vector& iShapeStoreVec); - void disable_ui_elements(const std::vector& iObjectVec, QWidget* iExceptThis); - void enable_ui_elements(const std::vector& iObjectVec, QWidget* iExceptThis); - void higlight_object(Part::Feature* iCurrentObject, const std::string& iShapeName, bool iHighlight, const unsigned int iColor); - void get_all_wire_from_face(SShapeStore& ioCurrentSahpe); - void create_projection_face_from_wire(std::vector& iCurrentShape); - TopoDS_Wire sort_and_heal_wire(const TopoDS_Shape& iShape, const TopoDS_Face& iFaceToProject); - TopoDS_Wire sort_and_heal_wire(const std::vector& iEdgeVec, const TopoDS_Face& iFaceToProject); - void create_face_extrude(std::vector& iCurrentShape); - void store_wire_in_vector(const SShapeStore& iCurrentShape, const TopoDS_Shape& iParentShape, std::vector& iStoreVec, const unsigned int iColor); - void set_xyz_dir_spinbox(QDoubleSpinBox* icurrentSpinBox); - void transform_shape_to_global_position(TopoDS_Shape& ioShape, Part::Feature* iPart); + void get_camera_direction(); + void store_current_selected_parts(std::vector& iStoreVec, + unsigned int iColor); + bool store_part_in_vector(SShapeStore& iCurrentShape, std::vector& iStoreVec); + void create_projection_wire(std::vector& iCurrentShape); + TopoDS_Shape create_compound(const std::vector& iShapeVec); + void show_projected_shapes(const std::vector& iShapeStoreVec); + void disable_ui_elements(const std::vector& iObjectVec, QWidget* iExceptThis); + void enable_ui_elements(const std::vector& iObjectVec, QWidget* iExceptThis); + void higlight_object(Part::Feature* iCurrentObject, + const std::string& iShapeName, + bool iHighlight, + unsigned int iColor); + void get_all_wire_from_face(SShapeStore& ioCurrentSahpe); + void create_projection_face_from_wire(std::vector& iCurrentShape); + TopoDS_Wire sort_and_heal_wire(const TopoDS_Shape& iShape, const TopoDS_Face& iFaceToProject); + TopoDS_Wire sort_and_heal_wire(const std::vector& iEdgeVec, + const TopoDS_Face& iFaceToProject); + void create_face_extrude(std::vector& iCurrentShape); + void store_wire_in_vector(const SShapeStore& iCurrentShape, + const TopoDS_Shape& iParentShape, + std::vector& iStoreVec, + unsigned int iColor); + void set_xyz_dir_spinbox(QDoubleSpinBox* icurrentSpinBox); + void transform_shape_to_global_position(TopoDS_Shape& ioShape, Part::Feature* iPart); private: - /** Checks if the given document is about to be closed */ - void slotDeletedDocument(const App::Document& Doc) override; - /** Checks if the given object is about to be removed. */ - void slotDeletedObject(const App::DocumentObject& Obj) override; + /** Checks if the given document is about to be closed */ + void slotDeletedDocument(const App::Document& Doc) override; + /** Checks if the given object is about to be removed. */ + void slotDeletedObject(const App::DocumentObject& Obj) override; private: - Ui::DlgProjectionOnSurface *ui; + Ui::DlgProjectionOnSurface* ui; std::vector m_shapeVec; std::vector m_projectionSurfaceVec; @@ -134,9 +143,9 @@ private: std::vector m_guiObjectVec; const QString m_projectionObjectName; - Part::Feature* m_projectionObject; - App::Document* m_partDocument; - float m_lastDepthVal; + Part::Feature* m_projectionObject = nullptr; + App::Document* m_partDocument = nullptr; + double m_lastDepthVal; class EdgeSelection; EdgeSelection* filterEdge; @@ -145,28 +154,28 @@ private: FaceSelection* filterFace; }; -class TaskProjectionOnSurface : public Gui::TaskView::TaskDialog +class TaskProjectionOnSurface: public Gui::TaskView::TaskDialog { - Q_OBJECT + Q_OBJECT public: - TaskProjectionOnSurface(); + TaskProjectionOnSurface(); public: - bool accept() override; - bool reject() override; - void clicked(int) override; + bool accept() override; + bool reject() override; + void clicked(int id) override; - QDialogButtonBox::StandardButtons getStandardButtons() const override - { - return QDialogButtonBox::Ok | QDialogButtonBox::Cancel; - } + QDialogButtonBox::StandardButtons getStandardButtons() const override + { + return QDialogButtonBox::Ok | QDialogButtonBox::Cancel; + } private: - DlgProjectionOnSurface* widget; - Gui::TaskView::TaskBox* taskbox; + DlgProjectionOnSurface* widget = nullptr; + Gui::TaskView::TaskBox* taskbox = nullptr; }; -} // namespace PartGui -#endif // PARTGUI_DLGPROJECTIONONSURFACE_H +} // namespace PartGui +#endif // PARTGUI_DLGPROJECTIONONSURFACE_H diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp index e9730b1a79..97c52a42fb 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.cpp +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -232,79 +232,12 @@ App::DocumentObjectExecReturn* Helix::execute() // generate the helix path TopoDS_Shape path = generateHelixPath(); - TopoDS_Shape auxpath = generateHelixPath(1.0); - // Use MakePipe for frenet ( Angle is 0 ) calculations, faster than MakePipeShell - if ( Angle.getValue() == 0 ) { - TopoDS_Shape face = Part::FaceMakerCheese::makeFace(wires); - face.Move(invObjLoc); - BRepOffsetAPI_MakePipe mkPS(TopoDS::Wire(path), face, GeomFill_Trihedron::GeomFill_IsFrenet, Standard_False); - mkPS.Build(); - result = mkPS.Shape(); - } else { - std::vector> wiresections; - for (TopoDS_Wire& wire : wires) - wiresections.emplace_back(1, wire); - - //build all shells - std::vector shells; - std::vector frontwires, backwires; - for (std::vector& wires : wiresections) { - - BRepOffsetAPI_MakePipeShell mkPS(TopoDS::Wire(path)); - - // Frenet mode doesn't place the face quite right on an angled helix, so - // use the auxiliary spine to force that. - mkPS.SetMode(TopoDS::Wire(auxpath), true); // this is for auxiliary - - for (TopoDS_Wire& wire : wires) { - wire.Move(invObjLoc); - mkPS.Add(wire); - } - - if (!mkPS.IsReady()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Could not build")); - mkPS.Build(); - - shells.push_back(mkPS.Shape()); - - if (!mkPS.Shape().Closed()) { - // // shell is not closed - use simulate to get the end wires - TopTools_ListOfShape sim; - mkPS.Simulate(2, sim); - - frontwires.push_back(TopoDS::Wire(sim.First())); - backwires.push_back(TopoDS::Wire(sim.Last())); - } - BRepBuilderAPI_MakeSolid mkSolid; - - if (!frontwires.empty()) { - // build the end faces, sew the shell and build the final solid - TopoDS_Shape front = Part::FaceMakerCheese::makeFace(frontwires); - TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); - - BRepBuilderAPI_Sewing sewer; - sewer.SetTolerance(Precision::Confusion()); - sewer.Add(front); - sewer.Add(back); - - for (TopoDS_Shape& s : shells) - sewer.Add(s); - sewer.Perform(); - mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); - } - else { - // shells are already closed - add them directly - for (TopoDS_Shape& s : shells) { - mkSolid.Add(TopoDS::Shell(s)); - } - } - if (!mkSolid.IsDone()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Result is not a solid")); - - result = mkSolid.Shape(); - } - } + TopoDS_Shape face = Part::FaceMakerCheese::makeFace(wires); + face.Move(invObjLoc); + BRepOffsetAPI_MakePipe mkPS(TopoDS::Wire(path), face, GeomFill_Trihedron::GeomFill_IsFrenet, Standard_False); + mkPS.Build(); + result = mkPS.Shape(); BRepClass3d_SolidClassifier SC(result); SC.PerformInfinitePoint(Precision::Confusion()); @@ -403,7 +336,7 @@ void Helix::updateAxis() Axis.setValue(dir.x, dir.y, dir.z); } -TopoDS_Shape Helix::generateHelixPath(double startOffset0) +TopoDS_Shape Helix::generateHelixPath() { double turns = Turns.getValue(); double height = Height.getValue(); @@ -449,7 +382,7 @@ TopoDS_Shape Helix::generateHelixPath(double startOffset0) bool turned = axisOffset < 0; // since the factor does not only change the radius but also the path position, we must shift its offset back // using the square of the factor - double startOffset = 10000.0 * std::fabs(startOffset0 + profileCenter * axisVector - baseVector * axisVector); + double startOffset = 10000.0 * std::fabs(baseVector * axisVector); if (radius < Precision::Confusion()) { // in this case ensure that axis is not in the sketch plane @@ -466,8 +399,7 @@ TopoDS_Shape Helix::generateHelixPath(double startOffset0) radiusTop = radius + height * tan(Base::toRadians(angle)); //build the helix path - //TopoShape helix = TopoShape().makeLongHelix(pitch, height, radius, angle, leftHanded); - TopoDS_Shape path = TopoShape().makeSpiralHelix(radius, radiusTop, height, turns, 1, leftHanded); + TopoDS_Shape path = TopoShape().makeSpiralHelix(radius, radiusTop, height, turns, 1000, leftHanded); /* * The helix wire is created with the axis coinciding with z-axis and the start point at (radius, 0, 0) diff --git a/src/Mod/PartDesign/App/FeatureHelix.h b/src/Mod/PartDesign/App/FeatureHelix.h index 531ded92d6..3a4a3a7cb7 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.h +++ b/src/Mod/PartDesign/App/FeatureHelix.h @@ -80,7 +80,7 @@ protected: void updateAxis(); /// generate helix and move it to the right location. - TopoDS_Shape generateHelixPath(double startOffset0 = 0.0); + TopoDS_Shape generateHelixPath(); // project shape on plane. Used for detecting self intersection. TopoDS_Shape projectShape(const TopoDS_Shape& input, const gp_Ax2& plane); diff --git a/src/Mod/PartDesign/PartDesignTests/TestHelix.py b/src/Mod/PartDesign/PartDesignTests/TestHelix.py index ea938b5a3e..3be150af22 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestHelix.py +++ b/src/Mod/PartDesign/PartDesignTests/TestHelix.py @@ -87,15 +87,15 @@ class TestHelix(unittest.TestCase): helix.Angle = 0 helix.Mode = 1 self.Doc.recompute() - self.assertAlmostEqual(helix.Shape.Volume, 78.95687956849457,places=5) + self.assertAlmostEqual(helix.Shape.Volume, 78.957,places=3) helix.Angle = 25 self.Doc.recompute() - self.assertAlmostEqual(helix.Shape.Volume, 134.17450779511307,places=5) + self.assertAlmostEqual(helix.Shape.Volume, 134.17,places=2) profileSketch.addGeometry(Part.Circle(FreeCAD.Vector(2, 0, 0), FreeCAD.Vector(0,0,1), 0.5) ) self.Doc.recompute() - self.assertAlmostEqual(helix.Shape.Volume, 100.63088079046352,places=5) + self.assertAlmostEqual(helix.Shape.Volume, 100.63,places=2) def testRectangle(self): @@ -174,7 +174,7 @@ class TestHelix(unittest.TestCase): helix.Mode = 0 helix.Reversed = True self.Doc.recompute() - self.assertAlmostEqual(helix.Shape.Volume, 388285.4117047924,places=5) + self.assertAlmostEqual(helix.Shape.Volume/1e5, 3.8828,places=4) def tearDown(self): FreeCAD.closeDocument("PartDesignTestHelix") diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index ecb6c9abe6..6452b8607f 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -4227,105 +4227,298 @@ bool SketchObject::isCarbonCopyAllowed(App::Document* pDoc, App::DocumentObject* } int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, - Sketcher::PointPos refPosId /*=Sketcher::PointPos::none*/) + Sketcher::PointPos refPosId /*=Sketcher::PointPos::none*/, + bool addSymmetryConstraints /*= false*/) { // no need to check input data validity as this is an sketchobject managed operation. Base::StateLocker lock(managedoperation, true); - const std::vector& geovals = getInternalGeometry(); - std::vector newgeoVals(geovals); - const std::vector& constrvals = this->Constraints.getValues(); std::vector newconstrVals(constrvals); - newgeoVals.reserve(geovals.size() + geoIdList.size()); - - int cgeoid = getHighestCurveIndex() + 1; - - std::map geoIdMap; - std::map isStartEndInverted; - // Find out if reference is aligned with V or H axis, // if so we can keep Vertical and Horizontal constraints in the mirrored geometry. + bool refIsLine = refPosId == Sketcher::PointPos::none; bool refIsAxisAligned = false; - if (refGeoId == Sketcher::GeoEnum::VAxis || refGeoId == Sketcher::GeoEnum::HAxis) { + if (refGeoId == Sketcher::GeoEnum::VAxis || refGeoId == Sketcher::GeoEnum::HAxis || !refIsLine) { refIsAxisAligned = true; } else { - for (std::vector::const_iterator it = constrvals.begin(); - it != constrvals.end(); - ++it) { - Constraint* constr = *(it); + for (auto* constr : constrvals) { if (constr->First == refGeoId - && (constr->Type == Sketcher::Vertical || constr->Type == Sketcher::Horizontal)) + && (constr->Type == Sketcher::Vertical || constr->Type == Sketcher::Horizontal)){ refIsAxisAligned = true; + } } } - // reference is a line - if (refPosId == Sketcher::PointPos::none) { - const Part::Geometry* georef = getGeometry(refGeoId); - if (georef->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { - Base::Console().Error("Reference for symmetric is neither a point nor a line.\n"); - return -1; + // add the geometry + std::map geoIdMap; + std::map isStartEndInverted; + std::vector newgeoVals(getInternalGeometry()); + std::vector symmetricVals = getSymmetric(geoIdList, geoIdMap, isStartEndInverted, refGeoId, refPosId); + newgeoVals.insert(newgeoVals.end(), symmetricVals.begin(), symmetricVals.end()); + + // Block acceptGeometry in OnChanged to avoid unnecessary checks and updates + { + Base::StateLocker lock(internaltransaction, true); + Geometry.setValues(std::move(newgeoVals)); + + + for (auto* constr : constrvals) { + // we look in the map, because we might have skipped internal alignment geometry + auto fit = geoIdMap.find(constr->First); + + if (fit != geoIdMap.end()) {// if First of constraint is in geoIdList + if (addSymmetryConstraints && constr->Type != Sketcher::InternalAlignment) { + // if we are making symmetric constraints, then we don't want to copy all constraints + continue; + } + + if (constr->Second == GeoEnum::GeoUndef /*&& constr->Third == GeoEnum::GeoUndef*/) { + if (refIsAxisAligned) { + // in this case we want to keep the Vertical, Horizontal constraints + // DistanceX ,and DistanceY constraints should also be possible to keep in + // this case, but keeping them causes segfault, not sure why. + + if (constr->Type != Sketcher::DistanceX + && constr->Type != Sketcher::DistanceY) { + Constraint* constNew = constr->copy(); + constNew->First = fit->second; + newconstrVals.push_back(constNew); + } + } + else if (constr->Type != Sketcher::DistanceX + && constr->Type != Sketcher::DistanceY + && constr->Type != Sketcher::Vertical + && constr->Type != Sketcher::Horizontal) { + // this includes all non-directional single GeoId constraints, as radius, + // diameter, weight,... + + Constraint* constNew = constr->copy(); + constNew->First = fit->second; + newconstrVals.push_back(constNew); + } + } + else {// other geoids intervene in this constraint + + auto sit = geoIdMap.find(constr->Second); + + if (sit != geoIdMap.end()) {// Second is also in the list + + if (constr->Third == GeoEnum::GeoUndef) { + if (constr->Type == Sketcher::Coincident + || constr->Type == Sketcher::Perpendicular + || constr->Type == Sketcher::Parallel + || constr->Type == Sketcher::Tangent + || constr->Type == Sketcher::Distance + || constr->Type == Sketcher::Equal || constr->Type == Sketcher::Angle + || constr->Type == Sketcher::PointOnObject + || constr->Type == Sketcher::InternalAlignment) { + Constraint* constNew = constr->copy(); + + constNew->First = fit->second; + constNew->Second = sit->second; + if (isStartEndInverted[constr->First]) { + if (constr->FirstPos == Sketcher::PointPos::start) + constNew->FirstPos = Sketcher::PointPos::end; + else if (constr->FirstPos == Sketcher::PointPos::end) + constNew->FirstPos = Sketcher::PointPos::start; + } + if (isStartEndInverted[constr->Second]) { + if (constr->SecondPos == Sketcher::PointPos::start) + constNew->SecondPos = Sketcher::PointPos::end; + else if (constr->SecondPos == Sketcher::PointPos::end) + constNew->SecondPos = Sketcher::PointPos::start; + } + + if (constNew->Type == Tangent || constNew->Type == Perpendicular) + AutoLockTangencyAndPerpty(constNew, true); + + if ((constr->Type == Sketcher::Angle) + && (refPosId == Sketcher::PointPos::none)) { + constNew->setValue(-constr->getValue()); + } + + newconstrVals.push_back(constNew); + } + } + else {// three GeoIds intervene in constraint + auto tit = geoIdMap.find(constr->Third); + + if (tit != geoIdMap.end()) {// Third is also in the list + Constraint* constNew = constr->copy(); + constNew->First = fit->second; + constNew->Second = sit->second; + constNew->Third = tit->second; + if (isStartEndInverted[constr->First]) { + if (constr->FirstPos == Sketcher::PointPos::start) + constNew->FirstPos = Sketcher::PointPos::end; + else if (constr->FirstPos == Sketcher::PointPos::end) + constNew->FirstPos = Sketcher::PointPos::start; + } + if (isStartEndInverted[constr->Second]) { + if (constr->SecondPos == Sketcher::PointPos::start) + constNew->SecondPos = Sketcher::PointPos::end; + else if (constr->SecondPos == Sketcher::PointPos::end) + constNew->SecondPos = Sketcher::PointPos::start; + } + if (isStartEndInverted[constr->Third]) { + if (constr->ThirdPos == Sketcher::PointPos::start) + constNew->ThirdPos = Sketcher::PointPos::end; + else if (constr->ThirdPos == Sketcher::PointPos::end) + constNew->ThirdPos = Sketcher::PointPos::start; + } + newconstrVals.push_back(constNew); + } + } + } + } + } } - const Part::GeomLineSegment* refGeoLine = static_cast(georef); + if (addSymmetryConstraints) { + auto createSymConstr = [&] + (int first, int second, Sketcher::PointPos firstPos, Sketcher::PointPos secondPos) { + auto symConstr = new Constraint(); + symConstr->Type = Symmetric; + symConstr->First = first; + symConstr->Second = second; + symConstr->Third = refGeoId; + symConstr->FirstPos = firstPos; + symConstr->SecondPos = secondPos; + symConstr->ThirdPos = refPosId; + newconstrVals.push_back(symConstr); + }; + auto createEqualityConstr = [&] + (int first, int second) { + auto symConstr = new Constraint(); + symConstr->Type = Equal; + symConstr->First = first; + symConstr->Second = second; + newconstrVals.push_back(symConstr); + }; + + for (auto geoIdPair : geoIdMap) { + int geoId1 = geoIdPair.first; + int geoId2 = geoIdPair.second; + const Part::Geometry* geo = getGeometry(geoId1); + + if (geo->is()) { + auto gf = GeometryFacade::getFacade(geo); + if (!gf->isInternalAligned()) { + // Note internal aligned lines (ellipse, parabola, hyperbola) are causing redundant constraint. + createSymConstr(geoId1, geoId2, PointPos::start, isStartEndInverted[geoId1] ? PointPos::end : PointPos::start); + createSymConstr(geoId1, geoId2, PointPos::end, isStartEndInverted[geoId1] ? PointPos::start : PointPos::end); + } + } + else if (geo->is() || geo->is()) { + createEqualityConstr(geoId1, geoId2); + createSymConstr(geoId1, geoId2, PointPos::mid, PointPos::mid); + } + else if (geo->is() + || geo->is() + || geo->is() + || geo->is()) { + createEqualityConstr(geoId1, geoId2); + createSymConstr(geoId1, geoId2, PointPos::start, isStartEndInverted[geoId1] ? PointPos::end : PointPos::start); + createSymConstr(geoId1, geoId2, PointPos::end, isStartEndInverted[geoId1] ? PointPos::start : PointPos::end); + } + else if (geo->is()) { + auto gf = GeometryFacade::getFacade(geo); + if (!gf->isInternalAligned()) { + createSymConstr(geoId1, geoId2, PointPos::start, PointPos::start); + } + } + // Note bspline has symmetric by the internal aligned circles. + } + } + + if (newconstrVals.size() > constrvals.size()){ + Constraints.setValues(std::move(newconstrVals)); + } + } + + // we delayed update, so trigger it now. + // Update geometry indices and rebuild vertexindex now via onChanged, so that + // ViewProvider::UpdateData is triggered. + Geometry.touch(); + + return Geometry.getSize() - 1; +} + + +std::vector SketchObject::getSymmetric(const std::vector& geoIdList, + std::map& geoIdMap, + std::map& isStartEndInverted, + int refGeoId, + Sketcher::PointPos refPosId) +{ + std::vector symmetricVals; + bool refIsLine = refPosId == Sketcher::PointPos::none; + int cgeoid = getHighestCurveIndex() + 1; + + auto shouldCopyGeometry = [&](auto* geo, int geoId) -> bool { + auto gf = GeometryFacade::getFacade(geo); + if (gf->isInternalAligned()) { + // only add if the corresponding geometry it defines is also in the list. + int definedGeo = GeoEnum::GeoUndef; + for (auto c : Constraints.getValues()) { + if (c->Type == Sketcher::InternalAlignment && c->First == geoId) { + definedGeo = c->Second; + break; + } + } + // Return true if definedGeo is in geoIdList, false otherwise + return std::find(geoIdList.begin(), geoIdList.end(), definedGeo) != geoIdList.end(); + } + // Return true if not internal aligned, indicating it should always be copied + return true; + }; + + if (refIsLine) { + const Part::Geometry* georef = getGeometry(refGeoId); + if (!georef->is()) { + Base::Console().Error("Reference for symmetric is neither a point nor a line.\n"); + return {}; + } + + auto* refGeoLine = static_cast(georef); // line Base::Vector3d refstart = refGeoLine->getStartPoint(); Base::Vector3d vectline = refGeoLine->getEndPoint() - refstart; - for (std::vector::const_iterator it = geoIdList.begin(); it != geoIdList.end(); ++it) { - const Part::Geometry* geo = getGeometry(*it); + for (auto geoId : geoIdList) { + const Part::Geometry* geo = getGeometry(geoId); Part::Geometry* geosym; - auto gf = GeometryFacade::getFacade(geo); - - if (gf->isInternalAligned()) { - // only add this geometry if the corresponding geometry it defines is also in the - // list. - int definedGeo = GeoEnum::GeoUndef; - - for (auto c : Constraints.getValues()) { - if (c->Type == Sketcher::InternalAlignment && c->First == *it) { - definedGeo = c->Second; - break; - } - } - - if (std::find(geoIdList.begin(), geoIdList.end(), definedGeo) != geoIdList.end()) - geosym = geo->copy(); - else { - // we should not mirror internal alignment geometry, unless the element they - // define is also mirrored - continue; - } - } - else { - geosym = geo->copy(); + if (!shouldCopyGeometry(geo, geoId)) { + continue; } + geosym = geo->copy(); + // Handle Geometry if (geosym->is()) { - Part::GeomLineSegment* geosymline = static_cast(geosym); + auto* geosymline = static_cast(geosym); Base::Vector3d sp = geosymline->getStartPoint(); Base::Vector3d ep = geosymline->getEndPoint(); geosymline->setPoints( sp + 2.0 * (sp.Perpendicular(refGeoLine->getStartPoint(), vectline) - sp), ep + 2.0 * (ep.Perpendicular(refGeoLine->getStartPoint(), vectline) - ep)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomCircle* geosymcircle = static_cast(geosym); + auto* geosymcircle = static_cast(geosym); Base::Vector3d cp = geosymcircle->getCenter(); geosymcircle->setCenter( cp + 2.0 * (cp.Perpendicular(refGeoLine->getStartPoint(), vectline) - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfCircle* geoaoc = static_cast(geosym); + auto* geoaoc = static_cast(geosym); Base::Vector3d sp = geoaoc->getStartPoint(true); Base::Vector3d ep = geoaoc->getEndPoint(true); Base::Vector3d cp = geoaoc->getCenter(); @@ -4342,10 +4535,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geoaoc->setCenter(scp); geoaoc->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomEllipse* geosymellipse = static_cast(geosym); + auto* geosymellipse = static_cast(geosym); Base::Vector3d cp = geosymellipse->getCenter(); Base::Vector3d majdir = geosymellipse->getMajorAxisDir(); @@ -4362,10 +4555,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymellipse->setMajorAxisDir(sf1 - scp); geosymellipse->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfEllipse* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4394,11 +4587,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, } geosymaoe->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomArcOfHyperbola* geosymaoe = - static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4423,10 +4615,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, std::swap(theta1, theta2); geosymaoe->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomArcOfParabola* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); // double df= geosymaoe->getFocal(); @@ -4447,45 +4639,41 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, std::swap(theta1, theta2); geosymaoe->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomBSplineCurve* geosymbsp = static_cast(geosym); + auto* geosymbsp = static_cast(geosym); std::vector poles = geosymbsp->getPoles(); - for (std::vector::iterator jt = poles.begin(); jt != poles.end(); - ++jt) { - - (*jt) = (*jt) - + 2.0 - * ((*jt).Perpendicular(refGeoLine->getStartPoint(), vectline) - (*jt)); + for (auto& pole : poles) { + pole = pole + + 2.0 * (pole.Perpendicular(refGeoLine->getStartPoint(), vectline) - pole); } geosymbsp->setPoles(poles); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomPoint* geosympoint = static_cast(geosym); + auto* geosympoint = static_cast(geosym); Base::Vector3d cp = geosympoint->getPoint(); geosympoint->setPoint( cp + 2.0 * (cp.Perpendicular(refGeoLine->getStartPoint(), vectline) - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else { Base::Console().Error("Unsupported Geometry!! Just copying it.\n"); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } - newgeoVals.push_back(geosym); - geoIdMap.insert(std::make_pair(*it, cgeoid)); + symmetricVals.push_back(geosym); + geoIdMap.insert(std::make_pair(geoId, cgeoid)); cgeoid++; } } else {// reference is a point - refIsAxisAligned = true; Vector3d refpoint; const Part::Geometry* georef = getGeometry(refGeoId); @@ -4496,160 +4684,43 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, refpoint = Vector3d(0, 0, 0); } else { - switch (refPosId) { - case Sketcher::PointPos::start: - if (georef->is()) { - const Part::GeomLineSegment* geosymline = - static_cast(georef); - refpoint = geosymline->getStartPoint(); - } - else if (georef->is()) { - const Part::GeomArcOfCircle* geoaoc = - static_cast(georef); - refpoint = geoaoc->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfEllipse* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfHyperbola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfParabola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomBSplineCurve* geosymbsp = - static_cast(georef); - refpoint = geosymbsp->getStartPoint(); - } - break; - case Sketcher::PointPos::end: - if (georef->is()) { - const Part::GeomLineSegment* geosymline = - static_cast(georef); - refpoint = geosymline->getEndPoint(); - } - else if (georef->is()) { - const Part::GeomArcOfCircle* geoaoc = - static_cast(georef); - refpoint = geoaoc->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfEllipse* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfHyperbola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfParabola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomBSplineCurve* geosymbsp = - static_cast(georef); - refpoint = geosymbsp->getEndPoint(); - } - break; - case Sketcher::PointPos::mid: - if (georef->is()) { - const Part::GeomCircle* geosymcircle = - static_cast(georef); - refpoint = geosymcircle->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfCircle* geoaoc = - static_cast(georef); - refpoint = geoaoc->getCenter(); - } - else if (georef->is()) { - const Part::GeomEllipse* geosymellipse = - static_cast(georef); - refpoint = geosymellipse->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfEllipse* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfHyperbola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfParabola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getCenter(); - } - break; - default: - Base::Console().Error("Wrong PointPosId.\n"); - return -1; + if (refPosId == Sketcher::PointPos::none) { + Base::Console().Error("Wrong PointPosId.\n"); + return {}; } + refpoint = getPoint(georef, refPosId); } - for (std::vector::const_iterator it = geoIdList.begin(); it != geoIdList.end(); ++it) { - const Part::Geometry* geo = getGeometry(*it); - + for (auto geoId : geoIdList) { + const Part::Geometry* geo = getGeometry(geoId); Part::Geometry* geosym; - auto gf = GeometryFacade::getFacade(geo); - - if (gf->isInternalAligned()) { - // only add this geometry if the corresponding geometry it defines is also in the - // list. - int definedGeo = GeoEnum::GeoUndef; - - for (auto c : Constraints.getValues()) { - if (c->Type == Sketcher::InternalAlignment && c->First == *it) { - definedGeo = c->Second; - break; - } - } - - if (std::find(geoIdList.begin(), geoIdList.end(), definedGeo) != geoIdList.end()) - geosym = geo->copy(); - else { - // we should not mirror internal alignment geometry, unless the element they - // define is also mirrored - continue; - } - } - else { - geosym = geo->copy(); + if (!shouldCopyGeometry(geo, geoId)) { + continue; } + geosym = geo->copy(); + // Handle Geometry if (geosym->is()) { - Part::GeomLineSegment* geosymline = static_cast(geosym); + auto* geosymline = static_cast(geosym); Base::Vector3d sp = geosymline->getStartPoint(); Base::Vector3d ep = geosymline->getEndPoint(); Base::Vector3d ssp = sp + 2.0 * (refpoint - sp); Base::Vector3d sep = ep + 2.0 * (refpoint - ep); geosymline->setPoints(ssp, sep); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomCircle* geosymcircle = static_cast(geosym); + auto* geosymcircle = static_cast(geosym); Base::Vector3d cp = geosymcircle->getCenter(); geosymcircle->setCenter(cp + 2.0 * (refpoint - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfCircle* geoaoc = static_cast(geosym); + auto* geoaoc = static_cast(geosym); Base::Vector3d sp = geoaoc->getStartPoint(true); Base::Vector3d ep = geoaoc->getEndPoint(true); Base::Vector3d cp = geoaoc->getCenter(); @@ -4663,10 +4734,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geoaoc->setCenter(scp); geoaoc->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomEllipse* geosymellipse = static_cast(geosym); + auto* geosymellipse = static_cast(geosym); Base::Vector3d cp = geosymellipse->getCenter(); Base::Vector3d majdir = geosymellipse->getMajorAxisDir(); @@ -4681,10 +4752,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymellipse->setMajorAxisDir(sf1 - scp); geosymellipse->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfEllipse* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4699,11 +4770,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymaoe->setMajorAxisDir(sf1 - scp); geosymaoe->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfHyperbola* geosymaoe = - static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4718,10 +4788,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymaoe->setMajorAxisDir(sf1 - scp); geosymaoe->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfParabola* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); /*double df= geosymaoe->getFocal();*/ @@ -4733,167 +4803,39 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymaoe->setXAxisDir(sf1 - scp); geosymaoe->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomBSplineCurve* geosymbsp = static_cast(geosym); + auto* geosymbsp = static_cast(geosym); std::vector poles = geosymbsp->getPoles(); - for (std::vector::iterator it = poles.begin(); it != poles.end(); - ++it) { - (*it) = (*it) + 2.0 * (refpoint - (*it)); + for (auto& pole : poles) { + pole = pole + 2.0 * (refpoint - pole); } geosymbsp->setPoles(poles); - // isStartEndInverted.insert(std::make_pair(*it, false)); + // isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomPoint* geosympoint = static_cast(geosym); + auto* geosympoint = static_cast(geosym); Base::Vector3d cp = geosympoint->getPoint(); geosympoint->setPoint(cp + 2.0 * (refpoint - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else { Base::Console().Error("Unsupported Geometry!! Just copying it.\n"); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } - newgeoVals.push_back(geosym); - geoIdMap.insert(std::make_pair(*it, cgeoid)); + symmetricVals.push_back(geosym); + geoIdMap.insert(std::make_pair(geoId, cgeoid)); cgeoid++; } } - - // add the geometry - - // Block acceptGeometry in OnChanged to avoid unnecessary checks and updates - { - Base::StateLocker lock(internaltransaction, true); - Geometry.setValues(std::move(newgeoVals)); - - for (std::vector::const_iterator it = constrvals.begin(); - it != constrvals.end(); - ++it) { - // we look in the map, because we might have skipped internal alignment geometry - auto fit = geoIdMap.find((*it)->First); - - if (fit != geoIdMap.end()) {// if First of constraint is in geoIdList - - if ((*it)->Second == GeoEnum::GeoUndef /*&& (*it)->Third == GeoEnum::GeoUndef*/) { - if (refIsAxisAligned) { - // in this case we want to keep the Vertical, Horizontal constraints - // DistanceX ,and DistanceY constraints should also be possible to keep in - // this case, but keeping them causes segfault, not sure why. - - if ((*it)->Type != Sketcher::DistanceX - && (*it)->Type != Sketcher::DistanceY) { - Constraint* constNew = (*it)->copy(); - constNew->First = fit->second; - newconstrVals.push_back(constNew); - } - } - else if ((*it)->Type != Sketcher::DistanceX - && (*it)->Type != Sketcher::DistanceY - && (*it)->Type != Sketcher::Vertical - && (*it)->Type != Sketcher::Horizontal) { - // this includes all non-directional single GeoId constraints, as radius, - // diameter, weight,... - - Constraint* constNew = (*it)->copy(); - constNew->First = fit->second; - newconstrVals.push_back(constNew); - } - } - else {// other geoids intervene in this constraint - - auto sit = geoIdMap.find((*it)->Second); - - if (sit != geoIdMap.end()) {// Second is also in the list - - if ((*it)->Third == GeoEnum::GeoUndef) { - if ((*it)->Type == Sketcher::Coincident - || (*it)->Type == Sketcher::Perpendicular - || (*it)->Type == Sketcher::Parallel - || (*it)->Type == Sketcher::Tangent - || (*it)->Type == Sketcher::Distance - || (*it)->Type == Sketcher::Equal || (*it)->Type == Sketcher::Angle - || (*it)->Type == Sketcher::PointOnObject - || (*it)->Type == Sketcher::InternalAlignment) { - Constraint* constNew = (*it)->copy(); - - constNew->First = fit->second; - constNew->Second = sit->second; - if (isStartEndInverted[(*it)->First]) { - if ((*it)->FirstPos == Sketcher::PointPos::start) - constNew->FirstPos = Sketcher::PointPos::end; - else if ((*it)->FirstPos == Sketcher::PointPos::end) - constNew->FirstPos = Sketcher::PointPos::start; - } - if (isStartEndInverted[(*it)->Second]) { - if ((*it)->SecondPos == Sketcher::PointPos::start) - constNew->SecondPos = Sketcher::PointPos::end; - else if ((*it)->SecondPos == Sketcher::PointPos::end) - constNew->SecondPos = Sketcher::PointPos::start; - } - - if (constNew->Type == Tangent || constNew->Type == Perpendicular) - AutoLockTangencyAndPerpty(constNew, true); - - if (((*it)->Type == Sketcher::Angle) - && (refPosId == Sketcher::PointPos::none)) { - constNew->setValue(-(*it)->getValue()); - } - - newconstrVals.push_back(constNew); - } - } - else {// three GeoIds intervene in constraint - auto tit = geoIdMap.find((*it)->Third); - - if (tit != geoIdMap.end()) {// Third is also in the list - Constraint* constNew = (*it)->copy(); - constNew->First = fit->second; - constNew->Second = sit->second; - constNew->Third = tit->second; - if (isStartEndInverted[(*it)->First]) { - if ((*it)->FirstPos == Sketcher::PointPos::start) - constNew->FirstPos = Sketcher::PointPos::end; - else if ((*it)->FirstPos == Sketcher::PointPos::end) - constNew->FirstPos = Sketcher::PointPos::start; - } - if (isStartEndInverted[(*it)->Second]) { - if ((*it)->SecondPos == Sketcher::PointPos::start) - constNew->SecondPos = Sketcher::PointPos::end; - else if ((*it)->SecondPos == Sketcher::PointPos::end) - constNew->SecondPos = Sketcher::PointPos::start; - } - if (isStartEndInverted[(*it)->Third]) { - if ((*it)->ThirdPos == Sketcher::PointPos::start) - constNew->ThirdPos = Sketcher::PointPos::end; - else if ((*it)->ThirdPos == Sketcher::PointPos::end) - constNew->ThirdPos = Sketcher::PointPos::start; - } - newconstrVals.push_back(constNew); - } - } - } - } - } - } - - if (newconstrVals.size() > constrvals.size()) - Constraints.setValues(std::move(newconstrVals)); - } - - // we delayed update, so trigger it now. - // Update geometry indices and rebuild vertexindex now via onChanged, so that - // ViewProvider::UpdateData is triggered. - Geometry.touch(); - - return Geometry.getSize() - 1; + return symmetricVals; } int SketchObject::addCopy(const std::vector& geoIdList, const Base::Vector3d& displacement, diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index 5d748bdadb..1d64977edc 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -368,7 +368,16 @@ public: /// adds symmetric geometric elements with respect to the refGeoId (line or point) int addSymmetric(const std::vector& geoIdList, int refGeoId, - Sketcher::PointPos refPosId = Sketcher::PointPos::none); + Sketcher::PointPos refPosId = Sketcher::PointPos::none, + bool addSymmetryConstraints = false); + // get the symmetric geometries of the geoIdList + std::vector + getSymmetric(const std::vector& geoIdList, + std::map& geoIdMap, + std::map& isStartEndInverted, + int refGeoId, + Sketcher::PointPos refPosId = Sketcher::PointPos::none); + /// with default parameters adds a copy of the geometric elements displaced by the displacement /// vector. It creates an array of csize elements in the direction of the displacement vector by /// rsize elements in the direction perpendicular to the displacement vector, wherein the diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index 1b1c42cd93..1c500f3b9a 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -80,6 +80,7 @@ SET(SketcherGui_SRCS DrawSketchHandlerOffset.h DrawSketchHandlerRotate.h DrawSketchHandlerScale.h + DrawSketchHandlerSymmetry.h CommandCreateGeo.cpp CommandConstraints.h CommandConstraints.cpp diff --git a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp index b3d56aa851..93de8d79d8 100644 --- a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp +++ b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp @@ -58,6 +58,7 @@ #include "DrawSketchHandlerOffset.h" #include "DrawSketchHandlerRotate.h" #include "DrawSketchHandlerScale.h" +#include "DrawSketchHandlerSymmetry.h" // Hint: this is to prevent to re-format big parts of the file. Remove it later again. // clang-format off @@ -1092,7 +1093,7 @@ CmdSketcherSymmetry::CmdSketcherSymmetry() sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Symmetry"); sToolTipText = - QT_TR_NOOP("Creates symmetric geometry with respect to the last selected line or point"); + QT_TR_NOOP("Creates symmetric of selected geometry. After starting the tool select the reference line or point."); sWhatsThis = "Sketcher_Symmetry"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Symmetry"; @@ -1103,190 +1104,12 @@ CmdSketcherSymmetry::CmdSketcherSymmetry() void CmdSketcherSymmetry::activated(int iMsg) { Q_UNUSED(iMsg); + std::vector listOfGeoIds = getListOfSelectedGeoIds(true); - // Cancel any in-progress operation - Gui::Document* doc = Gui::Application::Instance->activeDocument(); - SketcherGui::ReleaseHandler(doc); - - // get the selection - std::vector selection; - selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); - - // only one sketch with its subelements are allowed to be selected - if (selection.size() != 1) { - Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), - QObject::tr("Wrong selection"), - QObject::tr("Select elements from a single sketch.")); - return; + if (!listOfGeoIds.empty()) { + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerSymmetry(listOfGeoIds)); } - - // get the needed lists and objects - const std::vector& SubNames = selection[0].getSubNames(); - if (SubNames.empty()) { - Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), - QObject::tr("Wrong selection"), - QObject::tr("Select elements from a single sketch.")); - - return; - } - - Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); getSelection().clearSelection(); - - int LastGeoId = 0; - Sketcher::PointPos LastPointPos = Sketcher::PointPos::none; - const Part::Geometry* LastGeo; - using GeoType = enum { invalid = -1, line = 0, point = 1 }; - - GeoType lastgeotype = invalid; - - // create python command with list of elements - std::stringstream stream; - int geoids = 0; - - for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); - ++it) { - // only handle non-external edges - if ((it->size() > 4 && it->substr(0, 4) == "Edge") - || (it->size() > 12 && it->substr(0, 12) == "ExternalEdge")) { - - if (it->substr(0, 4) == "Edge") { - LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1; - LastPointPos = Sketcher::PointPos::none; - } - else { - LastGeoId = -std::atoi(it->substr(12, 4000).c_str()) - 2; - LastPointPos = Sketcher::PointPos::none; - } - - // reference can be external or non-external - LastGeo = Obj->getGeometry(LastGeoId); - // Only for supported types - if (LastGeo->is()) - lastgeotype = line; - else - lastgeotype = invalid; - - // lines to make symmetric (only non-external) - if (LastGeoId >= 0) { - geoids++; - stream << LastGeoId << ","; - } - } - else if (it->size() > 6 && it->substr(0, 6) == "Vertex") { - // only if it is a GeomPoint - int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1; - int GeoId; - Sketcher::PointPos PosId; - Obj->getGeoVertexIndex(VtId, GeoId, PosId); - - if (Obj->getGeometry(GeoId)->is()) { - LastGeoId = GeoId; - LastPointPos = Sketcher::PointPos::start; - lastgeotype = point; - - // points to make symmetric - if (LastGeoId >= 0) { - geoids++; - stream << LastGeoId << ","; - } - } - } - } - - bool lastvertexoraxis = false; - // check if last selected element is a Vertex, not being a GeomPoint - if (SubNames.rbegin()->size() > 6 && SubNames.rbegin()->substr(0, 6) == "Vertex") { - int VtId = std::atoi(SubNames.rbegin()->substr(6, 4000).c_str()) - 1; - int GeoId; - Sketcher::PointPos PosId; - Obj->getGeoVertexIndex(VtId, GeoId, PosId); - if (Obj->getGeometry(GeoId)->getTypeId() != Part::GeomPoint::getClassTypeId()) { - LastGeoId = GeoId; - LastPointPos = PosId; - lastgeotype = point; - lastvertexoraxis = true; - } - } - // check if last selected element is horizontal axis - else if (SubNames.rbegin()->size() == 6 && SubNames.rbegin()->substr(0, 6) == "H_Axis") { - LastGeoId = Sketcher::GeoEnum::HAxis; - LastPointPos = Sketcher::PointPos::none; - lastgeotype = line; - lastvertexoraxis = true; - } - // check if last selected element is vertical axis - else if (SubNames.rbegin()->size() == 6 && SubNames.rbegin()->substr(0, 6) == "V_Axis") { - LastGeoId = Sketcher::GeoEnum::VAxis; - LastPointPos = Sketcher::PointPos::none; - lastgeotype = line; - lastvertexoraxis = true; - } - // check if last selected element is the root point - else if (SubNames.rbegin()->size() == 9 && SubNames.rbegin()->substr(0, 9) == "RootPoint") { - LastGeoId = Sketcher::GeoEnum::RtPnt; - LastPointPos = Sketcher::PointPos::start; - lastgeotype = point; - lastvertexoraxis = true; - } - - if (geoids == 0 || (geoids == 1 && LastGeoId >= 0 && !lastvertexoraxis)) { - Gui::TranslatedUserWarning(Obj, - QObject::tr("Wrong selection"), - QObject::tr("A symmetric construction requires " - "at least two geometric elements, " - "the last geometric element being the reference " - "for the symmetry construction.")); - return; - } - - if (lastgeotype == invalid) { - Gui::TranslatedUserWarning(Obj, - QObject::tr("Wrong selection"), - QObject::tr("The last element must be a point " - "or a line serving as reference " - "for the symmetry construction.")); - - return; - } - - std::string geoIdList = stream.str(); - - // missing cases: - // 1- Last element is an edge, and is V or H axis - // 2- Last element is a point GeomPoint - // 3- Last element is a point (Vertex) - - if (LastGeoId >= 0 && !lastvertexoraxis) { - // if LastGeoId was added remove the last element - int index = geoIdList.rfind(','); - index = geoIdList.rfind(',', index - 1); - geoIdList.resize(index); - } - else { - int index = geoIdList.rfind(','); - geoIdList.resize(index); - } - - geoIdList.insert(0, 1, '['); - geoIdList.append(1, ']'); - - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create symmetric geometry")); - - try { - Gui::cmdAppObjectArgs(Obj, - "addSymmetric(%s, %d, %d)", - geoIdList.c_str(), - LastGeoId, - static_cast(LastPointPos)); - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Gui::NotifyUserError( - Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"), e.what()); - Gui::Command::abortCommand(); - } - tryAutoRecomputeIfNotSolve(Obj); } bool CmdSketcherSymmetry::isActive() diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h new file mode 100644 index 0000000000..b151a77056 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h @@ -0,0 +1,291 @@ +/*************************************************************************** + * Copyright (c) 2022 Boyer Pierre-Louis * + * * + * 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 SKETCHERGUI_DrawSketchHandlerSymmetry_H +#define SKETCHERGUI_DrawSketchHandlerSymmetry_H + +#include + +#include +#include +#include +#include + +#include +#include + +#include "DrawSketchDefaultWidgetController.h" +#include "DrawSketchControllableHandler.h" + +#include "GeometryCreationMode.h" +#include "Utils.h" + +using namespace Sketcher; + +namespace SketcherGui +{ + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerSymmetry; + +using DSHSymmetryController = + DrawSketchDefaultWidgetController, + /*WidgetParametersT =*/WidgetParameters<0>, + /*WidgetCheckboxesT =*/WidgetCheckboxes<2>, + /*WidgetComboboxesT =*/WidgetComboboxes<0>>; + +using DSHSymmetryControllerBase = DSHSymmetryController::ControllerBase; + +using DrawSketchHandlerSymmetryBase = DrawSketchControllableHandler; + +class DrawSketchHandlerSymmetry: public DrawSketchHandlerSymmetryBase +{ + friend DSHSymmetryController; + friend DSHSymmetryControllerBase; + +public: + explicit DrawSketchHandlerSymmetry(std::vector listOfGeoIds) + : listOfGeoIds(listOfGeoIds) + , refGeoId(Sketcher::GeoEnum::GeoUndef) + , refPosId(Sketcher::PointPos::none) + , deleteOriginal(false) + , createSymConstraints(false) + {} + + DrawSketchHandlerSymmetry(const DrawSketchHandlerSymmetry&) = delete; + DrawSketchHandlerSymmetry(DrawSketchHandlerSymmetry&&) = delete; + DrawSketchHandlerSymmetry& operator=(const DrawSketchHandlerSymmetry&) = delete; + DrawSketchHandlerSymmetry& operator=(DrawSketchHandlerSymmetry&&) = delete; + + ~DrawSketchHandlerSymmetry() override = default; + +private: + void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override + { + switch (state()) { + case SelectMode::SeekFirst: { + int VtId = getPreselectPoint(); + int CrvId = getPreselectCurve(); + int CrsId = getPreselectCross(); + + if (VtId >= 0) { // Vertex + SketchObject* Obj = sketchgui->getSketchObject(); + Obj->getGeoVertexIndex(VtId, refGeoId, refPosId); + } + else if (CrsId == 0) { // RootPoint + refGeoId = Sketcher::GeoEnum::RtPnt; + refPosId = Sketcher::PointPos::start; + } + else if (CrsId == 1) { // H_Axis + refGeoId = Sketcher::GeoEnum::HAxis; + refPosId = Sketcher::PointPos::none; + } + else if (CrsId == 2) { // V_Axis + refGeoId = Sketcher::GeoEnum::VAxis; + refPosId = Sketcher::PointPos::none; + } + else if (CrvId >= 0 || CrvId <= Sketcher::GeoEnum::RefExt) { // Curves + refGeoId = CrvId; + refPosId = Sketcher::PointPos::none; + } + else { + refGeoId = Sketcher::GeoEnum::GeoUndef; + refPosId = Sketcher::PointPos::none; + } + + + CreateAndDrawShapeGeometry(); + } break; + default: + break; + } + } + + void executeCommands() override + { + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Symmetry geometries")); + + SketchObject* Obj = sketchgui->getSketchObject(); + createSymConstraints = !deleteOriginal && createSymConstraints; + Obj->addSymmetric(listOfGeoIds, refGeoId, refPosId, createSymConstraints); + + if (deleteOriginal) { + deleteOriginalGeos(); + } + tryAutoRecomputeIfNotSolve(Obj); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + e.ReportException(); + Gui::NotifyError(sketchgui, + QT_TRANSLATE_NOOP("Notifications", "Error"), + QT_TRANSLATE_NOOP("Notifications", "Failed to create symmetry")); + + Gui::Command::abortCommand(); + THROWM(Base::RuntimeError, + QT_TRANSLATE_NOOP( + "Notifications", + "Tool execution aborted") "\n") // This prevents constraints from being + // applied on non existing geometry + } + } + + void createAutoConstraints() override + { + // none + } + + std::string getToolName() const override + { + return "DSH_Symmetry"; + } + + QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Symmetry"); + } + + std::unique_ptr createWidget() const override + { + return std::make_unique(); + } + + bool isWidgetVisible() const override + { + return true; + }; + + QPixmap getToolIcon() const override + { + return Gui::BitmapFactory().pixmap("Sketcher_Symmetry"); + } + + QString getToolWidgetText() const override + { + return QString(QObject::tr("Symmetry parameters")); + } + + void activated() override + { + DrawSketchDefaultHandler::activated(); + continuousMode = false; + } + + bool canGoToNextMode() override + { + if (state() == SelectMode::SeekFirst && refGeoId == Sketcher::GeoEnum::GeoUndef) { + // Prevent validation if no reference selected. + return false; + } + return true; + } + +private: + std::vector listOfGeoIds; + int refGeoId; + Sketcher::PointPos refPosId; + bool deleteOriginal, createSymConstraints; + + void deleteOriginalGeos() + { + std::stringstream stream; + for (size_t j = 0; j < listOfGeoIds.size() - 1; j++) { + stream << listOfGeoIds[j] << ","; + } + stream << listOfGeoIds[listOfGeoIds.size() - 1]; + try { + Gui::cmdAppObjectArgs(sketchgui->getObject(), + "delGeometries([%s])", + stream.str().c_str()); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + } + } + + void createShape(bool onlyeditoutline) override + { + SketchObject* Obj = sketchgui->getSketchObject(); + + ShapeGeometry.clear(); + + if (refGeoId == Sketcher::GeoEnum::GeoUndef) { + return; + } + + if (onlyeditoutline) { + std::map dummy1; + std::map dummy2; + std::vector symGeos = + Obj->getSymmetric(listOfGeoIds, dummy1, dummy2, refGeoId, refPosId); + + for (auto* geo : symGeos) { + ShapeGeometry.emplace_back(std::move(std::unique_ptr(geo))); + } + } + } +}; + +template<> +void DSHSymmetryController::configureToolWidget() +{ + if (!init) { // Code to be executed only upon initialisation + toolWidget->setCheckboxLabel(WCheckbox::FirstBox, + QApplication::translate("TaskSketcherTool_c1_symmetry", + "Delete original geometries (U)")); + toolWidget->setCheckboxLabel(WCheckbox::SecondBox, + QApplication::translate("TaskSketcherTool_c2_symmetry", + "Create Symmetry Constraints (J)")); + } +} + +template<> +void DSHSymmetryController::adaptDrawingToCheckboxChange(int checkboxindex, bool value) +{ + switch (checkboxindex) { + case WCheckbox::FirstBox: { + handler->deleteOriginal = value; + if (value && toolWidget->getCheckboxChecked(WCheckbox::SecondBox)) { + toolWidget->setCheckboxChecked(WCheckbox::SecondBox, false); + } + } break; + case WCheckbox::SecondBox: { + handler->createSymConstraints = value; + if (value && toolWidget->getCheckboxChecked(WCheckbox::FirstBox)) { + toolWidget->setCheckboxChecked(WCheckbox::FirstBox, false); + } + } break; + } +} + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerSymmetry_H diff --git a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc index 6572d92280..621488dec2 100644 --- a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc +++ b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc @@ -246,6 +246,7 @@ icons/pointers/Sketcher_Pointer_Create_Offset.svg icons/pointers/Sketcher_Pointer_Create_Rotate.svg icons/pointers/Sketcher_Pointer_Create_Scale.svg + icons/pointers/Sketcher_Pointer_Create_Symmetry.svg icons/pointers/Sketcher_Pointer_Extension.svg icons/pointers/Sketcher_Pointer_External.svg icons/pointers/Sketcher_Pointer_Heptagon.svg diff --git a/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_Create_Symmetry.svg b/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_Create_Symmetry.svg new file mode 100644 index 0000000000..6b8026d8d4 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_Create_Symmetry.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Start/StartPage/StartPage.py b/src/Mod/Start/StartPage/StartPage.py index 7025843d2c..879d305298 100644 --- a/src/Mod/Start/StartPage/StartPage.py +++ b/src/Mod/Start/StartPage/StartPage.py @@ -554,13 +554,13 @@ def handle(): SECTION_NEW_FILE = "

" + TranslationTexts.get("T_NEWFILE") + "

" SECTION_NEW_FILE += "
    " - SECTION_NEW_FILE += build_new_file_card("empty_file") - SECTION_NEW_FILE += build_new_file_card("open_file") SECTION_NEW_FILE += build_new_file_card("parametric_part") SECTION_NEW_FILE += build_new_file_card("assembly") # SECTION_NEW_FILE += build_new_file_card("csg_part") SECTION_NEW_FILE += build_new_file_card("2d_draft") SECTION_NEW_FILE += build_new_file_card("architecture") + SECTION_NEW_FILE += build_new_file_card("empty_file") + SECTION_NEW_FILE += build_new_file_card("open_file") SECTION_NEW_FILE += "
" HTML = HTML.replace("SECTION_NEW_FILE", SECTION_NEW_FILE) diff --git a/src/Mod/Start/StartPage/TranslationTexts.py b/src/Mod/Start/StartPage/TranslationTexts.py index 8f6d1b428c..61d1099ce8 100644 --- a/src/Mod/Start/StartPage/TranslationTexts.py +++ b/src/Mod/Start/StartPage/TranslationTexts.py @@ -51,12 +51,14 @@ def get(handle): T_TEMPLATE_EMPTYFILE_DESC = translate("StartPage", "Create an empty FreeCAD file") T_TEMPLATE_OPENFILE_NAME = translate("StartPage", "Open File") T_TEMPLATE_OPENFILE_DESC = translate("StartPage", "Open an existing CAD file or 3D model") - T_TEMPLATE_PARAMETRICPART_NAME = translate("StartPage", "Standard Part") + T_TEMPLATE_PARAMETRICPART_NAME = translate("StartPage", "Parametric Part") T_TEMPLATE_PARAMETRICPART_DESC = translate( "StartPage", "Create a part with the Part Design workbench" ) T_TEMPLATE_ASSEMBLY_NAME = translate("StartPage", "Assembly") - T_TEMPLATE_ASSEMBLY_DESC = translate("StartPage", "Create an assembly project") + T_TEMPLATE_ASSEMBLY_DESC = translate( + "StartPage", "Create an assembly with the Assembly workbench" + ) # T_TEMPLATE_CSGPART_NAME = translate("StartPage", "CSG Part") # T_TEMPLATE_CSGPART_DESC = translate("StartPage", "Create a part with the Part workbench") T_TEMPLATE_2DDRAFT_NAME = translate("StartPage", "2D Draft") diff --git a/src/Mod/Start/StartPage/images/new_empty_file.png b/src/Mod/Start/StartPage/images/new_empty_file.png index 98a739e91a..00ec1c5a17 100644 Binary files a/src/Mod/Start/StartPage/images/new_empty_file.png and b/src/Mod/Start/StartPage/images/new_empty_file.png differ diff --git a/src/Mod/TechDraw/App/DrawSVGTemplate.cpp b/src/Mod/TechDraw/App/DrawSVGTemplate.cpp index fe36964dcc..abd145f84c 100644 --- a/src/Mod/TechDraw/App/DrawSVGTemplate.cpp +++ b/src/Mod/TechDraw/App/DrawSVGTemplate.cpp @@ -132,10 +132,10 @@ QString DrawSVGTemplate::processTemplate() query.processItems(QString::fromUtf8( "declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan"), + "//text[@" FREECAD_ATTR_EDITABLE "]/tspan"), [&substitutions, &templateDocument](QDomElement& tspan) -> bool { // Replace the editable text spans with new nodes holding actual values - QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable")); + QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8(FREECAD_ATTR_EDITABLE)); std::map::iterator item = substitutions.find(editableName.toStdString()); if (item != substitutions.end()) { @@ -296,15 +296,28 @@ std::map DrawSVGTemplate::getEditableTextsFromTemplate query.processItems(QString::fromUtf8( "declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan"), - [&editables](QDomElement& tspan) -> bool { - QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable")); - QString editableValue = tspan.firstChild().nodeValue(); + "//text[@" FREECAD_ATTR_EDITABLE "]/tspan"), + [this, &editables](QDomElement& tspan) -> bool { + QDomElement parent = tspan.parentNode().toElement(); + QString editableName = parent.attribute(QString::fromUtf8(FREECAD_ATTR_EDITABLE)); - editables[std::string(editableName.toUtf8().constData())] = - std::string(editableValue.toUtf8().constData()); - return true; - }); + QString editableValue; + if (parent.hasAttribute(QString::fromUtf8(FREECAD_ATTR_AUTOFILL))) { + QString autofillValue = getAutofillValue(parent.attribute(QString::fromUtf8(FREECAD_ATTR_AUTOFILL))); + if (!autofillValue.isNull()) { + editableValue = autofillValue; + } + } + + // If the autofill value is not specified or unsupported, use the default text value + if (editableValue.isNull()) { + editableValue = tspan.firstChild().nodeValue(); + } + + editables[std::string(editableName.toUtf8().constData())] = + std::string(editableValue.toUtf8().constData()); + return true; + }); return editables; } diff --git a/src/Mod/TechDraw/App/DrawTemplate.cpp b/src/Mod/TechDraw/App/DrawTemplate.cpp index 35709cd26e..7ba1cfbe50 100644 --- a/src/Mod/TechDraw/App/DrawTemplate.cpp +++ b/src/Mod/TechDraw/App/DrawTemplate.cpp @@ -24,13 +24,19 @@ #ifndef _PreComp_ # include +# include +# include #endif #include +#include +#include + #include "DrawTemplate.h" #include "DrawTemplatePy.h" #include "DrawPage.h" +#include "DrawUtil.h" using namespace TechDraw; @@ -94,6 +100,67 @@ DrawPage* DrawTemplate::getParentPage() const return page; } +QString DrawTemplate::getAutofillValue(const QString &id) const +{ + // author + if (id.compare(QString::fromUtf8(Autofill::Author)) == 0) { + std::string value = App::GetApplication().GetUserParameter().GetGroup("BaseApp")->GetGroup("Preferences") + ->GetGroup("Document")->GetASCII("prefAuthor"); + if (!value.empty()) { + return QString::fromUtf8(value.c_str()); + } + } + // date + else if (id.compare(QString::fromUtf8(Autofill::Date)) == 0) { + QDateTime date = QDateTime::currentDateTime(); + return date.toString(QLocale().dateFormat(QLocale::ShortFormat)); + } + // organization + else if (id.compare(QString::fromUtf8(Autofill::Organization)) == 0) { + std::string value = App::GetApplication().GetUserParameter().GetGroup("BaseApp")->GetGroup("Preferences") + ->GetGroup("Document")->GetASCII("prefCompany"); + if (!value.empty()) { + return QString::fromUtf8(value.c_str()); + } + } + // scale + else if (id.compare(QString::fromUtf8(Autofill::Scale)) == 0) { + DrawPage *page = getParentPage(); + if (page) { + std::pair scale = DrawUtil::nearestFraction(page->Scale.getValue()); + return QString::asprintf("%d : %d", scale.first, scale.second); + } + } + // sheet + else if (id.compare(QString::fromUtf8(Autofill::Sheet)) == 0) { + std::vector pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); + std::vector pageNames; + for (auto page : pages) { + pageNames.push_back(QString::fromUtf8(page->Label.getValue())); + } + + QCollator collator; + std::sort(pageNames.begin(), pageNames.end(), collator); + + int pos = 0; + DrawPage *page = getParentPage(); + if (page) { + auto it = std::find(pageNames.begin(), pageNames.end(), QString::fromUtf8(page->Label.getValue())); + if (it != pageNames.end()) { + pos = it - pageNames.begin() + 1; + } + } + + return QString::asprintf("%d / %d", pos, (int) pageNames.size()); + } + // title + else if (id.compare(QString::fromUtf8(Autofill::Title)) == 0) { + return QString::fromUtf8(getDocument()->Label.getValue()); + } + + return QString(); +} + // Python Template feature --------------------------------------------------------- namespace App { diff --git a/src/Mod/TechDraw/App/DrawTemplate.h b/src/Mod/TechDraw/App/DrawTemplate.h index 4849381aab..f2c6be5390 100644 --- a/src/Mod/TechDraw/App/DrawTemplate.h +++ b/src/Mod/TechDraw/App/DrawTemplate.h @@ -57,6 +57,8 @@ public: virtual DrawPage* getParentPage() const; + virtual QString getAutofillValue(const QString &id) const; + /// returns the type name of the ViewProvider const char* getViewProviderName(void) const override{ return "TechDrawGui::ViewProviderTemplate"; @@ -65,6 +67,17 @@ public: // from base class PyObject *getPyObject(void) override; + class Autofill + { + public: + static constexpr const char *Author = "author"; + static constexpr const char *Date = "date"; + static constexpr const char *Organization = "organization"; + static constexpr const char *Scale = "scale"; + static constexpr const char *Sheet = "sheet"; + static constexpr const char *Title = "title"; + }; + private: static const char* OrientationEnums[]; diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index e6c6385eb8..915e9e5807 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -1312,6 +1312,80 @@ double DrawUtil::angleDifference(double fi1, double fi2, bool reflex) return fi1; } +std::pair DrawUtil::nearestFraction(double val, int maxDenom) +{ +// Find rational approximation to given real number +// David Eppstein / UC Irvine / 8 Aug 1993 +// +// With corrections from Arno Formella, May 2008 +// and additional fiddles by WF 2017 +// usage: a.out r d +// r is real number to approx +// d is the maximum denominator allowed +// +// Based on the theory of continued fractions +// if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...))) +// then best approximation is found by truncating this series +// (with some adjustments in the last term). +// +// Note the fraction can be recovered as the first column of the matrix +// ( a1 1 ) ( a2 1 ) ( a3 1 ) ... +// ( 1 0 ) ( 1 0 ) ( 1 0 ) +// Instead of keeping the sequence of continued fraction terms, +// we just keep the last partial product of these matrices. + std::pair result; + long m[2][2]; + long maxden = maxDenom; + long ai; + double x = val; + double startx = x; + + /* initialize matrix */ + m[0][0] = m[1][1] = 1; + m[0][1] = m[1][0] = 0; + + /* loop finding terms until denom gets too big */ + while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden) { + long t; + t = m[0][0] * ai + m[0][1]; + m[0][1] = m[0][0]; + m[0][0] = t; + t = m[1][0] * ai + m[1][1]; + m[1][1] = m[1][0]; + m[1][0] = t; + if(x == (double) ai) + break; // AF: division by zero + x = 1/(x - (double) ai); + if(x > (double) std::numeric_limits::max()) + break; // AF: representation failure + } + + /* now remaining x is between 0 and 1/ai */ + /* approx as either 0 or 1/m where m is max that will fit in maxden */ + /* first try zero */ + double error1 = startx - ((double) m[0][0] / (double) m[1][0]); + int n1 = m[0][0]; + int d1 = m[1][0]; + + /* now try other possibility */ + ai = (maxden - m[1][1]) / m[1][0]; + m[0][0] = m[0][0] * ai + m[0][1]; + m[1][0] = m[1][0] * ai + m[1][1]; + double error2 = startx - ((double) m[0][0] / (double) m[1][0]); + int n2 = m[0][0]; + int d2 = m[1][0]; + + if (std::fabs(error1) <= std::fabs(error2)) { + result.first = n1; + result.second = d1; + } else { + result.first = n2; + result.second = d2; + } + + return result; +} + // Interval marking functions // ========================== diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index 503de91462..c6b6bdd069 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -58,6 +58,9 @@ #define SVG_NS_URI "http://www.w3.org/2000/svg" #define FREECAD_SVG_NS_URI "https://www.freecad.org/wiki/index.php?title=Svg_Namespace" +#define FREECAD_ATTR_EDITABLE "freecad:editable" +#define FREECAD_ATTR_AUTOFILL "freecad:autofill" + //some shapes are being passed in where edges that should be connected are in fact //separated by more than 2*Precision::Confusion (expected tolerance for 2 TopoDS_Vertex) //this value is used in EdgeWalker, DrawProjectSplit and DrawUtil and needs to be in sync in @@ -217,6 +220,7 @@ public: static void angleNormalize(double& fi); static double angleComposition(double fi, double delta); static double angleDifference(double fi1, double fi2, bool reflex = false); + static std::pair nearestFraction(double val, int maxDenom = 999); // Interval marking functions static unsigned int intervalMerge(std::vector>& marking, diff --git a/src/Mod/TechDraw/App/DrawViewSection.cpp b/src/Mod/TechDraw/App/DrawViewSection.cpp index 01ab923556..1b9a53695b 100644 --- a/src/Mod/TechDraw/App/DrawViewSection.cpp +++ b/src/Mod/TechDraw/App/DrawViewSection.cpp @@ -385,7 +385,7 @@ TopoDS_Shape DrawViewSection::getShapeForDetail() const App::DocumentObjectExecReturn* DrawViewSection::execute() { - // Base::Console().Message("DVS::execute() - %s\n", getNameInDocument()); + // Base::Console().Message("DVS::execute() - %s\n", Label.getValue()); if (!keepUpdated()) { return App::DocumentObject::StdReturn; } @@ -445,9 +445,7 @@ bool DrawViewSection::isBaseValid() const void DrawViewSection::sectionExec(TopoDS_Shape& baseShape) { - // Base::Console().Message("DVS::sectionExec() - %s baseShape.IsNull: - // %d\n", - // getNameInDocument(), baseShape.IsNull()); + // Base::Console().Message("DVS::sectionExec() - %s baseShape.IsNull: %d\n", Label.getValue(), baseShape.IsNull()); if (waitingForHlr() || waitingForCut()) { return; @@ -486,9 +484,7 @@ void DrawViewSection::sectionExec(TopoDS_Shape& baseShape) void DrawViewSection::makeSectionCut(const TopoDS_Shape& baseShape) { - // Base::Console().Message("DVS::makeSectionCut() - %s - baseShape.IsNull: - // %d\n", - // getNameInDocument(), baseShape.IsNull()); + // Base::Console().Message("DVS::makeSectionCut() - %s - baseShape.IsNull:%d\n", Label.getValue(), baseShape.IsNull()); showProgressMessage(getNameInDocument(), "is making section cut"); @@ -638,8 +634,7 @@ void DrawViewSection::onSectionCutFinished() // activities that depend on updated geometry object void DrawViewSection::postHlrTasks(void) { - // Base::Console().Message("DVS::postHlrTasks() - %s\n", - // getNameInDocument()); + // Base::Console().Message("DVS::postHlrTasks() - %s\n", Label.getValue()); DrawViewPart::postHlrTasks(); @@ -1172,8 +1167,10 @@ gp_Ax2 DrawViewSection::getProjectionCS(const Base::Vector3d pt) const std::vector DrawViewSection::getDrawableLines(int i) { - // Base::Console().Message("DVS::getDrawableLines(%d) - lineSets: %d\n", i, - // m_lineSets.size()); + // Base::Console().Message("DVS::getDrawableLines(%d) - lineSets: %d\n", i, m_lineSets.size()); + if (m_lineSets.empty()) { + makeLineSets(); + } std::vector result; return DrawGeomHatch::getTrimmedLinesSection(this, m_lineSets, @@ -1236,7 +1233,7 @@ void DrawViewSection::setupObject() // create geometric hatch lines void DrawViewSection::makeLineSets(void) { - // Base::Console().Message("DVS::makeLineSets()\n"); + // Base::Console().Message("DVS::makeLineSets()\n"); if (PatIncluded.isEmpty()) { return; } @@ -1277,8 +1274,7 @@ void DrawViewSection::replaceSvgIncluded(std::string newSvgFile) void DrawViewSection::replacePatIncluded(std::string newPatFile) { - // Base::Console().Message("DVS::replacePatIncluded(%s)\n", - // newPatFile.c_str()); + // Base::Console().Message("DVS::replacePatIncluded(%s)\n", newPatFile.c_str()); if (newPatFile.empty()) { return; } diff --git a/src/Mod/TechDraw/App/DrawViewSymbol.cpp b/src/Mod/TechDraw/App/DrawViewSymbol.cpp index a7488fe082..d2bfa7d638 100644 --- a/src/Mod/TechDraw/App/DrawViewSymbol.cpp +++ b/src/Mod/TechDraw/App/DrawViewSymbol.cpp @@ -125,7 +125,7 @@ std::vector DrawViewSymbol::getEditableFields() // has "freecad:editable" attribute query.processItems(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan"), + "//text[@" FREECAD_ATTR_EDITABLE "]/tspan"), [&editables](QDomElement& tspan) -> bool { QString editableValue = tspan.firstChild().nodeValue(); editables.emplace_back(editableValue.toStdString()); @@ -154,7 +154,7 @@ void DrawViewSymbol::updateFieldsInSymbol() // has "freecad:editable" attribute query.processItems(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan"), + "//text[@" FREECAD_ATTR_EDITABLE "]/tspan"), [&symbolDocument, &editText, &count](QDomElement& tspanElement) -> bool { if (count >= editText.size()) { diff --git a/src/Mod/TechDraw/App/LineGenerator.cpp b/src/Mod/TechDraw/App/LineGenerator.cpp index 21952ffd55..8770c137f1 100644 --- a/src/Mod/TechDraw/App/LineGenerator.cpp +++ b/src/Mod/TechDraw/App/LineGenerator.cpp @@ -362,6 +362,17 @@ std::string LineGenerator::getLineStandardsBody() { int activeStandard = Preferences::lineStandard(); std::vector choices = getAvailableLineStandards(); + if (activeStandard < 0 || + (size_t) activeStandard >= choices.size()) { + // there is a condition where the LineStandard parameter exists, but is -1 (the + // qt value for no current index in a combobox). This is likely caused by an old + // development version writing an unvalidated value. In this case, the existing but + // invalid value will be returned. This is a temporary fix and can be removed for + // production. + // Preferences::lineStandard() will print a message about this every time it is called + // (lots of messages!). + activeStandard = 0; + } return getBodyFromString(choices.at(activeStandard)); } diff --git a/src/Mod/TechDraw/App/Preferences.cpp b/src/Mod/TechDraw/App/Preferences.cpp index ae35b68fa0..3517849a19 100644 --- a/src/Mod/TechDraw/App/Preferences.cpp +++ b/src/Mod/TechDraw/App/Preferences.cpp @@ -23,7 +23,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ # include - +# include # include #endif @@ -422,6 +422,18 @@ bool Preferences::SectionUsePreviousCut() //! an index into the list of available line standards/version found in LineGroupDirectory int Preferences::lineStandard() { + // there is a condition where the LineStandard parameter exists, but is -1 (the + // qt value for no current index in a combobox). This is likely caused by an old + // development version writing an unvalidated value. In this case, the + // existing but invalid value will be returned. This is a temporary fix and + // can be removed for production. + // this message will appear many times if the parameter is invalid. + int parameterValue = getPreferenceGroup("Standards")->GetInt("LineStandard", 1); + if (parameterValue < 0) { + Base::Console().Warning(qPrintable(QApplication::translate( + "Preferences", "The LineStandard parameter is invalid. Using zero instead.", nullptr))); + return 0; + } return getPreferenceGroup("Standards")->GetInt("LineStandard", 1); } diff --git a/src/Mod/TechDraw/App/XMLQuery.cpp b/src/Mod/TechDraw/App/XMLQuery.cpp index 6a88c3b6a3..adae6bac12 100644 --- a/src/Mod/TechDraw/App/XMLQuery.cpp +++ b/src/Mod/TechDraw/App/XMLQuery.cpp @@ -27,6 +27,7 @@ #include #endif +#include "DrawUtil.h" #include "XMLQuery.h" @@ -51,7 +52,7 @@ static bool processElements(const QDomElement& element, const QString& queryStr, for(int i = 0; i < editable.count(); i++) { QDomNode node = editable.item(i); QDomElement element = node.toElement(); - if (element.hasAttribute(QString(QLatin1String("freecad:editable")))) { + if (element.hasAttribute(QString(QLatin1String(FREECAD_ATTR_EDITABLE)))) { if (find_tspan) { element = element.firstChildElement(); } diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index f6ba42655b..dea656de90 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -105,40 +106,33 @@ void CmdTechDrawPageDefault::activated(int iMsg) Q_UNUSED(iMsg); QString templateFileName = Preferences::defaultTemplate(); - - std::string PageName = getUniqueObjectName("Page"); - std::string TemplateName = getUniqueObjectName("Template"); - QFileInfo tfi(templateFileName); if (tfi.isReadable()) { Gui::WaitCursor wc; openCommand(QT_TRANSLATE_NOOP("Command", "Drawing create page")); - doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawPage', '%s')", - PageName.c_str()); - doCommand(Doc, "App.activeDocument().%s.translateLabel('DrawPage', 'Page', '%s')", - PageName.c_str(), PageName.c_str()); - doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawSVGTemplate', '%s')", - TemplateName.c_str()); - doCommand(Doc, "App.activeDocument().%s.translateLabel('DrawSVGTemplate', 'Template', '%s')", - TemplateName.c_str(), TemplateName.c_str()); + auto page = dynamic_cast + (getDocument()->addObject("TechDraw::DrawPage", "Page")); + if (!page) { + throw Base::TypeError("CmdTechDrawPageDefault - page not created"); + } + page->translateLabel("DrawPage", "Page", page->getNameInDocument()); - doCommand(Doc, "App.activeDocument().%s.Template = '%s'", TemplateName.c_str(), - templateFileName.toStdString().c_str()); - doCommand(Doc, "App.activeDocument().%s.Template = App.activeDocument().%s", - PageName.c_str(), TemplateName.c_str()); + auto svgTemplate = dynamic_cast + (getDocument()->addObject("TechDraw::DrawSVGTemplate", "Template")); + if (!svgTemplate) { + throw Base::TypeError("CmdTechDrawPageDefault - template not created"); + } + svgTemplate->translateLabel("DrawSVGTemplate", "Template", svgTemplate->getNameInDocument()); + + page->Template.setValue(svgTemplate); + svgTemplate->Template.setValue(templateFileName.toStdString()); updateActive(); commitCommand(); - TechDraw::DrawPage* fp = - dynamic_cast(getDocument()->getObject(PageName.c_str())); - if (!fp) { - throw Base::TypeError("CmdTechDrawPageDefault fp not found\n"); - } - Gui::ViewProvider* vp = - Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); - TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); + TechDrawGui::ViewProviderPage *dvp = dynamic_cast + (Gui::Application::Instance->getViewProvider(page)); if (dvp) { dvp->show(); } @@ -182,44 +176,33 @@ void CmdTechDrawPageTemplate::activated(int iMsg) return; } - std::string PageName = getUniqueObjectName("Page"); - std::string TemplateName = getUniqueObjectName("Template"); - QFileInfo tfi(templateFileName); if (tfi.isReadable()) { Gui::WaitCursor wc; openCommand(QT_TRANSLATE_NOOP("Command", "Drawing create page")); - doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawPage', '%s')", - PageName.c_str()); - doCommand(Doc, "App.activeDocument().%s.translateLabel('DrawPage', 'Page', '%s')", - PageName.c_str(), PageName.c_str()); - // Create the Template Object to attach to the page - doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawSVGTemplate', '%s')", - TemplateName.c_str()); - doCommand(Doc, "App.activeDocument().%s.translateLabel('DrawSVGTemplate', 'Template', '%s')", - TemplateName.c_str(), TemplateName.c_str()); + auto page = dynamic_cast + (getDocument()->addObject("TechDraw::DrawPage", "Page")); + if (!page) { + throw Base::TypeError("CmdTechDrawPageTemplate - page not created"); + } + page->translateLabel("DrawPage", "Page", page->getNameInDocument()); - //why is "Template" property set twice? -wf - // once to set DrawSVGTemplate.Template to OS template file name - templateFileName = Base::Tools::escapeEncodeFilename(templateFileName); - doCommand(Doc, "App.activeDocument().%s.Template = \"%s\"", TemplateName.c_str(), - templateFileName.toUtf8().constData()); - // once to set Page.Template to DrawSVGTemplate.Name - doCommand(Doc, "App.activeDocument().%s.Template = App.activeDocument().%s", - PageName.c_str(), TemplateName.c_str()); - // consider renaming DrawSVGTemplate.Template property? + auto svgTemplate = dynamic_cast + (getDocument()->addObject("TechDraw::DrawSVGTemplate", "Template")); + if (!svgTemplate) { + throw Base::TypeError("CmdTechDrawPageTemplate - template not created"); + } + svgTemplate->translateLabel("DrawSVGTemplate", "Template", svgTemplate->getNameInDocument()); + + page->Template.setValue(svgTemplate); + svgTemplate->Template.setValue(templateFileName.toStdString()); updateActive(); commitCommand(); - TechDraw::DrawPage* fp = - dynamic_cast(getDocument()->getObject(PageName.c_str())); - if (!fp) { - throw Base::TypeError("CmdTechDrawNewPagePick fp not found\n"); - } - Gui::ViewProvider* vp = - Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); - TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); + + TechDrawGui::ViewProviderPage *dvp = dynamic_cast + (Gui::Application::Instance->getViewProvider(page)); if (dvp) { dvp->show(); } diff --git a/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp b/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp index 919616c9c1..1762ea90a2 100644 --- a/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp +++ b/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp @@ -178,9 +178,9 @@ void QGISVGTemplate::createClickHandles() // XPath query to select all nodes with "freecad:editable" attribute query.processItems(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]"), + "//text[@" FREECAD_ATTR_EDITABLE "]"), [&](QDomElement& textElement) -> bool { - QString name = textElement.attribute(QString::fromUtf8("freecad:editable")); + QString name = textElement.attribute(QString::fromUtf8(FREECAD_ATTR_EDITABLE)); double x = Rez::guiX( textElement.attribute(QString::fromUtf8("x"), QString::fromUtf8("0.0")).toDouble()); double y = Rez::guiX( diff --git a/src/Mod/TechDraw/Gui/QGIViewSection.cpp b/src/Mod/TechDraw/Gui/QGIViewSection.cpp index 07f90a1a1a..86a623fb2f 100644 --- a/src/Mod/TechDraw/Gui/QGIViewSection.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewSection.cpp @@ -47,6 +47,7 @@ void QGIViewSection::draw() void QGIViewSection::drawSectionFace() { + // Base::Console().Message("QGIVS::drawSectionFace()\n"); auto section( dynamic_cast(getViewObject()) ); if (!section) { return; @@ -85,13 +86,14 @@ void QGIViewSection::drawSectionFace() return; } - QColor faceColor = (sectionVp->CutSurfaceColor.getValue()).asValue(); - faceColor.setAlpha((100 - sectionVp->CutSurfaceTransparency.getValue())*255/100); - newFace->setFillColor(faceColor); - if (section->CutSurfaceDisplay.isValue("Color")) { + newFace->isHatched(true); + QColor faceColor = (sectionVp->CutSurfaceColor.getValue()).asValue(); + faceColor.setAlpha((100 - sectionVp->CutSurfaceTransparency.getValue())*255/100); + newFace->setFillColor(faceColor); newFace->setFillMode(faceColor.alpha() ? QGIFace::PlainFill : QGIFace::NoFill); } else if (section->CutSurfaceDisplay.isValue("SvgHatch")) { + newFace->isHatched(true); newFace->setFillMode(QGIFace::SvgFill); newFace->setHatchColor(sectionVp->HatchColor.getValue()); newFace->setHatchScale(section->HatchScale.getValue()); @@ -104,9 +106,9 @@ void QGIViewSection::drawSectionFace() newFace->setFillMode(QGIFace::GeomHatchFill); newFace->setHatchColor(sectionVp->GeomHatchColor.getValue()); newFace->setHatchScale(section->HatchScale.getValue()); - newFace->setLineWeight(sectionVp->WeightPattern.getValue()); newFace->setHatchRotation(section->HatchRotation.getValue()); newFace->setHatchOffset(section->HatchOffset.getValue()); + newFace->setLineWeight(sectionVp->WeightPattern.getValue()); std::vector lineSets = section->getDrawableLines(i); if (!lineSets.empty()) { newFace->clearLineSets(); diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.cpp b/src/Mod/TechDraw/Gui/TaskProjGroup.cpp index 0916dfc9f5..d2cc2638bb 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.cpp +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "TaskProjGroup.h" #include "ui_TaskProjGroup.h" @@ -306,81 +307,6 @@ void TaskProjGroup::spacingChanged() multiView->recomputeFeature(); } -std::pair TaskProjGroup::nearestFraction(const double val, const long int maxDenom) const -{ -/* -** find rational approximation to given real number -** David Eppstein / UC Irvine / 8 Aug 1993 -** -** With corrections from Arno Formella, May 2008 -** and additional fiddles by WF 2017 -** usage: a.out r d -** r is real number to approx -** d is the maximum denominator allowed -** -** based on the theory of continued fractions -** if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...))) -** then best approximation is found by truncating this series -** (with some adjustments in the last term). -** -** Note the fraction can be recovered as the first column of the matrix -** ( a1 1 ) ( a2 1 ) ( a3 1 ) ... -** ( 1 0 ) ( 1 0 ) ( 1 0 ) -** Instead of keeping the sequence of continued fraction terms, -** we just keep the last partial product of these matrices. -*/ - std::pair result; - long m[2][2]; - long maxden = maxDenom; - long ai; - double x = val; - double startx = x; - - /* initialize matrix */ - m[0][0] = m[1][1] = 1; - m[0][1] = m[1][0] = 0; - - /* loop finding terms until denom gets too big */ - while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden) { - long t; - t = m[0][0] * ai + m[0][1]; - m[0][1] = m[0][0]; - m[0][0] = t; - t = m[1][0] * ai + m[1][1]; - m[1][1] = m[1][0]; - m[1][0] = t; - if(x == (double) ai) - break; // AF: division by zero - x = 1/(x - (double) ai); - if(x > (double) std::numeric_limits::max()) - break; // AF: representation failure - } - - /* now remaining x is between 0 and 1/ai */ - /* approx as either 0 or 1/m where m is max that will fit in maxden */ - /* first try zero */ - double error1 = startx - ((double) m[0][0] / (double) m[1][0]); - int n1 = m[0][0]; - int d1 = m[1][0]; - - /* now try other possibility */ - ai = (maxden - m[1][1]) / m[1][0]; - m[0][0] = m[0][0] * ai + m[0][1]; - m[1][0] = m[1][0] * ai + m[1][1]; - double error2 = startx - ((double) m[0][0] / (double) m[1][0]); - int n2 = m[0][0]; - int d2 = m[1][0]; - - if (std::fabs(error1) <= std::fabs(error2)) { - result.first = n1; - result.second = d1; - } else { - result.first = n2; - result.second = d2; - } - return result; -} - void TaskProjGroup::updateTask() { // Update the scale type @@ -398,7 +324,7 @@ void TaskProjGroup::setFractionalScale(double newScale) { blockUpdate = true; - std::pair fraction = nearestFraction(newScale); + std::pair fraction = DrawUtil::nearestFraction(newScale); ui->sbScaleNum->setValue(fraction.first); ui->sbScaleDen->setValue(fraction.second); diff --git a/src/Mod/TechDraw/Gui/TaskProjGroup.h b/src/Mod/TechDraw/Gui/TaskProjGroup.h index 9e1dbfa3e3..7a3062443c 100644 --- a/src/Mod/TechDraw/Gui/TaskProjGroup.h +++ b/src/Mod/TechDraw/Gui/TaskProjGroup.h @@ -60,7 +60,6 @@ public: QPushButton* btnApply); void updateTask(); - std::pair nearestFraction(double val, long int maxDenom = 999) const; // Sets the numerator and denominator widgets to match newScale void setFractionalScale(double newScale); void setCreateMode(bool mode) { m_createMode = mode;} diff --git a/src/Mod/TechDraw/Gui/ViewProviderPage.cpp b/src/Mod/TechDraw/Gui/ViewProviderPage.cpp index eeb751330c..ddd5c3dd07 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderPage.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderPage.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -262,6 +263,16 @@ void ViewProviderPage::unsetEdit(int ModNum) bool ViewProviderPage::doubleClicked(void) { + // assure the TechDraw workbench + if (App::GetApplication() + .GetUserParameter() + .GetGroup("BaseApp") + ->GetGroup("Preferences") + ->GetGroup("Mod/TechDraw") + ->GetBool("SwitchToWB", true)) { + Gui::Command::assureWorkbench("TechDrawWorkbench"); + } + show(); if (m_mdiView) { Gui::getMainWindow()->setActiveWindow(m_mdiView); diff --git a/src/Mod/TechDraw/Gui/ViewProviderPageExtension.cpp b/src/Mod/TechDraw/Gui/ViewProviderPageExtension.cpp index 5cf0e88bbb..48c56d1e78 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderPageExtension.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderPageExtension.cpp @@ -71,6 +71,22 @@ bool ViewProviderPageExtension::extensionCanDropObject(App::DocumentObject* obj) return false; } +bool ViewProviderPageExtension::extensionCanDropObjectEx(App::DocumentObject* obj, App::DocumentObject* owner, + const char* subname, + const std::vector& elements) const +{ + //only DrawView objects can live on pages (except special case Template) + if (obj->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { + return true; + } + if (obj->isDerivedFrom(TechDraw::DrawTemplate::getClassTypeId())) { + //don't let another extension try to drop templates + return true; + } + + return false; +} + void ViewProviderPageExtension::extensionDropObject(App::DocumentObject* obj) { if (obj->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { diff --git a/src/Mod/TechDraw/Gui/ViewProviderPageExtension.h b/src/Mod/TechDraw/Gui/ViewProviderPageExtension.h index 755e986a1c..0971827105 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderPageExtension.h +++ b/src/Mod/TechDraw/Gui/ViewProviderPageExtension.h @@ -46,6 +46,9 @@ public: void extensionDragObject(App::DocumentObject*) override; bool extensionCanDropObjects() const override; bool extensionCanDropObject(App::DocumentObject*) const override; + bool extensionCanDropObjectEx(App::DocumentObject* obj, App::DocumentObject* owner, + const char* subname, + const std::vector& elements) const override; void extensionDropObject(App::DocumentObject*) override; void dropObject(App::DocumentObject* docObj); diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewClip.cpp b/src/Mod/TechDraw/Gui/ViewProviderViewClip.cpp index af7b8513b0..d653b9bb25 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewClip.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderViewClip.cpp @@ -31,6 +31,9 @@ #endif #include +#include +#include + #include "ViewProviderViewClip.h" using namespace TechDrawGui; @@ -104,3 +107,40 @@ TechDraw::DrawViewClip* ViewProviderViewClip::getObject() const { return getViewObject(); } + + +void ViewProviderViewClip::dragObject(App::DocumentObject* docObj) +{ + if (!docObj->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { + return; + } + + auto dv = static_cast(docObj); + + getObject()->removeView(dv); +} + +void ViewProviderViewClip::dropObject(App::DocumentObject* docObj) +{ + if (docObj->isDerivedFrom(TechDraw::DrawProjGroupItem::getClassTypeId())) { + //DPGI can not be dropped onto the Page as it belongs to DPG, not Page + return; + } + if (!docObj->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { + return; + } + + auto dv = static_cast(docObj); + TechDraw::DrawPage* pageClip = getObject()->findParentPage(); + TechDraw::DrawPage* pageView = dv->findParentPage(); + if (!pageClip || !pageView) { + return; + } + + if (pageClip != pageView) { + pageView->removeView(dv); + pageClip->addView(dv); + } + + getObject()->addView(dv); +} \ No newline at end of file diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewClip.h b/src/Mod/TechDraw/Gui/ViewProviderViewClip.h index 123b33bafd..a33f61bbe4 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewClip.h +++ b/src/Mod/TechDraw/Gui/ViewProviderViewClip.h @@ -56,6 +56,8 @@ public: bool canDelete(App::DocumentObject* obj) const override; + void dragObject(App::DocumentObject* docObj) override; + void dropObject(App::DocumentObject* docObj) override; }; } // namespace TechDrawGui diff --git a/src/Mod/TechDraw/Gui/Workbench.cpp b/src/Mod/TechDraw/Gui/Workbench.cpp index 9cc7c249c2..0dbd7f64a7 100644 --- a/src/Mod/TechDraw/Gui/Workbench.cpp +++ b/src/Mod/TechDraw/Gui/Workbench.cpp @@ -49,7 +49,6 @@ using namespace TechDrawGui; qApp->translate("Workbench", "TechDraw Annotation"); qApp->translate("Workbench", "TechDraw Attributes"); qApp->translate("Workbench", "TechDraw Centerlines"); - qApp->translate("Workbench", "TechDraw Clips"); qApp->translate("Workbench", "TechDraw Decoration"); qApp->translate("Workbench", "TechDraw Dimensions"); qApp->translate("Workbench", "TechDraw Extend Dimensions"); @@ -217,11 +216,11 @@ Gui::MenuItem* Workbench::setupMenuBar() const *views << "TechDraw_ComplexSection"; *views << "TechDraw_DetailView"; *views << "TechDraw_ProjectionGroup"; + *views << "TechDraw_ClipGroup"; *views << "Separator"; *views << "TechDraw_Symbol"; *views << "TechDraw_Image"; *views << "Separator"; - *views << "TechDraw_MoveView"; *views << "TechDraw_ShareView"; *views << "Separator"; *views << "TechDraw_ToggleFrame"; @@ -236,13 +235,6 @@ Gui::MenuItem* Workbench::setupMenuBar() const *other << "TechDraw_ArchView"; *other << "TechDraw_SpreadsheetView"; - // clip groups - Gui::MenuItem* clips = new Gui::MenuItem; - clips->setCommand("Clipped Views"); - *clips << "TechDraw_ClipGroup"; - *clips << "TechDraw_ClipGroupAdd"; - *clips << "TechDraw_ClipGroupRemove"; - // hatching Gui::MenuItem* hatch = new Gui::MenuItem; hatch->setCommand("Hatching"); @@ -264,8 +256,6 @@ Gui::MenuItem* Workbench::setupMenuBar() const *draw << "Separator"; *draw << other; *draw << "Separator"; - *draw << clips; - *draw << "Separator"; *draw << dimensions; *draw << "Separator"; *draw << hatch; @@ -308,16 +298,10 @@ Gui::ToolBarItem* Workbench::setupToolBars() const *views << "TechDraw_DraftView"; *views << "TechDraw_ArchView"; *views << "TechDraw_SpreadsheetView"; - *views << "TechDraw_MoveView"; + *views << "TechDraw_ClipGroup"; *views << "TechDraw_ShareView"; *views << "TechDraw_ProjectShape"; - Gui::ToolBarItem* clips = new Gui::ToolBarItem(root); - clips->setCommand("TechDraw Clips"); - *clips << "TechDraw_ClipGroup"; - *clips << "TechDraw_ClipGroupAdd"; - *clips << "TechDraw_ClipGroupRemove"; - Gui::ToolBarItem* stacking = new Gui::ToolBarItem(root); stacking->setCommand("TechDraw Stacking"); *stacking << "TechDraw_StackGroup"; @@ -422,16 +406,10 @@ Gui::ToolBarItem* Workbench::setupCommandBars() const *views << "TechDraw_DetailView"; *views << "TechDraw_DraftView"; *views << "TechDraw_SpreadsheetView"; - *views << "TechDraw_MoveView"; + *views << "TechDraw_ClipGroup"; *views << "TechDraw_ShareView"; *views << "TechDraw_ProjectShape"; - Gui::ToolBarItem* clips = new Gui::ToolBarItem(root); - clips->setCommand("TechDraw Clips"); - *clips << "TechDraw_ClipGroup"; - *clips << "TechDraw_ClipGroupAdd"; - *clips << "TechDraw_ClipGroupRemove"; - Gui::ToolBarItem* stacking = new Gui::ToolBarItem(root); stacking->setCommand("TechDraw Stacking"); *stacking << "TechDraw_StackGroup"; diff --git a/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py index a96aa0062e..96605f69e1 100644 --- a/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py +++ b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py @@ -35,53 +35,86 @@ import os translate = App.Qt.translate + class TaskHoleShaftFit: - def __init__(self,sel): + def __init__(self, sel): loose = translate("TechDraw_HoleShaftFit", "loose fit") snug = translate("TechDraw_HoleShaftFit", "snug fit") press = translate("TechDraw_HoleShaftFit", "press fit") self.isHole = True self.sel = sel - self.holeValues = [["h9","D10",loose],["h9","E9",loose],["h9","F8",loose],["h6","G7",loose], - ["c11","H11",loose],["f7","H8",loose],["h6","H7",loose],["h7","H8",loose], - ["k6","H7",snug],["n6","H7",snug],["r6","H7",press],["s6","H7",press], - ["h6","K7",snug],["h6","N7",snug],["h6","R7",press],["h6","S7",press]] - self.shaftValues = [["H11","c11",loose],["H8","f7",loose],["H7","h6",loose],["H8","h7",loose], - ["D10","h9",loose],["E9","h9",loose],["F8","h9",loose],["G7","h6",loose], - ["K7","h6",snug],["N7","h6",snug],["R7","h6",press],["S7","h6",press], - ["H7","k6",snug],["H7","n6",snug],["H7","r6",press],["H7","s6",press]] + self.holeValues = [ + ["h9", "D10", loose], + ["h9", "E9", loose], + ["h9", "F8", loose], + ["h6", "G7", loose], + ["c11", "H11", loose], + ["f7", "H8", loose], + ["h6", "H7", loose], + ["h7", "H8", loose], + ["k6", "H7", snug], + ["n6", "H7", snug], + ["r6", "H7", press], + ["s6", "H7", press], + ["h6", "K7", snug], + ["h6", "N7", snug], + ["h6", "R7", press], + ["h6", "S7", press], + ] + self.shaftValues = [ + ["H11", "c11", loose], + ["H8", "f7", loose], + ["H7", "h6", loose], + ["H8", "h7", loose], + ["D10", "h9", loose], + ["E9", "h9", loose], + ["F8", "h9", loose], + ["G7", "h6", loose], + ["K7", "h6", snug], + ["N7", "h6", snug], + ["R7", "h6", press], + ["S7", "h6", press], + ["H7", "k6", snug], + ["H7", "n6", snug], + ["H7", "r6", press], + ["H7", "s6", press], + ] self._uiPath = App.getHomePath() - self._uiPath = os.path.join(self._uiPath, "Mod/TechDraw/TechDrawTools/Gui/TaskHoleShaftFit.ui") + self._uiPath = os.path.join( + self._uiPath, "Mod/TechDraw/TechDrawTools/Gui/TaskHoleShaftFit.ui" + ) self.form = Gui.PySideUic.loadUi(self._uiPath) - self.form.setWindowTitle(translate("TechDraw_HoleShaftFit", "Hole / Shaft Fit ISO 286")) + self.form.setWindowTitle( + translate("TechDraw_HoleShaftFit", "Hole / Shaft Fit ISO 286") + ) - self.form.rbHoleBase.clicked.connect(partial(self.on_HoleShaftChanged,True)) - self.form.rbShaftBase.clicked.connect(partial(self.on_HoleShaftChanged,False)) + self.form.rbHoleBase.clicked.connect(partial(self.on_HoleShaftChanged, True)) + self.form.rbShaftBase.clicked.connect(partial(self.on_HoleShaftChanged, False)) self.form.cbField.currentIndexChanged.connect(self.on_FieldChanged) def setHoleFields(self): - '''set hole fields in the combo box''' + """set hole fields in the combo box""" for i in range(self.form.cbField.count()): self.form.cbField.removeItem(0) for value in self.holeValues: self.form.cbField.addItem(value[1]) - self.form.lbBaseField.setText(' '+self.holeValues[0][0]+" /") + self.form.lbBaseField.setText(" " + self.holeValues[0][0] + " /") self.form.lbFitType.setText(self.holeValues[0][2]) def setShaftFields(self): - '''set shaft fields in the combo box''' + """set shaft fields in the combo box""" for i in range(self.form.cbField.count()): self.form.cbField.removeItem(0) for value in self.shaftValues: self.form.cbField.addItem(value[1]) - self.form.lbBaseField.setText(' '+self.shaftValues[0][0]+" /") + self.form.lbBaseField.setText(" " + self.shaftValues[0][0] + " /") self.form.lbFitType.setText(self.shaftValues[0][2]) - def on_HoleShaftChanged(self,isHole): - '''slot: change the used base fit hole/shaft''' + def on_HoleShaftChanged(self, isHole): + """slot: change the used base fit hole/shaft""" if isHole: self.isHole = isHole self.setShaftFields() @@ -90,17 +123,21 @@ class TaskHoleShaftFit: self.setHoleFields() def on_FieldChanged(self): - '''slot: change of the desired field''' + """slot: change of the desired field""" currentIndex = self.form.cbField.currentIndex() if self.isHole: - self.form.lbBaseField.setText(' '+self.shaftValues[currentIndex][0]+" /") + self.form.lbBaseField.setText( + " " + self.shaftValues[currentIndex][0] + " /" + ) self.form.lbFitType.setText(self.shaftValues[currentIndex][2]) else: - self.form.lbBaseField.setText(' '+self.holeValues[currentIndex][0]+" /") + self.form.lbBaseField.setText( + " " + self.holeValues[currentIndex][0] + " /" + ) self.form.lbFitType.setText(self.holeValues[currentIndex][2]) def accept(self): - '''slot: OK pressed''' + """slot: OK pressed""" currentIndex = self.form.cbField.currentIndex() if self.isHole: selectedField = self.shaftValues[currentIndex][1] @@ -111,77 +148,726 @@ class TaskHoleShaftFit: dim = self.sel[0].Object value = dim.getRawValue() iso = ISO286() - iso.calculate(value,fieldChar,quality) + iso.calculate(value, fieldChar, quality) rangeValues = iso.getValues() mainFormat = dim.FormatSpec - dim.FormatSpec = mainFormat+' '+selectedField + dim.FormatSpec = mainFormat + " " + selectedField dim.EqualTolerance = False - dim.FormatSpecOverTolerance = '(%-0.6w)' - dim.FormatSpecUnderTolerance = '(%-0.6w)' dim.OverTolerance = rangeValues[0] dim.UnderTolerance = rangeValues[1] + if dim.OverTolerance < 0: + dim.FormatSpecOverTolerance = "(%-0.6w)" + elif dim.OverTolerance > 0: + dim.FormatSpecOverTolerance = "(+%-0.6w)" + else: + dim.FormatSpecOverTolerance = "( %-0.6w)" + if dim.UnderTolerance < 0: + dim.FormatSpecUnderTolerance = "(%-0.6w)" + elif dim.UnderTolerance > 0: + dim.FormatSpecUnderTolerance = "(+%-0.6w)" + else: + dim.FormatSpecUnderTolerance = "( %-0.6w)" Gui.Control.closeDialog() def reject(self): return True -class ISO286: - '''This class represents a subset of the ISO 286 standard''' - def getNominalRange(self,measureValue): - '''return index of selected nominal range field, 0 < measureValue < 500 mm''' - measureRanges = [0,3,6,10,14,18,24,30,40,50,65,80,100,120,140,160,180,200,225,250,280,315,355,400,450,500] +class ISO286: + """This class represents a subset of the ISO 286 standard""" + + def getNominalRange(self, measureValue): + """return index of selected nominal range field, 0 < measureValue < 500 mm""" + measureRanges = [ + 0, + 3, + 6, + 10, + 14, + 18, + 24, + 30, + 40, + 50, + 65, + 80, + 100, + 120, + 140, + 160, + 180, + 200, + 225, + 250, + 280, + 315, + 355, + 400, + 450, + 500, + ] index = 1 while measureValue > measureRanges[index]: - index = index+1 - return index-1 + index = index + 1 + return index - 1 - def getITValue(self,valueQuality,valueNominalRange): - '''return IT-value (value of quality in micrometers)''' - '''tables IT6 to IT11 from 0 to 500 mm''' - IT6 = [6,8,9,11,11,13,13,16,16,19,19,22,22,25,25,25,29,29,29,32,32,36,36,40,40] - IT7 = [10,12,15,18,18,21,21,25,25,30,30,35,35,40,40,40,46,46,46,52,52,57,57,63,63] - IT8 = [14,18,22,27,27,33,33,39,39,46,46,54,54,63,63,63,72,72,72,81,81,89,89,97,97] - IT9 = [25,30,36,43,43,52,52,62,62,74,74,87,87,100,100,100,115,115,115,130,130,140,140,155,155] - IT10 = [40,48,58,70,70,84,84,100,100,120,120,140,140,160,160,160,185,185,185,210,210,230,230,250,250] - IT11 = [60,75,90,110,110,130,130,160,160,190,190,220,220,250,250,250,290,290,290,320,320,360,360,400,400] - qualityTable = [IT6,IT7,IT8,IT9,IT10,IT11] - return qualityTable[valueQuality-6][valueNominalRange] + def getITValue(self, valueQuality, valueNominalRange): + """return IT-value (value of quality in micrometers)""" + """tables IT6 to IT11 from 0 to 500 mm""" + IT6 = [ + 6, + 8, + 9, + 11, + 11, + 13, + 13, + 16, + 16, + 19, + 19, + 22, + 22, + 25, + 25, + 25, + 29, + 29, + 29, + 32, + 32, + 36, + 36, + 40, + 40, + ] + IT7 = [ + 10, + 12, + 15, + 18, + 18, + 21, + 21, + 25, + 25, + 30, + 30, + 35, + 35, + 40, + 40, + 40, + 46, + 46, + 46, + 52, + 52, + 57, + 57, + 63, + 63, + ] + IT8 = [ + 14, + 18, + 22, + 27, + 27, + 33, + 33, + 39, + 39, + 46, + 46, + 54, + 54, + 63, + 63, + 63, + 72, + 72, + 72, + 81, + 81, + 89, + 89, + 97, + 97, + ] + IT9 = [ + 25, + 30, + 36, + 43, + 43, + 52, + 52, + 62, + 62, + 74, + 74, + 87, + 87, + 100, + 100, + 100, + 115, + 115, + 115, + 130, + 130, + 140, + 140, + 155, + 155, + ] + IT10 = [ + 40, + 48, + 58, + 70, + 70, + 84, + 84, + 100, + 100, + 120, + 120, + 140, + 140, + 160, + 160, + 160, + 185, + 185, + 185, + 210, + 210, + 230, + 230, + 250, + 250, + ] + IT11 = [ + 60, + 75, + 90, + 110, + 110, + 130, + 130, + 160, + 160, + 190, + 190, + 220, + 220, + 250, + 250, + 250, + 290, + 290, + 290, + 320, + 320, + 360, + 360, + 400, + 400, + ] + qualityTable = [IT6, IT7, IT8, IT9, IT10, IT11] + return qualityTable[valueQuality - 6][valueNominalRange] - def getFieldValue(self,fieldCharacter,valueNominalRange): - '''return es or ES value of the field in micrometers''' - cField = [-60,-70,-80,-95,-95,-110,-110,-120,-130,-140,-150,-170,-180,-200,-210,-230,-240,-260,-280,-300,-330,-360,-400,-440,-480] - fField = [-6,-10,-13,-16,-16,-20,-20,-25,-25,-30,-30,-36,-36,-43,-43,-43,-50,-50,-50,-56,-56,-62,-62,-68,-68] - gField = [-2,-4,-5,-6,-6,-7,-7,-9,-9,-10,-10,-12,-12,-14,-14,-14,-15,-15,-15,-17,-17,-18,-18,-20,-20] - hField = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] - kField = [6,9,10,12,12,15,15,18,18,21,21,25,25,28,28,28,33,33,33,36,36,40,40,45,45] - nField = [10,16,19,23,23,28,28,33,33,39,39,45,45,52,52,60,60,66,66,73,73,80,80] - rField = [16,23,28,34,34,41,41,50,50,60,62,73,76,88,90,93,106,109,113,126,130,144,150,166,172] - sField = [20,27,32,39,39,48,48,59,59,72,78,93,101,117,125,133,151,159,169,190,202,226,244,272,292] - DField = [60,78,98,120,120,149,149,180,180,220,220,260,260,305,305,305,355,355,355,400,400,440,440,480,480] - EField = [39,50,61,75,75,92,92,112,112,134,134,159,159,185,185,185,215,215,215,240,240,265,265,290,290] - FField = [20,28,35,43,43,53,53,64,64,76,76,90,90,106,106,106,122,122,122,137,137,151,151,165,165] - GField = [12,16,20,24,24,28,28,34,34,40,40,47,47,54,54,54,61,61,61,69,69,75,75,83,83] - HField = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] - KField = [0,3,5,6,6,6,6,7,7,9,9,10,10,12,12,12,13,13,13,16,16,17,17,18,18] - NField = [-4,-4,-4,-5,-5,-7,-7,-8,-8,-9,-9,-10,-10,-12,-12,-12,-14,-14,-14,-14,-14,-16,-16,-17,-17] - RField = [-10,-11,-13,-16,-16,-20,-20,-25,-25,-30,-32,-38,-41,-48,-50,-53,-60,-63,-67,-74,-78,-87,-93,-103,-109] - SField = [-14,-15,-17,-21,-21,-27,-27,-34,-34,-42,-48,-58,-66,-77,-85,-93,-105,-113,-123,-138,-150,-169,-187,-209,-229] - fieldDict = {'c':cField,'f':fField,'g':gField,'h':hField,'k':kField,'n':nField,'r':rField,'s':sField, - 'D':DField,'E':EField,'F':FField,'G':GField,'H':HField,'K':KField,'N':NField,'R':RField,'S':SField} + def getFieldValue(self, fieldCharacter, valueNominalRange): + """return es or ES value of the field in micrometers""" + cField = [ + -60, + -70, + -80, + -95, + -95, + -110, + -110, + -120, + -130, + -140, + -150, + -170, + -180, + -200, + -210, + -230, + -240, + -260, + -280, + -300, + -330, + -360, + -400, + -440, + -480, + ] + fField = [ + -6, + -10, + -13, + -16, + -16, + -20, + -20, + -25, + -25, + -30, + -30, + -36, + -36, + -43, + -43, + -43, + -50, + -50, + -50, + -56, + -56, + -62, + -62, + -68, + -68, + ] + gField = [ + -2, + -4, + -5, + -6, + -6, + -7, + -7, + -9, + -9, + -10, + -10, + -12, + -12, + -14, + -14, + -14, + -15, + -15, + -15, + -17, + -17, + -18, + -18, + -20, + -20, + ] + hField = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ] + kField = [ + 6, + 9, + 10, + 12, + 12, + 15, + 15, + 18, + 18, + 21, + 21, + 25, + 25, + 28, + 28, + 28, + 33, + 33, + 33, + 36, + 36, + 40, + 40, + 45, + 45, + ] + nField = [ + 10, + 16, + 19, + 23, + 23, + 28, + 28, + 33, + 33, + 39, + 39, + 45, + 45, + 52, + 52, + 60, + 60, + 66, + 66, + 73, + 73, + 80, + 80, + ] + rField = [ + 16, + 23, + 28, + 34, + 34, + 41, + 41, + 50, + 50, + 60, + 62, + 73, + 76, + 88, + 90, + 93, + 106, + 109, + 113, + 126, + 130, + 144, + 150, + 166, + 172, + ] + sField = [ + 20, + 27, + 32, + 39, + 39, + 48, + 48, + 59, + 59, + 72, + 78, + 93, + 101, + 117, + 125, + 133, + 151, + 159, + 169, + 190, + 202, + 226, + 244, + 272, + 292, + ] + DField = [ + 60, + 78, + 98, + 120, + 120, + 149, + 149, + 180, + 180, + 220, + 220, + 260, + 260, + 305, + 305, + 305, + 355, + 355, + 355, + 400, + 400, + 440, + 440, + 480, + 480, + ] + EField = [ + 39, + 50, + 61, + 75, + 75, + 92, + 92, + 112, + 112, + 134, + 134, + 159, + 159, + 185, + 185, + 185, + 215, + 215, + 215, + 240, + 240, + 265, + 265, + 290, + 290, + ] + FField = [ + 20, + 28, + 35, + 43, + 43, + 53, + 53, + 64, + 64, + 76, + 76, + 90, + 90, + 106, + 106, + 106, + 122, + 122, + 122, + 137, + 137, + 151, + 151, + 165, + 165, + ] + GField = [ + 12, + 16, + 20, + 24, + 24, + 28, + 28, + 34, + 34, + 40, + 40, + 47, + 47, + 54, + 54, + 54, + 61, + 61, + 61, + 69, + 69, + 75, + 75, + 83, + 83, + ] + HField = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ] + KField = [ + 0, + 3, + 5, + 6, + 6, + 6, + 6, + 7, + 7, + 9, + 9, + 10, + 10, + 12, + 12, + 12, + 13, + 13, + 13, + 16, + 16, + 17, + 17, + 18, + 18, + ] + NField = [ + -4, + -4, + -4, + -5, + -5, + -7, + -7, + -8, + -8, + -9, + -9, + -10, + -10, + -12, + -12, + -12, + -14, + -14, + -14, + -14, + -14, + -16, + -16, + -17, + -17, + ] + RField = [ + -10, + -11, + -13, + -16, + -16, + -20, + -20, + -25, + -25, + -30, + -32, + -38, + -41, + -48, + -50, + -53, + -60, + -63, + -67, + -74, + -78, + -87, + -93, + -103, + -109, + ] + SField = [ + -14, + -15, + -17, + -21, + -21, + -27, + -27, + -34, + -34, + -42, + -48, + -58, + -66, + -77, + -85, + -93, + -105, + -113, + -123, + -138, + -150, + -169, + -187, + -209, + -229, + ] + fieldDict = { + "c": cField, + "f": fField, + "g": gField, + "h": hField, + "k": kField, + "n": nField, + "r": rField, + "s": sField, + "D": DField, + "E": EField, + "F": FField, + "G": GField, + "H": HField, + "K": KField, + "N": NField, + "R": RField, + "S": SField, + } return fieldDict[fieldCharacter][valueNominalRange] - def calculate(self,value,fieldChar,quality): - '''calculate upper and lower field values''' - self.nominalRange = self. getNominalRange(value) - self.upperValue = self.getFieldValue(fieldChar,self.nominalRange) - self.lowerValue = self.upperValue-self.getITValue(quality,self.nominalRange) - if fieldChar == 'H': + def calculate(self, value, fieldChar, quality): + """calculate upper and lower field values""" + self.nominalRange = self.getNominalRange(value) + self.upperValue = self.getFieldValue(fieldChar, self.nominalRange) + self.lowerValue = self.upperValue - self.getITValue(quality, self.nominalRange) + if fieldChar == "H": self.upperValue = -self.lowerValue self.lowerValue = 0 def getValues(self): - '''return range values in mm''' - return (self.upperValue/1000,self.lowerValue/1000) - - + """return range values in mm""" + return (self.upperValue / 1000, self.lowerValue / 1000) diff --git a/src/Mod/TechDraw/Templates/A4_LandscapeTD.svg b/src/Mod/TechDraw/Templates/A4_LandscapeTD.svg index 1ea6c239bc..c3b68cf97a 100644 --- a/src/Mod/TechDraw/Templates/A4_LandscapeTD.svg +++ b/src/Mod/TechDraw/Templates/A4_LandscapeTD.svg @@ -192,13 +192,13 @@ - Designed by Name - Date - Scale + Designed by Name + Date + Scale Weight - Title + Title Subtitle Drawing number - Sheet + Sheet diff --git a/src/Mod/TechDraw/Templates/ANSIC_Landscape.svg b/src/Mod/TechDraw/Templates/ANSIC_Landscape.svg index 04981c38a7..50357387ce 100644 --- a/src/Mod/TechDraw/Templates/ANSIC_Landscape.svg +++ b/src/Mod/TechDraw/Templates/ANSIC_Landscape.svg @@ -314,18 +314,18 @@ - AUTHOR NAME - CREATION DATE + AUTHOR NAME + CREATION DATE SUPERVISOR NAME CHECK DATE ANSI C - SCALE + SCALE WEIGHT NUMBER - SHEET - TITLE + SHEET + TITLE SUBTITLE - COMPANY NAME + COMPANY NAME COPYRIGHT _________ _________ diff --git a/tests/src/Mod/Part/App/Attacher.cpp b/tests/src/Mod/Part/App/Attacher.cpp index f279e185a2..4ce0429739 100644 --- a/tests/src/Mod/Part/App/Attacher.cpp +++ b/tests/src/Mod/Part/App/Attacher.cpp @@ -12,9 +12,11 @@ using namespace Attacher; using namespace PartTestHelpers; /* - * Testing note: It looks like there are about 45 different attachment modes, and these tests all - * only look at one of them - to prove that adding elementMap code doesn't break anything. A - * comprehensive test of the Attacher would definitely want to try many more code paths. + * Testing note: It looks like there are about 45 different attachment modes, and these tests + * mostly only look at some of them - to prove that adding elementMap code doesn't break anything. + * While a trivial bounding box test is used to ensure no hard crashes in any of the modes, any + * mode that requires additional shapes beyond a couple of boxes would need a more comprehensive + * test. */ class AttacherTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass @@ -105,3 +107,247 @@ TEST_F(AttacherTest, TestCalculateAttachedPlacement) EXPECT_EQ(placement.getPosition().y, 0); EXPECT_EQ(placement.getPosition().z, 0); } + +TEST_F(AttacherTest, TestAllStringModesValid) +{ + // Arrange + const char* modes[] = { + "Deactivated", + "Translate", + "ObjectXY", + "ObjectXZ", + "ObjectYZ", + "FlatFace", + "TangentPlane", + "NormalToEdge", + "FrenetNB", + "FrenetTN", + "FrenetTB", + "Concentric", + "SectionOfRevolution", + "ThreePointsPlane", + "ThreePointsNormal", + "Folding", + + "ObjectX", + "ObjectY", + "ObjectZ", + "AxisOfCurvature", + "Directrix1", + "Directrix2", + "Asymptote1", + "Asymptote2", + "Tangent", + "Normal", + "Binormal", + "TangentU", + "TangentV", + "TwoPointLine", + "IntersectionLine", + "ProximityLine", + + "ObjectOrigin", + "Focus1", + "Focus2", + "OnEdge", + "CenterOfCurvature", + "CenterOfMass", + "IntersectionPoint", + "Vertex", + "ProximityPoint1", + "ProximityPoint2", + + "AxisOfInertia1", + "AxisOfInertia2", + "AxisOfInertia3", + + "InertialCS", + + "FaceNormal", + + "OZX", + "OZY", + "OXY", + "OXZ", + "OYZ", + "OYX", + }; + int index = 0; + for (auto mode : modes) { + _boxes[1]->MapMode.setValue(mode); // There are lots of attachment modes! + _boxes[1]->recomputeFeature(); + EXPECT_STREQ(_boxes[1]->MapMode.getValueAsString(), mode); + EXPECT_EQ(_boxes[1]->MapMode.getValue(), index); + index++; + } +} + +TEST_F(AttacherTest, TestAllModesBoundaries) +{ + _boxes[1]->MapMode.setValue(mmTranslate); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 1, 2, 3))); + _boxes[1]->MapMode.setValue(mmObjectXY); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 1, 2, 3))); + _boxes[1]->MapMode.setValue(mmObjectXZ); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, -3, 0, 1, 0, 2))); + _boxes[1]->MapMode.setValue(mmObjectYZ); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + + _boxes[1]->MapMode.setValue(mmFlatFace); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmTangentPlane); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Normal); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + + _boxes[1]->MapMode.setValue(mmFrenetNB); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmFrenetTN); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmFrenetTB); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmConcentric); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmRevolutionSection); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmThreePointsNormal); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmThreePointsPlane); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mmFolding); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + + _boxes[1]->MapMode.setValue(mm1AxisX); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1AxisY); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1AxisZ); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1AxisCurv); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Directrix1); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Directrix2); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Asymptote1); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Asymptote2); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Tangent); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1TangentU); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1TangentV); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1TwoPoints); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Intersection); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Proximity); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + + _boxes[1]->MapMode.setValue(mm0Origin); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0Focus1); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0Focus2); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0OnEdge); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0CenterOfCurvature); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0CenterOfMass); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1Intersection); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0Vertex); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0ProximityPoint1); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm0ProximityPoint2); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + + _boxes[1]->MapMode.setValue(mm1AxisInertia1); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1AxisInertia2); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + _boxes[1]->MapMode.setValue(mm1AxisInertia3); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE(boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0, 0, 0, 3, 1, 2))); + + _boxes[1]->MapMode.setValue(mmInertialCS); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); + + _boxes[1]->MapMode.setValue(mm1FaceNormal); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); + + _boxes[1]->MapMode.setValue(mmOZX); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); + _boxes[1]->MapMode.setValue(mmOZY); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); + _boxes[1]->MapMode.setValue(mmOXY); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); + _boxes[1]->MapMode.setValue(mmOXZ); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); + _boxes[1]->MapMode.setValue(mmOYZ); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); + _boxes[1]->MapMode.setValue(mmOYX); + _boxes[1]->recomputeFeature(); + EXPECT_TRUE( + boxesMatch(_boxes[1]->Shape.getBoundingBox(), Base::BoundBox3d(0.5, 1, 1.5, 3.5, 2, 3.5))); +}