diff --git a/src/Base/UnitsApi.cpp b/src/Base/UnitsApi.cpp index 0cde90a3b0..5bdff83351 100644 --- a/src/Base/UnitsApi.cpp +++ b/src/Base/UnitsApi.cpp @@ -156,7 +156,7 @@ void UnitsApi::setSchema(UnitSystem s) QString UnitsApi::toString(const Base::Quantity& q, const QuantityFormat& f) { - QString value = QString::fromLatin1("'%1 %2'").arg(q.getValue(), 0, f.toFormat(), f.precision+1) + QString value = QString::fromLatin1("'%1 %2'").arg(q.getValue(), 0, f.toFormat(), f.precision+2) .arg(q.getUnit().getString()); return value; } diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index d59831672b..b4eb6fc118 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -2173,6 +2173,21 @@ void Application::runApplication(void) SoDebugError::setHandlerCallback( messageHandlerCoin, 0 ); #endif + // Now run the background autoload, for workbenches that should be loaded at startup, but not + // displayed to the user immediately + std::string autoloadCSV = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + GetASCII("BackgroundAutoloadModules", ""); + + // Tokenize the comma-separated list and load the requested workbenches if they exist in this installation + std::vector backgroundAutoloadedModules; + std::stringstream stream(autoloadCSV); + std::string workbench; + while (std::getline(stream, workbench, ',')) + if (wb.contains(QString::fromLatin1(workbench.c_str()))) + app.activateWorkbench(workbench.c_str()); + + // Reactivate the startup workbench + app.activateWorkbench(start.c_str()); Instance->d->startingUp = false; diff --git a/src/Gui/DlgSettingsLazyLoaded.ui b/src/Gui/DlgSettingsLazyLoaded.ui index 3bfd0e9490..656dd1eaee 100644 --- a/src/Gui/DlgSettingsLazyLoaded.ui +++ b/src/Gui/DlgSettingsLazyLoaded.ui @@ -14,53 +14,37 @@ Unloaded Workbenches - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - <html><head/><body><p>Load the selected workbenches, adding their preference windows to the preferences dialog.</p></body></html> - - - Load Selected - - - - - - - - - 0 - 0 - + + + false - - - 0 - 150 - - - - <html><head/><body><p>Available unloaded workbenches</p></body></html> - - - QAbstractItemView::ExtendedSelection + + false + + false + + + + + + + + + Workbench Name + + + + + Autoload? + + + + + Load Now + + @@ -78,26 +62,13 @@ - <html><head/><body><p>To preserve resources, FreeCAD does not load workbenches until they are used. Loading them may provide access to additional preferences related to their functionality.</p><p>The following workbenches are available in your installation, but are not yet loaded:</p></body></html> + <html><head/><body><p>To preserve resources, FreeCAD does not load workbenches until they are used. Loading them may provide access to additional preferences related to their functionality.</p><p>The following workbenches are available in your installation:</p></body></html> true - - - - Qt::Vertical - - - - 429 - 37 - - - - diff --git a/src/Gui/DlgSettingsLazyLoadedImp.cpp b/src/Gui/DlgSettingsLazyLoadedImp.cpp index 660c10c79b..9d0a9b9bfa 100644 --- a/src/Gui/DlgSettingsLazyLoadedImp.cpp +++ b/src/Gui/DlgSettingsLazyLoadedImp.cpp @@ -32,6 +32,10 @@ #include "WorkbenchManager.h" #include "Workbench.h" +#include + +#include + using namespace Gui::Dialog; const uint DlgSettingsLazyLoadedImp::WorkbenchNameRole = Qt::UserRole; @@ -46,8 +50,6 @@ DlgSettingsLazyLoadedImp::DlgSettingsLazyLoadedImp( QWidget* parent ) , ui(new Ui_DlgSettingsLazyLoaded) { ui->setupUi(this); - buildUnloadedWorkbenchList(); - connect(ui->loadButton, SIGNAL(clicked()), this, SLOT(onLoadClicked())); } /** @@ -60,22 +62,45 @@ DlgSettingsLazyLoadedImp::~DlgSettingsLazyLoadedImp() void DlgSettingsLazyLoadedImp::saveSettings() { - + std::ostringstream csv; + for (const auto& checkbox : _autoloadCheckboxes) { + if (checkbox.second->isChecked()) { + if (!csv.str().empty()) + csv << ","; + csv << checkbox.first.toStdString(); + } + } + App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + SetASCII("BackgroundAutoloadModules", csv.str().c_str()); } void DlgSettingsLazyLoadedImp::loadSettings() { - + // There are two different "autoload" settings: the first, in FreeCAD since 2004, + // controls the module the user sees first when starting FreeCAD, and defaults to the Start workbench + std::string start = App::Application::Config()["StartWorkbench"]; + _startupModule = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + GetASCII("AutoloadModule", start.c_str()); + + // The second autoload setting does a background autoload of any number of other modules + std::string autoloadCSV = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> + GetASCII("BackgroundAutoloadModules", ""); + + // Tokenize the comma-separated list + _backgroundAutoloadedModules.clear(); + std::stringstream stream(autoloadCSV); + std::string workbench; + while (std::getline(stream, workbench, ',')) + _backgroundAutoloadedModules.push_back(workbench); + + + buildUnloadedWorkbenchList(); } -void DlgSettingsLazyLoadedImp::onLoadClicked() +void DlgSettingsLazyLoadedImp::onLoadClicked(const QString &wbName) { Workbench* originalActiveWB = WorkbenchManager::instance()->active(); - auto selection = ui->workbenchList->selectedItems(); - for (const auto& item : selection) { - auto name = item->data(WorkbenchNameRole).toString().toStdString(); - Application::Instance->activateWorkbench(name.c_str()); - } + Application::Instance->activateWorkbench(wbName.toStdString().c_str()); Application::Instance->activateWorkbench(originalActiveWB->name().c_str()); buildUnloadedWorkbenchList(); } @@ -86,21 +111,87 @@ Build the list of unloaded workbenches. */ void DlgSettingsLazyLoadedImp::buildUnloadedWorkbenchList() { - ui->workbenchList->clear(); QStringList workbenches = Application::Instance->workbenches(); + workbenches.sort(); + + ui->workbenchTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + ui->workbenchTable->setRowCount(0); + _autoloadCheckboxes.clear(); // setRowCount(0) just invalidated all of these pointers + ui->workbenchTable->setColumnCount(4); + ui->workbenchTable->setSelectionMode(QAbstractItemView::SelectionMode::NoSelection); + ui->workbenchTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeMode::ResizeToContents); + ui->workbenchTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch); + ui->workbenchTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeMode::ResizeToContents); + ui->workbenchTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeMode::ResizeToContents); + QStringList columnHeaders; + columnHeaders << QString() << tr("Workbench") << tr("Autoload") << QString(); + ui->workbenchTable->setHorizontalHeaderLabels(columnHeaders); + + unsigned int rowNumber = 0; for (const auto& wbName : workbenches) { - const auto& wb = WorkbenchManager::instance()->getWorkbench(wbName.toStdString()); - if (!wb) { - auto wbIcon = Application::Instance->workbenchIcon(wbName); - auto wbDisplayName = Application::Instance->workbenchMenuText(wbName); - auto wbTooltip = Application::Instance->workbenchToolTip(wbName); - QListWidgetItem *wbRow = new QListWidgetItem(wbIcon, wbDisplayName); - wbRow->setData(WorkbenchNameRole, QVariant(wbName)); // Store the actual internal name for easier loading - wbRow->setToolTip(wbTooltip); - ui->workbenchList->addItem(wbRow); // Transfers ownership to the QListWidget + if (wbName.toStdString() == "NoneWorkbench") + continue; // Do not list the default empty Workbench + + ui->workbenchTable->insertRow(rowNumber); + auto wbTooltip = Application::Instance->workbenchToolTip(wbName); + + // Column 1: Workbench Icon + auto wbIcon = Application::Instance->workbenchIcon(wbName); + auto iconLabel = new QLabel(); + iconLabel->setPixmap(wbIcon.scaled(QSize(20,20), Qt::AspectRatioMode::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation)); + iconLabel->setToolTip(wbTooltip); + iconLabel->setContentsMargins(5, 3, 3, 3); // Left, top, right, bottom + ui->workbenchTable->setCellWidget(rowNumber, 0, iconLabel); + + // Column 2: Workbench Display Name + auto wbDisplayName = Application::Instance->workbenchMenuText(wbName); + auto textLabel = new QLabel(wbDisplayName); + textLabel->setToolTip(wbTooltip); + ui->workbenchTable->setCellWidget(rowNumber, 1, textLabel); + + // Column 3: Autoloaded checkbox + // + // To get the checkbox centered, we have to jump through some hoops... + QWidget* checkWidget = new QWidget(this); + auto autoloadCheckbox = new QCheckBox(this); + autoloadCheckbox->setToolTip(tr("If checked") + + QString::fromUtf8(", ") + wbDisplayName + QString::fromUtf8(" ") + + tr("will be loaded automatically when FreeCAD starts up")); + QHBoxLayout* checkLayout = new QHBoxLayout(checkWidget); + checkLayout->addWidget(autoloadCheckbox); + checkLayout->setAlignment(Qt::AlignCenter); + checkLayout->setContentsMargins(0, 0, 0, 0); + + // Figure out whether to check and/or disable this checkbox: + if (wbName.toStdString() == _startupModule) { + autoloadCheckbox->setChecked(true); + autoloadCheckbox->setEnabled(false); + autoloadCheckbox->setToolTip(tr("This is the current startup module, and must be autoloaded. See Preferences/General/Autoload to change.")); } + else if (std::find(_backgroundAutoloadedModules.begin(), _backgroundAutoloadedModules.end(), + wbName.toStdString()) != _backgroundAutoloadedModules.end()) { + autoloadCheckbox->setChecked(true); + _autoloadCheckboxes.insert(std::make_pair(wbName, autoloadCheckbox)); + } + else { + _autoloadCheckboxes.insert(std::make_pair(wbName, autoloadCheckbox)); + } + ui->workbenchTable->setCellWidget(rowNumber, 2, checkWidget); + + // Column 4: Load button/loaded indicator + if (WorkbenchManager::instance()->getWorkbench(wbName.toStdString())) { + auto label = new QLabel(tr("Loaded")); + label->setAlignment(Qt::AlignCenter); + ui->workbenchTable->setCellWidget(rowNumber, 3, label); + } + else { + auto button = new QPushButton(tr("Load now")); + connect(button, &QPushButton::clicked, this, [this,wbName]() { onLoadClicked(wbName); }); + ui->workbenchTable->setCellWidget(rowNumber, 3, button); + } + + ++rowNumber; } - ui->workbenchList->sortItems(); } /** diff --git a/src/Gui/DlgSettingsLazyLoadedImp.h b/src/Gui/DlgSettingsLazyLoadedImp.h index 71c9ae07d0..4521afc023 100644 --- a/src/Gui/DlgSettingsLazyLoadedImp.h +++ b/src/Gui/DlgSettingsLazyLoadedImp.h @@ -27,6 +27,8 @@ #include "PropertyPage.h" #include +class QCheckBox; + namespace Gui { namespace Dialog { class Ui_DlgSettingsLazyLoaded; @@ -49,7 +51,7 @@ public: void loadSettings(); protected Q_SLOTS: - void onLoadClicked(); + void onLoadClicked(const QString& wbName); protected: void buildUnloadedWorkbenchList(); @@ -58,6 +60,10 @@ protected: private: std::unique_ptr ui; static const uint WorkbenchNameRole; + + std::vector _backgroundAutoloadedModules; + std::string _startupModule; + std::map _autoloadCheckboxes; }; } // namespace Dialog diff --git a/src/Gui/ViewParams.h b/src/Gui/ViewParams.h index cee80eb4fa..6dc897cff0 100644 --- a/src/Gui/ViewParams.h +++ b/src/Gui/ViewParams.h @@ -54,6 +54,7 @@ public: FC_VIEW_PARAM(MarkerSize,int,Int,9) \ FC_VIEW_PARAM(DefaultLinkColor,unsigned long,Unsigned,0x66FFFF00) \ FC_VIEW_PARAM(DefaultShapeLineColor,unsigned long,Unsigned,421075455UL) \ + FC_VIEW_PARAM(DefaultShapeVertexColor,unsigned long,Unsigned,421075455UL) \ FC_VIEW_PARAM(DefaultShapeColor,unsigned long,Unsigned,0xCCCCCC00) \ FC_VIEW_PARAM(DefaultShapeLineWidth,int,Int,2) \ FC_VIEW_PARAM(DefaultShapePointSize,int,Int,2) \ diff --git a/src/Gui/Widgets.h b/src/Gui/Widgets.h index 3f868697cf..86baff6a51 100644 --- a/src/Gui/Widgets.h +++ b/src/Gui/Widgets.h @@ -373,7 +373,7 @@ private: // ---------------------------------------------------------------------- -class PropertyListEditor : public QPlainTextEdit +class GuiExport PropertyListEditor : public QPlainTextEdit { Q_OBJECT diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 512e7589cb..e94075eb73 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -1051,7 +1051,7 @@ void PropertyUnitItem::setValue(const QVariant& value) return; const Base::Quantity& val = value.value(); - Base::QuantityFormat format(Base::QuantityFormat::Default, decimals()); + Base::QuantityFormat format(Base::QuantityFormat::Fixed, decimals()); QString unit = Base::UnitsApi::toString(val, format); setPropertyValue(unit); } @@ -1644,7 +1644,7 @@ void PropertyVectorDistanceItem::setValue(const QVariant& variant) Base::Quantity y = Base::Quantity(value.y, Base::Unit::Length); Base::Quantity z = Base::Quantity(value.z, Base::Unit::Length); - Base::QuantityFormat format(Base::QuantityFormat::Default, decimals()); + Base::QuantityFormat format(Base::QuantityFormat::Fixed, decimals()); QString data = QString::fromLatin1("(%1, %2, %3)") .arg(Base::UnitsApi::toNumber(x, format)) .arg(Base::UnitsApi::toNumber(y, format)) @@ -2370,7 +2370,7 @@ void PropertyPlacementItem::setValue(const QVariant& value) const Base::Placement& val = value.value(); Base::Vector3d pos = val.getPosition(); - Base::QuantityFormat format(Base::QuantityFormat::Default, decimals()); + Base::QuantityFormat format(Base::QuantityFormat::Fixed, decimals()); QString data = QString::fromLatin1("App.Placement(" "App.Vector(%1,%2,%3)," "App.Rotation(App.Vector(%4,%5,%6),%7))") diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index a3aa93284d..2e254f123b 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -1095,7 +1095,7 @@ class DraftToolBar: self.taskUi(title, icon="Draft_Trimex") self.radiusUi() self.labelRadius.setText(translate("draft","Distance")) - self.radiusValue.setToolTip(translate("draft", "Trim distance")) + self.radiusValue.setToolTip(translate("draft", "Offset distance")) self.radiusValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Length).UserString) todo.delay(self.radiusValue.setFocus,None) self.radiusValue.selectAll() diff --git a/src/Mod/Draft/draftguitools/gui_trimex.py b/src/Mod/Draft/draftguitools/gui_trimex.py index 85fe2c6799..65f97a3212 100644 --- a/src/Mod/Draft/draftguitools/gui_trimex.py +++ b/src/Mod/Draft/draftguitools/gui_trimex.py @@ -203,9 +203,21 @@ class Trimex(gui_base_original.Modifier): if self.extrudeMode: dist = self.extrude(self.shift) else: - dist = self.redraw(self.point, self.snapped, - self.shift, self.alt) - self.ui.setRadiusValue(dist, unit="Length") + # If the geomType of the edge is "Line" ang will be None, + # else dist will be None. + dist, ang = self.redraw(self.point, self.snapped, + self.shift, self.alt) + + if dist: + self.ui.labelRadius.setText(translate("draft", "Distance")) + self.ui.radiusValue.setToolTip(translate("draft", + "Offset distance")) + self.ui.setRadiusValue(dist, unit="Length") + else: + self.ui.labelRadius.setText(translate("draft", "Angle")) + self.ui.radiusValue.setToolTip(translate("draft", + "Offset angle")) + self.ui.setRadiusValue(ang, unit="Angle") self.ui.radiusValue.setFocus() self.ui.radiusValue.selectAll() gui_tool_utils.redraw3DView() @@ -301,6 +313,7 @@ class Trimex(gui_base_original.Modifier): # modifying active edge if DraftGeomUtils.geomType(edge) == "Line": + ang = None ve = DraftGeomUtils.vec(edge) chord = v1.sub(point) n = ve.cross(chord) @@ -313,9 +326,6 @@ class Trimex(gui_base_original.Modifier): dist = v1.sub(self.newpoint).Length ghost.p1(self.newpoint) ghost.p2(v2) - self.ui.labelRadius.setText(translate("draft", "Distance")) - self.ui.radiusValue.setToolTip(translate("draft", - "The offset distance")) if real: if self.force: ray = self.newpoint.sub(v1) @@ -323,16 +333,14 @@ class Trimex(gui_base_original.Modifier): self.newpoint = App.Vector.add(v1, ray) newedges.append(Part.LineSegment(self.newpoint, v2).toShape()) else: + dist = None center = edge.Curve.Center rad = edge.Curve.Radius ang1 = DraftVecUtils.angle(v2.sub(center)) ang2 = DraftVecUtils.angle(point.sub(center)) _rot_rad = DraftVecUtils.rotate(App.Vector(rad, 0, 0), -ang2) self.newpoint = App.Vector.add(center, _rot_rad) - self.ui.labelRadius.setText(translate("draft", "Angle")) - self.ui.radiusValue.setToolTip(translate("draft", - "The offset angle")) - dist = math.degrees(-ang2) + ang = math.degrees(-ang2) # if ang1 > ang2: # ang1, ang2 = ang2, ang1 # print("last calculated:", @@ -384,7 +392,7 @@ class Trimex(gui_base_original.Modifier): if real: return newedges else: - return dist + return [dist, ang] def trimObject(self): """Trim the actual object.""" diff --git a/src/Mod/Fem/femexamples/boxanalysis_frequency.py b/src/Mod/Fem/femexamples/boxanalysis_frequency.py index b00e1a70af..a12065cd15 100644 --- a/src/Mod/Fem/femexamples/boxanalysis_frequency.py +++ b/src/Mod/Fem/femexamples/boxanalysis_frequency.py @@ -35,7 +35,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": [], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "frequency" } diff --git a/src/Mod/Fem/femexamples/boxanalysis_static.py b/src/Mod/Fem/femexamples/boxanalysis_static.py index e08ac1194c..95bfb07311 100644 --- a/src/Mod/Fem/femexamples/boxanalysis_static.py +++ b/src/Mod/Fem/femexamples/boxanalysis_static.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "force", "pressure"], - "solvers": ["calculix", "elmer"], + "solvers": ["calculix", "ccxtools", "elmer"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/buckling_lateraltorsionalbuckling.py b/src/Mod/Fem/femexamples/buckling_lateraltorsionalbuckling.py index 527b14e6e9..06dcb8b3f7 100644 --- a/src/Mod/Fem/femexamples/buckling_lateraltorsionalbuckling.py +++ b/src/Mod/Fem/femexamples/buckling_lateraltorsionalbuckling.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria6", "constraints": ["displacement", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "buckling" } diff --git a/src/Mod/Fem/femexamples/buckling_platebuckling.py b/src/Mod/Fem/femexamples/buckling_platebuckling.py index 16f7f3caa0..71917f99c0 100644 --- a/src/Mod/Fem/femexamples/buckling_platebuckling.py +++ b/src/Mod/Fem/femexamples/buckling_platebuckling.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria6", "constraints": ["displacement", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "buckling" } diff --git a/src/Mod/Fem/femexamples/ccx_buckling_flexuralbuckling.py b/src/Mod/Fem/femexamples/ccx_buckling_flexuralbuckling.py index dafae06425..b4a75cf3f8 100644 --- a/src/Mod/Fem/femexamples/ccx_buckling_flexuralbuckling.py +++ b/src/Mod/Fem/femexamples/ccx_buckling_flexuralbuckling.py @@ -38,7 +38,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Hexa8", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "buckling" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_base_solid.py b/src/Mod/Fem/femexamples/ccx_cantilever_base_solid.py index f92fc3377a..0b15acc2c1 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_base_solid.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_base_solid.py @@ -84,7 +84,6 @@ def setup_cantilever_base_solid(doc=None, solvertype="ccxtools"): mat["Name"] = "CalculiX-Steel" mat["YoungsModulus"] = "210000 MPa" mat["PoissonRatio"] = "0.30" - mat["Density"] = "7900 kg/m^3" material_obj.Material = mat analysis.addObject(material_obj) diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_beam_circle.py b/src/Mod/Fem/femexamples/ccx_cantilever_beam_circle.py index f4e26bdc68..a32273d4ac 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_beam_circle.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_beam_circle.py @@ -34,7 +34,7 @@ def get_information(): "meshtype": "edge", "meshelement": "Seg3", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_beam_pipe.py b/src/Mod/Fem/femexamples/ccx_cantilever_beam_pipe.py index 0fb9b85e78..46fbe8c726 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_beam_pipe.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_beam_pipe.py @@ -34,7 +34,7 @@ def get_information(): "meshtype": "edge", "meshelement": "Seg3", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py b/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py index 6ac31dd33a..577a9fae50 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py @@ -34,7 +34,7 @@ def get_information(): "meshtype": "edge", "meshelement": "Seg3", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_hexa20.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_hexa20.py index 012c1ea9e8..87f8d3ed7e 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_hexa20.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_hexa20.py @@ -38,7 +38,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Hexa20", "constraints": ["fixed", "force"], - "solvers": ["calculix", "elmer", "z88"], + "solvers": ["calculix", "ccxtools", "elmer", "z88"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad4.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad4.py index 29bcf33718..42bcd33fce 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad4.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad4.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "face", "meshelement": "Quad4", "constraints": ["fixed", "force"], - "solvers": ["calculix", "mystran"], + "solvers": ["calculix", "ccxtools", "mystran"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad8.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad8.py index ddba8a2b52..11ce8d0e3f 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad8.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_quad8.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "face", "meshelement": "Quad8", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg2.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg2.py index cbdadbc8a3..b8d30bcc99 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg2.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg2.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "edge", "meshelement": "Seg2", "constraints": ["fixed", "force"], - "solvers": ["calculix", "mystran"], + "solvers": ["calculix", "ccxtools", "mystran"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg3.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg3.py index ce075ecb15..3f2d86da31 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg3.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_seg3.py @@ -32,7 +32,7 @@ def get_information(): "meshtype": "edge", "meshelement": "Seg3", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_tetra4.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_tetra4.py index 3d30a8009d..42c33b54f6 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_tetra4.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_tetra4.py @@ -36,7 +36,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tetra4", "constraints": ["fixed", "force"], - "solvers": ["calculix", "elmer", "mystran", "z88"], + "solvers": ["calculix", "ccxtools", "elmer", "mystran", "z88"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria3.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria3.py index 081bde28fd..a16c752a1c 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria3.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria3.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria3", "constraints": ["fixed", "force"], - "solvers": ["calculix", "mystran"], + "solvers": ["calculix", "ccxtools", "mystran"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria6.py b/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria6.py index 036fc3587f..6423bdf782 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria6.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_ele_tria6.py @@ -32,7 +32,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria6", "constraints": ["fixed", "force"], - "solvers": ["calculix", "z88"], + "solvers": ["calculix", "ccxtools", "z88"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_faceload.py b/src/Mod/Fem/femexamples/ccx_cantilever_faceload.py index 7928b48d30..4b8bf5e81d 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_faceload.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_faceload.py @@ -35,7 +35,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "force"], - "solvers": ["calculix", "elmer", "mystran", "z88"], + "solvers": ["calculix", "ccxtools", "elmer", "mystran", "z88"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_nodeload.py b/src/Mod/Fem/femexamples/ccx_cantilever_nodeload.py index fd82e68d4d..d88066d88f 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_nodeload.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_nodeload.py @@ -35,7 +35,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "force"], - "solvers": ["calculix", "elmer", "mystran", "z88"], + "solvers": ["calculix", "ccxtools", "elmer", "mystran", "z88"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_prescribeddisplacement.py b/src/Mod/Fem/femexamples/ccx_cantilever_prescribeddisplacement.py index 8b601737a5..6dd8a8586f 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_prescribeddisplacement.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_prescribeddisplacement.py @@ -35,7 +35,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "displacement"], - "solvers": ["calculix", "elmer"], + "solvers": ["calculix", "ccxtools", "elmer"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/constraint_centrif.py b/src/Mod/Fem/femexamples/constraint_centrif.py index d22749d98e..44ab08280b 100644 --- a/src/Mod/Fem/femexamples/constraint_centrif.py +++ b/src/Mod/Fem/femexamples/constraint_centrif.py @@ -42,7 +42,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["centrif", "fixed"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "multimaterial", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py b/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py index 74f1be4dff..259fe26194 100644 --- a/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py +++ b/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py @@ -41,7 +41,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria3", "constraints": ["fixed", "force", "contact"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py index 5b10f5cf6d..a914ecd468 100644 --- a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py +++ b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py @@ -42,7 +42,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "pressure", "contact"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/constraint_section_print.py b/src/Mod/Fem/femexamples/constraint_section_print.py index cd50b133dc..92979181f6 100644 --- a/src/Mod/Fem/femexamples/constraint_section_print.py +++ b/src/Mod/Fem/femexamples/constraint_section_print.py @@ -48,7 +48,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["section_print", "fixed", "pressure"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py b/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py index 650db6459b..a6c1074e67 100644 --- a/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py +++ b/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "self weight"], - "solvers": ["calculix", "elmer"], + "solvers": ["calculix", "ccxtools", "elmer"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/constraint_tie.py b/src/Mod/Fem/femexamples/constraint_tie.py index ead2f05820..f476771101 100644 --- a/src/Mod/Fem/femexamples/constraint_tie.py +++ b/src/Mod/Fem/femexamples/constraint_tie.py @@ -42,7 +42,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "force", "tie"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/constraint_transform_beam_hinged.py b/src/Mod/Fem/femexamples/constraint_transform_beam_hinged.py index 14260ac99d..bb6e326c06 100644 --- a/src/Mod/Fem/femexamples/constraint_transform_beam_hinged.py +++ b/src/Mod/Fem/femexamples/constraint_transform_beam_hinged.py @@ -42,7 +42,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["pressure", "displacement", "transform"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } @@ -141,8 +141,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "CalculiX-Steel" mat["YoungsModulus"] = "210000 MPa" mat["PoissonRatio"] = "0.30" - mat["Density"] = "7900 kg/m^3" - mat["ThermalExpansionCoefficient"] = "0.012 mm/m/K" material_obj.Material = mat analysis.addObject(material_obj) diff --git a/src/Mod/Fem/femexamples/constraint_transform_torque.py b/src/Mod/Fem/femexamples/constraint_transform_torque.py index 4f9370a731..f11de2dc99 100644 --- a/src/Mod/Fem/femexamples/constraint_transform_torque.py +++ b/src/Mod/Fem/femexamples/constraint_transform_torque.py @@ -50,7 +50,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "force", "transform"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/frequency_beamsimple.py b/src/Mod/Fem/femexamples/frequency_beamsimple.py index 0362047c97..b412aab389 100644 --- a/src/Mod/Fem/femexamples/frequency_beamsimple.py +++ b/src/Mod/Fem/femexamples/frequency_beamsimple.py @@ -37,7 +37,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "frequency" } diff --git a/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fiveboxes.py b/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fiveboxes.py index 1a85f49fd4..7f90c44a4b 100644 --- a/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fiveboxes.py +++ b/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fiveboxes.py @@ -40,7 +40,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "multimaterial", "equation": "mechanical" } @@ -148,7 +148,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Concrete-Generic" mat["YoungsModulus"] = "32000 MPa" mat["PoissonRatio"] = "0.17" - mat["Density"] = "0 kg/m^3" material_obj1.Material = mat analysis.addObject(material_obj1) @@ -158,7 +157,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "PLA" mat["YoungsModulus"] = "3640 MPa" mat["PoissonRatio"] = "0.36" - mat["Density"] = "0 kg/m^3" material_obj2.Material = mat analysis.addObject(material_obj2) @@ -168,7 +166,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Steel-Generic" mat["YoungsModulus"] = "200000 MPa" mat["PoissonRatio"] = "0.30" - mat["Density"] = "7900 kg/m^3" material_obj3.Material = mat analysis.addObject(material_obj3) diff --git a/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fivefaces.py b/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fivefaces.py index 3a2ee24267..d5bac29b75 100644 --- a/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fivefaces.py +++ b/src/Mod/Fem/femexamples/material_multiple_bendingbeam_fivefaces.py @@ -38,7 +38,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria6", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "multimaterial", "equation": "mechanical" } @@ -141,7 +141,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Concrete-Generic" mat["YoungsModulus"] = "32000 MPa" mat["PoissonRatio"] = "0.17" - mat["Density"] = "0 kg/m^3" material_obj1.Material = mat analysis.addObject(material_obj1) @@ -154,7 +153,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "PLA" mat["YoungsModulus"] = "3640 MPa" mat["PoissonRatio"] = "0.36" - mat["Density"] = "0 kg/m^3" material_obj2.Material = mat analysis.addObject(material_obj2) @@ -164,7 +162,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Steel-Generic" mat["YoungsModulus"] = "200000 MPa" mat["PoissonRatio"] = "0.30" - mat["Density"] = "7900 kg/m^3" material_obj3.Material = mat analysis.addObject(material_obj3) diff --git a/src/Mod/Fem/femexamples/material_multiple_tensionrod_twoboxes.py b/src/Mod/Fem/femexamples/material_multiple_tensionrod_twoboxes.py index 056f74f706..8ae258cf40 100644 --- a/src/Mod/Fem/femexamples/material_multiple_tensionrod_twoboxes.py +++ b/src/Mod/Fem/femexamples/material_multiple_tensionrod_twoboxes.py @@ -41,7 +41,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "pressure"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "multimaterial", "equation": "mechanical" } @@ -132,7 +132,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Aluminium-Generic" mat["YoungsModulus"] = "70000 MPa" mat["PoissonRatio"] = "0.35" - mat["Density"] = "2700 kg/m^3" material_obj_low.Material = mat material_obj_low.References = [(boxlow, "Solid1")] analysis.addObject(material_obj_low) @@ -142,7 +141,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Steel-Generic" mat["YoungsModulus"] = "200000 MPa" mat["PoissonRatio"] = "0.30" - mat["Density"] = "7980 kg/m^3" material_obj_upp.Material = mat material_obj_upp.References = [(boxupp, "Solid1")] analysis.addObject(material_obj_upp) diff --git a/src/Mod/Fem/femexamples/material_nl_platewithhole.py b/src/Mod/Fem/femexamples/material_nl_platewithhole.py index 9f2d635069..e81d72f71a 100644 --- a/src/Mod/Fem/femexamples/material_nl_platewithhole.py +++ b/src/Mod/Fem/femexamples/material_nl_platewithhole.py @@ -49,7 +49,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "nonlinear", "equation": "mechanical" } @@ -143,7 +143,6 @@ def setup(doc=None, solvertype="ccxtools"): matprop["Name"] = "CalculiX-Steel" matprop["YoungsModulus"] = "210000 MPa" matprop["PoissonRatio"] = "0.30" - matprop["Density"] = "7900 kg/m^3" material_obj.Material = matprop analysis.addObject(material_obj) diff --git a/src/Mod/Fem/femexamples/mystran_plate.py b/src/Mod/Fem/femexamples/mystran_plate.py index 70a7d5ef39..0f187dd969 100644 --- a/src/Mod/Fem/femexamples/mystran_plate.py +++ b/src/Mod/Fem/femexamples/mystran_plate.py @@ -38,8 +38,8 @@ def get_information(): "name": "Mystran Plate", "meshtype": "face", "meshelement": "Quad4", - "constraints": ["displacement", "force"], - "solvers": ["calculix", "elmer", "mystran"], + "constraints": ["fixed", "force"], + "solvers": ["calculix", "ccxtools", "elmer", "mystran"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femexamples/rc_wall_2d.py b/src/Mod/Fem/femexamples/rc_wall_2d.py index 5f1f0dd2a0..ca6bcf6695 100644 --- a/src/Mod/Fem/femexamples/rc_wall_2d.py +++ b/src/Mod/Fem/femexamples/rc_wall_2d.py @@ -42,7 +42,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria6", "constraints": ["fixed", "force", "displacement"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "reinforced", "equation": "mechanical" } @@ -133,7 +133,6 @@ def setup(doc=None, solvertype="ccxtools"): matrixprop["CompressiveStrength"] = "15.75 MPa" # make some hint on the possible angle units in material system matrixprop["AngleOfFriction"] = "30 deg" - matrixprop["Density"] = "2500 kg/m^3" reinfoprop = {} reinfoprop["Name"] = "Reinforcement-FIB-B500" reinfoprop["YieldStrength"] = "315 MPa" @@ -160,6 +159,7 @@ def setup(doc=None, solvertype="ccxtools"): # constraint displacement con_disp = ObjectsFem.makeConstraintDisplacement(doc, "ConstraintDisplacmentPrescribed") con_disp.References = [(geom_obj, "Face1")] + con_disp.zFree = False con_disp.zFix = True analysis.addObject(con_disp) diff --git a/src/Mod/Fem/femexamples/square_pipe_end_twisted_edgeforces.py b/src/Mod/Fem/femexamples/square_pipe_end_twisted_edgeforces.py index ea5baa76e9..e34bd0da16 100644 --- a/src/Mod/Fem/femexamples/square_pipe_end_twisted_edgeforces.py +++ b/src/Mod/Fem/femexamples/square_pipe_end_twisted_edgeforces.py @@ -41,7 +41,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria6", "constraints": ["force", "fixed"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } @@ -120,7 +120,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Steel-Generic" mat["YoungsModulus"] = "200000 MPa" mat["PoissonRatio"] = "0.30" - mat["Density"] = "7900 kg/m^3" material_obj.Material = mat analysis.addObject(material_obj) diff --git a/src/Mod/Fem/femexamples/square_pipe_end_twisted_nodeforces.py b/src/Mod/Fem/femexamples/square_pipe_end_twisted_nodeforces.py index fc0d6fb079..1b70ffb811 100644 --- a/src/Mod/Fem/femexamples/square_pipe_end_twisted_nodeforces.py +++ b/src/Mod/Fem/femexamples/square_pipe_end_twisted_nodeforces.py @@ -41,7 +41,7 @@ def get_information(): "meshtype": "face", "meshelement": "Tria6", "constraints": ["force", "fixed"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } @@ -230,7 +230,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["Name"] = "Steel-Generic" mat["YoungsModulus"] = "200000 MPa" mat["PoissonRatio"] = "0.30" - mat["Density"] = "7900 kg/m^3" material_obj.Material = mat analysis.addObject(material_obj) diff --git a/src/Mod/Fem/femexamples/thermomech_bimetall.py b/src/Mod/Fem/femexamples/thermomech_bimetall.py index 28acc934e9..ef4c4b2a88 100644 --- a/src/Mod/Fem/femexamples/thermomech_bimetall.py +++ b/src/Mod/Fem/femexamples/thermomech_bimetall.py @@ -49,7 +49,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "initial temperature", "temperature"], - "solvers": ["calculix", "elmer"], + "solvers": ["calculix", "ccxtools", "elmer"], "material": "multimaterial", "equation": "thermomechanical" } @@ -174,7 +174,6 @@ def setup(doc=None, solvertype="ccxtools"): mat["SpecificHeat"] = "510 J/kg/K" mat["ThermalConductivity"] = "13 W/m/K" mat["ThermalExpansionCoefficient"] = "0.0000012 m/m/K" - mat["Density"] = "1.00 kg/m^3" material_obj_top.Material = mat material_obj_top.References = [(geom_obj, "Solid2")] analysis.addObject(material_obj_top) diff --git a/src/Mod/Fem/femexamples/thermomech_flow1d.py b/src/Mod/Fem/femexamples/thermomech_flow1d.py index 905739297e..df9059c47c 100644 --- a/src/Mod/Fem/femexamples/thermomech_flow1d.py +++ b/src/Mod/Fem/femexamples/thermomech_flow1d.py @@ -41,7 +41,7 @@ def get_information(): "meshtype": "edge", "meshelement": "Seg3", "constraints": ["self weight"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "fluid", "equation": "thermomechanical" } diff --git a/src/Mod/Fem/femexamples/thermomech_spine.py b/src/Mod/Fem/femexamples/thermomech_spine.py index 7820d986a8..6ea6cce4f8 100644 --- a/src/Mod/Fem/femexamples/thermomech_spine.py +++ b/src/Mod/Fem/femexamples/thermomech_spine.py @@ -38,7 +38,7 @@ def get_information(): "meshtype": "solid", "meshelement": "Tet10", "constraints": ["fixed", "initial temperature", "temperature", "heatflux"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "thermomechanical" } diff --git a/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py b/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py index f945655931..d123da7f72 100644 --- a/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py +++ b/src/Mod/Fem/femexamples/truss_3d_cs_circle_ele_seg3.py @@ -41,7 +41,7 @@ def get_information(): "meshtype": "edge", "meshelement": "Seg3", "constraints": ["fixed", "force"], - "solvers": ["calculix"], + "solvers": ["calculix", "ccxtools"], "material": "solid", "equation": "mechanical" } diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index ac4a7aca22..3f04ec7c0c 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -2225,7 +2225,7 @@ def get_femmesh_eletype( if not femmesh: FreeCAD.Console.PrintError("Error: No femmesh.\n") if not femelement_table: - FreeCAD.Console.PrintError("The femelement_table need to be calculated.\n") + FreeCAD.Console.PrintWarning("The femelement_table need to be calculated.\n") femelement_table = get_femelement_table(femmesh) # in some cases lowest key in femelement_table is not [1] for elem in sorted(femelement_table): diff --git a/src/Mod/Fem/femsolver/calculix/tasks.py b/src/Mod/Fem/femsolver/calculix/tasks.py index a6c6c1018f..6cce64a458 100644 --- a/src/Mod/Fem/femsolver/calculix/tasks.py +++ b/src/Mod/Fem/femsolver/calculix/tasks.py @@ -51,7 +51,7 @@ _inputFileName = None class Check(run.Check): def run(self): - self.pushStatus("Checking analysis...\n") + self.pushStatus("Checking analysis member...\n") self.check_mesh_exists() # workaround use Calculix ccxtools pre checks @@ -73,13 +73,12 @@ class Prepare(run.Prepare): def run(self): global _inputFileName - self.pushStatus("Preparing input files...\n") - - mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0] # pre check done already + self.pushStatus("Preparing input...\n") # get mesh set data # TODO evaluate if it makes sense to add new task # between check and prepare to the solver frame work + mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0] # pre check done already meshdatagetter = meshsetsgetter.MeshSetsGetter( self.analysis, self.solver, @@ -88,7 +87,7 @@ class Prepare(run.Prepare): ) meshdatagetter.get_mesh_sets() - # write input file + # write solver input w = writer.FemInputWriterCcx( self.analysis, self.solver, @@ -100,9 +99,9 @@ class Prepare(run.Prepare): path = w.write_solver_input() # report to user if task succeeded if path != "" and os.path.isfile(path): - self.pushStatus("Write completed.") + self.pushStatus("Writing solver input completed.") else: - self.pushStatus("Writing CalculiX solver input file failed,") + self.pushStatus("Writing solver input failed.") self.fail() _inputFileName = os.path.splitext(os.path.basename(path))[0] @@ -112,7 +111,13 @@ class Solve(run.Solve): def run(self): self.pushStatus("Executing solver...\n") + # get solver binary + self.pushStatus("Get solver binary...\n") binary = settings.get_binary("Calculix") + if binary is None: + self.fail() # a print has been made in settings module + + # run solver self._process = subprocess.Popen( [binary, "-i", _inputFileName], cwd=self.directory, @@ -131,10 +136,6 @@ class Solve(run.Solve): class Results(run.Results): def run(self): - if not _inputFileName: - # TODO do not run solver - # do not try to read results in a smarter way than an Exception - raise Exception("Error on writing CalculiX input file.\n") prefs = FreeCAD.ParamGet( "User parameter:BaseApp/Preferences/Mod/Fem/General") if not prefs.GetBool("KeepResultsOnReRun", False): @@ -142,10 +143,12 @@ class Results(run.Results): self.load_results() def purge_results(self): - - # dat file will not be removed - # results from other solvers will be removed too - # the user should decide if purge should only delete the solver results or all results + self.pushStatus("Purge existing results...\n") + # TODO dat file will not be removed + # TODO implement a generic purge method + # TODO results from other solvers will be removed too + # the user should decide if purge should only + # delete this solver results or results from all solvers for m in membertools.get_member(self.analysis, "Fem::FemResultObject"): if m.Mesh and femutils.is_of_type(m.Mesh, "Fem::MeshResult"): self.analysis.Document.removeObject(m.Mesh.Name) @@ -153,10 +156,11 @@ class Results(run.Results): self.analysis.Document.recompute() def load_results(self): - self.load_results_ccxfrd() - self.load_results_ccxdat() + self.pushStatus("Import new results...\n") + self.load_ccxfrd_results() + self.load_ccxdat_results() - def load_results_ccxfrd(self): + def load_ccxfrd_results(self): frd_result_file = os.path.join( self.directory, _inputFileName + ".frd") if os.path.isfile(frd_result_file): @@ -164,18 +168,26 @@ class Results(run.Results): importCcxFrdResults.importFrd( frd_result_file, self.analysis, result_name_prefix) else: - raise Exception( - "FEM: No results found at {}!".format(frd_result_file)) + # TODO: use solver framework status message system + FreeCAD.Console.PrintError( + "FEM: No results found at {}!\n" + .format(frd_result_file) + ) + self.fail() - def load_results_ccxdat(self): + def load_ccxdat_results(self): dat_result_file = os.path.join( self.directory, _inputFileName + ".dat") if os.path.isfile(dat_result_file): mode_frequencies = importCcxDatResults.import_dat( dat_result_file, self.analysis) else: - raise Exception( - "FEM: No .dat results found at {}!".format(dat_result_file)) + # TODO: use solver framework status message system + FreeCAD.Console.PrintError( + "FEM: No results found at {}!\n" + .format(dat_result_file) + ) + self.fail() if mode_frequencies: for m in membertools.get_member(self.analysis, "Fem::FemResultObject"): if m.Eigenmode > 0: diff --git a/src/Mod/Fem/femsolver/calculix/write_step_output.py b/src/Mod/Fem/femsolver/calculix/write_step_output.py index 117cf15f44..d7ac4d3b6c 100644 --- a/src/Mod/Fem/femsolver/calculix/write_step_output.py +++ b/src/Mod/Fem/femsolver/calculix/write_step_output.py @@ -60,8 +60,9 @@ def write_step_output(f, ccxwriter): # reaction forces: freecadweb.org/tracker/view.php?id=2934 # some hint can be found in this topic: # https://forum.freecadweb.org/viewtopic.php?f=18&t=20664&start=10#p520642 - if ccxwriter.member.cons_fixed: + if ccxwriter.member.cons_fixed or ccxwriter.member.cons_displacement: f.write("** outputs --> dat file\n") + if ccxwriter.member.cons_fixed: # reaction forces for all Constraint fixed f.write("** reaction forces for Constraint fixed\n") for femobj in ccxwriter.member.cons_fixed: @@ -69,7 +70,16 @@ def write_step_output(f, ccxwriter): fix_obj_name = femobj["Object"].Name f.write("*NODE PRINT, NSET={}, TOTALS=ONLY\n".format(fix_obj_name)) f.write("RF\n") - # TODO: add Constraint Displacement if nodes are restrained + if ccxwriter.member.cons_displacement: + # reaction forces for Constraint displacement constraining translation + f.write("** reaction forces for Constraint displacement constraining translations\n") + for femobj in ccxwriter.member.cons_displacement: + if not femobj["Object"].xFree or not femobj["Object"].yFree or not femobj["Object"].zFree: + # femobj --> dict, FreeCAD document object is femobj["Object"] + disp_obj_name = femobj["Object"].Name + f.write("*NODE PRINT, NSET={}, TOTALS=ONLY\n".format(disp_obj_name)) + f.write("RF\n") + if ccxwriter.member.cons_fixed or ccxwriter.member.cons_displacement: f.write("\n") # there is no need to write all integration point results diff --git a/src/Mod/Fem/femsolver/mystran/add_con_fixed.py b/src/Mod/Fem/femsolver/mystran/add_con_fixed.py index 81be959d58..605c330d39 100644 --- a/src/Mod/Fem/femsolver/mystran/add_con_fixed.py +++ b/src/Mod/Fem/femsolver/mystran/add_con_fixed.py @@ -35,7 +35,7 @@ def add_con_fixed(f, model, mystran_writer): # spc1 card spc_ids = [] fixed_code = "# spc1 card, Defines a set of single-point constraints\n" - for i, femobj in enumerate(mystran_writer.fixed_objects): + for i, femobj in enumerate(mystran_writer.member.cons_fixed): conid = i + 2 # 1 will be the conid of the spcadd card spc_ids.append(conid) diff --git a/src/Mod/Fem/femsolver/mystran/add_con_force.py b/src/Mod/Fem/femsolver/mystran/add_con_force.py index db40356c04..152d7572cc 100644 --- a/src/Mod/Fem/femsolver/mystran/add_con_force.py +++ b/src/Mod/Fem/femsolver/mystran/add_con_force.py @@ -36,7 +36,7 @@ def add_con_force(f, model, mystran_writer): scale_factors = [] load_ids = [] force_code = "# force cards, mesh node loads\n" - for i, femobj in enumerate(mystran_writer.force_objects): + for i, femobj in enumerate(mystran_writer.member.cons_force): sid = i + 2 # 1 will be the id of the load card scale_factors.append(1.0) diff --git a/src/Mod/Fem/femsolver/mystran/tasks.py b/src/Mod/Fem/femsolver/mystran/tasks.py index 72c2560bd0..b1e0ce0f51 100644 --- a/src/Mod/Fem/femsolver/mystran/tasks.py +++ b/src/Mod/Fem/femsolver/mystran/tasks.py @@ -57,7 +57,7 @@ _inputFileName = None class Check(run.Check): def run(self): - self.pushStatus("Checking analysis...\n") + self.pushStatus("Checking analysis member...\n") self.check_mesh_exists() self.check_material_exists() self.check_material_single() # no multiple material @@ -70,13 +70,11 @@ class Prepare(run.Prepare): def run(self): global _inputFileName - self.pushStatus("Preparing input files...\n") - - mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0] # pre check done already + self.pushStatus("Preparing solver input...\n") # get mesh set data - # TODO evaluate if it makes sense to add new task - # between check and prepare to the solver frame work + # TODO see calculix tasks get mesh set data + mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0] # pre check done already meshdatagetter = meshsetsgetter.MeshSetsGetter( self.analysis, self.solver, @@ -85,44 +83,36 @@ class Prepare(run.Prepare): ) meshdatagetter.get_mesh_sets() - # write input file + # write solver input w = writer.FemInputWriterMystran( self.analysis, self.solver, mesh_obj, meshdatagetter.member, self.directory, - meshdatagetter.mat_geo_sets ) path = w.write_solver_input() # report to user if task succeeded if path != "": - self.pushStatus("Write completed!") + self.pushStatus("Writing solver input completed.") else: - self.pushStatus("Writing CalculiX input file failed!") + self.pushStatus("Writing solver input failed.") + self.fail() _inputFileName = os.path.splitext(os.path.basename(path))[0] class Solve(run.Solve): def run(self): - # print(_inputFileName) - if not _inputFileName: - # TODO do not run solver, do not try to read results in a smarter way than an Exception - raise Exception("Error on writing Mystran input file.\n") + self.pushStatus("Executing solver...\n") + infile = _inputFileName + ".bdf" - # TODO use solver framework status system - FreeCAD.Console.PrintMessage("Mystran: solver input file: {} \n\n".format(infile)) - - # get binary - self.pushStatus("Get solver...\n") + # get solver binary + self.pushStatus("Get solver binary...\n") binary = settings.get_binary("Mystran") - # use preferences editor to add a group Mystran and the prefs: - # "UseStandardMystranLocation" --> bool, set to False - # "mystranBinaryPath, string" --> the binary path if binary is None: - return # a print has been made in settings module + self.fail() # a print has been made in settings module # run solver self.pushStatus("Executing solver...\n") @@ -147,19 +137,19 @@ class Results(run.Results): if not prefs.GetBool("KeepResultsOnReRun", False): self.purge_results() if result_reading is True: - self.load_results() # ToDo in all solvers generischer name + self.load_results() def purge_results(self): + self.pushStatus("Purge existing results...\n") + # TODO see calculix result tasks for m in membertools.get_member(self.analysis, "Fem::FemResultObject"): if femutils.is_of_type(m.Mesh, "Fem::MeshResult"): self.analysis.Document.removeObject(m.Mesh.Name) self.analysis.Document.removeObject(m.Name) self.analysis.Document.recompute() - # deletes all results from any solver - # TODO: delete only the mystran results, fix in all solver def load_results(self): - self.pushStatus("Import results...\n") + self.pushStatus("Import new results...\n") neu_result_file = os.path.join(self.directory, _inputFileName + ".NEU") if os.path.isfile(neu_result_file): hfcMystranNeuIn.import_neu(neu_result_file) @@ -169,11 +159,11 @@ class Results(run.Results): self.analysis.addObject(o) break else: - # TODO: use solver framework error and status message system + # TODO: use solver framework status message system FreeCAD.Console.PrintError( - "FEM: No results found at {}!\n".format(neu_result_file) + "FEM: No results found at {}!\n" + .format(neu_result_file) ) - return - + self.fail() ## @} diff --git a/src/Mod/Fem/femsolver/run.py b/src/Mod/Fem/femsolver/run.py index 99fa849a70..5d35dccb0f 100644 --- a/src/Mod/Fem/femsolver/run.py +++ b/src/Mod/Fem/femsolver/run.py @@ -450,7 +450,15 @@ class Check(BaseTask): def check_material_single(self): objs = self.get_several_member("App::MaterialObjectPython") if len(objs) > 1: - self.report.error("Only one Material allowed for this solver.") + self.report.error("Only one Material is supported for this solver.") + self.fail() + return False + return True + + def check_geos_beamsection_no(self): + objs = self.get_several_member("Fem::ElementGeometry1D") + if len(objs) > 0: + self.report.error("Beamsections are not supported for this solver.") self.fail() return False return True @@ -458,7 +466,15 @@ class Check(BaseTask): def check_geos_beamsection_single(self): objs = self.get_several_member("Fem::ElementGeometry1D") if len(objs) > 1: - self.report.error("Only one beamsection allowed for this solver.") + self.report.error("Only one beamsection is supported for this solver.") + self.fail() + return False + return True + + def check_geos_shellthickness_no(self): + objs = self.get_several_member("Fem::ElementGeometry2D") + if len(objs) > 0: + self.report.error("Shellsections are not supported for this solver.") self.fail() return False return True @@ -466,7 +482,7 @@ class Check(BaseTask): def check_geos_shellthickness_single(self): objs = self.get_several_member("Fem::ElementGeometry2D") if len(objs) > 1: - self.report.error("Only one shellthickness allowed for this solver.") + self.report.error("Only one shellthickness is supported for this solver.") self.fail() return False return True @@ -476,8 +492,8 @@ class Check(BaseTask): shellth_obj = self.get_several_member("Fem::ElementGeometry2D") if len(beamsec_obj) > 0 and len(shellth_obj) > 0: self.report.error( - "Either beamsection or shellthickness objects are allowed for this solver, " - "but not both in one analysis." + "Either beamsection or shellthickness objects are " + "supported for this solver, but not both in one analysis." ) self.fail() return False diff --git a/src/Mod/Fem/femsolver/solver_taskpanel.py b/src/Mod/Fem/femsolver/solver_taskpanel.py index 84f60437b2..a399ab843d 100644 --- a/src/Mod/Fem/femsolver/solver_taskpanel.py +++ b/src/Mod/Fem/femsolver/solver_taskpanel.py @@ -76,9 +76,9 @@ class ControlTaskPanel(QtCore.QObject): # as soon as the widget of the task panel gets destroyed. self.form.destroyed.connect(self._disconnectMachine) self.form.destroyed.connect(self._timer.stop) - self.form.destroyed.connect( - lambda: self.machineStatusChanged.disconnect( - self.form.appendStatus)) + # self.form.destroyed.connect( + # lambda: self.machineStatusChanged.disconnect( + # self.form.appendStatus)) # Connect all proxy signals. self.machineStarted.connect(self._timer.start) diff --git a/src/Mod/Fem/femsolver/z88/tasks.py b/src/Mod/Fem/femsolver/z88/tasks.py index 6a626fe662..6568b5c4a8 100644 --- a/src/Mod/Fem/femsolver/z88/tasks.py +++ b/src/Mod/Fem/femsolver/z88/tasks.py @@ -38,6 +38,7 @@ from . import writer from .. import run from .. import settings from feminout import importZ88O2Results +from femmesh import meshsetsgetter from femtools import femutils from femtools import membertools @@ -45,7 +46,7 @@ from femtools import membertools class Check(run.Check): def run(self): - self.pushStatus("Checking analysis...\n") + self.pushStatus("Checking analysis member...\n") self.check_mesh_exists() self.check_material_exists() self.check_material_single() # no multiple material @@ -57,44 +58,69 @@ class Check(run.Check): class Prepare(run.Prepare): def run(self): - self.pushStatus("Preparing input files...\n") + self.pushStatus("Preparing solver input...\n") + + # get mesh set data + # TODO see calculix tasks get mesh set data + mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0] # pre check done already + meshdatagetter = meshsetsgetter.MeshSetsGetter( + self.analysis, + self.solver, + mesh_obj, + membertools.AnalysisMember(self.analysis), + ) + meshdatagetter.get_mesh_sets() + + # write solver input w = writer.FemInputWriterZ88( self.analysis, self.solver, - membertools.get_mesh_to_solve(self.analysis)[0], # pre check has been done already - membertools.AnalysisMember(self.analysis), + mesh_obj, + meshdatagetter.member, self.directory ) path = w.write_solver_input() # report to user if task succeeded if path is not None: - self.pushStatus("Write completed!") + self.pushStatus("Writing solver input completed.") else: - self.pushStatus("Writing Z88 solver input files failed!") + self.pushStatus("Writing solver input failed.") self.fail() # print(path) + # z88 does not pass a main input file to the solver + # it passes the directory all input files are in + # not _inputFileName is needed class Solve(run.Solve): def run(self): - # AFAIK: z88r needs to be run twice, once in test mode and once in real solve mode - # the subprocess was just copied, it seems to work :-) - # TODO: search out for "Vektor GS" and "Vektor KOI" and print values - # may be compared with the used ones self.pushStatus("Executing test solver...\n") + + # get solver binary + self.pushStatus("Get solver binary...\n") binary = settings.get_binary("Z88") + if binary is None: + self.fail() # a print has been made in settings module + + # run solver test mode + # AFAIK: z88r needs to be run twice + # once in test mode and once in real solve mode + # the subprocess was just copied, it works :-) + # TODO: search out for "Vektor GS" and "Vektor KOI" and print values + # may be compare with the used ones + self.pushStatus("Executing solver in test mode...\n") self._process = subprocess.Popen( [binary, "-t", "-choly"], cwd=self.directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.signalAbort.add(self._process.terminate) - # output = self._observeSolver(self._process) self._process.communicate() self.signalAbort.remove(self._process.terminate) - self.pushStatus("Executing real solver...\n") + # run solver real mode + self.pushStatus("Executing solver in real mode...\n") binary = settings.get_binary("Z88") self._process = subprocess.Popen( [binary, "-c", "-choly"], @@ -102,12 +128,10 @@ class Solve(run.Solve): stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.signalAbort.add(self._process.terminate) - # output = self._observeSolver(self._process) self._process.communicate() self.signalAbort.remove(self._process.terminate) - # if not self.aborted: - # self._updateOutput(output) - # del output # get flake8 quiet + + # for chatching the output see CalculiX or Elmer solver tasks module class Results(run.Results): @@ -120,6 +144,8 @@ class Results(run.Results): self.load_results() def purge_results(self): + self.pushStatus("Purge existing results...\n") + # TODO see calculix result tasks for m in membertools.get_member(self.analysis, "Fem::FemResultObject"): if femutils.is_of_type(m.Mesh, "Fem::MeshResult"): self.analysis.Document.removeObject(m.Mesh.Name) @@ -127,6 +153,7 @@ class Results(run.Results): self.analysis.Document.recompute() def load_results(self): + self.pushStatus("Import new results...\n") # displacements from z88o2 file disp_result_file = os.path.join( self.directory, "z88o2.txt") @@ -135,7 +162,11 @@ class Results(run.Results): importZ88O2Results.import_z88_disp( disp_result_file, self.analysis, result_name_prefix) else: - raise Exception( - "FEM: No results found at {}!".format(disp_result_file)) + # TODO: use solver framework status message system + FreeCAD.Console.PrintError( + "FEM: No results found at {}!\n" + .format(disp_result_file) + ) + self.fail() ## @} diff --git a/src/Mod/Fem/femsolver/z88/writer.py b/src/Mod/Fem/femsolver/z88/writer.py index 1c796fe728..1824e26c88 100644 --- a/src/Mod/Fem/femsolver/z88/writer.py +++ b/src/Mod/Fem/femsolver/z88/writer.py @@ -29,6 +29,7 @@ __url__ = "https://www.freecadweb.org" # @{ import time +from os.path import join import FreeCAD @@ -54,23 +55,26 @@ class FemInputWriterZ88(writerbase.FemInputWriter): member, dir_name ) - from os.path import join self.file_name = join(self.dir_name, "z88") - FreeCAD.Console.PrintLog( - "FemInputWriterZ88 --> self.dir_name --> " + self.dir_name + "\n" - ) - FreeCAD.Console.PrintMessage( - "FemInputWriterZ88 --> self.file_name --> " + self.file_name + "\n" - ) + # ******************************************************************************************** + # write solver input def write_solver_input(self): timestart = time.process_time() - FreeCAD.Console.PrintMessage("Write z88 input files to: {}\n".format(self.dir_name)) - if not self.femnodes_mesh: - self.femnodes_mesh = self.femmesh.Nodes - if not self.femelement_table: - self.femelement_table = meshtools.get_femelement_table(self.femmesh) - self.element_count = len(self.femelement_table) + FreeCAD.Console.PrintMessage("\n") # because of time print in separate line + FreeCAD.Console.PrintMessage("Z88 solver input writing...\n") + FreeCAD.Console.PrintLog( + "FemInputWriterZ88 --> self.dir_name --> {}\n" + .format(self.dir_name) + ) + FreeCAD.Console.PrintMessage( + "FemInputWriterZ88 --> self.file_name --> {}\n" + .format(self.file_name) + ) + FreeCAD.Console.PrintMessage( + "Write z88 input files to: {}\n" + .format(self.dir_name) + ) control = self.set_z88_elparam() if control is False: return None @@ -86,9 +90,11 @@ class FemInputWriterZ88(writerbase.FemInputWriter): "Writing time input file: {} seconds" .format(round((time.process_time() - timestart), 2)) ) - FreeCAD.Console.PrintMessage(writing_time_string + " \n\n") + FreeCAD.Console.PrintMessage( + "{}\n\n".format(writing_time_string)) return self.dir_name + # ******************************************************************************************** def set_z88_elparam(self): # TODO: param should be moved to the solver object like the known analysis z8804 = {"INTORD": "0", "INTOS": "0", "IHFLAG": "0", "ISFLAG": "1"} # seg2 --> stab4 @@ -114,7 +120,13 @@ class FemInputWriterZ88(writerbase.FemInputWriter): FreeCAD.Console.PrintMessage("\n") return True + # ******************************************************************************************** def write_z88_mesh(self): + if not self.femnodes_mesh: + self.femnodes_mesh = self.femmesh.Nodes + if not self.femelement_table: + self.femelement_table = meshtools.get_femelement_table(self.femmesh) + self.element_count = len(self.femelement_table) mesh_file_path = self.file_name + "i1.txt" f = open(mesh_file_path, "w") importZ88Mesh.write_z88_mesh_to_file( @@ -125,25 +137,22 @@ class FemInputWriterZ88(writerbase.FemInputWriter): ) f.close() + # ******************************************************************************************** def write_z88_constraints(self): constraints_data = [] # will be a list of tuple for better sorting # fixed constraints - # get nodes - self.get_constraints_fixed_nodes() # write nodes to constraints_data (different from writing to file in ccxInpWriter - for femobj in self.fixed_objects: + for femobj in self.member.cons_fixed: for n in femobj["Nodes"]: - constraints_data.append((n, str(n) + " 1 2 0\n")) - constraints_data.append((n, str(n) + " 2 2 0\n")) - constraints_data.append((n, str(n) + " 3 2 0\n")) + constraints_data.append((n, "{} 1 2 0\n".format(n))) + constraints_data.append((n, "{} 2 2 0\n".format(n))) + constraints_data.append((n, "{} 3 2 0\n".format(n))) # forces constraints - # check shape type of reference shape and get node loads - self.get_constraints_force_nodeloads() # write node loads to constraints_data # a bit different from writing to file for ccxInpWriter - for femobj in self.force_objects: + for femobj in self.member.cons_force: # femobj --> dict, FreeCAD document object is femobj["Object"] direction_vec = femobj["Object"].DirectionVector for ref_shape in femobj["NodeLoadTable"]: @@ -151,13 +160,13 @@ class FemInputWriterZ88(writerbase.FemInputWriter): node_load = ref_shape[1][n] if (direction_vec.x != 0.0): v1 = direction_vec.x * node_load - constraints_data.append((n, str(n) + " 1 1 " + str(v1) + "\n")) + constraints_data.append((n, "{} 1 1 {}\n".format(n, v1))) if (direction_vec.y != 0.0): v2 = direction_vec.y * node_load - constraints_data.append((n, str(n) + " 2 1 " + str(v2) + "\n")) + constraints_data.append((n, "{} 2 1 {}\n".format(n, v2))) if (direction_vec.z != 0.0): v3 = direction_vec.z * node_load - constraints_data.append((n, str(n) + " 3 1 " + str(v3) + "\n")) + constraints_data.append((n, "{} 3 1 {}\n".format(n, v3))) # write constraints_data to file constraints_file_path = self.file_name + "i2.txt" @@ -167,6 +176,7 @@ class FemInputWriterZ88(writerbase.FemInputWriter): f.write(c[1]) f.close() + # ******************************************************************************************** def write_z88_face_loads(self): # not yet supported face_load_file_path = self.file_name + "i5.txt" @@ -175,16 +185,17 @@ class FemInputWriterZ88(writerbase.FemInputWriter): f.write("\n") f.close() + # ******************************************************************************************** def write_z88_materials(self): - mat_obj = self.material_objects[0]["Object"] + mat_obj = self.member.mats_linear[0]["Object"] material_data_file_name = "51.txt" materials_file_path = self.file_name + "mat.txt" fms = open(materials_file_path, "w") fms.write("1\n") - fms.write("1 " + str(self.element_count) + " " + material_data_file_name) + fms.write("1 {} {}".format(self.element_count, material_data_file_name)) fms.write("\n") fms.close() - material_data_file_path = self.dir_name + "/" + material_data_file_name + material_data_file_path = join(self.dir_name, material_data_file_name) fmd = open(material_data_file_path, "w") YM = FreeCAD.Units.Quantity(mat_obj.Material["YoungsModulus"]) YM_in_MPa = YM.getValueAs("MPa") @@ -193,11 +204,12 @@ class FemInputWriterZ88(writerbase.FemInputWriter): fmd.write("\n") fmd.close() + # ******************************************************************************************** def write_z88_elements_properties(self): element_properties_file_path = self.file_name + "elp.txt" elements_data = [] if meshtools.is_edge_femmesh(self.femmesh): - beam_obj = self.beamsection_objects[0]["Object"] + beam_obj = self.member.geos_beamsection[0]["Object"] area = 0 if beam_obj.SectionType == "Rectangular": width = beam_obj.RectWidth.getValueAs("mm").Value @@ -224,13 +236,17 @@ class FemInputWriterZ88(writerbase.FemInputWriter): "Be aware, only trusses are supported for edge meshes!\n" ) elif meshtools.is_face_femmesh(self.femmesh): - thick_obj = self.shellthickness_objects[0]["Object"] - thickness = str(thick_obj.Thickness.getValueAs("mm")) + thick_obj = self.member.geos_shellthickness[0]["Object"] + thickness = thick_obj.Thickness.getValueAs("mm").Value elements_data.append( - "1 " + str(self.element_count) + " " + thickness + " 0 0 0 0 0 0 " + "1 {} {} 0 0 0 0 0 0 " + .format(self.element_count, thickness) ) elif meshtools.is_solid_femmesh(self.femmesh): - elements_data.append("1 " + str(self.element_count) + " 0 0 0 0 0 0 0") + elements_data.append( + "1 {} 0 0 0 0 0 0 0" + .format(self.element_count) + ) else: FreeCAD.Console.PrintError("Error!\n") f = open(element_properties_file_path, "w") @@ -240,6 +256,7 @@ class FemInputWriterZ88(writerbase.FemInputWriter): f.write("\n") f.close() + # ******************************************************************************************** def write_z88_integration_properties(self): integration_data = [] integration_data.append("1 {} {} {}".format( @@ -249,12 +266,13 @@ class FemInputWriterZ88(writerbase.FemInputWriter): )) integration_properties_file_path = self.file_name + "int.txt" f = open(integration_properties_file_path, "w") - f.write(str(len(integration_data)) + "\n") + f.write("{}\n".format(len(integration_data))) for i in integration_data: f.write(i) f.write("\n") f.close() + # ******************************************************************************************** def write_z88_solver_parameter(self): global z88_man_template z88_man_template = z88_man_template.replace( @@ -268,13 +286,14 @@ class FemInputWriterZ88(writerbase.FemInputWriter): f.write(z88_man_template) f.close() + # ******************************************************************************************** def write_z88_memory_parameter(self): # self.z88_param_maxgs = 6000000 self.z88_param_maxgs = 50000000 # vierkantrohr global z88_dyn_template z88_dyn_template = z88_dyn_template.replace( "$z88_param_maxgs", - str(self.z88_param_maxgs) + "{}".format(self.z88_param_maxgs) ) solver_parameter_file_path = self.file_name + ".dyn" f = open(solver_parameter_file_path, "w") diff --git a/src/Mod/Fem/femtaskpanels/task_material_common.py b/src/Mod/Fem/femtaskpanels/task_material_common.py index e4cac0bf6b..b25b376fb3 100644 --- a/src/Mod/Fem/femtaskpanels/task_material_common.py +++ b/src/Mod/Fem/femtaskpanels/task_material_common.py @@ -545,8 +545,10 @@ class _TaskPanel: old_value = Units.Quantity(self.material[matProperty]).getValueAs(qUnit) else: # for example PoissonRatio - value = float(inputfield_text) - old_value = float(self.material[matProperty]) + value = Units.Quantity(inputfield_text).Value + old_value = Units.Quantity(self.material[matProperty]).Value + # value = float(inputfield_text) # this fails on locale with komma + # https://forum.freecadweb.org/viewtopic.php?f=18&t=56912&p=523313#p523313 if value: if not (1 - variation < float(old_value) / value < 1 + variation): material = self.material diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp index 0a75d6db51..c3d571db8c 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp @@ -407,6 +407,9 @@ S, E ** reaction forces for Constraint fixed *NODE PRINT, NSET=ConstraintFixed, TOTALS=ONLY RF +** reaction forces for Constraint displacement constraining translation +*NODE PRINT, NSET=ConstraintDisplacmentPrescribed, TOTALS=ONLY +RF *********************************************************** diff --git a/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp b/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp index d176b65293..3203aa1474 100644 --- a/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp +++ b/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp @@ -17062,6 +17062,13 @@ Fix_YZ,3 U *EL FILE S, E +** outputs --> dat file +** reaction forces for Constraint displacement constraining translation +*NODE PRINT, NSET=Fix_XYZ, TOTALS=ONLY +RF +*NODE PRINT, NSET=Fix_YZ, TOTALS=ONLY +RF + *********************************************************** *END STEP diff --git a/src/Mod/OpenSCAD/importCSG.py b/src/Mod/OpenSCAD/importCSG.py index 8b82d6d8b9..98197cec53 100644 --- a/src/Mod/OpenSCAD/importCSG.py +++ b/src/Mod/OpenSCAD/importCSG.py @@ -66,19 +66,22 @@ def shallHide(subject): return True return False -def setColorRecursively(obj,color,transp): - if(obj.TypeId=="Part::Fuse" or obj.TypeId=="Part::MultiFuse"): - for currentObject in obj.OutList: - if (currentObject.TypeId=="Part::Fuse" or currentObject.TypeId=="Part::MultiFuse"): - setColorRecursively(currentObject,color,transp) - else: - print("Fixing up colors for: "+str(currentObject.FullName)) - if(currentObject not in hassetcolor): - currentObject.ViewObject.ShapeColor=color - currentObject.ViewObject.Transparency=transp - setColorRecursively(currentObject,color,transp) - else: - setColorRecursively(currentObject,color,transp) +def setColorRecursively(obj, color, transp): + ''' + For some reason a part made by cutting or fusing other parts do not have a color + unless its constituents are also colored. This code sets colors for those + constituents unless already set elsewhere. + ''' + obj.ViewObject.ShapeColor = color + obj.ViewObject.Transparency = transp + # Add any other relevant features to this list + boolean_features = ["Part::Fuse", "Part::MultiFuse", "Part::Cut", + "Part::Common", "Part::MultiCommon"] + if obj.TypeId in boolean_features: + for currentObject in obj.OutList: + print(f"Fixing up colors for: {currentObject.FullName}") + if currentObject not in hassetcolor: + setColorRecursively(currentObject, color, transp) def fixVisibility(): for obj in FreeCAD.ActiveDocument.Objects: @@ -555,22 +558,7 @@ def p_color_action(p): if "Group" in obj.FullName: obj.ViewObject.Visibility=False alreadyhidden.append(obj) - if(obj.TypeId=="Part::Fuse" or obj.TypeId=="Part::MultiFuse"): - for currentObject in obj.OutList: - if (currentObject.TypeId=="Part::Fuse" or currentObject.TypeId=="Part::MultiFuse"): - setColorRecursively(currentObject,color,transp) - if(currentObject not in hassetcolor): - currentObject.ViewObject.ShapeColor=color - currentObject.ViewObject.Transparency=transp - setColorRecursively(currentObject,color,transp) - else: - setColorRecursively(currentObject,color,transp) - else: - obj.ViewObject.ShapeColor =color - obj.ViewObject.Transparency = transp - else: - obj.ViewObject.ShapeColor =color - obj.ViewObject.Transparency = transp + setColorRecursively(obj, color, transp) hassetcolor.append(obj) p[0] = p[6] diff --git a/src/Mod/Part/App/BSplineSurfacePyImp.cpp b/src/Mod/Part/App/BSplineSurfacePyImp.cpp index 9b368f476f..6c0bc66281 100644 --- a/src/Mod/Part/App/BSplineSurfacePyImp.cpp +++ b/src/Mod/Part/App/BSplineSurfacePyImp.cpp @@ -447,10 +447,10 @@ PyObject* BSplineSurfacePy::setVKnot(PyObject *args) Handle(Geom_BSplineSurface) surf = Handle(Geom_BSplineSurface)::DownCast (getGeometryPtr()->handle()); if (M == -1) { - surf->SetUKnot(Index, K); + surf->SetVKnot(Index, K); } else { - surf->SetUKnot(Index, K, M); + surf->SetVKnot(Index, K, M); } Py_Return; diff --git a/src/Mod/Part/Gui/ViewProviderExt.cpp b/src/Mod/Part/Gui/ViewProviderExt.cpp index 5dcfd97135..ba197f1811 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.cpp +++ b/src/Mod/Part/Gui/ViewProviderExt.cpp @@ -236,11 +236,18 @@ ViewProviderPartExt::ViewProviderPartExt() forceUpdateCount = 0; NormalsFromUV = true; + // get default line color unsigned long lcol = Gui::ViewParams::instance()->getDefaultShapeLineColor(); // dark grey (25,25,25) - float r,g,b; - r = ((lcol >> 24) & 0xff) / 255.0; g = ((lcol >> 16) & 0xff) / 255.0; b = ((lcol >> 8) & 0xff) / 255.0; + float lr,lg,lb; + lr = ((lcol >> 24) & 0xff) / 255.0; lg = ((lcol >> 16) & 0xff) / 255.0; lb = ((lcol >> 8) & 0xff) / 255.0; + // get default vertex color + unsigned long vcol = Gui::ViewParams::instance()->getDefaultShapeVertexColor(); + float vr,vg,vb; + vr = ((vcol >> 24) & 0xff) / 255.0; vg = ((vcol >> 16) & 0xff) / 255.0; vb = ((vcol >> 8) & 0xff) / 255.0; int lwidth = Gui::ViewParams::instance()->getDefaultShapeLineWidth(); int psize = Gui::ViewParams::instance()->getDefaultShapePointSize(); + + ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/Mod/Part"); @@ -256,17 +263,26 @@ ViewProviderPartExt::ViewProviderPartExt() static const char *osgroup = "Object Style"; - App::Material mat; - mat.ambientColor.set(0.2f,0.2f,0.2f); - mat.diffuseColor.set(r,g,b); - mat.specularColor.set(0.0f,0.0f,0.0f); - mat.emissiveColor.set(0.0f,0.0f,0.0f); - mat.shininess = 1.0f; - mat.transparency = 0.0f; - ADD_PROPERTY_TYPE(LineMaterial,(mat), osgroup, App::Prop_None, "Object line material."); - ADD_PROPERTY_TYPE(PointMaterial,(mat), osgroup, App::Prop_None, "Object point material."); - ADD_PROPERTY_TYPE(LineColor, (mat.diffuseColor), osgroup, App::Prop_None, "Set object line color."); - ADD_PROPERTY_TYPE(PointColor, (mat.diffuseColor), osgroup, App::Prop_None, "Set object point color"); + App::Material lmat; + lmat.ambientColor.set(0.2f,0.2f,0.2f); + lmat.diffuseColor.set(lr,lg,lb); + lmat.specularColor.set(0.0f,0.0f,0.0f); + lmat.emissiveColor.set(0.0f,0.0f,0.0f); + lmat.shininess = 1.0f; + lmat.transparency = 0.0f; + + App::Material vmat; + vmat.ambientColor.set(0.2f,0.2f,0.2f); + vmat.diffuseColor.set(vr,vg,vb); + vmat.specularColor.set(0.0f,0.0f,0.0f); + vmat.emissiveColor.set(0.0f,0.0f,0.0f); + vmat.shininess = 1.0f; + vmat.transparency = 0.0f; + + ADD_PROPERTY_TYPE(LineMaterial,(lmat), osgroup, App::Prop_None, "Object line material."); + ADD_PROPERTY_TYPE(PointMaterial,(vmat), osgroup, App::Prop_None, "Object point material."); + ADD_PROPERTY_TYPE(LineColor, (lmat.diffuseColor), osgroup, App::Prop_None, "Set object line color."); + ADD_PROPERTY_TYPE(PointColor, (vmat.diffuseColor), osgroup, App::Prop_None, "Set object point color"); ADD_PROPERTY_TYPE(PointColorArray, (PointColor.getValue()), osgroup, App::Prop_None, "Object point color array."); ADD_PROPERTY_TYPE(DiffuseColor,(ShapeColor.getValue()), osgroup, App::Prop_None, "Object diffuse color."); ADD_PROPERTY_TYPE(LineColorArray,(LineColor.getValue()), osgroup, App::Prop_None, "Object line color array."); diff --git a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp index 9563e773cf..64f0ac1b58 100644 --- a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp @@ -691,7 +691,7 @@ bool TaskBoxPrimitives::setPrimitive(App::DocumentObject *obj) return false; } - Base::QuantityFormat format(Base::QuantityFormat::Default, Base::UnitsApi::getDecimals()); + Base::QuantityFormat format(Base::QuantityFormat::Fixed, Base::UnitsApi::getDecimals()); switch(ui->widgetStack->currentIndex()) { case 1: // box cmd = QString::fromLatin1( diff --git a/src/Mod/PartDesign/Scripts/FilletArc.py b/src/Mod/PartDesign/Scripts/FilletArc.py index c59646c8b6..b710dde829 100644 --- a/src/Mod/PartDesign/Scripts/FilletArc.py +++ b/src/Mod/PartDesign/Scripts/FilletArc.py @@ -13,108 +13,120 @@ import math # 3d vector class class Vector: - def __init__(self,x,y,z): - self.x=x - self.y=y - self.z=z - def add(self,vec): - return Vector(self.x+vec.x,self.y+vec.y,self.z+vec.z) - def sub(self,vec): - return Vector(self.x-vec.x,self.y-vec.y,self.z-vec.z) - def dot(self,vec): - return self.x*vec.x+self.y*vec.y+self.z*vec.z - def mult(self,s): - return Vector(self.x*s,self.y*s,self.z*s) - def cross(self,vec): - return Vector( - self.y * vec.z - self.z * vec.y, - self.z * vec.x - self.x * vec.z, - self.x * vec.y - self.y * vec.x) - def length(self): - return math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) - def norm(self): - l = self.length() - if l > 0: - self.x /= l - self.y /= l - self.z /= l - def __repr__(self): - return "(%f,%f,%f)" % (self.x,self.y,self.z) + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + def add(self, vec): + return Vector(self.x+vec.x, self.y+vec.y, self.z+vec.z) + + def sub(self, vec): + return Vector(self.x-vec.x, self.y-vec.y, self.z-vec.z) + + def dot(self, vec): + return self.x*vec.x+self.y*vec.y+self.z*vec.z + + def mult(self, s): + return Vector(self.x*s, self.y*s, self.z*s) + + def cross(self,vec): + return Vector( + self.y * vec.z - self.z * vec.y, + self.z * vec.x - self.x * vec.z, + self.x * vec.y - self.y * vec.x) + + def length(self): + return math.sqrt(self.x*self.x+self.y*self.y+self.z*self.z) + + def norm(self): + l = self.length() + if l > 0: + self.x /= l + self.y /= l + self.z /= l + + def __repr__(self): + return "(%f,%f,%f)" % (self.x, self.y, self.z) + # A signum function def sgn(val): - if val > 0: - return 1 - elif val < 0: - return -1 - else: - return 0 + if val > 0: + return 1 + elif val < 0: + return -1 + else: + return 0 + # M1 ... is the center of the arc # P ... is the end point of the arc and start point of the line # Q .. is a second point on the line -# N ... is the normal of the plane where the arc and the line lie on, usually N=(0,0,1) +# N ... is the normal of the plane where the arc and the line lie on, usually N=(0,0,1) # r2 ... the fillet radius -# ccw ... counter-clockwise means which part of the arc is given. ccw must be either True or False +# ccw ... counter-clockwise means which part of the arc is given. ccw must be either True or False + + def makeFilletArc(M1,P,Q,N,r2,ccw): - u = Q.sub(P) - v = P.sub(M1) - if ccw: - b = u.cross(N) - else: - b = N.cross(u) - b.norm() - - uu = u.dot(u) - uv = u.dot(v) - r1 = v.length() + u = Q.sub(P) + v = P.sub(M1) + if ccw: + b = u.cross(N) + else: + b = N.cross(u) + b.norm() + + uu = u.dot(u) + uv = u.dot(v) + r1 = v.length() + + # distinguish between internal and external fillets + r2 *= sgn(uv) + + cc = 2.0 * r2 * (b.dot(v)-r1) + dd = uv * uv - uu * cc + if dd < 0: + raise RuntimeError("Unable to calculate intersection points") + t1 = (-uv + math.sqrt(dd)) / uu + t2 = (-uv - math.sqrt(dd)) / uu + + if (abs(t1) < abs(t2)): + t = t1 + else: + t = t2 + + br2 = b.mult(r2) + print(br2) + ut = u.mult(t) + print(ut) + M2 = P.add(ut).add(br2) + S1 = M1.mult(r2/(r1+r2)).add(M2.mult(r1/(r1+r2))) + S2 = M2.sub(br2) + + return (S1, S2, M2) - # distinguish between internal and external fillets - r2 *= sgn(uv); - cc = 2.0 * r2 * (b.dot(v)-r1) - dd = uv * uv - uu * cc - if dd < 0: - raise RuntimeError("Unable to calculate intersection points") - t1 = (-uv + math.sqrt(dd)) / uu - t2 = (-uv - math.sqrt(dd)) / uu - - if (abs(t1) < abs(t2)): - t = t1 - else: - t = t2 - - br2 = b.mult(r2) - print(br2) - ut = u.mult(t) - print(ut) - M2 = P.add(ut).add(br2) - S1 = M1.mult(r2/(r1+r2)).add(M2.mult(r1/(r1+r2))) - S2 = M2.sub(br2) - - return (S1,S2,M2) - - def test(): - from FreeCAD import Base - import Part + from FreeCAD import Base + import Part - P1=Base.Vector(1,-5,0) - P2=Base.Vector(-5,2,0) - P3=Base.Vector(1,5,0) - #Q=Base.Vector(5,10,0) - #Q=Base.Vector(5,11,0) - Q=Base.Vector(5,0,0) - r2=3.0 - axis=Base.Vector(0,0,1) - ccw=False + P1 = Base.Vector(1, -5, 0) + P2 = Base.Vector(-5, 2, 0) + P3 = Base.Vector(1, 5, 0) + # Q = Base.Vector(5, 10, 0) + # Q = Base.Vector(5, 11, 0) + Q = Base.Vector(5, 0, 0) + r2 = 3.0 + axis = Base.Vector(0, 0, 1) + ccw = False - arc=Part.ArcOfCircle(P1,P2,P3) - C=arc.Center - Part.show(Part.makeLine(P3,Q)) - Part.show(arc.toShape()) + arc = Part.ArcOfCircle(P1, P2, P3) + C = arc.Center + Part.show(Part.makeLine(P3, Q)) + Part.show(arc.toShape()) - (S1,S2,M2) = makeArc(Vector(C.x,C.y,C.z),Vector(P3.x,P3.y,P3.z),Vector(Q.x,Q.y,Q.z),Vector(axis.x,axis.y,axis.z),r2,ccw) - circle=Part.Circle(Base.Vector(M2.x,M2.y,M2.z), Base.Vector(0,0,1), math.fabs(r2)) - Part.show(circle.toShape()) + (S1, S2, M2) = makeArc(Vector(C.x,C.y,C.z), Vector(P3.x,P3.y,P3.z), Vector(Q.x, Q.y, Q.z), Vector(axis.x, axis.y, axis.z), r2, ccw) + circle = Part.Circle(Base.Vector(M2.x, M2.y, M2.z), Base.Vector(0, 0, 1), math.fabs(r2)) + Part.show(circle.toShape()) diff --git a/src/Mod/PartDesign/Scripts/Gear.py b/src/Mod/PartDesign/Scripts/Gear.py index 75f617e0cd..508bd6081f 100644 --- a/src/Mod/PartDesign/Scripts/Gear.py +++ b/src/Mod/PartDesign/Scripts/Gear.py @@ -1,12 +1,20 @@ -#Involute Gears Generation Script -#by Marcin Wanczyk (dj_who) -#(c) 2011 LGPL +# Involute Gears Generation Script +# by Marcin Wanczyk (dj_who) +# (c) 2011 LGPL -import FreeCAD, FreeCADGui, Part, Draft, math, MeshPart, Mesh -from PySide import QtGui,QtCore -App=FreeCAD -Gui=FreeCADGui +import FreeCAD +import FreeCADGui +import Part +import Draft +import MeshPart +import Mesh +import math +from PySide import QtGui, QtCore + +App = FreeCAD +Gui = FreeCADGui + def proceed(): try: @@ -15,150 +23,150 @@ def proceed(): hide() QtGui.QApplication.restoreOverrideCursor() -def compute(): + +def compute(): QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) if FreeCAD.ActiveDocument is None: FreeCAD.newDocument("Gear") - oldDocumentObjects=App.ActiveDocument.Objects + oldDocumentObjects = App.ActiveDocument.Objects try: - N = int(l1.text()) + N = int(l1.text()) p = float(l2.text()) alfa = int(l3.text()) - y = float(l4.text()) #standard value y<1 for gear drives y>1 for Gear pumps - m=p/math.pi #standard value 0.06, 0.12, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 60 (polish norm) - c = float(l5.text())*m #standard value 0,1*m - 0,3*m - j = float(l6.text())*m #standard value 0,015 - 0,04*m - width = float(l7.text()) #gear width + y = float(l4.text()) # standard value y<1 for gear drives y>1 for Gear pumps + m = p/math.pi # standard value 0.06, 0.12, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 60 (polish norm) + c = float(l5.text())*m # standard value 0,1*m - 0,3*m + j = float(l6.text())*m # standard value 0,015 - 0,04*m + width = float(l7.text()) # gear width except ValueError: FreeCAD.Console.PrintError("Wrong input! Only numbers allowed...\n") - - - #tooth height - h=2*y*m+c - #pitch diameter - d=N*m - #root diameter - df=d - 2*y*m - 2*c #df=d-2hf where and hf=y*m+c + # tooth height + h = 2*y*m+c - #addendum diameter - da=d + 2*y*m #da=d+2ha where ha=y*m + # pitch diameter + d = N*m - #base diameter for involute - db=d * math.cos(math.radians(alfa)) + # root diameter + df = d - 2*y*m - 2*c # df=d-2hf where and hf=y*m+c + + # addendum diameter + da = d + 2*y*m # da=d+2ha where ha=y*m + + # base diameter for involute + db = d * math.cos(math.radians(alfa)) #Base circle - baseCircle=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","BaseCircle") + baseCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "BaseCircle") Draft._Circle(baseCircle) Draft._ViewProviderDraft(baseCircle.ViewObject) baseCircle.Radius = db/2 - baseCircle.FirstAngle=0.0 - baseCircle.LastAngle=0.0 - - #Root circle - rootCircle=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","RootCircle") + baseCircle.FirstAngle = 0.0 + baseCircle.LastAngle = 0.0 + + # Root circle + rootCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "RootCircle") Draft._Circle(rootCircle) Draft._ViewProviderDraft(rootCircle.ViewObject) rootCircle.Radius = df/2 - rootCircle.FirstAngle=0.0 - rootCircle.LastAngle=0.0 + rootCircle.FirstAngle = 0.0 + rootCircle.LastAngle = 0.0 - #Addendum circle - addendumCircle=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","AddendumCircle") + # Addendum circle + addendumCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "AddendumCircle") Draft._Circle(addendumCircle) Draft._ViewProviderDraft(addendumCircle.ViewObject) addendumCircle.Radius = da/2 - addendumCircle.FirstAngle=0.0 - addendumCircle.LastAngle=0.0 + addendumCircle.FirstAngle = 0.0 + addendumCircle.LastAngle = 0.0 - #Pitch circle - pitchCircle=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","PitchCircle") + # Pitch circle + pitchCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PitchCircle") Draft._Circle(pitchCircle) Draft._ViewProviderDraft(pitchCircle.ViewObject) pitchCircle.Radius = d/2 - pitchCircle.FirstAngle=0.0 - pitchCircle.LastAngle=0.0 + pitchCircle.FirstAngle = 0.0 + pitchCircle.LastAngle = 0.0 #************ Calculating right sides of teeth - #Involute of base circle - involute=[] - involutee=[] - involutesav=[] + # Involute of base circle + involute = [] + involutee = [] + involutesav = [] - for t in range(0,60,1): - x=db/2*(math.cos(math.radians(t))+math.radians(t)*math.sin(math.radians(t))) - y=db/2*(math.sin(math.radians(t))-math.radians(t)*math.cos(math.radians(t))) - involute.append(Part.Vertex(x,y,0).Point) + for t in range(0, 60, 1): + x = db/2*(math.cos(math.radians(t))+math.radians(t)*math.sin(math.radians(t))) + y = db/2*(math.sin(math.radians(t))-math.radians(t)*math.cos(math.radians(t))) + involute.append(Part.Vertex(x, y, 0).Point) -#************ Drawing right sides of teeth +#************ Drawing right sides of teeth involutesav.extend(involute) involutee.extend(involute) - for angle in range(1,N+1,1): - involuteobj = FreeCAD.ActiveDocument.addObject("Part::Feature","InvoluteL"+str(angle)) - involutee.insert(0,(0,0,0)) - involuteshape = Part.makePolygon(involutee) + for angle in range(1, N+1, 1): + involuteobj = FreeCAD.ActiveDocument.addObject("Part::Feature", "InvoluteL" + str(angle)) + involutee.insert(0, (0, 0, 0)) + involuteshape = Part.makePolygon(involutee) involuteobj.Shape=involuteshape - involutee=[] - for num in range(0,60,1): - point=involute.pop() - pointt=Part.Vertex(point.x*math.cos(math.radians(angle*360/N)) - point.y*math.sin(math.radians(angle*360/N)),point.x*math.sin(math.radians(angle*360/N)) + point.y*math.cos(math.radians(angle*360/N)),0).Point + involutee = [] + for num in range(0, 60, 1): + point = involute.pop() + pointt = Part.Vertex(point.x*math.cos(math.radians(angle*360/N)) - point.y*math.sin(math.radians(angle*360/N)),point.x*math.sin(math.radians(angle*360/N)) + point.y*math.cos(math.radians(angle*360/N)),0).Point involutee.insert(0,pointt) involute.extend(involutesav) - involutee=[] - + involutee = [] + #************ Calculating difference between tooth spacing on BaseCircle and PitchCircle - pc=App.ActiveDocument.getObject("PitchCircle") - inv=App.ActiveDocument.getObject("InvoluteL1") - cut=inv.Shape.cut(pc.Shape) + pc = App.ActiveDocument.getObject("PitchCircle") + inv = App.ActiveDocument.getObject("InvoluteL1") + cut = inv.Shape.cut(pc.Shape) # FreeCAD.ActiveDocument.addObject("Part::Feature","CutInv").Shape=cut - invPoint=cut.Vertexes[0].Point + invPoint = cut.Vertexes[0].Point - - diff=invPoint.y*2 # instead of making axial symmetry and calculating point distance. - anglediff=2*math.asin(diff/d) + diff = invPoint.y*2 # instead of making axial symmetry and calculating point distance. + anglediff = 2*math.asin(diff/d) #************ Calculating left sides of teeth #************ Inversing Involute - for num in range(0,60,1): - point=involute.pop() - pointt=Part.Vertex(point.x,point.y*-1,0).Point - involutee.insert(0,pointt) + for num in range(0, 60, 1): + point = involute.pop() + pointt = Part.Vertex(point.x, point.y*-1, 0).Point + involutee.insert(0, pointt) involute.extend(involutee) - involutee=[] + involutee = [] -#Normal tooth size calculated as: 0,5* p - j j=m * 0,1 below are calculations +#Normal tooth size calculated as: 0,5* p - j j = m * 0,1 below are calculations # 0,5* p - m * 0,1 # 0,5* p - p /pi * 0,1 # 0,5*360/N - ((360/N)/pi)* 0,1 -# 0,5*360/N - (360/N)*((1/pi)*0,1) j=(p/pi)*0,1 +# 0,5*360/N - (360/N)*((1/pi)*0,1) j = (p/pi)*0,1 # 0,5*360/N - (360/N)*((p/pi)*0,1)/p # 0,5*360/N - (360/N)*( j )/p - for num in range(0,60,1): - point=involute.pop() - pointt=Part.Vertex(point.x*math.cos(math.radians(180/N-(360/N)*(j/p))+anglediff) - point.y*math.sin(math.radians(180/N-(360/N)*(j/p))+anglediff),point.x*math.sin(math.radians(180/N-(360/N)*(j/p))+anglediff) + point.y*math.cos(math.radians(180/N-(360/N)*(j/p))+anglediff),0).Point - involutee.insert(0,pointt) + for num in range(0, 60, 1): + point = involute.pop() + pointt = Part.Vertex(point.x*math.cos(math.radians(180/N-(360/N)*(j/p))+anglediff) - point.y*math.sin(math.radians(180/N-(360/N)*(j/p))+anglediff),point.x*math.sin(math.radians(180/N-(360/N)*(j/p))+anglediff) + point.y*math.cos(math.radians(180/N-(360/N)*(j/p))+anglediff),0).Point + involutee.insert(0, pointt) involute.extend(involutee) - involutesav=[] + involutesav = [] involutesav.extend(involute) #************ Drawing left sides of teeth - for angle in range(1,N+1,1): - involuteobj = FreeCAD.ActiveDocument.addObject("Part::Feature","InvoluteR"+str(angle)) - involutee.insert(0,(0,0,0)) + for angle in range(1, N+1, 1): + involuteobj = FreeCAD.ActiveDocument.addObject("Part::Feature", "InvoluteR" + str(angle)) + involutee.insert(0, (0, 0, 0)) involuteshape = Part.makePolygon(involutee) - involuteobj.Shape=involuteshape - involutee=[] + involuteobj.Shape = involuteshape + involutee = [] for num in range(0,60,1): - point=involute.pop() - pointt=Part.Vertex(point.x*math.cos(math.radians(angle*360/N)) - point.y*math.sin(math.radians(angle*360/N)),point.x*math.sin(math.radians(angle*360/N)) + point.y*math.cos(math.radians(angle*360/N)),0).Point + point = involute.pop() + pointt = Part.Vertex(point.x*math.cos(math.radians(angle*360/N)) - point.y*math.sin(math.radians(angle*360/N)),point.x*math.sin(math.radians(angle*360/N)) + point.y*math.cos(math.radians(angle*360/N)),0).Point involutee.insert(0,pointt) involute.extend(involutesav) @@ -166,56 +174,56 @@ def compute(): #************ Forming teeth - cutCircle=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","CutCircle") + cutCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutCircle") Draft._Circle(cutCircle) Draft._ViewProviderDraft(cutCircle.ViewObject) - cutCircle.Radius = da # da because must be bigger than addendumCircle and bigger than whole construction da is right for this but it not has to be. - cutCircle.FirstAngle=0.0 - cutCircle.LastAngle=0.0 + cutCircle.Radius = da # da because must be bigger than addendumCircle and bigger than whole construction da is right for this but it not has to be. + cutCircle.FirstAngle = 0.0 + cutCircle.LastAngle = 0.0 - - cutTool=cutCircle.Shape.cut(addendumCircle.Shape) - #cutshape=Part.show(cutTool) - - gearShape=rootCircle.Shape - for invNum in range(1,N+1,1): - invL=App.ActiveDocument.getObject("InvoluteL"+str(invNum)) - invR=App.ActiveDocument.getObject("InvoluteR"+str(invNum)) - cutL=invL.Shape.cut(cutTool) - cutR=invR.Shape.cut(cutTool) - pointL=cutL.Vertexes.pop().Point - pointR=cutR.Vertexes.pop().Point - faceEdge=Part.makeLine(pointL,pointR) - - toothWhole=cutL.fuse(cutR) - toothWhole=toothWhole.fuse(faceEdge) - toothWire=Part.Wire(toothWhole.Edges) - toothShape=Part.Face(toothWire) -# tooth=App.ActiveDocument.addObject("Part::Feature","Tooth"+str(invNum)) + cutTool = cutCircle.Shape.cut(addendumCircle.Shape) + # cutshape = Part.show(cutTool) + + gearShape = rootCircle.Shape + + for invNum in range(1, N+1, 1): + invL = App.ActiveDocument.getObject("InvoluteL" + str(invNum)) + invR = App.ActiveDocument.getObject("InvoluteR" + str(invNum)) + cutL = invL.Shape.cut(cutTool) + cutR = invR.Shape.cut(cutTool) + pointL = cutL.Vertexes.pop().Point + pointR = cutR.Vertexes.pop().Point + faceEdge = Part.makeLine(pointL, pointR) + + toothWhole = cutL.fuse(cutR) + toothWhole = toothWhole.fuse(faceEdge) + toothWire = Part.Wire(toothWhole.Edges) + toothShape = Part.Face(toothWire) +# tooth = App.ActiveDocument.addObject("Part::Feature", "Tooth" +str(invNum)) # tooth.Shape=toothShape - gearShape=gearShape.fuse(toothShape) + gearShape = gearShape.fuse(toothShape) for o in App.ActiveDocument.Objects: - if oldDocumentObjects.count(o)==0: + if oldDocumentObjects.count(o) == 0: App.ActiveDocument.removeObject(o.Name) - gearFlat=App.ActiveDocument.addObject("Part::Feature","GearFlat") - gearFlat.Shape=gearShape - Gui.ActiveDocument.getObject(gearFlat.Name).Visibility=False + gearFlat = App.ActiveDocument.addObject("Part::Feature", "GearFlat") + gearFlat.Shape = gearShape + Gui.ActiveDocument.getObject(gearFlat.Name).Visibility = False - gear=App.ActiveDocument.addObject("Part::Extrusion","Gear3D") - gear.Base=gearFlat - gear.Dir=(0,0,width) + gear = App.ActiveDocument.addObject("Part::Extrusion", "Gear3D") + gear.Base = gearFlat + gear.Dir = (0, 0, width) App.ActiveDocument.recompute() - + if c1.isChecked()==True: - gearMesh=App.ActiveDocument.addObject("Mesh::Feature","Gear3D-mesh") + gearMesh = App.ActiveDocument.addObject("Mesh::Feature", "Gear3D-mesh") faces = [] - triangles = gear.Shape.tessellate(1) # the number represents the precision of the tessellation) + triangles = gear.Shape.tessellate(1) # the number represents the precision of the tessellation) for tri in triangles[1]: face = [] for i in range(3): @@ -224,16 +232,16 @@ def compute(): faces.append(face) mesh = Mesh.Mesh(faces) - gearMesh.Mesh=mesh + gearMesh.Mesh = mesh App.ActiveDocument.removeObject(gear.Name) App.ActiveDocument.removeObject(gearFlat.Name) App.ActiveDocument.recompute() Gui.SendMsgToActiveView("ViewFit") - + QtGui.QApplication.restoreOverrideCursor() - + hide() @@ -259,22 +267,22 @@ la.addWidget(t3) l3 = QtGui.QLineEdit() l3.setText("20") la.addWidget(l3) -t4 = QtGui.QLabel("Tooth height factor (y)") +t4 = QtGui.QLabel("Tooth height factor (y)") la.addWidget(t4) l4 = QtGui.QLineEdit() l4.setText("1.0") la.addWidget(l4) -t5 = QtGui.QLabel("Tooth clearance (c)") +t5 = QtGui.QLabel("Tooth clearance (c)") la.addWidget(t5) l5 = QtGui.QLineEdit() l5.setText("0.1") la.addWidget(l5) -t6 = QtGui.QLabel("Tooth lateral clearance (j)") +t6 = QtGui.QLabel("Tooth lateral clearance (j)") la.addWidget(t6) l6 = QtGui.QLineEdit() l6.setText("0.04") la.addWidget(l6) -t7 = QtGui.QLabel("Gear width") +t7 = QtGui.QLabel("Gear width") la.addWidget(t7) l7 = QtGui.QLineEdit() l7.setText("6.0") @@ -282,7 +290,7 @@ la.addWidget(l7) c1 = QtGui.QCheckBox("Create as a Mesh") la.addWidget(c1) e1 = QtGui.QLabel("(for faster rendering)") -commentFont=QtGui.QFont("Times",8,True) +commentFont = QtGui.QFont("Times", 8, True) e1.setFont(commentFont) la.addWidget(e1) diff --git a/src/Mod/PartDesign/Scripts/Spring.py b/src/Mod/PartDesign/Scripts/Spring.py index 0018c166ef..08337fc70f 100644 --- a/src/Mod/PartDesign/Scripts/Spring.py +++ b/src/Mod/PartDesign/Scripts/Spring.py @@ -9,10 +9,10 @@ from FreeCAD import Base class MySpring: def __init__(self, obj): ''' Add the properties: Pitch, Diameter, Height, BarDiameter ''' - obj.addProperty("App::PropertyLength","Pitch","MySpring","Pitch of the helix").Pitch=5.0 - obj.addProperty("App::PropertyLength","Diameter","MySpring","Diameter of the helix").Diameter=6.0 - obj.addProperty("App::PropertyLength","Height","MySpring","Height of the helix").Height=30.0 - obj.addProperty("App::PropertyLength","BarDiameter","MySpring","Diameter of the bar").BarDiameter=3.0 + obj.addProperty("App::PropertyLength", "Pitch", "MySpring", "Pitch of the helix").Pitch = 5.0 + obj.addProperty("App::PropertyLength", "Diameter", "MySpring", "Diameter of the helix").Diameter = 6.0 + obj.addProperty("App::PropertyLength", "Height", "MySpring", "Height of the helix").Height = 30.0 + obj.addProperty("App::PropertyLength", "BarDiameter", "MySpring", "Diameter of the bar").BarDiameter = 3.0 obj.Proxy = self def onChanged(self, fp, prop): @@ -24,28 +24,29 @@ class MySpring: radius = fp.Diameter/2 height = fp.Height barradius = fp.BarDiameter/2 - myhelix=Part.makeHelix(pitch,height,radius) - g=myhelix.Edges[0].Curve - c=Part.Circle() - c.Center=g.value(0) # start point of the helix - c.Axis=(0,1,0) - c.Radius=barradius - p=c.toShape() + myhelix = Part.makeHelix(pitch, height, radius) + g = myhelix.Edges[0].Curve + c = Part.Circle() + c.Center = g.value(0) # start point of the helix + c.Axis = (0, 1, 0) + c.Radius = barradius + p = c.toShape() section = Part.Wire([p]) - makeSolid=1 #change to 1 to make a solid - isFrenet=1 - myspring=Part.Wire(myhelix).makePipeShell([section],makeSolid,isFrenet) + makeSolid = 1 # change to 1 to make a solid + isFrenet = 1 + myspring = Part.Wire(myhelix).makePipeShell([section], makeSolid, isFrenet) fp.Shape = myspring def makeMySpring(): doc = FreeCAD.activeDocument() if doc is None: doc = FreeCAD.newDocument() - spring=doc.addObject("Part::FeaturePython","My_Spring") + spring = doc.addObject("Part::FeaturePython", "My_Spring") spring.Label = "My Spring" MySpring(spring) - spring.ViewObject.Proxy=0 + spring.ViewObject.Proxy = 0 doc.recompute() + if __name__ == "__main__": makeMySpring() diff --git a/src/Mod/PartDesign/WizardShaft/WizardShaft.py b/src/Mod/PartDesign/WizardShaft/WizardShaft.py index 85895b90f1..ae02afa5d7 100644 --- a/src/Mod/PartDesign/WizardShaft/WizardShaft.py +++ b/src/Mod/PartDesign/WizardShaft/WizardShaft.py @@ -31,7 +31,7 @@ class TaskWizardShaft: "Shaft Wizard" App = FreeCAD Gui = FreeCADGui - + def __init__(self, doc): mw = QtGui.QApplication.activeWindow() #cw = mw.centralWidget() # This is a qmdiarea widget @@ -48,20 +48,20 @@ class TaskWizardShaft: featureWindow = cw.subWindowList()[-1] else: featureWindow = cw.activeSubWindow() - + # Buttons for diagram display - buttonLayout = QtGui.QGridLayout() - bnames = [["All [x]", "All [y]", "All [z]" ], - ["N [x]", "Q [y]", "Q [z]"], - ["Mt [x]", "Mb [z]", "Mb [y]"], - ["", "w [y]", "w [z]"], - ["sigma [x]", "sigma [y]", "sigma [z]"], + buttonLayout = QtGui.QGridLayout() + bnames = [["All [x]", "All [y]", "All [z]" ], + ["N [x]", "Q [y]", "Q [z]"], + ["Mt [x]", "Mb [z]", "Mb [y]"], + ["", "w [y]", "w [z]"], + ["sigma [x]", "sigma [y]", "sigma [z]"], ["tau [x]", "sigmab [z]", "sigmab [y]"]] - slots = [[self.slotAllx, self.slotAlly, self.slotAllz], - [self.slotFx, self.slotQy, self.slotQz], - [self.slotMx, self.slotMz, self.slotMy], - [self.slotNone, self.slotWy, self.slotWz], - [self.slotSigmax, self.slotSigmay, self.slotSigmaz], + slots = [[self.slotAllx, self.slotAlly, self.slotAllz], + [self.slotFx, self.slotQy, self.slotQz], + [self.slotMx, self.slotMz, self.slotMy], + [self.slotNone, self.slotWy, self.slotWz], + [self.slotSigmax, self.slotSigmay, self.slotSigmaz], [self.slotTaut, self.slotSigmabz, self.slotSigmaby]] self.buttons = [[None, None, None], [None, None, None], [None, None, None], [None, None, None], [None, None, None], [None, None, None]] @@ -71,23 +71,23 @@ class TaskWizardShaft: buttonLayout.addWidget(button, row, col) self.buttons[row][col] = button button.clicked.connect(slots[row][col]) - + # Create Shaft object self.shaft = Shaft(self) # Create table widget self.form = QtGui.QWidget() self.table = WizardShaftTable(self, self.shaft) - + # The top layout will contain the Shaft Wizard layout plus the elements of the FEM constraints dialog layout = QtGui.QVBoxLayout() layout.setObjectName("ShaftWizard") # Do not change or translate: Required to detect whether Shaft Wizard is running in FemGui::ViewProviderFemConstraintXXX sublayout = QtGui.QVBoxLayout() - sublayout.setObjectName("ShaftWizardLayout") # Do not change or translate + sublayout.setObjectName("ShaftWizardLayout") # Do not change or translate sublayout.addWidget(self.table.widget) sublayout.addLayout(buttonLayout) layout.addLayout(sublayout) self.form.setLayout(layout) - + # Switch to feature window mdi=FreeCADGui.getMainWindow().findChild(QtGui.QMdiArea) cw.setActiveSubWindow(featureWindow) @@ -108,49 +108,49 @@ class TaskWizardShaft: self.showDiagram("Ally") def slotAllz(self): self.showDiagram("Allz") - + def slotFx(self): self.showDiagram("Nx") def slotQy(self): self.showDiagram("Qy") def slotQz(self): self.showDiagram("Qz") - + def slotMx(self): self.showDiagram("Mx") def slotMz(self): self.showDiagram("Mz") def slotMy(self): self.showDiagram("My") - + def slotNone(self): pass def slotWy(self): self.showDiagram("wy") def slotWz(self): self.showDiagram("wz") - + def slotSigmax(self): self.showDiagram("sigmax") def slotSigmay(self): self.showDiagram("sigmay") def slotSigmaz(self): self.showDiagram("sigmaz") - + def slotTaut(self): self.showDiagram("taut") def slotSigmabz(self): self.showDiagram("sigmabz") def slotSigmaby(self): self.showDiagram("sigmaby") - + def updateButton(self, row, col, flag): self.buttons[row][col].setEnabled(flag) - + def updateButtons(self, col, flag): for row in range(len(self.buttons)): self.updateButton(row, col, flag) - + def getStandardButtons(self): return int(QtGui.QDialogButtonBox.Ok) @@ -162,7 +162,7 @@ class TaskWizardShaft: if self.form: del self.form return True - + def isAllowedAlterDocument(self): return False @@ -170,44 +170,48 @@ class TaskWizardShaft: # Problem: From the FemConstraint ViewProvider, we need to tell the Shaft instance that the user finished editing the constraint # We can find the Shaft Wizard dialog object from C++, but there is no way to reach the Shaft instance # Also it seems to be impossible to access the active dialog from Python, so Gui::Command::runCommand() is not an option either -# Note: Another way would be to create a hidden widget in the Shaft Wizard dialog and write some data to it, triggering a slot +# Note: Another way would be to create a hidden widget in the Shaft Wizard dialog and write some data to it, triggering a slot # in the python code WizardShaftDlg = None -class WizardShaftGui: +class WizardShaftGui: def Activated(self): global WizardShaftDlg WizardShaftDlg = TaskWizardShaft(FreeCAD.ActiveDocument) FreeCADGui.Control.showDialog(WizardShaftDlg) - + def GetResources(self): IconPath = FreeCAD.ConfigGet("AppHomePath") + "Mod/PartDesign/WizardShaft/WizardShaft.svg" - MenuText = 'Shaft design wizard...' - ToolTip = 'Start the shaft design wizard' - return {'Pixmap' : IconPath, 'MenuText': MenuText, 'ToolTip': ToolTip} + MenuText = QtCore.QT_TRANSLATE_NOOP("WizardShaft", "Shaft design wizard...") + ToolTip = QtCore.QT_TRANSLATE_NOOP("WizardShaft", "Start the shaft design wizard") + return {'Pixmap': IconPath, + 'MenuText': MenuText, + 'ToolTip': ToolTip} def IsActive(self): return FreeCAD.ActiveDocument != None - + def __del__(self): global WizardShaftDlg WizardShaftDlg = None - -class WizardShaftGuiCallback: + +class WizardShaftGuiCallback: def Activated(self): global WizardShaftDlg if WizardShaftDlg != None and WizardShaftDlg.table != None: WizardShaftDlg.table.finishEditConstraint() - + def isActive(self): global WizardShaftDlg return (WizardShaftDlg is not None) - + def GetResources(self): IconPath = FreeCAD.ConfigGet("AppHomePath") + "Mod/PartDesign/WizardShaft/WizardShaft.svg" - MenuText = 'Shaft design wizard...' - ToolTip = 'Start the shaft design wizard' - return {'Pixmap' : IconPath, 'MenuText': MenuText, 'ToolTip': ToolTip} + MenuText = QtCore.QT_TRANSLATE_NOOP("WizardShaft", "Shaft design wizard...") + ToolTip = QtCore.QT_TRANSLATE_NOOP("WizardShaft", "Start the shaft design wizard") + return {'Pixmap': IconPath, + 'MenuText': MenuText, + 'ToolTip': ToolTip} FreeCADGui.addCommand('PartDesign_WizardShaft', WizardShaftGui()) FreeCADGui.addCommand('PartDesign_WizardShaftCallBack', WizardShaftGuiCallback()) diff --git a/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py b/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py index a37b205183..9b2139b76c 100644 --- a/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py +++ b/src/Mod/PartDesign/WizardShaft/WizardShaftTable.py @@ -32,7 +32,7 @@ class WizardShaftTable: "Length" : 0, "Diameter" : 1, "InnerDiameter" : 2, - "ConstraintType" : 3, + "ConstraintType": 3, "StartEdgeType" : 4, "StartEdgeSize" : 5, "EndEdgeType" : 6, @@ -40,15 +40,15 @@ class WizardShaftTable: } rowDictReverse = {} headers = [ - "Length [mm]", - "Diameter [mm]", - "Inner diameter [mm]", - "Constraint type", - "Start edge type", - "Start edge size", - "End edge type", - "End edge size" - ] + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "Length [mm]"), + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "Diameter [mm]"), + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "Inner diameter [mm]"), + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "Constraint type"), + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "Start edge type"), + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "Start edge size"), + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "End edge type"), + QtCore.QT_TRANSLATE_NOOP("WizardShaftTable", "End edge size") + ] def __init__(self, w, s): for key in iter(self.rowDict.keys()): @@ -57,7 +57,7 @@ class WizardShaftTable: self.wizard = w self.shaft = s # Create table widget - self.widget = QtGui.QTableWidget(len(self.rowDict), 0) + self.widget = QtGui.QTableWidget(len(self.rowDict), 0) self.widget.setObjectName("ShaftWizardTable") # Do not change or translate: Used in ViewProviderFemConstraintXXX self.widget.setWindowTitle("Shaft wizard") self.widget.resize(QtCore.QSize(300,200)) @@ -102,7 +102,7 @@ class WizardShaftTable: index = self.widget.columnCount() # Make an intelligent guess at the length/dia of the next segment if index > 0: - length = self.shaft.segments[index-1].length + length = self.shaft.segments[index-1].length diameter = self.shaft.segments[index-1].diameter if index > 2: diameter -= 5.0 @@ -156,7 +156,7 @@ class WizardShaftTable: widget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.widget.setCellWidget(self.rowDict["ConstraintType"], index, widget) widget.setCurrentIndex(0) - self.widget.connect(widget, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.slotConstraintType) + self.widget.connect(widget, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.slotConstraintType) # Start edge type widget = QtGui.QComboBox(self.widget) widget.insertItem(0, "None",) @@ -221,13 +221,13 @@ class WizardShaftTable: elif rowName == "EndEdgeSize": pass - def slotEditConstraint(self): + def slotEditConstraint(self): (self.editedRow, self.editedColumn) = self.getFocusedCell() # Because finishEditConstraint() will trigger slotEditingFinished() which requires this information self.shaft.editConstraint(self.editedColumn) - + def finishEditConstraint(self): self.shaft.updateConstraint(self.editedColumn, self.getConstraintType(self.editedColumn)) - + def setLength(self, column, l): self.setDoubleValue("Length", column, l) self.shaft.updateSegment(column, length = l) diff --git a/src/Mod/Path/PathScripts/PathAdaptive.py b/src/Mod/Path/PathScripts/PathAdaptive.py index f7b96cf96e..2c765c5d15 100644 --- a/src/Mod/Path/PathScripts/PathAdaptive.py +++ b/src/Mod/Path/PathScripts/PathAdaptive.py @@ -737,9 +737,19 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def SetupProperties(): + setup = ["Side", "OperationType", "Tolerance", "StepOver", + "LiftDistance", "KeepToolDownRatio", "StockToLeave", + "ForceInsideOut", "FinishingProfile", "Stopped", + "StopProcessing", "UseHelixArcs", "AdaptiveInputState", + "AdaptiveOutputState", "HelixAngle", "HelixConeAngle", + "HelixDiameterLimit", "UseOutline"] + return setup + + +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Adaptive operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = PathAdaptive(obj, name) + obj.Proxy = PathAdaptive(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index acfca878d2..73c4b4097e 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -70,9 +70,9 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Custom operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - proxy = ObjectCustom(obj, name) + obj.Proxy = ObjectCustom(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 51bbb96207..fbfdf7cf02 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -291,10 +291,9 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Deburr operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - - obj.Proxy = ObjectDeburr(obj, name) + obj.Proxy = ObjectDeburr(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py b/src/Mod/Path/PathScripts/PathDressupPathBoundary.py index dc6d57f2f6..68eda29544 100644 --- a/src/Mod/Path/PathScripts/PathDressupPathBoundary.py +++ b/src/Mod/Path/PathScripts/PathDressupPathBoundary.py @@ -35,6 +35,11 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + def _vstr(v): if v: return "(%.2f, %.2f, %.2f)" % (v.x, v.y, v.z) @@ -77,7 +82,29 @@ class DressupPathBoundary(object): obj.Stock = None return True - def boundaryCommands(self, obj, begin, end, verticalFeed): + def execute(self, obj): + pb = PathBoundary(obj.Base, obj.Stock.Shape, obj.Inside) + obj.Path = pb.execute() +# Eclass + + +class PathBoundary: + """class PathBoundary... + This class requires a base operation, boundary shape, and optional inside boolean (default is True). + The `execute()` method returns a Path object with path commands limited to cut paths inside or outside + the provided boundary shape. + """ + + def __init__(self, baseOp, boundaryShape, inside=True): + self.baseOp = baseOp + self.boundary = boundaryShape + self.inside = inside + self.safeHeight = None + self.clearanceHeight = None + self.strG1ZsafeHeight = None + self.strG0ZclearanceHeight = None + + def boundaryCommands(self, begin, end, verticalFeed): PathLog.track(_vstr(begin), _vstr(end)) if end and PathGeom.pointsCoincide(begin, end): return [] @@ -94,117 +121,117 @@ class DressupPathBoundary(object): cmds.append(Path.Command('G1', {'Z': end.z, 'F': verticalFeed})) return cmds - def execute(self, obj): - if not obj.Base or not obj.Base.isDerivedFrom('Path::Feature') or not obj.Base.Path: - return + def execute(self): + if not self.baseOp or not self.baseOp.isDerivedFrom('Path::Feature') or not self.baseOp.Path: + return None - tc = PathDressup.toolController(obj.Base) + if len(self.baseOp.Path.Commands) == 0: + PathLog.warning("No Path Commands for %s" % self.baseOp.Label) + return [] - if len(obj.Base.Path.Commands) > 0: - self.safeHeight = float(PathUtil.opProperty(obj.Base, 'SafeHeight')) - self.clearanceHeight = float(PathUtil.opProperty(obj.Base, 'ClearanceHeight')) - self.strG1ZsafeHeight = Path.Command('G1', {'Z': self.safeHeight, 'F': tc.VertFeed.Value}) - self.strG0ZclearanceHeight = Path.Command('G0', {'Z': self.clearanceHeight}) + tc = PathDressup.toolController(self.baseOp) - boundary = obj.Stock.Shape - cmd = obj.Base.Path.Commands[0] - pos = cmd.Placement.Base # bogus m/c position to create first edge - bogusX = True - bogusY = True - commands = [cmd] - lastExit = None - for cmd in obj.Base.Path.Commands[1:]: - if cmd.Name in PathGeom.CmdMoveAll: - if bogusX == True : - bogusX = ( 'X' not in cmd.Parameters ) - if bogusY : - bogusY = ( 'Y' not in cmd.Parameters ) - edge = PathGeom.edgeForCmd(cmd, pos) - if edge: - inside = edge.common(boundary).Edges - outside = edge.cut(boundary).Edges - if not obj.Inside: # UI "inside boundary" param - tmp = inside - inside = outside - outside = tmp - # it's really a shame that one cannot trust the sequence and/or - # orientation of edges - if 1 == len(inside) and 0 == len(outside): - PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd) - # cmd fully included by boundary - if lastExit: + self.safeHeight = float(PathUtil.opProperty(self.baseOp, 'SafeHeight')) + self.clearanceHeight = float(PathUtil.opProperty(self.baseOp, 'ClearanceHeight')) + self.strG1ZsafeHeight = Path.Command('G1', {'Z': self.safeHeight, 'F': tc.VertFeed.Value}) + self.strG0ZclearanceHeight = Path.Command('G0', {'Z': self.clearanceHeight}) + + cmd = self.baseOp.Path.Commands[0] + pos = cmd.Placement.Base # bogus m/c position to create first edge + bogusX = True + bogusY = True + commands = [cmd] + lastExit = None + for cmd in self.baseOp.Path.Commands[1:]: + if cmd.Name in PathGeom.CmdMoveAll: + if bogusX == True : + bogusX = ( 'X' not in cmd.Parameters ) + if bogusY : + bogusY = ( 'Y' not in cmd.Parameters ) + edge = PathGeom.edgeForCmd(cmd, pos) + if edge: + inside = edge.common(self.boundary).Edges + outside = edge.cut(self.boundary).Edges + if not self.inside: # UI "inside boundary" param + tmp = inside + inside = outside + outside = tmp + # it's really a shame that one cannot trust the sequence and/or + # orientation of edges + if 1 == len(inside) and 0 == len(outside): + PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd) + # cmd fully included by boundary + if lastExit: + if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position + commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value)) + lastExit = None + commands.append(cmd) + pos = PathGeom.commandEndPoint(cmd, pos) + elif 0 == len(inside) and 1 == len(outside): + PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd) + # cmd fully excluded by boundary + if not lastExit: + lastExit = pos + pos = PathGeom.commandEndPoint(cmd, pos) + else: + PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd) + # cmd pierces boundary + while inside or outside: + ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] + PathLog.track(ie) + if ie: + e = ie[0] + LastPt = e.valueAt(e.LastParameter) + flip = PathGeom.pointsCoincide(pos, LastPt) + newPos = e.valueAt(e.FirstParameter) if flip else LastPt + # inside edges are taken at this point (see swap of inside/outside + # above - so we can just connect the dots ... + if lastExit: + if not ( bogusX or bogusY ) : commands.extend(self.boundaryCommands(lastExit, pos, tc.VertFeed.Value)) + lastExit = None + PathLog.track(e, flip) if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position - commands.extend(self.boundaryCommands(obj, lastExit, pos, tc.VertFeed.Value)) - lastExit = None - commands.append(cmd) - pos = PathGeom.commandEndPoint(cmd, pos) - elif 0 == len(inside) and 1 == len(outside): - PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd) - # cmd fully excluded by boundary - if not lastExit: - lastExit = pos - pos = PathGeom.commandEndPoint(cmd, pos) - else: - PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd) - # cmd pierces boundary - while inside or outside: - ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)] - PathLog.track(ie) - if ie: - e = ie[0] - LastPt = e.valueAt(e.LastParameter) - flip = PathGeom.pointsCoincide(pos, LastPt) - newPos = e.valueAt(e.FirstParameter) if flip else LastPt - # inside edges are taken at this point (see swap of inside/outside - # above - so we can just connect the dots ... - if lastExit: - if not ( bogusX or bogusY ) : commands.extend(self.boundaryCommands(obj, lastExit, pos, tc.VertFeed.Value)) - lastExit = None - PathLog.track(e, flip) - if not ( bogusX or bogusY ) : # don't insert false paths based on bogus m/c position - commands.extend(PathGeom.cmdsForEdge(e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value)) - inside.remove(e) + commands.extend(PathGeom.cmdsForEdge(e, flip, False, 50, tc.HorizFeed.Value, tc.VertFeed.Value)) + inside.remove(e) + pos = newPos + lastExit = newPos + else: + oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)] + PathLog.track(oe) + if oe: + e = oe[0] + ptL = e.valueAt(e.LastParameter) + flip = PathGeom.pointsCoincide(pos, ptL) + newPos = e.valueAt(e.FirstParameter) if flip else ptL + # outside edges are never taken at this point (see swap of + # inside/outside above) - so just move along ... + outside.remove(e) pos = newPos - lastExit = newPos else: - oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)] - PathLog.track(oe) - if oe: - e = oe[0] - ptL = e.valueAt(e.LastParameter) - flip = PathGeom.pointsCoincide(pos, ptL) - newPos = e.valueAt(e.FirstParameter) if flip else ptL - # outside edges are never taken at this point (see swap of - # inside/outside above) - so just move along ... - outside.remove(e) - pos = newPos - else: - PathLog.error('huh?') - import Part - Part.show(Part.Vertex(pos), 'pos') - for e in inside: - Part.show(e, 'ei') - for e in outside: - Part.show(e, 'eo') - raise Exception('This is not supposed to happen') - # Eif + PathLog.error('huh?') + import Part + Part.show(Part.Vertex(pos), 'pos') + for e in inside: + Part.show(e, 'ei') + for e in outside: + Part.show(e, 'eo') + raise Exception('This is not supposed to happen') # Eif - # Ewhile - # Eif - # pos = PathGeom.commandEndPoint(cmd, pos) + # Eif + # Ewhile # Eif - else: - PathLog.track('no-move', cmd) - commands.append(cmd) - if lastExit: - commands.extend(self.boundaryCommands(obj, lastExit, None, tc.VertFeed.Value)) - lastExit = None - else: - PathLog.warning("No Path Commands for %s" % obj.Base.Label) - commands = [] - PathLog.track(commands) - obj.Path = Path.Path(commands) + # pos = PathGeom.commandEndPoint(cmd, pos) + # Eif + else: + PathLog.track('no-move', cmd) + commands.append(cmd) + if lastExit: + commands.extend(self.boundaryCommands(lastExit, None, tc.VertFeed.Value)) + lastExit = None + PathLog.track(commands) + return Path.Path(commands) +# Eclass def Create(base, name='DressupPathBoundary'): '''Create(base, name='DressupPathBoundary') ... creates a dressup limiting base's Path to a boundary.''' diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index 907749a904..fbcbce910a 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -159,12 +159,13 @@ def SetupProperties(): setup.append("RetractHeight") return setup -def Create(name, obj = None): + +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Drilling operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectDrilling(obj, name) + obj.Proxy = ObjectDrilling(obj, name, parentJob) if obj.Proxy: obj.Proxy.findAllHoles(obj) diff --git a/src/Mod/Path/PathScripts/PathEngrave.py b/src/Mod/Path/PathScripts/PathEngrave.py index 0edba1b07a..b60d2c977e 100644 --- a/src/Mod/Path/PathScripts/PathEngrave.py +++ b/src/Mod/Path/PathScripts/PathEngrave.py @@ -48,8 +48,8 @@ def translate(context, text, disambig=None): class ObjectEngrave(PathEngraveBase.ObjectOp): '''Proxy class for Engrave operation.''' - def __init__(self, obj, name): - super(ObjectEngrave, self).__init__(obj, name) + def __init__(self, obj, name, parentJob): + super(ObjectEngrave, self).__init__(obj, name, parentJob) self.wires = [] def opFeatures(self, obj): @@ -144,9 +144,9 @@ def SetupProperties(): return ["StartVertex"] -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns an Engrave operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectEngrave(obj, name) + obj.Proxy = ObjectEngrave(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index fb37725bf3..1d0d58f358 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -68,10 +68,10 @@ def updateInputField(obj, prop, widget, onBeforeChange=None): isDiff = True break if noExpr: - widget.setProperty('readonly', False) + widget.setReadOnly(False) widget.setStyleSheet("color: black") else: - widget.setProperty('readonly', True) + widget.setReadOnly(True) widget.setStyleSheet("color: gray") widget.update() @@ -100,6 +100,7 @@ class QuantitySpinBox: self.widget = widget self.onBeforeChange = onBeforeChange self.prop = None + self.obj = obj self.attachTo(obj, prop) def attachTo(self, obj, prop = None): @@ -139,9 +140,14 @@ class QuantitySpinBox: If no value is provided the value of the bound property is used. quantity can be of type Quantity or Float.''' PathLog.track(self.prop, self.valid) + if self.valid: + expr = self._hasExpression() if quantity is None: - quantity = PathUtil.getProperty(self.obj, self.prop) + if expr: + quantity = FreeCAD.Units.Quantity(self.obj.evalExpression(expr)) + else: + quantity = PathUtil.getProperty(self.obj, self.prop) value = quantity.Value if hasattr(quantity, 'Value') else quantity self.widget.setProperty('rawValue', value) @@ -151,3 +157,9 @@ class QuantitySpinBox: if self.valid: return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) return None + + def _hasExpression(self): + for (prop, exp) in self.obj.ExpressionEngine: + if prop == self.prop: + return exp + return None diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 59a729ab84..b74669f53f 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -214,11 +214,11 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Helix operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectHelix(obj, name) + obj.Proxy = ObjectHelix(obj, name, parentJob) if obj.Proxy: obj.Proxy.findAllHoles(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py index ce62fd0923..327b676b30 100644 --- a/src/Mod/Path/PathScripts/PathJobGui.py +++ b/src/Mod/Path/PathScripts/PathJobGui.py @@ -175,8 +175,8 @@ class ViewProvider: FreeCADGui.Control.closeDialog() FreeCADGui.Control.showDialog(self.taskPanel) self.taskPanel.setupUi(activate) - self.deleteOnReject = False self.showOriginAxis(True) + self.deleteOnReject = False def resetTaskPanel(self): self.showOriginAxis(False) diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index db01c49379..f3c1d93511 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -304,9 +304,9 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Mill Facing operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectFace(obj, name) + obj.Proxy = ObjectFace(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index fa53a20093..d9537f99f7 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -119,7 +119,7 @@ class ObjectOp(object): obj.addProperty("App::PropertyDistance", "OpStockZMin", "Op Values", QtCore.QT_TRANSLATE_NOOP("PathOp", "Holds the min Z value of Stock")) obj.setEditorMode('OpStockZMin', 1) # read-only - def __init__(self, obj, name): + def __init__(self, obj, name, parentJob=None): PathLog.track() obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code")) @@ -190,6 +190,8 @@ class ObjectOp(object): self.initOperation(obj) if not hasattr(obj, 'DoNotSetDefaultValues') or not obj.DoNotSetDefaultValues: + if parentJob: + self.job = PathUtils.addToJob(obj, jobname=parentJob.Name) job = self.setDefaultValues(obj) if job: job.SetupSheet.Proxy.setOperationProperties(obj, name) @@ -322,7 +324,10 @@ class ObjectOp(object): def setDefaultValues(self, obj): '''setDefaultValues(obj) ... base implementation. Do not overwrite, overwrite opSetDefaultValues() instead.''' - job = PathUtils.addToJob(obj) + if self.job: + job = self.job + else: + job = PathUtils.addToJob(obj) obj.Active = True @@ -622,5 +627,3 @@ class ObjectOp(object): This function can safely be overwritten by subclasses.''' return True - - diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 6fcdf86e54..0863fa9202 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -214,11 +214,6 @@ class TaskPanelPage(object): def _installTCUpdate(self): return hasattr(self.form, 'toolController') - def setParent(self, parent): - '''setParent() ... used to transfer parent object link to child class. - Do not overwrite.''' - self.parent = parent - def onDirtyChanged(self, callback): '''onDirtyChanged(callback) ... set callback when dirty state changes.''' self.signalDirtyChanged = callback @@ -1000,8 +995,10 @@ class TaskPanel(object): def __init__(self, obj, deleteOnReject, opPage, selectionFactory): PathLog.track(obj.Label, deleteOnReject, opPage, selectionFactory) FreeCAD.ActiveDocument.openTransaction(translate("Path", "AreaOp Operation")) + self.obj = obj self.deleteOnReject = deleteOnReject self.featurePages = [] + self.parent = None # members initialized later self.clearanceHeight = None @@ -1050,9 +1047,9 @@ class TaskPanel(object): self.featurePages.append(opPage) for page in self.featurePages: + page.parent = self # save pointer to this current class as "parent" page.initPage(obj) page.onDirtyChanged(self.pageDirtyChanged) - page.setParent(self) taskPanelLayout = PathPreferences.defaultTaskPanelLayout() @@ -1092,7 +1089,6 @@ class TaskPanel(object): self.form = forms self.selectionFactory = selectionFactory - self.obj = obj self.isdirty = deleteOnReject self.visibility = obj.ViewObject.Visibility obj.ViewObject.Visibility = True @@ -1207,7 +1203,18 @@ class TaskPanel(object): page.clearBase() page.addBaseGeometry(sel) + # Update properties based upon expressions in case expression value has changed + for (prp, expr) in self.obj.ExpressionEngine: + val = FreeCAD.Units.Quantity(self.obj.evalExpression(expr)) + value = val.Value if hasattr(val, 'Value') else val + prop = getattr(self.obj, prp) + if hasattr(prop, "Value"): + prop.Value = value + else: + prop = value + self.panelSetFields() + for page in self.featurePages: page.pageRegisterSignalHandlers() @@ -1280,12 +1287,12 @@ def Create(res): this function directly, but calls the Activated() function of the Command object that is created in each operations Gui implementation.''' FreeCAD.ActiveDocument.openTransaction("Create %s" % res.name) - obj = res.objFactory(res.name) + obj = res.objFactory(res.name, obj=None, parentJob=res.job) if obj.Proxy: obj.ViewObject.Proxy = ViewProvider(obj.ViewObject, res) obj.ViewObject.Visibility = False - FreeCAD.ActiveDocument.commitTransaction() + obj.ViewObject.Document.setEdit(obj.ViewObject, 0) return obj FreeCAD.ActiveDocument.abortTransaction() @@ -1329,6 +1336,7 @@ class CommandResources: self.menuText = menuText self.accelKey = accelKey self.toolTip = toolTip + self.job = None def SetupOperation(name, diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 643cfdc06a..65249c6b20 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -717,9 +717,9 @@ def SetupProperties(): return PathPocketBase.SetupProperties() + ["HandleMultipleFeatures"] -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Pocket operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectPocket(obj, name) + obj.Proxy = ObjectPocket(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index 902fb21c44..c57778be53 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -84,6 +84,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() + self.removalshapes = [] # self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False self.removalshapes = [] @@ -162,7 +163,8 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # shape.tessellate(0.05) # originally 0.1 if self.removalshapes: - obj.removalshape = self.removalshapes[0][0] + obj.removalshape = Part.makeCompound([tup[0] for tup in self.removalshapes]) + return self.removalshapes # Support methods @@ -242,11 +244,11 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Pocket operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject('Path::FeaturePython', name) - - obj.Proxy = ObjectPocket(obj, name) + obj.Proxy = ObjectPocket(obj, name, parentJob) + return obj return obj \ No newline at end of file diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index f555b00075..e2fc33e8f0 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -97,9 +97,9 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Probing operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - proxy = ObjectProbing(obj, name) + proxy = ObjectProbing(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 39f65f9956..07901949f8 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -1301,9 +1301,9 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Profile based on faces operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name) + obj.Proxy = ObjectProfile(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathProfileContour.py b/src/Mod/Path/PathScripts/PathProfileContour.py index e743cdc8eb..ba02016b48 100644 --- a/src/Mod/Path/PathScripts/PathProfileContour.py +++ b/src/Mod/Path/PathScripts/PathProfileContour.py @@ -42,9 +42,9 @@ def SetupProperties(): return PathProfile.SetupProperties() -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Profile operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectContour(obj, name) + obj.Proxy = ObjectContour(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index 26a28e701a..a79a93e2b1 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -43,9 +43,9 @@ def SetupProperties(): return PathProfile.SetupProperties() -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Profile operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name) + obj.Proxy = ObjectProfile(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 2896ab4390..f8f0b705dc 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -44,9 +44,9 @@ def SetupProperties(): return PathProfile.SetupProperties() -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Profile operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name) + obj.Proxy = ObjectProfile(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathSetupSheet.py b/src/Mod/Path/PathScripts/PathSetupSheet.py index 9cdfb0596c..79fbc339c8 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheet.py +++ b/src/Mod/Path/PathScripts/PathSetupSheet.py @@ -34,7 +34,7 @@ __doc__ = "A container for all default values and job specific configuration val _RegisteredOps = {} -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 3a01944abc..9fc2344cbf 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -27,6 +27,7 @@ import PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtil as PathUtil +import PathScripts.PathJob as PathJob import PathSimulator import math import os @@ -75,6 +76,7 @@ class PathSimulation: self.simperiod = 20 self.accuracy = 0.1 self.resetSimulation = False + self.jobs = [] def Connect(self, but, sig): QtCore.QObject.connect(but, QtCore.SIGNAL("clicked()"), sig) @@ -96,13 +98,9 @@ class PathSimulation: self.onSpeedBarChange() form.sliderAccuracy.valueChanged.connect(self.onAccuracyBarChange) self.onAccuracyBarChange() + self._populateJobSelection(form) form.comboJobs.currentIndexChanged.connect(self.onJobChange) - jobList = FreeCAD.ActiveDocument.findObjects("Path::FeaturePython", "Job.*") - form.comboJobs.clear() - self.jobs = [] - for j in jobList: - self.jobs.append(j) - form.comboJobs.addItem(j.ViewObject.Icon, j.Label) + self.onJobChange() FreeCADGui.Control.showDialog(self.taskForm) self.disableAnim = False self.isVoxel = True @@ -111,6 +109,40 @@ class PathSimulation: self.SimulateMill() self.initdone = True + def _populateJobSelection(self, form): + # Make Job selection combobox + setJobIdx = 0 + jobName = '' + jIdx = 0 + # Get list of Job objects in active document + jobList = FreeCAD.ActiveDocument.findObjects("Path::FeaturePython", "Job.*") + jCnt = len(jobList) + + # Check if user has selected a specific job for simulation + guiSelection = FreeCADGui.Selection.getSelectionEx() + if guiSelection: # Identify job selected by user + sel = guiSelection[0] + if hasattr(sel.Object, "Proxy") and isinstance(sel.Object.Proxy, PathJob.ObjectJob): + jobName = sel.Object.Name + FreeCADGui.Selection.clearSelection() + + # populate the job selection combobox + form.comboJobs.blockSignals(True) + form.comboJobs.clear() + form.comboJobs.blockSignals(False) + for j in jobList: + form.comboJobs.addItem(j.ViewObject.Icon, j.Label) + self.jobs.append(j) + if j.Name == jobName or jCnt == 1: + setJobIdx = jIdx + jIdx += 1 + + # Pre-select GUI-selected job in the combobox + if jobName or jCnt == 1: + form.comboJobs.setCurrentIndex(setJobIdx) + else: + form.comboJobs.setCurrentIndex(0) + def SetupSimulation(self): form = self.taskForm.form self.activeOps = [] diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index 472a7fbe8f..9048b66a08 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -1767,9 +1767,9 @@ def SetupProperties(): return [tup[1] for tup in ObjectSlot.opPropertyDefinitions(False)] -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Slot operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectSlot(obj, name) + obj.Proxy = ObjectSlot(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 015fdbb579..bf2107b34c 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -2124,9 +2124,9 @@ def SetupProperties(): return [tup[1] for tup in ObjectSurface.opPropertyDefinitions(False)] -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Surface operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectSurface(obj, name) + obj.Proxy = ObjectSurface(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathThreadMilling.py b/src/Mod/Path/PathScripts/PathThreadMilling.py index f7ca04b19a..0c5580d8eb 100644 --- a/src/Mod/Path/PathScripts/PathThreadMilling.py +++ b/src/Mod/Path/PathScripts/PathThreadMilling.py @@ -327,11 +327,11 @@ def SetupProperties(): return setup -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a thread milling operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectThreadMilling(obj, name) + obj.Proxy = ObjectThreadMilling(obj, name, parentJob) if obj.Proxy: obj.Proxy.findAllHoles(obj) return obj diff --git a/src/Mod/Path/PathScripts/PathVcarve.py b/src/Mod/Path/PathScripts/PathVcarve.py index 4b93ed1ad3..45609ebcf3 100644 --- a/src/Mod/Path/PathScripts/PathVcarve.py +++ b/src/Mod/Path/PathScripts/PathVcarve.py @@ -363,9 +363,9 @@ def SetupProperties(): return ["Discretize"] -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Vcarve operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - ObjectVcarve(obj, name) + obj.Proxy = ObjectVcarve(obj, name, parentJob) return obj diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 764d4c723b..9ccc95bd68 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -1814,9 +1814,9 @@ def SetupProperties(): return [tup[1] for tup in ObjectWaterline.opPropertyDefinitions(False)] -def Create(name, obj=None): +def Create(name, obj=None, parentJob=None): '''Create(name) ... Creates and returns a Waterline operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectWaterline(obj, name) + obj.Proxy = ObjectWaterline(obj, name, parentJob) return obj diff --git a/src/Mod/TechDraw/App/DrawViewPart.cpp b/src/Mod/TechDraw/App/DrawViewPart.cpp index 658b194c04..46590dadae 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.cpp +++ b/src/Mod/TechDraw/App/DrawViewPart.cpp @@ -324,7 +324,10 @@ short DrawViewPart::mustExecute() const SeamHidden.isTouched() || IsoHidden.isTouched() || IsoCount.isTouched() || - CoarseView.isTouched()); + CoarseView.isTouched() || + CosmeticVertexes.isTouched() || + CosmeticEdges.isTouched() || + CenterLines.isTouched()); } if (result) { diff --git a/src/Mod/TechDraw/Gui/CommandAnnotate.cpp b/src/Mod/TechDraw/Gui/CommandAnnotate.cpp index 369e84a776..2cb79cefb2 100644 --- a/src/Mod/TechDraw/Gui/CommandAnnotate.cpp +++ b/src/Mod/TechDraw/Gui/CommandAnnotate.cpp @@ -348,6 +348,8 @@ void execMidpoints(Gui::Command* cmd) return; } + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Midpont Vertices")); + const std::vector edges = dvp->getEdgeGeometry(); double scale = dvp->getScale(); for (auto& s: selectedEdges) { @@ -357,6 +359,9 @@ void execMidpoints(Gui::Command* cmd) mid = DrawUtil::invertY(mid); dvp->addCosmeticVertex(mid / scale); } + + Gui::Command::commitCommand(); + dvp->recomputeFeature(); } @@ -371,6 +376,8 @@ void execQuadrants(Gui::Command* cmd) return; } + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Quadrant Vertices")); + const std::vector edges = dvp->getEdgeGeometry(); double scale = dvp->getScale(); for (auto& s: selectedEdges) { @@ -382,6 +389,9 @@ void execQuadrants(Gui::Command* cmd) dvp->addCosmeticVertex(iq / scale); } } + + Gui::Command::commitCommand(); + dvp->recomputeFeature(); } diff --git a/src/Mod/TechDraw/Gui/CommandDecorate.cpp b/src/Mod/TechDraw/Gui/CommandDecorate.cpp index 6678a98971..7dabfb89c8 100644 --- a/src/Mod/TechDraw/Gui/CommandDecorate.cpp +++ b/src/Mod/TechDraw/Gui/CommandDecorate.cpp @@ -73,124 +73,6 @@ using namespace std; //internal functions bool _checkSelectionHatch(Gui::Command* cmd); -////=========================================================================== -//// TechDraw_Leader -////=========================================================================== - -//DEF_STD_CMD_A(CmdTechDrawLeaderLine) - -//CmdTechDrawLeaderLine::CmdTechDrawLeaderLine() -// : Command("TechDraw_LeaderLine") -//{ -// sAppModule = "TechDraw"; -// sGroup = QT_TR_NOOP("TechDraw"); -// sMenuText = QT_TR_NOOP("Add a line to a view"); -// sToolTipText = QT_TR_NOOP("Add a line to a view"); -// sWhatsThis = "TechDraw_LeaderLine"; -// sStatusTip = sToolTipText; -// sPixmap = "actions/techdraw-LeaderLine"; -//} - -//void CmdTechDrawLeaderLine::activated(int iMsg) -//{ -// Q_UNUSED(iMsg); - -// Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); -// if (dlg != nullptr) { -// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Task In Progress"), -// QObject::tr("Close active task dialog and try again.")); -// return; -// } - -// TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); -// if (!page) { -// return; -// } - -// std::vector selection = getSelection().getSelectionEx(); -// TechDraw::DrawView* baseFeat = nullptr; -// if (!selection.empty()) { -// baseFeat = dynamic_cast(selection[0].getObject()); -// if( baseFeat == nullptr ) { -// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Selection Error"), -// QObject::tr("Can not attach leader. No base View selected.")); -// return; -// } -// } else { -// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Selection Error"), -// QObject::tr("You must select a base View for the line.")); -// return; -// } - -// Gui::Control().showDialog(new TechDrawGui::TaskDlgLeaderLine(baseFeat, -// page)); -//} - -//bool CmdTechDrawLeaderLine::isActive(void) -//{ -// bool havePage = DrawGuiUtil::needPage(this); -// bool haveView = DrawGuiUtil::needView(this, false); -// return (havePage && haveView); -//} - -////=========================================================================== -//// TechDraw_RichTextAnnotation -////=========================================================================== - -//DEF_STD_CMD_A(CmdTechDrawRichTextAnnotation) - -//CmdTechDrawRichTextAnnotation::CmdTechDrawRichTextAnnotation() -// : Command("TechDraw_RichTextAnnotation") -//{ -// sAppModule = "TechDraw"; -// sGroup = QT_TR_NOOP("TechDraw"); -// sMenuText = QT_TR_NOOP("Add Rich Text Annotation"); -// sToolTipText = sMenuText; -// sWhatsThis = "TechDraw_RichTextAnnotation"; -// sStatusTip = sToolTipText; -// sPixmap = "actions/techdraw-RichTextAnnotation"; -//} - -//void CmdTechDrawRichTextAnnotation::activated(int iMsg) -//{ -// Q_UNUSED(iMsg); -// Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); -// if (dlg != nullptr) { -// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Task In Progress"), -// QObject::tr("Close active task dialog and try again.")); -// return; -// } - -// TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); -// if (!page) { -// return; -// } - -// std::vector selection = getSelection().getSelectionEx(); -// TechDraw::DrawView* baseFeat = nullptr; -// if (!selection.empty()) { -// baseFeat = dynamic_cast(selection[0].getObject()); -//// if( baseFeat == nullptr ) { -//// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Selection Error"), -//// QObject::tr("Can not attach leader. No base View selected.")); -//// return; -//// } -//// } else { -//// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Selection Error"), -//// QObject::tr("You must select a base View for the line.")); -//// return; -// } - -// Gui::Control().showDialog(new TaskDlgRichAnno(baseFeat, -// page)); -//} - -//bool CmdTechDrawRichTextAnnotation::isActive(void) -//{ -// bool havePage = DrawGuiUtil::needPage(this); -// bool haveView = DrawGuiUtil::needView(this, false); -// return (havePage && haveView); -//} //=========================================================================== // TechDraw_Hatch diff --git a/src/Mod/TechDraw/Gui/QGIRichAnno.cpp b/src/Mod/TechDraw/Gui/QGIRichAnno.cpp index 5920005bb4..d5e0c5286c 100644 --- a/src/Mod/TechDraw/Gui/QGIRichAnno.cpp +++ b/src/Mod/TechDraw/Gui/QGIRichAnno.cpp @@ -43,6 +43,7 @@ #include #include #include +#include # include @@ -80,6 +81,7 @@ #include "QGCustomRect.h" #include "QGIRichAnno.h" +#include "mrichtextedit.h" using namespace TechDraw; using namespace TechDrawGui; @@ -360,4 +362,36 @@ double QGIRichAnno::prefPointSize(void) return ptsSize; } +void QGIRichAnno::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) { + Q_UNUSED(event); + + TechDraw::DrawRichAnno *annotation = dynamic_cast(getViewObject()); + if (annotation == nullptr) { + return; + } + + QString text = QString::fromUtf8(annotation->AnnoText.getValue()); + + QDialog dialog(0); + dialog.setWindowTitle(QObject::tr("Rich text editor")); + dialog.setMinimumWidth(400); + dialog.setMinimumHeight(400); + + MRichTextEdit richEdit(&dialog, text); + QGridLayout gridLayout(&dialog); + gridLayout.addWidget(&richEdit, 0, 0, 1, 1); + + connect(&richEdit, SIGNAL(saveText(QString)), &dialog, SLOT(accept())); + connect(&richEdit, SIGNAL(editorFinished(void)), &dialog, SLOT(reject())); + + if (dialog.exec()) { + QString newText = richEdit.toHtml(); + if (newText != text) { + App::GetApplication().setActiveTransaction("Set Rich Annotation Text"); + annotation->AnnoText.setValue(newText.toStdString()); + App::GetApplication().closeActiveTransaction(); + } + } +} + #include diff --git a/src/Mod/TechDraw/Gui/QGIRichAnno.h b/src/Mod/TechDraw/Gui/QGIRichAnno.h index cc96b1a18e..552793cf9c 100644 --- a/src/Mod/TechDraw/Gui/QGIRichAnno.h +++ b/src/Mod/TechDraw/Gui/QGIRichAnno.h @@ -98,6 +98,8 @@ protected: double prefPointSize(void); QFont prefFont(void); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + bool m_isExporting; QGCustomText* m_text; bool m_hasHover; diff --git a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp index 5af8a8768f..7c88c270cc 100644 --- a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp @@ -52,6 +52,8 @@ #include #include #include +#include + #include #include "Rez.h" @@ -189,4 +191,54 @@ void QGIViewAnnotation::rotateView(void) m_textItem->setRotation(-rot); } +void QGIViewAnnotation::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + Q_UNUSED(event); + TechDraw::DrawViewAnnotation *annotation = dynamic_cast(getViewObject()); + if (annotation == nullptr) { + return; + } + + const std::vector &values = annotation->Text.getValues(); + QString text; + if (values.size() > 0) { + text = QString::fromUtf8(Base::Tools::escapedUnicodeToUtf8(values[0]).c_str()); + + for (unsigned int i = 1; i < values.size(); ++i) { + text += QChar::fromLatin1('\n'); + text += QString::fromUtf8(Base::Tools::escapedUnicodeToUtf8(values[i]).c_str()); + } + } + + QDialog dialog(0); + dialog.setWindowTitle(tr("Text")); + + Gui::PropertyListEditor editor(&dialog); + editor.setPlainText(text); + + QDialogButtonBox buttonBox(&dialog); + buttonBox.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + QVBoxLayout boxLayout(&dialog); + boxLayout.addWidget(&editor); + boxLayout.addWidget(&buttonBox); + + connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); + connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); + if (dialog.exec() == QDialog::Accepted) { + QString newText = editor.toPlainText(); + if (newText != text) { + QStringList list = newText.split(QChar::fromLatin1('\n')); + std::vector newValues; + + for (int i = 0; i < list.size(); ++i) { + newValues.push_back(Base::Tools::escapedUnicodeFromUtf8(list[i].toStdString().c_str())); + } + + App::GetApplication().setActiveTransaction("Set Annotation Text"); + annotation->Text.setValues(newValues); + App::GetApplication().closeActiveTransaction(); + } + } +} diff --git a/src/Mod/TechDraw/Gui/QGIViewAnnotation.h b/src/Mod/TechDraw/Gui/QGIViewAnnotation.h index d45846f017..0552d99125 100644 --- a/src/Mod/TechDraw/Gui/QGIViewAnnotation.h +++ b/src/Mod/TechDraw/Gui/QGIViewAnnotation.h @@ -56,6 +56,8 @@ protected: void drawAnnotation(); QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + QGCustomText *m_textItem; QColor m_colNormal; QColor m_colSel; diff --git a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp index bc2eaec6fe..d5532159cd 100644 --- a/src/Mod/TechDraw/Gui/TaskCosVertex.cpp +++ b/src/Mod/TechDraw/Gui/TaskCosVertex.cpp @@ -161,11 +161,15 @@ void TaskCosVertex::updateUi(void) void TaskCosVertex::addCosVertex(QPointF qPos) { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Cosmetic Vertex")); + // Base::Console().Message("TCV::addCosVertex(%s)\n", TechDraw::DrawUtil::formatVector(qPos).c_str()); Base::Vector3d pos(qPos.x(), -qPos.y()); // int idx = (void) m_baseFeat->addCosmeticVertex(pos); m_baseFeat->requestPaint(); + + Gui::Command::commitCommand(); } diff --git a/src/Mod/TechDraw/Gui/TaskCosmeticLine.cpp b/src/Mod/TechDraw/Gui/TaskCosmeticLine.cpp index b439ab4972..67d13b954a 100644 --- a/src/Mod/TechDraw/Gui/TaskCosmeticLine.cpp +++ b/src/Mod/TechDraw/Gui/TaskCosmeticLine.cpp @@ -203,6 +203,8 @@ void TaskCosmeticLine::setUiEdit() //****************************************************************************** void TaskCosmeticLine::createCosmeticLine(void) { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create Cosmetic Line")); + double x = ui->qsbx1->value().getValue(); double y = ui->qsby1->value().getValue(); double z = ui->qsbz1->value().getValue(); @@ -227,6 +229,8 @@ void TaskCosmeticLine::createCosmeticLine(void) m_tag = m_partFeat->addCosmeticEdge(p0, p1); m_ce = m_partFeat->getCosmeticEdge(m_tag); + + Gui::Command::commitCommand(); } void TaskCosmeticLine::updateCosmeticLine(void) diff --git a/src/Mod/TechDraw/Gui/mrichtextedit.cpp b/src/Mod/TechDraw/Gui/mrichtextedit.cpp index 630ae0f6d4..7cc23e368c 100644 --- a/src/Mod/TechDraw/Gui/mrichtextedit.cpp +++ b/src/Mod/TechDraw/Gui/mrichtextedit.cpp @@ -317,6 +317,15 @@ void MRichTextEdit::focusInEvent(QFocusEvent *) { f_textedit->setFocus(Qt::TabFocusReason); } +void MRichTextEdit::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Return && event->modifiers() == Qt::ControlModifier) { + onSave(); + return; + } + + QWidget::keyPressEvent(event); +} + void MRichTextEdit::textUnderline() { QTextCharFormat fmt; diff --git a/src/Mod/TechDraw/Gui/mrichtextedit.h b/src/Mod/TechDraw/Gui/mrichtextedit.h index 4e5a31cc13..28a8bf5859 100644 --- a/src/Mod/TechDraw/Gui/mrichtextedit.h +++ b/src/Mod/TechDraw/Gui/mrichtextedit.h @@ -94,6 +94,7 @@ Q_SIGNALS: void list(bool checked, QTextListFormat::Style style); void indent(int delta); void focusInEvent(QFocusEvent *event); + void keyPressEvent(QKeyEvent *event); bool hasMultipleSizes(void); void addFontSize(QString fs); diff --git a/src/Tools/updatets.py b/src/Tools/updatets.py index 96e6ece07b..1f9e2a0a43 100755 --- a/src/Tools/updatets.py +++ b/src/Tools/updatets.py @@ -102,6 +102,12 @@ PyCommands = [["src/Mod/Draft", 'lconvert -i Gui/Resources/translations/Partpy.ts Gui/Resources/translations/Part.ts -o Gui/Resources/translations/Part.ts'], ["src/Mod/Part", 'rm Gui/Resources/translations/Partpy.ts'], + ["src/Mod/PartDesign", + 'pylupdate `find ./ -name "*.py"` -ts Gui/Resources/translations/PartDesignpy.ts'], + ["src/Mod/PartDesign", + 'lconvert -i Gui/Resources/translations/PartDesignpy.ts Gui/Resources/translations/PartDesign.ts -o Gui/Resources/translations/PartDesign.ts'], + ["src/Mod/PartDesign", + 'rm Gui/Resources/translations/PartDesignpy.ts'], ["src/Mod/Image", 'pylupdate `find ./ -name "*.py"` -ts Gui/Resources/translations/Imagepy.ts'], ["src/Mod/Image",