Merge branch 'main' into Draft-fix-handling-of-shapes-in-shape2dview.py

This commit is contained in:
Yorik van Havre
2024-03-25 18:36:24 +01:00
committed by GitHub
82 changed files with 3461 additions and 2008 deletions
+4 -3
View File
@@ -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
assignees: maxwxyz
+2 -3
View File
@@ -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.*
+1 -1
View File
@@ -73,7 +73,7 @@ dependencies:
- graphviz
- hdf5
- libcxx
- mamba==1.4.9
- mamba
- matplotlib
- ninja
- numpy
@@ -281,31 +281,6 @@ but slower response to any scene changes.</string>
<property name="prefPath" stdset="0">
<cstring>View</cstring>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Line Smoothing</string>
</property>
</item>
<item>
<property name="text">
<string>MSAA 2x</string>
</property>
</item>
<item>
<property name="text">
<string>MSAA 4x</string>
</property>
</item>
<item>
<property name="text">
<string>MSAA 8x</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
+144 -36
View File
@@ -25,6 +25,9 @@
#ifndef _PreComp_
# include <QApplication>
# include <QMessageBox>
# include <QOffscreenSurface>
# include <QOpenGLContext>
# include <QSurfaceFormat>
#endif
#include <App/Application.h>
@@ -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<std::pair<QString, int>> 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<int>(&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()));
}
}
+11 -1
View File
@@ -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_DlgSettings3DView> ui;
static bool showMsg;
Q_DISABLE_COPY_MOVE(DlgSettings3DViewImp)
};
} // namespace Dialog
+2
View File
@@ -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:
+6 -5
View File
@@ -117,11 +117,12 @@ public:
*/
//@{
enum AntiAliasing {
None,
Smoothing,
MSAA2x,
MSAA4x,
MSAA8x
None = 0,
Smoothing = 1,
MSAA2x = 2,
MSAA4x = 3,
MSAA6x = 5,
MSAA8x = 4
};
//@}
+1
View File
@@ -124,5 +124,6 @@ WidgetFactorySupplier::WidgetFactorySupplier()
new WidgetProducer<Gui::IntSpinBox>;
new WidgetProducer<Gui::DoubleSpinBox>;
new WidgetProducer<Gui::QuantitySpinBox>;
new WidgetProducer<Gui::ExpLineEdit>;
}
// clang-format on
+9 -2
View File
@@ -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
+152 -39
View File
@@ -393,14 +393,14 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="3" column="0">
<widget class="QLabel" name="l_use_iterations_param">
<property name="text">
<string>Time incrementation control parameter</string>
</property>
</widget>
</item>
<item row="4" column="2">
<item row="3" column="2">
<widget class="Gui::PrefCheckBox" name="cb_use_iterations_param">
<property name="text">
<string>Use non ccx defaults</string>
@@ -416,6 +416,38 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="l_ccx_max_iterations">
<property name="text">
<string>Maximum number of iterations</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="Gui::PrefSpinBox" name="sb_ccx_max_iterations">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10000000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>2000</number>
</property>
<property name="prefEntry" stdset="0">
<cstring>AnalysisMaxIterations</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Fem/Ccx</cstring>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="l_ccx_initial_time_step">
<property name="text">
@@ -445,10 +477,10 @@
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="decimals">
<number>3</number>
<number>9</number>
</property>
<property name="minimum">
<double>0.010000000000000</double>
<double>0.000000001000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
@@ -465,7 +497,7 @@
</widget>
</item>
<item row="5" column="3">
<widget class="QLabel" name="l_hz_3">
<widget class="QLabel" name="l_hz_1">
<property name="text">
<string>s</string>
</property>
@@ -484,10 +516,10 @@
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="decimals">
<number>3</number>
<number>9</number>
</property>
<property name="minimum">
<double>0.010000000000000</double>
<double>0.000000001000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
@@ -504,20 +536,130 @@
</widget>
</item>
<item row="6" column="3">
<widget class="QLabel" name="l_hz_2">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="l_ccx_minimum_time_step">
<property name="text">
<string>Time Minimum Step</string>
</property>
</widget>
</item>
<item row="7" column="1">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="2">
<widget class="Gui::PrefDoubleSpinBox" name="dsb_ccx_minimum_time_step">
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="decimals">
<number>9</number>
</property>
<property name="minimum">
<double>0.000000001000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.000010000000000</double>
</property>
<property name="prefEntry" stdset="0">
<cstring>AnalysisTimeMinimumStep</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Fem/Ccx</cstring>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QLabel" name="l_hz_3">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="l_ccx_maximum_time_step">
<property name="text">
<string>Time Maximum Step</string>
</property>
</widget>
</item>
<item row="8" column="1">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="2">
<widget class="Gui::PrefDoubleSpinBox" name="dsb_ccx_maximum_time_step">
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="decimals">
<number>9</number>
</property>
<property name="minimum">
<double>0.000000001000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
<property name="prefEntry" stdset="0">
<cstring>AnalysisTimeMaximumStep</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Fem/Ccx</cstring>
</property>
</widget>
</item>
<item row="8" column="3">
<widget class="QLabel" name="l_hz_4">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
<item row="7" column="0">
<item row="9" column="0">
<widget class="QLabel" name="l_BeamShellOutput">
<property name="text">
<string>Beam, shell element 3D output format</string>
</property>
</widget>
</item>
<item row="7" column="2">
<item row="9" column="2">
<widget class="Gui::PrefCheckBox" name="cb_BeamShellOutput">
<property name="text">
<string>3D Output, unchecked for 2D</string>
@@ -569,35 +711,6 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="l_ccx_max_iterations">
<property name="text">
<string>Maximum number of iterations</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::PrefSpinBox" name="sb_ccx_max_iterations">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10000000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>2000</number>
</property>
<property name="prefEntry" stdset="0">
<cstring>AnalysisMaxIterations</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Fem/Ccx</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@@ -695,7 +808,7 @@
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="l_hz_2">
<widget class="QLabel" name="l_hz_5">
<property name="text">
<string>Hz</string>
</property>
+4
View File
@@ -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
@@ -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)
+31 -7
View File
@@ -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",
@@ -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)
@@ -489,7 +489,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -62,7 +62,7 @@ Eedges
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -62,7 +62,7 @@ Eedges
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -62,7 +62,7 @@ Eedges
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -385,7 +385,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -86,7 +86,7 @@ Efaces
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -74,7 +74,7 @@ Efaces
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -204,7 +204,7 @@ Eedges
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -62,7 +62,7 @@ Eedges
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -1562,7 +1562,7 @@ Efaces
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -292,7 +292,7 @@ Efaces
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -359,7 +359,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -359,7 +359,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -377,7 +377,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -38373,7 +38373,7 @@ DEPConstraintContact, INDConstraintContact
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -3401,7 +3401,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -2153,7 +2153,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -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
@@ -3639,7 +3639,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -10980,7 +10980,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -27634,7 +27634,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -2548,7 +2548,7 @@ Efaces
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -1231,7 +1231,7 @@ Evolumes
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -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
@@ -2560,7 +2560,7 @@ Efaces
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -2560,7 +2560,7 @@ Efaces
***********************************************************
** At least one step is needed to run an CalculiX analysis of FreeCAD
*STEP
*STEP, INC=200
*STATIC
@@ -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
File diff suppressed because it is too large Load Diff
+79 -70
View File
@@ -36,22 +36,24 @@
#include <Mod/Part/App/PartFeature.h>
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<TopoDS_Wire> aWireVec;
std::vector<TopoDS_Wire> aProjectedWireVec;
std::vector<TopoDS_Edge> aProjectedEdgeVec;
std::vector<TopoDS_Wire> 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<TopoDS_Wire> aWireVec;
std::vector<TopoDS_Wire> aProjectedWireVec;
std::vector<TopoDS_Edge> aProjectedEdgeVec;
std::vector<TopoDS_Wire> 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<SShapeStore>& iStoreVec, const unsigned int iColor);
bool store_part_in_vector(SShapeStore& iCurrentShape, std::vector<SShapeStore>& iStoreVec);
void create_projection_wire(std::vector<SShapeStore>& iCurrentShape);
TopoDS_Shape create_compound(const std::vector<SShapeStore>& iShapeVec);
void show_projected_shapes(const std::vector<SShapeStore>& iShapeStoreVec);
void disable_ui_elements(const std::vector<QWidget*>& iObjectVec, QWidget* iExceptThis);
void enable_ui_elements(const std::vector<QWidget*>& 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<SShapeStore>& iCurrentShape);
TopoDS_Wire sort_and_heal_wire(const TopoDS_Shape& iShape, const TopoDS_Face& iFaceToProject);
TopoDS_Wire sort_and_heal_wire(const std::vector<TopoDS_Edge>& iEdgeVec, const TopoDS_Face& iFaceToProject);
void create_face_extrude(std::vector<SShapeStore>& iCurrentShape);
void store_wire_in_vector(const SShapeStore& iCurrentShape, const TopoDS_Shape& iParentShape, std::vector<SShapeStore>& 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<SShapeStore>& iStoreVec,
unsigned int iColor);
bool store_part_in_vector(SShapeStore& iCurrentShape, std::vector<SShapeStore>& iStoreVec);
void create_projection_wire(std::vector<SShapeStore>& iCurrentShape);
TopoDS_Shape create_compound(const std::vector<SShapeStore>& iShapeVec);
void show_projected_shapes(const std::vector<SShapeStore>& iShapeStoreVec);
void disable_ui_elements(const std::vector<QWidget*>& iObjectVec, QWidget* iExceptThis);
void enable_ui_elements(const std::vector<QWidget*>& 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<SShapeStore>& iCurrentShape);
TopoDS_Wire sort_and_heal_wire(const TopoDS_Shape& iShape, const TopoDS_Face& iFaceToProject);
TopoDS_Wire sort_and_heal_wire(const std::vector<TopoDS_Edge>& iEdgeVec,
const TopoDS_Face& iFaceToProject);
void create_face_extrude(std::vector<SShapeStore>& iCurrentShape);
void store_wire_in_vector(const SShapeStore& iCurrentShape,
const TopoDS_Shape& iParentShape,
std::vector<SShapeStore>& 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<SShapeStore> m_shapeVec;
std::vector<SShapeStore> m_projectionSurfaceVec;
@@ -134,9 +143,9 @@ private:
std::vector<QWidget*> 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
+8 -76
View File
@@ -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<std::vector<TopoDS_Wire>> wiresections;
for (TopoDS_Wire& wire : wires)
wiresections.emplace_back(1, wire);
//build all shells
std::vector<TopoDS_Shape> shells;
std::vector<TopoDS_Wire> frontwires, backwires;
for (std::vector<TopoDS_Wire>& 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)
+1 -1
View File
@@ -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);
@@ -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")
+301 -359
View File
@@ -4227,105 +4227,298 @@ bool SketchObject::isCarbonCopyAllowed(App::Document* pDoc, App::DocumentObject*
}
int SketchObject::addSymmetric(const std::vector<int>& 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<Part::Geometry*>& geovals = getInternalGeometry();
std::vector<Part::Geometry*> newgeoVals(geovals);
const std::vector<Constraint*>& constrvals = this->Constraints.getValues();
std::vector<Constraint*> newconstrVals(constrvals);
newgeoVals.reserve(geovals.size() + geoIdList.size());
int cgeoid = getHighestCurveIndex() + 1;
std::map<int, int> geoIdMap;
std::map<int, bool> 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<Constraint*>::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<int, int> geoIdMap;
std::map<int, bool> isStartEndInverted;
std::vector<Part::Geometry*> newgeoVals(getInternalGeometry());
std::vector<Part::Geometry*> 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<const Part::GeomLineSegment*>(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<Part::GeomLineSegment>()) {
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<Part::GeomCircle>() || geo->is<Part::GeomEllipse>()) {
createEqualityConstr(geoId1, geoId2);
createSymConstr(geoId1, geoId2, PointPos::mid, PointPos::mid);
}
else if (geo->is<Part::GeomArcOfCircle>()
|| geo->is<Part::GeomArcOfEllipse>()
|| geo->is<Part::GeomArcOfHyperbola>()
|| geo->is<Part::GeomArcOfParabola>()) {
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<Part::GeomPoint>()) {
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<Part::Geometry*> SketchObject::getSymmetric(const std::vector<int>& geoIdList,
std::map<int, int>& geoIdMap,
std::map<int, bool>& isStartEndInverted,
int refGeoId,
Sketcher::PointPos refPosId)
{
std::vector<Part::Geometry*> 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<Part::GeomLineSegment>()) {
Base::Console().Error("Reference for symmetric is neither a point nor a line.\n");
return {};
}
auto* refGeoLine = static_cast<const Part::GeomLineSegment*>(georef);
// line
Base::Vector3d refstart = refGeoLine->getStartPoint();
Base::Vector3d vectline = refGeoLine->getEndPoint() - refstart;
for (std::vector<int>::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>()) {
Part::GeomLineSegment* geosymline = static_cast<Part::GeomLineSegment*>(geosym);
auto* geosymline = static_cast<Part::GeomLineSegment*>(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>()) {
Part::GeomCircle* geosymcircle = static_cast<Part::GeomCircle*>(geosym);
auto* geosymcircle = static_cast<Part::GeomCircle*>(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>()) {
Part::GeomArcOfCircle* geoaoc = static_cast<Part::GeomArcOfCircle*>(geosym);
auto* geoaoc = static_cast<Part::GeomArcOfCircle*>(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<int>& 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>()) {
Part::GeomEllipse* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
auto* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
Base::Vector3d cp = geosymellipse->getCenter();
Base::Vector3d majdir = geosymellipse->getMajorAxisDir();
@@ -4362,10 +4555,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfEllipse* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4394,11 +4587,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfHyperbola* geosymaoe =
static_cast<Part::GeomArcOfHyperbola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfHyperbola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4423,10 +4615,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfParabola* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
// double df= geosymaoe->getFocal();
@@ -4447,45 +4639,41 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomBSplineCurve* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
auto* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
std::vector<Base::Vector3d> poles = geosymbsp->getPoles();
for (std::vector<Base::Vector3d>::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>()) {
Part::GeomPoint* geosympoint = static_cast<Part::GeomPoint*>(geosym);
auto* geosympoint = static_cast<Part::GeomPoint*>(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<int>& geoIdList, int refGeoId,
refpoint = Vector3d(0, 0, 0);
}
else {
switch (refPosId) {
case Sketcher::PointPos::start:
if (georef->is<Part::GeomLineSegment>()) {
const Part::GeomLineSegment* geosymline =
static_cast<const Part::GeomLineSegment*>(georef);
refpoint = geosymline->getStartPoint();
}
else if (georef->is<Part::GeomArcOfCircle>()) {
const Part::GeomArcOfCircle* geoaoc =
static_cast<const Part::GeomArcOfCircle*>(georef);
refpoint = geoaoc->getStartPoint(true);
}
else if (georef->is<Part::GeomArcOfEllipse>()) {
const Part::GeomArcOfEllipse* geosymaoe =
static_cast<const Part::GeomArcOfEllipse*>(georef);
refpoint = geosymaoe->getStartPoint(true);
}
else if (georef->is<Part::GeomArcOfHyperbola>()) {
const Part::GeomArcOfHyperbola* geosymaoe =
static_cast<const Part::GeomArcOfHyperbola*>(georef);
refpoint = geosymaoe->getStartPoint(true);
}
else if (georef->is<Part::GeomArcOfParabola>()) {
const Part::GeomArcOfParabola* geosymaoe =
static_cast<const Part::GeomArcOfParabola*>(georef);
refpoint = geosymaoe->getStartPoint(true);
}
else if (georef->is<Part::GeomBSplineCurve>()) {
const Part::GeomBSplineCurve* geosymbsp =
static_cast<const Part::GeomBSplineCurve*>(georef);
refpoint = geosymbsp->getStartPoint();
}
break;
case Sketcher::PointPos::end:
if (georef->is<Part::GeomLineSegment>()) {
const Part::GeomLineSegment* geosymline =
static_cast<const Part::GeomLineSegment*>(georef);
refpoint = geosymline->getEndPoint();
}
else if (georef->is<Part::GeomArcOfCircle>()) {
const Part::GeomArcOfCircle* geoaoc =
static_cast<const Part::GeomArcOfCircle*>(georef);
refpoint = geoaoc->getEndPoint(true);
}
else if (georef->is<Part::GeomArcOfEllipse>()) {
const Part::GeomArcOfEllipse* geosymaoe =
static_cast<const Part::GeomArcOfEllipse*>(georef);
refpoint = geosymaoe->getEndPoint(true);
}
else if (georef->is<Part::GeomArcOfHyperbola>()) {
const Part::GeomArcOfHyperbola* geosymaoe =
static_cast<const Part::GeomArcOfHyperbola*>(georef);
refpoint = geosymaoe->getEndPoint(true);
}
else if (georef->is<Part::GeomArcOfParabola>()) {
const Part::GeomArcOfParabola* geosymaoe =
static_cast<const Part::GeomArcOfParabola*>(georef);
refpoint = geosymaoe->getEndPoint(true);
}
else if (georef->is<Part::GeomBSplineCurve>()) {
const Part::GeomBSplineCurve* geosymbsp =
static_cast<const Part::GeomBSplineCurve*>(georef);
refpoint = geosymbsp->getEndPoint();
}
break;
case Sketcher::PointPos::mid:
if (georef->is<Part::GeomCircle>()) {
const Part::GeomCircle* geosymcircle =
static_cast<const Part::GeomCircle*>(georef);
refpoint = geosymcircle->getCenter();
}
else if (georef->is<Part::GeomArcOfCircle>()) {
const Part::GeomArcOfCircle* geoaoc =
static_cast<const Part::GeomArcOfCircle*>(georef);
refpoint = geoaoc->getCenter();
}
else if (georef->is<Part::GeomEllipse>()) {
const Part::GeomEllipse* geosymellipse =
static_cast<const Part::GeomEllipse*>(georef);
refpoint = geosymellipse->getCenter();
}
else if (georef->is<Part::GeomArcOfEllipse>()) {
const Part::GeomArcOfEllipse* geosymaoe =
static_cast<const Part::GeomArcOfEllipse*>(georef);
refpoint = geosymaoe->getCenter();
}
else if (georef->is<Part::GeomArcOfHyperbola>()) {
const Part::GeomArcOfHyperbola* geosymaoe =
static_cast<const Part::GeomArcOfHyperbola*>(georef);
refpoint = geosymaoe->getCenter();
}
else if (georef->is<Part::GeomArcOfParabola>()) {
const Part::GeomArcOfParabola* geosymaoe =
static_cast<const Part::GeomArcOfParabola*>(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<int>::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>()) {
Part::GeomLineSegment* geosymline = static_cast<Part::GeomLineSegment*>(geosym);
auto* geosymline = static_cast<Part::GeomLineSegment*>(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>()) {
Part::GeomCircle* geosymcircle = static_cast<Part::GeomCircle*>(geosym);
auto* geosymcircle = static_cast<Part::GeomCircle*>(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>()) {
Part::GeomArcOfCircle* geoaoc = static_cast<Part::GeomArcOfCircle*>(geosym);
auto* geoaoc = static_cast<Part::GeomArcOfCircle*>(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<int>& 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>()) {
Part::GeomEllipse* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
auto* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
Base::Vector3d cp = geosymellipse->getCenter();
Base::Vector3d majdir = geosymellipse->getMajorAxisDir();
@@ -4681,10 +4752,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfEllipse* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4699,11 +4770,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfHyperbola* geosymaoe =
static_cast<Part::GeomArcOfHyperbola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfHyperbola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4718,10 +4788,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfParabola* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
/*double df= geosymaoe->getFocal();*/
@@ -4733,167 +4803,39 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomBSplineCurve* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
auto* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
std::vector<Base::Vector3d> poles = geosymbsp->getPoles();
for (std::vector<Base::Vector3d>::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>()) {
Part::GeomPoint* geosympoint = static_cast<Part::GeomPoint*>(geosym);
auto* geosympoint = static_cast<Part::GeomPoint*>(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<Constraint*>::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<int>& geoIdList, const Base::Vector3d& displacement,
+10 -1
View File
@@ -368,7 +368,16 @@ public:
/// adds symmetric geometric elements with respect to the refGeoId (line or point)
int addSymmetric(const std::vector<int>& 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<Part::Geometry*>
getSymmetric(const std::vector<int>& geoIdList,
std::map<int, int>& geoIdMap,
std::map<int, bool>& 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
+1
View File
@@ -80,6 +80,7 @@ SET(SketcherGui_SRCS
DrawSketchHandlerOffset.h
DrawSketchHandlerRotate.h
DrawSketchHandlerScale.h
DrawSketchHandlerSymmetry.h
CommandCreateGeo.cpp
CommandConstraints.h
CommandConstraints.cpp
+5 -182
View File
@@ -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<int> listOfGeoIds = getListOfSelectedGeoIds(true);
// Cancel any in-progress operation
Gui::Document* doc = Gui::Application::Instance->activeDocument();
SketcherGui::ReleaseHandler(doc);
// get the selection
std::vector<Gui::SelectionObject> 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<std::string>& 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<Sketcher::SketchObject*>(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<std::string>::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<Part::GeomLineSegment>())
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<Part::GeomPoint>()) {
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<int>(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()
@@ -0,0 +1,291 @@
/***************************************************************************
* Copyright (c) 2022 Boyer Pierre-Louis <pierrelouis.boyer@gmail.com> *
* *
* 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 <QApplication>
#include <Gui/BitmapFactory.h>
#include <Gui/Notifications.h>
#include <Gui/Command.h>
#include <Gui/CommandT.h>
#include <Mod/Sketcher/App/GeometryFacade.h>
#include <Mod/Sketcher/App/SketchObject.h>
#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<DrawSketchHandlerSymmetry,
StateMachines::OneSeekEnd,
/*PAutoConstraintSize =*/0,
/*OnViewParametersT =*/OnViewParameters<0>,
/*WidgetParametersT =*/WidgetParameters<0>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<2>,
/*WidgetComboboxesT =*/WidgetComboboxes<0>>;
using DSHSymmetryControllerBase = DSHSymmetryController::ControllerBase;
using DrawSketchHandlerSymmetryBase = DrawSketchControllableHandler<DSHSymmetryController>;
class DrawSketchHandlerSymmetry: public DrawSketchHandlerSymmetryBase
{
friend DSHSymmetryController;
friend DSHSymmetryControllerBase;
public:
explicit DrawSketchHandlerSymmetry(std::vector<int> 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<QWidget> createWidget() const override
{
return std::make_unique<SketcherToolDefaultWidget>();
}
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<int> 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<int, int> dummy1;
std::map<int, bool> dummy2;
std::vector<Part::Geometry*> symGeos =
Obj->getSymmetric(listOfGeoIds, dummy1, dummy2, refGeoId, refPosId);
for (auto* geo : symGeos) {
ShapeGeometry.emplace_back(std::move(std::unique_ptr<Part::Geometry>(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
@@ -246,6 +246,7 @@
<file>icons/pointers/Sketcher_Pointer_Create_Offset.svg</file>
<file>icons/pointers/Sketcher_Pointer_Create_Rotate.svg</file>
<file>icons/pointers/Sketcher_Pointer_Create_Scale.svg</file>
<file>icons/pointers/Sketcher_Pointer_Create_Symmetry.svg</file>
<file>icons/pointers/Sketcher_Pointer_Extension.svg</file>
<file>icons/pointers/Sketcher_Pointer_External.svg</file>
<file>icons/pointers/Sketcher_Pointer_Heptagon.svg</file>
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
height="64"
width="64"
id="svg12"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs16">
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0.0"
refY="0.0"
orient="auto">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4362" />
</marker>
</defs>
<g
id="crosshair"
style="stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter">
<path
d="m16,3v9m0,8v9m-13-13h9m8,0h9"
id="path9" />
</g>
<path
style="fill:none;stroke:#cc0000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 33.612274,29.732474 v 0 c 0,0 -6.759671,4.19863 -6.731131,13.462262 0.02854,9.263631 6.731131,13.58246 6.731131,13.58246"
id="path1221" />
<path
style="fill:none;stroke:#cc0000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0;stroke-opacity:1"
d="M 43.348375,32.497046 V 55.455009"
id="path1256" />
<path
style="fill:none;stroke:#cc0000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 52.363237,56.837294 v 0 c 0,0 6.759671,-4.19863 6.731131,-13.462262 -0.02854,-9.263631 -6.731131,-13.58246 -6.731131,-13.58246"
id="path1221-9" />
<circle
cx="33.612274"
cy="29.732473"
r="4"
id="circle6"
style="fill:none;stroke:#cc0000;stroke-width:2" />
<circle
cx="33.612274"
cy="56.777195"
r="4"
id="circle6-4"
style="fill:none;stroke:#cc0000;stroke-width:2" />
<circle
cx="52.363235"
cy="29.792572"
r="4"
id="circle6-4-0"
style="fill:none;stroke:#cc0000;stroke-width:2" />
<circle
cx="52.363235"
cy="56.837296"
r="4"
id="circle6-4-0-2"
style="fill:none;stroke:#cc0000;stroke-width:2" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+2 -2
View File
@@ -554,13 +554,13 @@ def handle():
SECTION_NEW_FILE = "<h2>" + TranslationTexts.get("T_NEWFILE") + "</h2>"
SECTION_NEW_FILE += "<ul>"
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 += "</ul>"
HTML = HTML.replace("SECTION_NEW_FILE", SECTION_NEW_FILE)
+4 -2
View File
@@ -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")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+23 -10
View File
@@ -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<std::string, std::string>::iterator item =
substitutions.find(editableName.toStdString());
if (item != substitutions.end()) {
@@ -296,15 +296,28 @@ std::map<std::string, std::string> 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;
}
+67
View File
@@ -24,13 +24,19 @@
#ifndef _PreComp_
# include <sstream>
# include <QCollator>
# include <QDateTime>
#endif
#include <Base/Console.h>
#include <App/Application.h>
#include <App/Document.h>
#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<int, int> 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<DocumentObject *> pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId());
std::vector<QString> 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 {
+13
View File
@@ -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[];
+74
View File
@@ -1312,6 +1312,80 @@ double DrawUtil::angleDifference(double fi1, double fi2, bool reflex)
return fi1;
}
std::pair<int, int> 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<int, int> 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<int>::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
// ==========================
+4
View File
@@ -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<int, int> nearestFraction(double val, int maxDenom = 999);
// Interval marking functions
static unsigned int intervalMerge(std::vector<std::pair<double, bool>>& marking,
+10 -14
View File
@@ -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<LineSet> 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<LineSet> 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;
}
+2 -2
View File
@@ -125,7 +125,7 @@ std::vector<std::string> 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()) {
+11
View File
@@ -362,6 +362,17 @@ std::string LineGenerator::getLineStandardsBody()
{
int activeStandard = Preferences::lineStandard();
std::vector<std::string> 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));
}
+13 -1
View File
@@ -23,7 +23,7 @@
#include "PreCompiled.h"
#ifndef _PreComp_
# include <string>
# include <QApplication>
# include <QString>
#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);
}
+2 -1
View File
@@ -27,6 +27,7 @@
#include <QDomDocument>
#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();
}
+36 -53
View File
@@ -53,6 +53,7 @@
#include <Mod/TechDraw/App/DrawPage.h>
#include <Mod/TechDraw/App/DrawProjGroup.h>
#include <Mod/TechDraw/App/DrawUtil.h>
#include <Mod/TechDraw/App/DrawSVGTemplate.h>
#include <Mod/TechDraw/App/DrawViewArch.h>
#include <Mod/TechDraw/App/DrawViewClip.h>
#include <Mod/TechDraw/App/DrawViewDetail.h>
@@ -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<TechDraw::DrawPage *>
(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<TechDraw::DrawSVGTemplate *>
(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<TechDraw::DrawPage*>(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<TechDrawGui::ViewProviderPage*>(vp);
TechDrawGui::ViewProviderPage *dvp = dynamic_cast<TechDrawGui::ViewProviderPage *>
(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<TechDraw::DrawPage *>
(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<TechDraw::DrawSVGTemplate *>
(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<TechDraw::DrawPage*>(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<TechDrawGui::ViewProviderPage*>(vp);
TechDrawGui::ViewProviderPage *dvp = dynamic_cast<TechDrawGui::ViewProviderPage *>
(Gui::Application::Instance->getViewProvider(page));
if (dvp) {
dvp->show();
}
+2 -2
View File
@@ -178,9 +178,9 @@ void QGISVGTemplate::createClickHandles()
// XPath query to select all <text> 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(
+7 -5
View File
@@ -47,6 +47,7 @@ void QGIViewSection::draw()
void QGIViewSection::drawSectionFace()
{
// Base::Console().Message("QGIVS::drawSectionFace()\n");
auto section( dynamic_cast<TechDraw::DrawViewSection *>(getViewObject()) );
if (!section) {
return;
@@ -85,13 +86,14 @@ void QGIViewSection::drawSectionFace()
return;
}
QColor faceColor = (sectionVp->CutSurfaceColor.getValue()).asValue<QColor>();
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<QColor>();
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<TechDraw::LineSet> lineSets = section->getDrawableLines(i);
if (!lineSets.empty()) {
newFace->clearLineSets();
+2 -76
View File
@@ -38,6 +38,7 @@
#include <Mod/TechDraw/App/DrawPage.h>
#include <Mod/TechDraw/App/DrawProjGroupItem.h>
#include <Mod/TechDraw/App/DrawProjGroup.h>
#include <Mod/TechDraw/App/DrawUtil.h>
#include "TaskProjGroup.h"
#include "ui_TaskProjGroup.h"
@@ -306,81 +307,6 @@ void TaskProjGroup::spacingChanged()
multiView->recomputeFeature();
}
std::pair<int, int> 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<int, int> 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<int>::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<int, int> fraction = nearestFraction(newScale);
std::pair<int, int> fraction = DrawUtil::nearestFraction(newScale);
ui->sbScaleNum->setValue(fraction.first);
ui->sbScaleDen->setValue(fraction.second);
-1
View File
@@ -60,7 +60,6 @@ public:
QPushButton* btnApply);
void updateTask();
std::pair<int, int> 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;}
+11
View File
@@ -38,6 +38,7 @@
#include <App/DocumentObject.h>
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/CommandT.h>
#include <Gui/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/ViewProviderDocumentObject.h>
@@ -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);
@@ -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<std::string>& 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())) {
@@ -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<std::string>& elements) const override;
void extensionDropObject(App::DocumentObject*) override;
void dropObject(App::DocumentObject* docObj);
@@ -31,6 +31,9 @@
#endif
#include <App/DocumentObject.h>
#include <Mod/TechDraw/App/DrawPage.h>
#include <Mod/TechDraw/App/DrawProjGroupItem.h>
#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<TechDraw::DrawView*>(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<TechDraw::DrawView*>(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);
}
@@ -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
+3 -25
View File
@@ -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";
@@ -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)
@@ -192,13 +192,13 @@
</g>
</g>
<g id="g3298" fill="#000000" font-family="Arial" letter-spacing="0px" word-spacing="0px">
<text id="text3266" x="147.9312" y="160.67236" style="line-height:0%" freecad:editable="Designed_by_Name"><tspan id="tspan3268" x="147.9312" y="160.67236" font-size="3.95px" style="line-height:1.25">Designed by Name</tspan></text>
<text id="text3270" x="147.93056" y="168.59135" style="line-height:0%" freecad:editable="FC-Date"><tspan id="tspan3272" x="147.93056" y="168.59135" font-size="3.95px" style="line-height:1.25">Date</tspan></text>
<text id="text3274" x="154.46243" y="191.45177" text-align="center" text-anchor="middle" style="line-height:0%" freecad:editable="FC-SC"><tspan id="tspan3276" x="154.46243" y="191.45177" font-size="3.95px" style="line-height:1.25">Scale</tspan></text>
<text id="text3266" x="147.9312" y="160.67236" style="line-height:0%" freecad:editable="Designed_by_Name" freecad:autofill="author"><tspan id="tspan3268" x="147.9312" y="160.67236" font-size="3.95px" style="line-height:1.25">Designed by Name</tspan></text>
<text id="text3270" x="147.93056" y="168.59135" style="line-height:0%" freecad:editable="FC-Date" freecad:autofill="date"><tspan id="tspan3272" x="147.93056" y="168.59135" font-size="3.95px" style="line-height:1.25">Date</tspan></text>
<text id="text3274" x="154.46243" y="191.45177" text-align="center" text-anchor="middle" style="line-height:0%" freecad:editable="FC-SC" freecad:autofill="scale"><tspan id="tspan3276" x="154.46243" y="191.45177" font-size="3.95px" style="line-height:1.25">Scale</tspan></text>
<text id="text3278" x="173.94231" y="191.44733" text-align="center" text-anchor="middle" style="line-height:0%" freecad:editable="Weight"><tspan id="tspan3280" x="173.94231" y="191.44733" font-size="3.95px" style="line-height:1.25">Weight</tspan></text>
<text id="text3282" x="186.05237" y="158.72597" style="line-height:0%" freecad:editable="FC-Title"><tspan id="tspan3284" x="186.05237" y="158.72597" font-size="5.6444px" style="line-height:1.25">Title</tspan></text>
<text id="text3282" x="186.05237" y="158.72597" style="line-height:0%" freecad:editable="FC-Title" freecad:autofill="title"><tspan id="tspan3284" x="186.05237" y="158.72597" font-size="5.6444px" style="line-height:1.25">Title</tspan></text>
<text id="text3286" x="185.99422" y="165.73558" style="line-height:0%" freecad:editable="Subtitle"><tspan id="tspan3288" x="185.99422" y="165.73558" font-size="3.9511px" style="line-height:1.25">Subtitle</tspan></text>
<text id="text3290" x="185.6927" y="191.31752" style="line-height:0%" freecad:editable="Drawing_number"><tspan id="tspan3292" x="185.6927" y="191.31752" font-size="3.9511px" style="line-height:1.25">Drawing number</tspan></text>
<text id="text3294" x="248.32477" y="191.45177" style="line-height:0%" freecad:editable="FC-SH"><tspan id="tspan3296" x="248.32477" y="191.45177" font-size="3.9511px" style="line-height:1.25">Sheet</tspan></text>
<text id="text3294" x="248.32477" y="191.45177" style="line-height:0%" freecad:editable="FC-SH" freecad:autofill="sheet"><tspan id="tspan3296" x="248.32477" y="191.45177" font-size="3.9511px" style="line-height:1.25">Sheet</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

@@ -314,18 +314,18 @@
</g>
</g>
</g>
<text id="text3331" x="392.60703" y="372.66077" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="AuthorName"><tspan id="tspan3333" x="392.60703" y="372.66077" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">AUTHOR NAME</tspan></text>
<text id="text3331-5" x="392.4371" y="378.867" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="CreationDate"><tspan id="tspan3333-6" x="392.4371" y="378.867" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">CREATION DATE</tspan></text>
<text id="text3331" x="392.60703" y="372.66077" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="AuthorName" freecad:autofill="author"><tspan id="tspan3333" x="392.60703" y="372.66077" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">AUTHOR NAME</tspan></text>
<text id="text3331-5" x="392.4371" y="378.867" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="CreationDate" freecad:autofill="date"><tspan id="tspan3333-6" x="392.4371" y="378.867" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">CREATION DATE</tspan></text>
<text id="text3331-5-5" x="392.40024" y="384.9765" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="SupervisorName"><tspan id="tspan3333-6-9" x="392.40024" y="384.9765" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">SUPERVISOR NAME</tspan></text>
<text id="text3331-5-3" x="392.35773" y="391.07263" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="CheckDate"><tspan id="tspan3333-6-1" x="392.35773" y="391.07263" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">CHECK DATE</tspan></text>
<text id="text3331-5-9-1" x="399.95898" y="399.68958" fill="#000000" font-family="Arial" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="A3"><tspan id="tspan3316" x="399.95898" y="399.68958" font-size="4.1756px" stroke-width=".10686" style="line-height:1.25">ANSI C</tspan></text>
<text id="text3331-5-9" x="399.75266" y="410.16138" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="Scale"><tspan id="tspan3333-6-6" x="399.75266" y="410.16138" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">SCALE</tspan></text>
<text id="text3331-5-9" x="399.75266" y="410.16138" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="Scale" freecad:autofill="scale"><tspan id="tspan3333-6-6" x="399.75266" y="410.16138" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">SCALE</tspan></text>
<text id="text3331-5-9-4" x="418.39166" y="410.16138" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="Weight"><tspan id="tspan3333-6-6-3" x="418.39166" y="410.16138" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">WEIGHT</tspan></text>
<text id="text3331-5-9-4-3" x="430.0199" y="410.16138" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="DrawingNumber"><tspan id="tspan3333-6-6-3-8" x="430.0199" y="410.16138" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">NUMBER</tspan></text>
<text id="text3331-5-9-4-3-9" x="516.71234" y="410.16138" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="SheetNumber"><tspan id="tspan3333-6-6-3-8-7" x="516.71234" y="410.16138" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">SHEET</tspan></text>
<text id="text3331-0" x="467.7522" y="373.39777" fill="#000000" font-family="Arial" font-size="9.8636px" font-weight="bold" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="Title"><tspan id="tspan3333-5" x="467.7522" y="373.39777" font-family="Arial" font-size="6.9593px" font-weight="bold" stroke-width=".10686" text-align="center" text-anchor="middle" style="line-height:125%">TITLE</tspan></text>
<text id="text3331-5-9-4-3-9" x="516.71234" y="410.16138" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="SheetNumber" freecad:autofill="sheet"><tspan id="tspan3333-6-6-3-8-7" x="516.71234" y="410.16138" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">SHEET</tspan></text>
<text id="text3331-0" x="467.7522" y="373.39777" fill="#000000" font-family="Arial" font-size="9.8636px" font-weight="bold" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="Title" freecad:autofill="title"><tspan id="tspan3333-5" x="467.7522" y="373.39777" font-family="Arial" font-size="6.9593px" font-weight="bold" stroke-width=".10686" text-align="center" text-anchor="middle" style="line-height:125%">TITLE</tspan></text>
<text id="text3331-0-4" x="467.74326" y="381.90521" fill="#000000" font-family="Arial" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" text-align="center" text-anchor="middle" word-spacing="0px" style="line-height:0%" freecad:editable="Subtitle"><tspan id="tspan3333-5-6" x="467.74326" y="381.90521" font-size="4.6396px" stroke-width=".10686" style="line-height:1.25">SUBTITLE</tspan></text>
<text id="text241" x="430.58435" y="398.39545" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="Company_name"><tspan id="tspan243" x="430.58435" y="398.39545" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">COMPANY NAME</tspan></text>
<text id="text241" x="430.58435" y="398.39545" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="Company_name" freecad:autofill="organization"><tspan id="tspan243" x="430.58435" y="398.39545" font-size="3.2879px" stroke-width=".10686" style="line-height:1.25">COMPANY NAME</tspan></text>
<text id="text287" x="392.32947" y="416.26276" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="COPYRIGHT"><tspan id="tspan289" x="392.32947" y="416.26276" font-size="2.5518px" stroke-width=".10686" style="line-height:1.25">COPYRIGHT</tspan></text>
<text id="text275" x="530.49188" y="415.13251" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="I__"><tspan id="tspan3245-0" x="530.49188" y="415.13251" font-size="2.5518px" stroke-width=".10686" style="line-height:1.25">_________</tspan></text>
<text id="text278" x="530.49188" y="409.67816" fill="#000000" font-family="sans" font-size="9.8636px" letter-spacing="0px" stroke-width=".10686" word-spacing="0px" style="line-height:0%" freecad:editable="H__"><tspan id="tspan280" x="530.49188" y="409.67816" font-size="2.5518px" stroke-width=".10686" style="line-height:1.25">_________</tspan></text>

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

+249 -3
View File
@@ -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)));
}