diff --git a/src/Base/Parameter.cpp b/src/Base/Parameter.cpp index 54a33bf907b7..95dba6079116 100644 --- a/src/Base/Parameter.cpp +++ b/src/Base/Parameter.cpp @@ -46,6 +46,7 @@ #endif #include +#include "fmt/printf.h" #include "Parameter.h" #include "Parameter.inl" @@ -805,9 +806,8 @@ long ParameterGrp::GetInt(const char* Name, long lPreset) const void ParameterGrp::SetInt(const char* Name, long lValue) { - char cBuf[256]; - sprintf(cBuf, "%li", lValue); - _SetAttribute(ParamType::FCInt, Name, cBuf); + std::string buf = fmt::sprintf("%li", lValue); + _SetAttribute(ParamType::FCInt, Name, buf.c_str()); } std::vector ParameterGrp::GetInts(const char* sFilter) const @@ -875,9 +875,8 @@ unsigned long ParameterGrp::GetUnsigned(const char* Name, unsigned long lPreset) void ParameterGrp::SetUnsigned(const char* Name, unsigned long lValue) { - char cBuf[256]; - sprintf(cBuf, "%lu", lValue); - _SetAttribute(ParamType::FCUInt, Name, cBuf); + std::string buf = fmt::sprintf("%lu", lValue); + _SetAttribute(ParamType::FCUInt, Name, buf.c_str()); } std::vector ParameterGrp::GetUnsigneds(const char* sFilter) const @@ -950,9 +949,9 @@ double ParameterGrp::GetFloat(const char* Name, double dPreset) const void ParameterGrp::SetFloat(const char* Name, double dValue) { - char cBuf[256]; - sprintf(cBuf, "%.12f", dValue); // use %.12f instead of %f to handle values < 1.0e-6 - _SetAttribute(ParamType::FCFloat, Name, cBuf); + // use %.12f instead of %f to handle values < 1.0e-6 + std::string buf = fmt::sprintf("%.12f", dValue); + _SetAttribute(ParamType::FCFloat, Name, buf.c_str()); } std::vector ParameterGrp::GetFloats(const char* sFilter) const diff --git a/src/Ext/freecad/CMakeLists.txt b/src/Ext/freecad/CMakeLists.txt index 8b4056d38865..c39e5e07ebb1 100644 --- a/src/Ext/freecad/CMakeLists.txt +++ b/src/Ext/freecad/CMakeLists.txt @@ -25,9 +25,19 @@ else() endif() configure_file(__init__.py.template ${NAMESPACE_INIT}) -configure_file(project_utility.py ${NAMESPACE_DIR}/project_utility.py) -configure_file(UiTools.py ${NAMESPACE_DIR}/UiTools.py) -configure_file(utils.py ${NAMESPACE_DIR}/utils.py) + +set(EXT_FILES + part.py + partdesign.py + project_utility.py + sketcher.py + UiTools.py + utils.py +) + +foreach (it ${EXT_FILES}) + configure_file(${it} ${NAMESPACE_DIR}/${it}) +endforeach() if (INSTALL_TO_SITEPACKAGES) SET(SITE_PACKAGE_DIR ${PYTHON_MAIN_DIR}/freecad) @@ -38,12 +48,7 @@ endif() INSTALL( FILES ${NAMESPACE_INIT} - part.py - partdesign.py - sketcher.py - project_utility.py - UiTools.py - utils.py + ${EXT_FILES} DESTINATION ${SITE_PACKAGE_DIR} ) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 4b033b62629f..c3ba38121653 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -803,8 +803,7 @@ void Application::createStandardOperations() void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc) { #ifdef FC_DEBUG - std::map::const_iterator it = d->documents.find(&Doc); - assert(it==d->documents.end()); + assert(d->documents.find(&Doc) == d->documents.end()); #endif auto pDoc = new Gui::Document(const_cast(&Doc),this); d->documents[&Doc] = pDoc; diff --git a/src/Gui/DocumentPy.xml b/src/Gui/DocumentPy.xml index 129bb8697a73..8a33f9c212ab 100644 --- a/src/Gui/DocumentPy.xml +++ b/src/Gui/DocumentPy.xml @@ -237,7 +237,7 @@ obj : Gui.ViewProvider - + Returns True if the document is marked as modified, and False otherwise. diff --git a/src/Gui/DocumentPyImp.cpp b/src/Gui/DocumentPyImp.cpp index ff4116d951cd..28451f14eed4 100644 --- a/src/Gui/DocumentPyImp.cpp +++ b/src/Gui/DocumentPyImp.cpp @@ -504,6 +504,11 @@ Py::Boolean DocumentPy::getModified() const return {getDocumentPtr()->isModified()}; } +void DocumentPy::setModified(Py::Boolean arg) +{ + getDocumentPtr()->setModified(arg); +} + PyObject *DocumentPy::getCustomAttributes(const char* attr) const { // Note: Here we want to return only a document object if its diff --git a/src/Gui/WorkbenchSelector.cpp b/src/Gui/WorkbenchSelector.cpp index 5df2ebe20b66..3b1eca1e9216 100644 --- a/src/Gui/WorkbenchSelector.cpp +++ b/src/Gui/WorkbenchSelector.cpp @@ -98,7 +98,7 @@ void WorkbenchComboBox::refreshList(QList actionList) } -WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) +WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) : QTabBar(parent) , wbActionGroup(aGroup) { @@ -124,7 +124,7 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) }); if (parent->inherits("QToolBar")) { - // set the initial orientation. We cannot do updateLayoutAndTabOrientation(false); + // set the initial orientation. We cannot do updateLayoutAndTabOrientation(false); // because on init the toolbar area is always TopToolBarArea. ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); @@ -145,7 +145,7 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) refreshList(aGroup->getEnabledWbActions()); connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchTabWidget::refreshList); connect(aGroup->groupAction(), &QActionGroup::triggered, this, [this, aGroup](QAction* action) { - int index = std::min(aGroup->actions().indexOf(action), this->count() - 1); + int index = std::min(aGroup->actions().indexOf(action), this->count() - 1); setCurrentIndex(index); }); connect(this, qOverload(&QTabBar::tabBarClicked), aGroup, [aGroup, moreAction](int index) { @@ -202,7 +202,7 @@ void WorkbenchTabWidget::refreshList(QList actionList) else { addTab(icon, tr("More")); } - + buildPrefMenu(); } @@ -238,7 +238,7 @@ void WorkbenchTabWidget::buildPrefMenu() menu->addSeparator(); QAction* preferencesAction = menu->addAction(tr("Preferences")); - connect(preferencesAction, &QAction::triggered, this, [this]() { + connect(preferencesAction, &QAction::triggered, this, []() { Gui::Dialog::DlgPreferencesImp cDlg(getMainWindow()); cDlg.activateGroupPage(QString::fromUtf8("Workbenches"), 0); cDlg.exec(); diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py index df23cf434d0a..7e9f972b69da 100644 --- a/src/Mod/Arch/ArchCommands.py +++ b/src/Mod/Arch/ArchCommands.py @@ -98,7 +98,7 @@ def addComponents(objectsList,host): if hostType in ["Floor","Building","Site","Project","BuildingPart"]: for o in objectsList: host.addObject(o) - elif hostType in ["Wall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component"]: + elif hostType in ["Wall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component","Pipe"]: import DraftGeomUtils a = host.Additions if hasattr(host,"Axes"): @@ -142,7 +142,7 @@ def removeComponents(objectsList,host=None): if not isinstance(objectsList,list): objectsList = [objectsList] if host: - if Draft.getType(host) in ["Wall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component"]: + if Draft.getType(host) in ["Wall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component","Pipe"]: if hasattr(host,"Tool"): if objectsList[0] == host.Tool: host.Tool = None diff --git a/src/Mod/Arch/ArchPipe.py b/src/Mod/Arch/ArchPipe.py index 596fac7cb008..20c9a801a716 100644 --- a/src/Mod/Arch/ArchPipe.py +++ b/src/Mod/Arch/ArchPipe.py @@ -279,7 +279,7 @@ def execute(self,obj): sh = shapes[0] else: sh = Part.makeCompound(shapes) - obj.Shape = sh + obj.Shape = self.processSubShapes(obj,sh,pl) if obj.Base: obj.Length = w.Length else: diff --git a/src/Mod/Arch/ArchRoof.py b/src/Mod/Arch/ArchRoof.py index e176261d9506..3e35f25f4978 100644 --- a/src/Mod/Arch/ArchRoof.py +++ b/src/Mod/Arch/ArchRoof.py @@ -841,13 +841,39 @@ def getSubVolume(self, obj): return None if obj.Base.Shape.Solids: - return obj.Shape + # For roof created from Base object as solids: + # Not only the solid of the base object itself be subtracted from + # a Wall, but all portion of the wall above the roof solid would be + # subtracted as well. + # + # FC forum discussion : Sketch based Arch_Roof and wall substraction + # - https://forum.freecad.org/viewtopic.php?t=84389 + # + faces = [] + solids = [] + for f in obj.Base.Shape.Faces: # obj.Base.Shape.Solids.Faces + p = f.findPlane() # Curve face (surface) seems return no Plane + if p: + if p.Axis[2] < 0: # z<0, i.e. normal pointing below horizon + faces.append(f) + else: + # Not sure if it is pointing towards and/or above horizon + # (upward or downward), or it is curve surface, just add. + faces.append(f) + + # Attempt to find normal at non-planar face to verify if + # it is pointing downward, but cannot conclude even all test + # points happens to be pointing upward. So add in any rate. + + for f in faces: + solid = f.extrude(Vector(0.0, 0.0, 1000000.0)) + solids.append(solid) + compound = Part.Compound(solids) + return compound - # self.sub is auto-generated subvolume for wire-based roof sub_field = getattr(self, 'sub', None) if not sub_field: self.execute(obj) - return self.sub diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index f7176d42ad90..fb492b36d633 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -1210,7 +1210,10 @@ def getExtrusionData(self,obj): if not height: return None if obj.Normal == Vector(0,0,0): - normal = Vector(0,0,1) + import DraftGeomUtils + normal = DraftGeomUtils.get_shape_normal(obj.Base.Shape) + if normal == None: + normal = Vector(0,0,1) else: normal = Vector(obj.Normal) base = None @@ -1275,8 +1278,10 @@ def getExtrusionData(self,obj): # If the object is a single edge, use that as the # basewires. + # TODO 2023.11.26: Need to check if it isn't Sketch after all first # or use algorithm for Sketch altogether? + elif len(obj.Base.Shape.Edges) == 1: self.basewires = [Part.Wire(obj.Base.Shape.Edges)] diff --git a/src/Mod/Arch/importIFCHelper.py b/src/Mod/Arch/importIFCHelper.py index 0939e5fdcb2f..a551f8602850 100644 --- a/src/Mod/Arch/importIFCHelper.py +++ b/src/Mod/Arch/importIFCHelper.py @@ -510,7 +510,7 @@ def getColorFromStyledItem(styled_item): # this is an error in the IFC file in my opinion else: # never seen an ifc with more than one Styles in IfcStyledItem - # the above seams to only apply for IFC2x3, IFC4 can have them + # the above seems to only apply for IFC2x3, IFC4 can have them # see https://forum.freecad.org/viewtopic.php?f=39&t=33560&p=437056#p437056 # Get the `IfcPresentationStyleAssignment`, there should only be one, diff --git a/src/Mod/CAM/Gui/Command.cpp b/src/Mod/CAM/Gui/Command.cpp index d31526b1606c..fa5deb9a6b5f 100644 --- a/src/Mod/CAM/Gui/Command.cpp +++ b/src/Mod/CAM/Gui/Command.cpp @@ -43,7 +43,7 @@ CmdPathArea::CmdPathArea() :Command("CAM_Area") { sAppModule = "Path"; - sGroup = QT_TR_NOOP("Path"); + sGroup = QT_TR_NOOP("CAM"); sMenuText = QT_TR_NOOP("Area"); sToolTipText = QT_TR_NOOP("Creates a feature area from selected objects"); sWhatsThis = "CAM_Area"; @@ -125,7 +125,7 @@ CmdPathAreaWorkplane::CmdPathAreaWorkplane() :Command("CAM_Area_Workplane") { sAppModule = "Path"; - sGroup = QT_TR_NOOP("Path"); + sGroup = QT_TR_NOOP("CAM"); sMenuText = QT_TR_NOOP("Area workplane"); sToolTipText = QT_TR_NOOP("Select a workplane for a FeatureArea"); sWhatsThis = "CAM_Area_Workplane"; @@ -215,7 +215,7 @@ CmdPathCompound::CmdPathCompound() :Command("CAM_Compound") { sAppModule = "Path"; - sGroup = QT_TR_NOOP("Path"); + sGroup = QT_TR_NOOP("CAM"); sMenuText = QT_TR_NOOP("Compound"); sToolTipText = QT_TR_NOOP("Creates a compound from selected toolpaths"); sWhatsThis = "CAM_Compound"; @@ -267,7 +267,7 @@ CmdPathShape::CmdPathShape() :Command("CAM_Shape") { sAppModule = "Path"; - sGroup = QT_TR_NOOP("Path"); + sGroup = QT_TR_NOOP("CAM"); sMenuText = QT_TR_NOOP("From Shape"); sToolTipText = QT_TR_NOOP("Creates a toolpath from a selected shape"); sWhatsThis = "CAM_Shape"; diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui index 572218d997ca..22dec12f54eb 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpDrillingEdit.ui @@ -170,6 +170,16 @@ + + + + Feed retract + + + G85: Retract from the hole at the given feedrate instead of rapid move + + + @@ -197,6 +207,7 @@ toolController peckEnabled + feedRetractEnabled dwellEnabled diff --git a/src/Mod/CAM/Init.py b/src/Mod/CAM/Init.py index 28d09d051d5b..b6ed99fad877 100644 --- a/src/Mod/CAM/Init.py +++ b/src/Mod/CAM/Init.py @@ -21,6 +21,23 @@ # * * # *************************************************************************** +# This code handles parameter groups following the CAM workbench renaming +# (PATH -> CAM). This code can be removed after the 1.0 release. + + +ParGrp = App.ParamGet("System parameter:Modules").GetGroup("Path") +if ParGrp.HasGroup("CAM"): + pass +elif ParGrp.HasGroup("Path"): + ParGrp.RenameGroup("Path", "CAM") + +ParGrp = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") +parent = ParGrp.Parent() +if parent.HasGroup("CAM"): + pass +elif parent.HasGroup("Path"): + result = parent.RenameGroup("Path", "CAM") + # Get the Parameter Group of this module ParGrp = App.ParamGet("System parameter:Modules").GetGroup("Path") diff --git a/src/Mod/CAM/Path/Base/Generator/drill.py b/src/Mod/CAM/Path/Base/Generator/drill.py index a8da08c4315b..73b43ddd485c 100644 --- a/src/Mod/CAM/Path/Base/Generator/drill.py +++ b/src/Mod/CAM/Path/Base/Generator/drill.py @@ -38,7 +38,7 @@ def generate( - edge, dwelltime=0.0, peckdepth=0.0, repeat=1, retractheight=None, chipBreak=False + edge, dwelltime=0.0, peckdepth=0.0, repeat=1, retractheight=None, chipBreak=False, feedRetract=False ): """ Generates Gcode for drilling a single hole. @@ -59,6 +59,11 @@ def generate( full retracts to clear chips from the hole. http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g73 + If feedRetract is True, the generator will produce G85 cycles which retract + the tool at the specified feed rate instead of performing a rapid move. + This is useful for boring or reaming operations. Peck or dwell cannot be used with feed retract. + http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g85 + """ startPoint = edge.Vertexes[0].Point endPoint = edge.Vertexes[1].Point @@ -73,6 +78,12 @@ def generate( if dwelltime > 0.0 and peckdepth > 0.0: raise ValueError("Peck and Dwell cannot be used together") + if dwelltime > 0.0 and feedRetract: + raise ValueError("Dwell and feed retract cannot be used together") + + if peckdepth > 0.0 and feedRetract: + raise ValueError("Peck and feed retract cannot be used together") + if repeat < 1: raise ValueError("repeat must be 1 or greater") @@ -112,7 +123,9 @@ def generate( if repeat > 1: cmdParams["L"] = repeat - if peckdepth == 0.0: + if feedRetract: + cmd = "G85" + elif peckdepth == 0.0: if dwelltime > 0.0: cmd = "G82" cmdParams["P"] = dwelltime diff --git a/src/Mod/CAM/Path/Op/Drilling.py b/src/Mod/CAM/Path/Op/Drilling.py index c0d44996489c..7d864a127dfc 100644 --- a/src/Mod/CAM/Path/Op/Drilling.py +++ b/src/Mod/CAM/Path/Op/Drilling.py @@ -105,6 +105,14 @@ def onDocumentRestored(self, obj): QT_TRANSLATE_NOOP("App::Property", "Use chipbreaking"), ) + if not hasattr(obj, "feedRetractEnabled"): + obj.addProperty( + "App::PropertyBool", "feedRetractEnabled", "Drill", + QT_TRANSLATE_NOOP( + "App::Property", + "Use G85 boring cycle with feed out") + ) + def initCircularHoleOperation(self, obj): """initCircularHoleOperation(obj) ... add drilling specific properties to obj.""" obj.addProperty( @@ -178,6 +186,12 @@ def initCircularHoleOperation(self, obj): "App::Property", "Apply G99 retraction: only retract to RetractHeight between holes in this operation") ) + obj.addProperty( + "App::PropertyBool", "feedRetractEnabled", "Drill", + QT_TRANSLATE_NOOP( + "App::Property", + "Use G85 boring cycle with feed out") + ) for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) @@ -276,6 +290,7 @@ def circularHoleExecute(self, obj, holes): repeat, obj.RetractHeight.Value, chipBreak=chipBreak, + feedRetract=obj.feedRetractEnabled ) except ValueError as e: # any targets that fail the generator are ignored diff --git a/src/Mod/CAM/Path/Op/Gui/Drilling.py b/src/Mod/CAM/Path/Op/Gui/Drilling.py index 64d01c3d67c8..aba21c970ba8 100644 --- a/src/Mod/CAM/Path/Op/Gui/Drilling.py +++ b/src/Mod/CAM/Path/Op/Gui/Drilling.py @@ -62,11 +62,20 @@ def initPage(self, obj): def registerSignalHandlers(self, obj): self.form.peckEnabled.toggled.connect(self.form.peckDepth.setEnabled) self.form.peckEnabled.toggled.connect(self.form.dwellEnabled.setDisabled) + self.form.peckEnabled.toggled.connect(self.form.feedRetractEnabled.setDisabled) self.form.peckEnabled.toggled.connect(self.setChipBreakControl) + self.form.feedRetractEnabled.toggled.connect(self.form.peckDepth.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.peckEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.dwellEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.chipBreakEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.form.peckEnabled.setDisabled) + self.form.feedRetractEnabled.toggled.connect(self.setChipBreakControl) + self.form.dwellEnabled.toggled.connect(self.form.dwellTime.setEnabled) self.form.dwellEnabled.toggled.connect(self.form.dwellTimelabel.setEnabled) self.form.dwellEnabled.toggled.connect(self.form.peckEnabled.setDisabled) + self.form.dwellEnabled.toggled.connect(self.form.feedRetractEnabled.setDisabled) self.form.dwellEnabled.toggled.connect(self.setChipBreakControl) self.form.peckRetractHeight.setEnabled(True) @@ -74,15 +83,18 @@ def registerSignalHandlers(self, obj): if self.form.peckEnabled.isChecked(): self.form.dwellEnabled.setEnabled(False) + self.form.feedRetractEnabled.setEnabled(False) self.form.peckDepth.setEnabled(True) self.form.peckDepthLabel.setEnabled(True) self.form.chipBreakEnabled.setEnabled(True) elif self.form.dwellEnabled.isChecked(): + self.form.feedRetractEnabled.setEnabled(False) self.form.peckEnabled.setEnabled(False) self.form.dwellTime.setEnabled(True) self.form.dwellTimelabel.setEnabled(True) self.form.chipBreakEnabled.setEnabled(False) else: + self.form.feedRetractEnabled.setEnabled(True) self.form.chipBreakEnabled.setEnabled(False) def setChipBreakControl(self): @@ -116,6 +128,8 @@ def getFields(self, obj): obj.DwellEnabled = self.form.dwellEnabled.isChecked() if obj.PeckEnabled != self.form.peckEnabled.isChecked(): obj.PeckEnabled = self.form.peckEnabled.isChecked() + if obj.feedRetractEnabled != self.form.feedRetractEnabled.isChecked(): + obj.feedRetractEnabled = self.form.feedRetractEnabled.isChecked() if obj.chipBreakEnabled != self.form.chipBreakEnabled.isChecked(): obj.chipBreakEnabled = self.form.chipBreakEnabled.isChecked() if obj.ExtraOffset != str(self.form.ExtraOffset.currentData()): @@ -154,6 +168,11 @@ def setFields(self, obj): else: self.form.chipBreakEnabled.setCheckState(QtCore.Qt.Unchecked) + if obj.feedRetractEnabled: + self.form.feedRetractEnabled.setCheckState(QtCore.Qt.Checked) + else: + self.form.feedRetractEnabled.setCheckState(QtCore.Qt.Unchecked) + self.selectInComboBox(obj.ExtraOffset, self.form.ExtraOffset) self.setupToolController(obj, self.form.toolController) @@ -173,6 +192,7 @@ def getSignalsForUpdate(self, obj): signals.append(self.form.coolantController.currentIndexChanged) signals.append(self.form.ExtraOffset.currentIndexChanged) signals.append(self.form.KeepToolDownEnabled.stateChanged) + signals.append(self.form.feedRetractEnabled.stateChanged) return signals diff --git a/src/Mod/CAM/Tests/TestPathDrillGenerator.py b/src/Mod/CAM/Tests/TestPathDrillGenerator.py index 5e1c008a8d23..bf707ff9df22 100644 --- a/src/Mod/CAM/Tests/TestPathDrillGenerator.py +++ b/src/Mod/CAM/Tests/TestPathDrillGenerator.py @@ -175,3 +175,69 @@ def test60(self): command = result[0] self.assertTrue(command.Name == "G73") + + def test70(self): + """Test feed retract enabled produces G85""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "feedRetract": True} + result = generator.generate(**args) + command = result[0] + + self.assertEqual(command.Name, "G85") + # G85 does not support peck or dwell + self.assertFalse(hasattr(command.Parameters, "Q")) + self.assertFalse(hasattr(command.Parameters, "P")) + + def test71(self): + """Test that dwell can be used when feed retract is not enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "dwelltime": 0.5, "feedRetract": False} + result = generator.generate(**args) + + command = result[0] + + self.assertEqual(command.Name, "G82") + self.assertEqual(command.Parameters["P"], 0.5) + + def test72(self): + """Test that peck can be used when feed retract is not enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "peckdepth": 1.0, "feedRetract": False} + result = generator.generate(**args) + + command = result[0] + + self.assertTrue(command.Name == "G83") + self.assertEqual(command.Parameters["Q"], 1.0) + + def test73(self): + """Test error when feed retract and dwell are enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "dwelltime": 1.0, "feedRetract": True} + self.assertRaises(ValueError, generator.generate, **args) + + def test74(self): + """Test error when feed retract and peck are enabled""" + v1 = FreeCAD.Vector(0, 0, 10) + v2 = FreeCAD.Vector(0, 0, 0) + + e = Part.makeLine(v1, v2) + + args = {"edge": e, "peckdepth": 1.0, "chipBreak": True, "feedRetract": True} + self.assertRaises(ValueError, generator.generate, **args) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index 661efbaeec87..0f76da33cb1c 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -70,6 +70,7 @@ get_spline_normal, getNormal, get_normal, + get_shape_normal, getRotation, isPlanar, is_planar, diff --git a/src/Mod/Draft/draftgeoutils/geometry.py b/src/Mod/Draft/draftgeoutils/geometry.py index bac846c3c578..9ad23f22d7c6 100644 --- a/src/Mod/Draft/draftgeoutils/geometry.py +++ b/src/Mod/Draft/draftgeoutils/geometry.py @@ -219,6 +219,65 @@ def get_spline_normal(edge, tol=-1): return None +def get_shape_normal(shape): + """Find the normal of a shape or list of points or colinear edges, if possible.""" + + # New function based on get_normal() drafted by @Roy_043 + # in discussion https://forum.freecad.org/viewtopic.php?p=717862#p717862 + # + # The normal would not be affected by the 3D view direction and flipped as + # get_normal would be. Placement of the Shape is taken into account in this + # new function instead : + # - Normal of a planar shape is usually bi-directional, this function return + # the one 'in the direction' of the shape's placement 'normal' (z-direction). + # As reference, shape.findPlane() favour positive axes, get_normal() + # favour the 'view direction' (obtained by getViewDirection() ). + # - Even when the Shape is an edge or colinear edges, its Placement is taken + # into account, and this function return one 'in the direction' of the + # shape's placement 'normal' (z-direction). + # https://forum.freecad.org/viewtopic.php?p=715850#p715850 + # The normal direction of a Draft wire (co-planar) or arc used to depends on + # the way the shape is constructed i.e. by vertexes in clockwise or + # anti-clockwise. This cryptic behaviour no longer matters. + + if shape.isNull(): + return None + + # Roy_043's remarks on the behavior of Shape.findPlane(): + # - https://forum.freecad.org/viewtopic.php?p=713993#p713993 + # Check if the shape is planar + # If True: check if the outer wire is closed: + # if True : Return a (plane with) normal based on the direction of the outer wire (CW or CCW). + # if False : ? Return a (plane with) normal that points towards positive global axes with a certain priority + # (probably? Z, Y and then X). + # If False: return None. + # + # Further remarks : + # - Tested Sketch with 2 colinear edges return a Plane with Axis + # - Tested Sketch with 1 edge return no Plane with no Axis + + shape_rot = shape.Placement.Rotation + if is_straight_line(shape): + start_edge = shape.Edges[0] + x_vec = start_edge.tangentAt(start_edge.FirstParameter) # Return vector is in global coordinate + local_x_vec = shape_rot.inverted().multVec(x_vec) # + local_rot = App.Rotation(local_x_vec, App.Vector(0, 1, 0), App.Vector(0, 0, 1), "XZY") + # see https://blog.freecad.org/2023/01/16/the-rotation-api-in-freecad/ for App.Rotation(vecx, vecy, vecz, string) + # discussion - https://forum.freecad.org/viewtopic.php?p=717738#p717738 + return shape_rot.multiply(local_rot).multVec(App.Vector(0, 0, 1)) + else: + plane = shape.findPlane() + + normal = plane.Axis + shape_normal = shape_rot.multVec(App.Vector(0, 0, 1)) + # Now, check the normal direction of the plane (plane.Axis). + # The final deduced normal should be 'in the direction' of the z-direction of the shape's placement, + # if plane.Axis is in the 'opposite' direction of the Z direction of the shape's placement, reverse it. + if normal.getAngle(shape_normal) > math.pi/2: + normal = normal.negative() + return normal + + def get_normal(shape, tol=-1): """Find the normal of a shape or list of points, if possible.""" @@ -329,6 +388,8 @@ def is_straight_line(shape, tol=-1): if len(shape.Edges) >= 1: start_edge = shape.Edges[0] dir_start_edge = start_edge.tangentAt(start_edge.FirstParameter) + point_start_edge = start_edge.firstVertex().Point + #set tolerance if tol <=0: err = shape.globalTolerance(tol) @@ -343,6 +404,25 @@ def is_straight_line(shape, tol=-1): # because sin(x) = x + O(x**3), for small angular deflection it's # enough use the cross product of directions (or dot with a normal) if (abs(edge.Length - first_point.distanceToPoint(last_point)) > err + + # https://forum.freecad.org/viewtopic.php?p=726101#p726101 + # Shape with parallel edges but not colinear used to return True + # by this function, below line added fixes this bug. + # Further remark on the below fix : + # - get_normal() use this function, may had created problems + # previously but not reported; on the other hand, this fix + # seems have no 'regression' created as get_normal use + # plane.Axis (with 3DView check). See get_shape_normal() + # which take into account shape's placement + # - other functions had been using this also : Keep In View to + # see if there is 'regression' : + # + # $ grep -rni "is_straight" ./ + # ./draftfunctions/upgrade.py + # ./draftgeoutils/geometry.py + # ./draftmake/make_wire.py + or first_point.distanceToLine(point_start_edge, dir_start_edge) > err \ + # or dir_start_edge.cross(dir_edge).Length > err): return False diff --git a/src/Mod/Drawing/Gui/TaskOrthoViews.cpp b/src/Mod/Drawing/Gui/TaskOrthoViews.cpp index f19c92a0649d..140ffc421832 100644 --- a/src/Mod/Drawing/Gui/TaskOrthoViews.cpp +++ b/src/Mod/Drawing/Gui/TaskOrthoViews.cpp @@ -26,6 +26,7 @@ #include #endif +#include #include #include #include @@ -176,8 +177,8 @@ void orthoview::set_data(int r_x, int r_y) rel_x = r_x; rel_y = r_y; - char label[15]; - sprintf(label, "Ortho_%i_%i", rel_x, rel_y); // label name for view, based on relative position + // label name for view, based on relative position + std::string label = fmt::sprintf("Ortho_%i_%i", rel_x, rel_y); this_view->Label.setValue(label); ortho = ((rel_x * rel_y) == 0); diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 726bc1b55ec1..91ed22746b46 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -418,7 +418,6 @@ SET(FemTestsZ88Main_SRCS ) SET(FemTestsZ88CcxcantiEleHex20_SRCS - femtest/data/z88/__init__.py femtest/data/z88/ccx_cantilever_ele_hexa20/51.txt femtest/data/z88/ccx_cantilever_ele_hexa20/z88.dyn femtest/data/z88/ccx_cantilever_ele_hexa20/z88elp.txt @@ -431,7 +430,6 @@ SET(FemTestsZ88CcxcantiEleHex20_SRCS ) SET(FemTestsZ88CcxcantiEleTria6_SRCS - femtest/data/z88/__init__.py femtest/data/z88/ccx_cantilever_ele_tria6/51.txt femtest/data/z88/ccx_cantilever_ele_tria6/z88.dyn femtest/data/z88/ccx_cantilever_ele_tria6/z88elp.txt @@ -444,7 +442,6 @@ SET(FemTestsZ88CcxcantiEleTria6_SRCS ) SET(FemTestsZ88Ccxcantifl_SRCS - femtest/data/z88/__init__.py femtest/data/z88/ccx_cantilever_faceload/51.txt femtest/data/z88/ccx_cantilever_faceload/z88.dyn femtest/data/z88/ccx_cantilever_faceload/z88elp.txt @@ -457,7 +454,6 @@ SET(FemTestsZ88Ccxcantifl_SRCS ) SET(FemTestsZ88Ccxcantinl_SRCS - femtest/data/z88/__init__.py femtest/data/z88/ccx_cantilever_nodeload/51.txt femtest/data/z88/ccx_cantilever_nodeload/z88.dyn femtest/data/z88/ccx_cantilever_nodeload/z88elp.txt diff --git a/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py b/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py index 70daab17bdf2..ef6a164404b0 100644 --- a/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py +++ b/src/Mod/Fem/femexamples/ccx_cantilever_beam_rect.py @@ -71,7 +71,7 @@ def get_explanation(header=""): CalculiX FEM max deflection: - 112.2 mm -- but the rotation seams 90 degree rotated (FIXME) +- but the rotation seems 90 degree rotated (FIXME) """ diff --git a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py index 6b17ba420394..fc3af04a129e 100644 --- a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py +++ b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py @@ -172,8 +172,8 @@ def setup(doc=None, solvertype="ccxtools"): # constraint contact con_contact = ObjectsFem.makeConstraintContact(doc, "ConstraintContact") con_contact.References = [ - (geom_obj, "Face7"), # first seams slave face, TODO proof in writer code! - (geom_obj, "Face3"), # second seams master face, TODO proof in writer code! + (geom_obj, "Face7"), # first seems slave face, TODO proof in writer code! + (geom_obj, "Face3"), # second seems master face, TODO proof in writer code! ] con_contact.Friction = False con_contact.Slope = "1000000.0 GPa/m" diff --git a/src/Mod/Fem/feminout/exportNastranMesh.py b/src/Mod/Fem/feminout/exportNastranMesh.py index c4c8991168b3..185ac219a2c9 100644 --- a/src/Mod/Fem/feminout/exportNastranMesh.py +++ b/src/Mod/Fem/feminout/exportNastranMesh.py @@ -132,7 +132,7 @@ def get_pynastran_mesh( # print(pynas_nodes) # elements - # Nastran seams to have the same node order as SMESH (FreeCAD) has + # Nastran seems to have the same node order as SMESH (FreeCAD) has # thus just write the nodes at once pynas_elements = "# elements cards\n" for element in femelement_table: diff --git a/src/Mod/Fem/feminout/importToolsFem.py b/src/Mod/Fem/feminout/importToolsFem.py index 8738daf9c18c..453ccae5d560 100644 --- a/src/Mod/Fem/feminout/importToolsFem.py +++ b/src/Mod/Fem/feminout/importToolsFem.py @@ -443,7 +443,7 @@ def fill_femresult_mechanical( nodes = len(disp.values()) for i in range(nodes): # how is this possible? An example is needed! - Console.PrintError("Temperature seams to have exptra nodes.\n") + Console.PrintError("Temperature seems to have extra nodes.\n") Temp_value = Temp_extra_nodes[i] Temp.append(Temp_value) res_obj.Temperature = list(map((lambda x: x), Temp)) diff --git a/src/Mod/Fem/femtools/ccxtools.py b/src/Mod/Fem/femtools/ccxtools.py index dae1621aff34..2b224f405499 100644 --- a/src/Mod/Fem/femtools/ccxtools.py +++ b/src/Mod/Fem/femtools/ccxtools.py @@ -595,14 +595,14 @@ def ccx_run(self): QtGui.QMessageBox.critical(None, "No CalculiX binary ccx", error_message) return progress_bar = FreeCAD.Base.ProgressIndicator() - progress_bar.start("Everything seams fine. CalculiX ccx will be executed ...", 0) + progress_bar.start("Everything seems fine. CalculiX ccx will be executed ...", 0) ret_code = self.start_ccx() self.finished.emit(ret_code) progress_bar.stop() if ret_code or self.ccx_stderr: if ret_code == 201 and self.solver.AnalysisType == "check": FreeCAD.Console.PrintMessage( - "It seams we run into NOANALYSIS problem, " + "It seems we run into NOANALYSIS problem, " "thus workaround for wrong exit code for *NOANALYSIS check " "and set ret_code to 0.\n" ) diff --git a/src/Mod/Material/CMakeLists.txt b/src/Mod/Material/CMakeLists.txt index 4b53cdac99dc..778f516b7c96 100644 --- a/src/Mod/Material/CMakeLists.txt +++ b/src/Mod/Material/CMakeLists.txt @@ -14,10 +14,13 @@ SET(MaterialScripts_Files importFCMat.py MaterialEditor.py TestMaterialsApp.py - Resources/ui/materials-editor.ui Templatematerial.yml ) +SET(Material_Ui_Files + Resources/ui/materials-editor.ui +) + # SOURCE_GROUP("MaterialScripts" FILES ${MaterialScripts_Files}) SET(MaterialTools_Files materialtools/__init__.py @@ -292,15 +295,17 @@ fc_target_copy_resource(MateriaTestLib ${MaterialTest_Files}) ADD_CUSTOM_TARGET(MaterialScripts ALL - SOURCES ${MaterialScripts_Files} ${Material_QRC_SRCS} + SOURCES ${MaterialScripts_Files} ${Material_Ui_Files} ${Material_QRC_SRCS} ) fc_target_copy_resource(MaterialScripts ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/Mod/Material - ${MaterialScripts_Files}) + ${MaterialScripts_Files} + ${Material_Ui_Files}) INSTALL(FILES ${MaterialScripts_Files} DESTINATION Mod/Material) +INSTALL(FILES ${Material_Ui_Files} DESTINATION Mod/Material/Resources/ui) ADD_CUSTOM_TARGET(MaterialToolsLib ALL SOURCES ${MaterialTools_Files} @@ -369,11 +374,6 @@ fc_target_copy_resource(MaterialModelLib ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Mod/Material/ ${MaterialModel_Files}) -INSTALL( - FILES ${Material_SRCS} ${Material_QRC_SRCS} - DESTINATION Mod/Material -) - INSTALL( FILES ${MaterialTest_Files} DESTINATION Mod/Material/materialtests diff --git a/src/Mod/Material/Gui/CMakeLists.txt b/src/Mod/Material/Gui/CMakeLists.txt index 8c250f8a7e66..9c3fb817ad77 100644 --- a/src/Mod/Material/Gui/CMakeLists.txt +++ b/src/Mod/Material/Gui/CMakeLists.txt @@ -121,7 +121,7 @@ SET(MatGuiImages Resources/images/default_image.png ) -add_library(MatGui SHARED ${MatGui_SRCS} ${MatGuiIcon_SVG}) +add_library(MatGui SHARED ${MatGui_SRCS} ${MatGuiIcon_SVG} ${MatGuiImages}) target_link_libraries(MatGui ${MatGui_LIBS}) SET_BIN_DIR(MatGui MatGui /Mod/Material) diff --git a/src/Mod/Mesh/App/WildMagic4/Wm4System.cpp b/src/Mod/Mesh/App/WildMagic4/Wm4System.cpp index 58266658255f..7370cc7aaed0 100644 --- a/src/Mod/Mesh/App/WildMagic4/Wm4System.cpp +++ b/src/Mod/Mesh/App/WildMagic4/Wm4System.cpp @@ -893,7 +893,7 @@ int System::Sprintf (char* acDst, size_t uiDstSize, const char* acFormat, ...) #ifdef WM4_USING_VC80 int iNumWritten = vsprintf_s(acDst,uiDstSize,acFormat,acArgs); #else - int iNumWritten = vsprintf(acDst,acFormat,acArgs); + int iNumWritten = vsnprintf(acDst,uiDstSize,acFormat,acArgs); #endif va_end(acArgs); diff --git a/src/Mod/Part/App/BSplineCurvePyImp.cpp b/src/Mod/Part/App/BSplineCurvePyImp.cpp index 537a054e1615..95955c22d951 100644 --- a/src/Mod/Part/App/BSplineCurvePyImp.cpp +++ b/src/Mod/Part/App/BSplineCurvePyImp.cpp @@ -828,8 +828,8 @@ PyObject* BSplineCurvePy::approximate(PyObject *args, PyObject *kwds) else c = GeomAbs_C2; - bool ok = this->getGeomBSplineCurvePtr()->approximate(tol3d, segMax, degMax, c); - return Py_BuildValue("O", (ok ? Py_True : Py_False)); + this->getGeomBSplineCurvePtr()->approximate(tol3d, segMax, degMax, c); + Py_Return; } // Approximate a list of points diff --git a/src/Mod/Part/App/FaceMaker.h b/src/Mod/Part/App/FaceMaker.h index 663911922dd9..78e0553a8b5d 100644 --- a/src/Mod/Part/App/FaceMaker.h +++ b/src/Mod/Part/App/FaceMaker.h @@ -51,11 +51,11 @@ namespace Part class PartExport FaceMaker: public BRepBuilderAPI_MakeShape, public Base::BaseClass { Q_DECLARE_TR_FUNCTIONS(FaceMaker) - TYPESYSTEM_HEADER(); + TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: FaceMaker() {} - virtual ~FaceMaker() {} + ~FaceMaker() override {} void addTopoShape(const TopoShape &s); void useTopoCompound(const TopoShape &comp); @@ -90,7 +90,7 @@ class PartExport FaceMaker: public BRepBuilderAPI_MakeShape, public Base::BaseCl #if OCC_VERSION_HEX >= 0x070600 void Build(const Message_ProgressRange& theRange = Message_ProgressRange()) override; #else - virtual void Build(); + void Build() override; #endif //fails to compile, huh! @@ -132,7 +132,7 @@ class PartExport FaceMaker: public BRepBuilderAPI_MakeShape, public Base::BaseCl */ class PartExport FaceMakerPublic : public FaceMaker { - TYPESYSTEM_HEADER(); + TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: virtual std::string getUserFriendlyName() const = 0; virtual std::string getBriefExplanation() const = 0; diff --git a/src/Mod/Part/App/Geometry.cpp b/src/Mod/Part/App/Geometry.cpp index 95c9c650d489..20519fe1eec0 100644 --- a/src/Mod/Part/App/Geometry.cpp +++ b/src/Mod/Part/App/Geometry.cpp @@ -65,6 +65,7 @@ # include # include # include +# include # include # include # include @@ -1713,28 +1714,115 @@ void GeomBSplineCurve::increaseDegree(int degree) * \param maxSegments * \param maxDegree * \param continuity - * \return true if the approximation succeeded, false otherwise */ -bool GeomBSplineCurve::approximate(double tol3d, int maxSegments, int maxDegree, int continuity) +void GeomBSplineCurve::approximate(double tol3d, int maxSegments, int maxDegree, + GeomAbs_Shape continuity) { try { - GeomAbs_Shape cont = GeomAbs_C0; - if (continuity >= 0 && continuity <= 6) - cont = static_cast(continuity); - GeomAdaptor_Curve adapt(myCurve); Handle(GeomAdaptor_HCurve) hCurve = new GeomAdaptor_HCurve(adapt); - Approx_Curve3d approx(hCurve, tol3d, cont, maxSegments, maxDegree); - if (approx.IsDone() && approx.HasResult()) { + Approx_Curve3d approx(hCurve, tol3d, continuity, maxSegments, maxDegree); + if (approx.IsDone()) { this->setHandle(approx.Curve()); - return true; + } + else if (approx.HasResult()) { + throw Standard_Failure("Approximation of B-Spline succeeded but is outside of tolerance"); + } + else { + throw Standard_Failure("Approximation of B-Spline failed"); } } catch (Standard_Failure& e) { - THROWM(Base::CADKernelError,e.GetMessageString()) + THROWM(Base::CADKernelError, e.GetMessageString()) } +} - return false; +void GeomBSplineCurve::approximate(const std::vector& pnts, + int minDegree, int maxDegree, + GeomAbs_Shape continuity, double tol3d) +{ + try { + TColgp_Array1OfPnt coords(1, static_cast(pnts.size())); + Standard_Integer index = 1; + for (const auto& it : pnts) { + coords(index++) = gp_Pnt(it.x, it.y, it.z); + } + + GeomAPI_PointsToBSpline fit(coords, minDegree, maxDegree, continuity, tol3d); + const Handle(Geom_BSplineCurve)& spline = fit.Curve(); + if (!spline.IsNull()) { + setHandle(spline); + } + else { + throw Standard_Failure("Failed to approximate B-Spline"); + } + } + catch (Standard_Failure& e) { + THROWM(Base::CADKernelError, e.GetMessageString()) + } +} + +void GeomBSplineCurve::approximate(const std::vector& pnts, + Approx_ParametrizationType parType, + int minDegree, int maxDegree, + GeomAbs_Shape continuity, double tol3d) +{ + try { + TColgp_Array1OfPnt coords(1, static_cast(pnts.size())); + Standard_Integer index = 1; + for (const auto& it : pnts) { + coords(index++) = gp_Pnt(it.x, it.y, it.z); + } + + GeomAPI_PointsToBSpline fit(coords, parType, minDegree, maxDegree, continuity, tol3d); + const Handle(Geom_BSplineCurve)& spline = fit.Curve(); + if (!spline.IsNull()) { + setHandle(spline); + } + else { + throw Standard_Failure("Failed to approximate B-Spline"); + } + } + catch (Standard_Failure& e) { + THROWM(Base::CADKernelError, e.GetMessageString()) + } +} + +/*! + * \brief GeomBSplineCurve::approximate + * \param pnts Points to fit + * \param weight1 Weight of curve length as smoothing criterion + * \param weight2 Weight of curvature as smoothing criterion + * \param weight3 Weight of torsion as smoothing criterion + * \param minDegree Minimum degree + * \param maxDegree Maximum degree + * \param continuity Continuity of the spline + * \param tol3d Tolerance to the data points + */ +void GeomBSplineCurve::approximate(const std::vector& pnts, + double weight1, double weight2, double weight3, + int maxDegree, GeomAbs_Shape continuity, double tol3d) +{ + try { + TColgp_Array1OfPnt coords(1, static_cast(pnts.size())); + Standard_Integer index = 1; + for (const auto& it : pnts) { + coords(index++) = gp_Pnt(it.x, it.y, it.z); + } + + GeomAPI_PointsToBSpline fit(coords, weight1, weight2, weight3, + maxDegree, continuity, tol3d); + const Handle(Geom_BSplineCurve)& spline = fit.Curve(); + if (!spline.IsNull()) { + setHandle(spline); + } + else { + throw Standard_Failure("Failed to approximate B-Spline"); + } + } + catch (Standard_Failure& e) { + THROWM(Base::CADKernelError, e.GetMessageString()) + } } void GeomBSplineCurve::increaseMultiplicity(int index, int multiplicity) @@ -1742,11 +1830,10 @@ void GeomBSplineCurve::increaseMultiplicity(int index, int multiplicity) try { Handle(Geom_BSplineCurve) curve = Handle(Geom_BSplineCurve)::DownCast(this->handle()); curve->IncreaseMultiplicity(index, multiplicity); - return; } catch (Standard_Failure& e) { THROWM(Base::CADKernelError,e.GetMessageString()) - } + } } void GeomBSplineCurve::insertKnot(double param, int multiplicity) diff --git a/src/Mod/Part/App/Geometry.h b/src/Mod/Part/App/Geometry.h index 183b5de2ba30..2525281493e0 100644 --- a/src/Mod/Part/App/Geometry.h +++ b/src/Mod/Part/App/Geometry.h @@ -24,6 +24,7 @@ #define PART_GEOMETRY_H #include +#include #include #include #include @@ -339,7 +340,15 @@ class PartExport GeomBSplineCurve : public GeomBoundedCurve std::list toBiArcs(double tolerance) const; void increaseDegree(int degree); - bool approximate(double tol3d, int maxSegments, int maxDegree, int continuity); + void approximate(double tol3d, int maxSegments, int maxDegree, GeomAbs_Shape continuity); + void approximate(const std::vector& pnts, int minDegree = 3, int maxDegree = 8, + GeomAbs_Shape continuity = GeomAbs_C2, double tol3d = 1.0e-3); + void approximate(const std::vector& pnts, Approx_ParametrizationType parType, + int minDegree = 3, int maxDegree = 8, + GeomAbs_Shape continuity = GeomAbs_C2, double tol3d = 1.0e-3); + void approximate(const std::vector& pnts, + double weight1, double weight2, double weight3, int maxDegree = 8, + GeomAbs_Shape continuity = GeomAbs_C2, double tol3d = 1.0e-3); void increaseMultiplicity(int index, int multiplicity); void insertKnot(double param, int multiplicity); @@ -353,7 +362,7 @@ class PartExport GeomBSplineCurve : public GeomBoundedCurve void Save(Base::Writer &/*writer*/) const override; void Restore(Base::XMLReader &/*reader*/) override; // Base implementer ---------------------------- - PyObject *getPyObject(void) override; + PyObject *getPyObject() override; bool isSame(const Geometry &other, double tol, double atol) const override; diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index 9c0cb0088c19..16cb49dbfe96 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -1186,6 +1186,7 @@ const App::PropertyComplexGeoData* Feature::getPropertyOfGeometry() const bool Feature::isElementMappingDisabled(App::PropertyContainer* container) { + (void)container; #ifdef FC_USE_TNP_FIX return false; #else diff --git a/src/Mod/Part/App/WireJoiner.cpp b/src/Mod/Part/App/WireJoiner.cpp index 42cc2a38e5b7..08b133cfb278 100644 --- a/src/Mod/Part/App/WireJoiner.cpp +++ b/src/Mod/Part/App/WireJoiner.cpp @@ -786,7 +786,11 @@ class WireJoiner::WireJoinerP { for (auto vit = vmap.qbegin(bgi::nearest(p1, INT_MAX)); vit != vmap.qend(); ++vit) { auto& vinfo = *vit; if (canShowShape()) { +#if OCC_VERSION_HEX < 0x070800 FC_MSG("addcheck " << vinfo.edge().HashCode(INT_MAX)); +#else + FC_MSG("addcheck " << std::hash {}(vinfo.edge())); +#endif } double d1 = vinfo.pt().SquareDistance(p1); if (d1 >= tol) { @@ -1724,9 +1728,7 @@ class WireJoiner::WireJoinerP { if (FC_LOG_INSTANCE.level() <= FC_LOGLEVEL_TRACE) { return; } - int idx = 0; for (auto &info : edges) { - ++idx; if (auto wire = info.wireInfo.get()) { // Originally here there was a call to the precompiler macro assertCheck(), which @@ -2712,7 +2714,12 @@ class WireJoiner::WireJoinerP { { FC_MSG("init:"); for (const auto& shape : sourceEdges) { +#if OCC_VERSION_HEX < 0x070800 FC_MSG(shape.getShape().TShape().get() << ", " << shape.getShape().HashCode(INT_MAX)); +#else + FC_MSG(shape.getShape().TShape().get() + << ", " << std::hash {}(shape.getShape())); +#endif } printHistory(aHistory, sourceEdges); printHistory(newHistory, inputEdges); @@ -2726,7 +2733,11 @@ class WireJoiner::WireJoinerP { FC_MSG("final:"); for (int i = 1; i <= wireData->NbEdges(); ++i) { auto shape = wireData->Edge(i); +#if OCC_VERSION_HEX < 0x070800 FC_MSG(shape.TShape().get() << ", " << shape.HashCode(INT_MAX)); +#else + FC_MSG(shape.TShape().get() << ", " << std::hash {}(shape)); +#endif } } @@ -2786,9 +2797,15 @@ class WireJoiner::WireJoinerP { { for (TopTools_ListIteratorOfListOfShape it(hist->Modified(shape.getShape())); it.More(); it.Next()) { +#if OCC_VERSION_HEX < 0x070800 FC_MSG(shape.getShape().TShape().get() << ", " << shape.getShape().HashCode(INT_MAX) << " -> " << it.Value().TShape().get() << ", " << it.Value().HashCode(INT_MAX)); +#else + FC_MSG(shape.getShape().TShape().get() + << ", " << std::hash {}(shape.getShape()) << " -> " + << it.Value().TShape().get() << ", " << std::hash {}(it.Value())); +#endif } } diff --git a/src/Mod/PartDesign/CMakeLists.txt b/src/Mod/PartDesign/CMakeLists.txt index a16b52d5cadc..a4cfe1409418 100644 --- a/src/Mod/PartDesign/CMakeLists.txt +++ b/src/Mod/PartDesign/CMakeLists.txt @@ -53,6 +53,7 @@ set(PartDesign_TestScripts PartDesignTests/TestChamfer.py PartDesignTests/TestDraft.py PartDesignTests/TestThickness.py + PartDesignTests/TestTopologicalNamingProblem.py PartDesignTests/TestInvoluteGear.py PartDesignTests/TestHelix.py ) diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py new file mode 100644 index 000000000000..d659e80536e7 --- /dev/null +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2024 bgbsww@gmail.com * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, but * +# * WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# *************************************************************************** + +""" Tests related to the Topological Naming Problem """ + +import unittest + +import FreeCAD as App +# import Part +# import Sketcher +import TestSketcherApp + + +class TestTopologicalNamingProblem(unittest.TestCase): + """ Tests related to the Topological Naming Problem """ + + # pylint: disable=attribute-defined-outside-init + + def setUp(self): + """ Create a document for the test suite """ + self.Doc = App.newDocument("PartDesignTestTNP") + + def testPadsOnBaseObject(self): + """ Simple TNP test case + By creating three Pads dependent on each other in succession, and then moving the + middle one we can determine if the last one breaks because of a broken reference + to the middle one. This is the essence of a TNP. Pretty much a duplicate of the + steps at https://wiki.freecad.org/Topological_naming_problem """ + + # Arrange + self.Body = self.Doc.addObject('PartDesign::Body', 'Body') + # Make first offset cube Pad + self.PadSketch = self.Doc.addObject('Sketcher::SketchObject', 'SketchPad') + self.Body.addObject(self.PadSketch) + TestSketcherApp.CreateRectangleSketch(self.PadSketch, (0, 0), (1, 1)) + self.Doc.recompute() + self.Pad = self.Doc.addObject("PartDesign::Pad", "Pad") + self.Body.addObject(self.Pad) + self.Pad.Profile = self.PadSketch + self.Pad.Length = 1 + self.Doc.recompute() + + # Attach a second pad to the top of the first. + self.PadSketch1 = self.Doc.addObject('Sketcher::SketchObject', 'SketchPad1') + self.Body.addObject(self.PadSketch1) + self.PadSketch1.MapMode = 'FlatFace' + self.PadSketch1.AttachmentSupport = [(self.Doc.getObject('Pad'), 'Face6')] + TestSketcherApp.CreateRectangleSketch(self.PadSketch1, (0, 0), (1, 1)) + self.Doc.recompute() + self.Pad1 = self.Doc.addObject("PartDesign::Pad", "Pad1") + self.Body.addObject(self.Pad1) + self.Pad1.Profile = self.PadSketch1 + self.Pad1.Length = 1 + self.Doc.recompute() + + # Attach a third pad to the top of the second. + self.PadSketch2 = self.Doc.addObject('Sketcher::SketchObject', 'SketchPad2') + self.Body.addObject(self.PadSketch2) + self.PadSketch2.MapMode = 'FlatFace' + self.PadSketch2.AttachmentSupport = [(self.Doc.getObject('Pad1'), 'Face6')] + TestSketcherApp.CreateRectangleSketch(self.PadSketch2, (0, 0), (1, 1)) + self.Doc.recompute() + self.Pad2 = self.Doc.addObject("PartDesign::Pad", "Pad2") + self.Body.addObject(self.Pad2) + self.Pad2.Profile = self.PadSketch2 + self.Pad2.Length = 1 + self.Doc.recompute() + + # Assert everything is valid + self.assertTrue(self.Pad.isValid()) + self.assertTrue(self.Pad1.isValid()) + self.assertTrue(self.Pad2.isValid()) + + # Act + # Move the second pad ( the sketch attachment point ) + self.PadSketch1.AttachmentOffset = App.Placement( + App.Vector(0.5000000000, 0.0000000000, 0.0000000000), + App.Rotation(0.0000000000, 0.0000000000, 0.0000000000)) + self.Doc.recompute() + + # Assert everything is still valid. + self.assertTrue(self.Pad.isValid()) + self.assertTrue(self.Pad1.isValid()) + + # Todo switch to actually asserting this and remove the printed lines as soon as + # the main branch is capable of passing this test. + # self.assertTrue(self.Pad2.isValid()) + if self.Pad2.isValid(): + print("Topological Naming Problem is not present.") + else: + print("TOPOLOGICAL NAMING PROBLEM IS PRESENT.") + + # def testFutureStuff(self): + # self.Doc.getObject('Body').newObject('Sketcher::SketchObject', 'Sketch') + # geoList = [] + # geoList.append(Part.LineSegment(App.Vector(0,0,0),App.Vector(20,0,0))) + # geoList.append(Part.LineSegment(App.Vector(20,0,0),App.Vector(20,10,0))) + # geoList.append(Part.LineSegment(App.Vector(20,10,0),App.Vector(10,10,0))) + # geoList.append(Part.LineSegment(App.Vector(10,10,0),App.Vector(10,20,0))) + # geoList.append(Part.LineSegment(App.Vector(10,20,0),App.Vector(0,20,0))) + # geoList.append(Part.LineSegment(App.Vector(0,20,0),App.Vector(0,0,0))) + # self.Doc.getObject('Sketch').addGeometry(geoList,False) + # conList = [] + # conList.append(Sketcher.Constraint('Coincident',0,2,1,1)) + # conList.append(Sketcher.Constraint('Coincident',1,2,2,1)) + # conList.append(Sketcher.Constraint('Coincident',2,2,3,1)) + # conList.append(Sketcher.Constraint('Coincident',3,2,4,1)) + # conList.append(Sketcher.Constraint('Coincident',4,2,5,1)) + # conList.append(Sketcher.Constraint('Coincident',5,2,0,1)) + # conList.append(Sketcher.Constraint('Horizontal',0)) + # conList.append(Sketcher.Constraint('Horizontal',2)) + # conList.append(Sketcher.Constraint('Horizontal',4)) + # conList.append(Sketcher.Constraint('Vertical',1)) + # conList.append(Sketcher.Constraint('Vertical',3)) + # conList.append(Sketcher.Constraint('Vertical',5)) + # self.Doc.getObject('Sketch').addConstraint(conList) + # del geoList, conList + # self.Doc.recompute() + # self.Doc.getObject('Body').newObject('PartDesign::Pad','Pad002') + # self.Doc.getObject('Pad002').Length = 10 + # self.Doc.Pad002.Profile = self.Doc.Sketch + # self.Doc.recompute() + # self.Doc.getObject('Pad002').ReferenceAxis = (self.Doc.getObject('Sketch'),['N_Axis']) + # self.Doc.getObject('Sketch').Visibility = False + # self.Doc.recompute() + + def tearDown(self): + """ Close our test document """ + App.closeDocument("PartDesignTestTNP") diff --git a/src/Mod/PartDesign/PartDesignTests/__init__.py b/src/Mod/PartDesign/PartDesignTests/__init__.py index a349530334f7..ffd099a4447a 100644 --- a/src/Mod/PartDesign/PartDesignTests/__init__.py +++ b/src/Mod/PartDesign/PartDesignTests/__init__.py @@ -18,3 +18,4 @@ from . import TestRevolve from . import TestShapeBinder from . import TestThickness +from . import TestTopologicalNamingProblem diff --git a/src/Mod/PartDesign/TestPartDesignApp.py b/src/Mod/PartDesign/TestPartDesignApp.py index 3668f646140a..36485bfff1df 100644 --- a/src/Mod/PartDesign/TestPartDesignApp.py +++ b/src/Mod/PartDesign/TestPartDesignApp.py @@ -55,3 +55,6 @@ # extras from PartDesignTests.TestInvoluteGear import TestInvoluteGear + +# Topological naming problem +from PartDesignTests.TestTopologicalNamingProblem import TestTopologicalNamingProblem diff --git a/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp b/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp index 8403212f3880..311cf73e8b81 100644 --- a/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp +++ b/src/Mod/ReverseEngineering/App/AppReverseEngineering.cpp @@ -74,6 +74,7 @@ class Module : public Py::ExtensionModule public: Module() : Py::ExtensionModule("ReverseEngineering") { + add_keyword_method("approxCurve", &Module::approxCurve, "Approximate curve"); add_keyword_method("approxSurface",&Module::approxSurface, "approxSurface(Points, UDegree=3, VDegree=3, NbUPoles=6, NbVPoles=6,\n" "Smooth=True, Weight=0.1, Grad=1.0, Bend=0.0, Curv=0.0\n" @@ -159,6 +160,169 @@ class Module : public Py::ExtensionModule } private: + static std::vector getPoints(PyObject* pts, bool closed) + { + std::vector data; + if (PyObject_TypeCheck(pts, &(Points::PointsPy::Type))) { + std::vector normal; + auto pypts = static_cast(pts); + Points::PointKernel* points = pypts->getPointKernelPtr(); + points->getPoints(data, normal, 0.0); + } + else { + Py::Sequence l(pts); + data.reserve(l.size()); + for (Py::Sequence::iterator it = l.begin(); it != l.end(); ++it) { + Py::Tuple t(*it); + data.emplace_back( + Py::Float(t.getItem(0)), + Py::Float(t.getItem(1)), + Py::Float(t.getItem(2)) + ); + } + } + + if (closed) { + if (!data.empty()) { + data.push_back(data.front()); + } + } + + return data; + } + + static PyObject* approx1(const Py::Tuple& args, const Py::Dict& kwds) + { + PyObject* pts {}; + PyObject* closed = Py_False; + int minDegree = 3; // NOLINT + int maxDegree = 8; // NOLINT + int cont = int(GeomAbs_C2); + double tol3d = 1.0e-3; // NOLINT + + static const std::array kwds_approx{"Points", + "Closed", + "MinDegree", + "MaxDegree", + "Continuity", + "Tolerance", + nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|O!iiid", kwds_approx, + &pts, &PyBool_Type, &closed, &minDegree, + &maxDegree, &cont, &tol3d)) { + return nullptr; + } + + std::vector data = getPoints(pts, Base::asBoolean(closed)); + + Part::GeomBSplineCurve curve; + curve.approximate(data, minDegree, maxDegree, GeomAbs_Shape(cont), tol3d); + return curve.getPyObject(); + } + + static PyObject* approx2(const Py::Tuple& args, const Py::Dict& kwds) + { + PyObject* pts {}; + char* parType {}; + PyObject* closed = Py_False; + int minDegree = 3; // NOLINT + int maxDegree = 8; // NOLINT + int cont = int(GeomAbs_C2); + double tol3d = 1.0e-3; // NOLINT + + static const std::array kwds_approx{"Points", + "ParametrizationType", + "Closed", + "MinDegree", + "MaxDegree", + "Continuity", + "Tolerance", + nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "Os|O!iiid", kwds_approx, + &pts, &parType, &PyBool_Type, &closed, &minDegree, + &maxDegree, &cont, &tol3d)) { + return nullptr; + } + + std::vector data = getPoints(pts, Base::asBoolean(closed)); + + Approx_ParametrizationType pt {Approx_ChordLength}; + std::string pstr = parType; + if (pstr == "Uniform") { + pt = Approx_IsoParametric; + } + else if (pstr == "Centripetal") { + pt = Approx_Centripetal; + } + + Part::GeomBSplineCurve curve; + curve.approximate(data, pt, minDegree, maxDegree, GeomAbs_Shape(cont), tol3d); + return curve.getPyObject(); + } + + static PyObject* approx3(const Py::Tuple& args, const Py::Dict& kwds) + { + PyObject* pts {}; + double weight1 {}; + double weight2 {}; + double weight3 {}; + PyObject* closed = Py_False; + int maxDegree = 8; // NOLINT + int cont = int(GeomAbs_C2); + double tol3d = 1.0e-3; // NOLINT + + static const std::array kwds_approx{"Points", + "Weight1", + "Weight2", + "Weight3", + "Closed", + "MaxDegree", + "Continuity", + "Tolerance", + nullptr}; + if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "Oddd|O!iid", kwds_approx, + &pts, &weight1, &weight2, &weight3, + &PyBool_Type, &closed, + &maxDegree, &cont, &tol3d)) { + return nullptr; + } + + std::vector data = getPoints(pts, Base::asBoolean(closed)); + + Part::GeomBSplineCurve curve; + curve.approximate(data, weight1, weight2, weight3, maxDegree, GeomAbs_Shape(cont), tol3d); + return curve.getPyObject(); + } + + Py::Object approxCurve(const Py::Tuple& args, const Py::Dict& kwds) + { + try { + using approxFunc = std::function; + + std::vector funcs; + funcs.emplace_back(approx3); + funcs.emplace_back(approx2); + funcs.emplace_back(approx1); + + for (const auto& func : funcs) { + if (PyObject* py = func(args, kwds)) { + return Py::asObject(py); + } + + PyErr_Clear(); + } + + throw Py::ValueError("Wrong arguments ReverseEngineering.approxCurve()"); + } + catch (const Base::Exception& e) { + std::string msg = e.what(); + if (msg.empty()) { + msg = "ReverseEngineering.approxCurve() failed"; + } + throw Py::RuntimeError(msg); + } + } + Py::Object approxSurface(const Py::Tuple& args, const Py::Dict& kwds) { PyObject *o; diff --git a/src/Mod/ReverseEngineering/Gui/CMakeLists.txt b/src/Mod/ReverseEngineering/Gui/CMakeLists.txt index 2bf3d465a047..1cdcd8944cc9 100644 --- a/src/Mod/ReverseEngineering/Gui/CMakeLists.txt +++ b/src/Mod/ReverseEngineering/Gui/CMakeLists.txt @@ -23,6 +23,7 @@ qt_create_resource_file(${ReverseEngineering_TR_QRC} ${QM_SRCS}) qt_add_resources(ReenGui_QRC_SRCS Resources/ReverseEngineering.qrc ${ReverseEngineering_TR_QRC}) set(Dialogs_UIC_SRCS + FitBSplineCurve.ui FitBSplineSurface.ui Poisson.ui Segmentation.ui @@ -32,6 +33,8 @@ set(Dialogs_UIC_SRCS SET(Dialogs_SRCS ${Dialogs_UIC_HDRS} ${Dialogs_UIC_SRCS} + FitBSplineCurve.cpp + FitBSplineCurve.h FitBSplineSurface.cpp FitBSplineSurface.h Poisson.cpp diff --git a/src/Mod/ReverseEngineering/Gui/Command.cpp b/src/Mod/ReverseEngineering/Gui/Command.cpp index aa46f9f88894..ad3b390e68e2 100644 --- a/src/Mod/ReverseEngineering/Gui/Command.cpp +++ b/src/Mod/ReverseEngineering/Gui/Command.cpp @@ -51,6 +51,7 @@ #include #include +#include "FitBSplineCurve.h" #include "FitBSplineSurface.h" #include "Poisson.h" #include "Segmentation.h" @@ -59,6 +60,39 @@ using namespace std; +DEF_STD_CMD_A(CmdApproxCurve) + +CmdApproxCurve::CmdApproxCurve() + : Command("Reen_ApproxCurve") +{ + sAppModule = "Reen"; + sGroup = QT_TR_NOOP("Reverse Engineering"); + sMenuText = QT_TR_NOOP("Approximate B-spline curve..."); + sToolTipText = QT_TR_NOOP("Approximate a B-spline curve"); + sWhatsThis = "Reen_ApproxCurve"; + sStatusTip = sToolTipText; +} + +void CmdApproxCurve::activated(int) +{ + App::DocumentObjectT objT; + auto obj = Gui::Selection().getObjectsOfType(App::GeoFeature::getClassTypeId()); + if (obj.size() != 1 || !(obj.at(0)->isDerivedFrom(Points::Feature::getClassTypeId()))) { + QMessageBox::warning(Gui::getMainWindow(), + qApp->translate("Reen_ApproxSurface", "Wrong selection"), + qApp->translate("Reen_ApproxSurface", "Please select a point cloud.")); + return; + } + + objT = obj.front(); + Gui::Control().showDialog(new ReenGui::TaskFitBSplineCurve(objT)); +} + +bool CmdApproxCurve::isActive() +{ + return (hasActiveDocument() && !Gui::Control().activeDialog()); +} + DEF_STD_CMD_A(CmdApproxSurface) CmdApproxSurface::CmdApproxSurface() @@ -648,6 +682,7 @@ bool CmdViewTriangulation::isActive() void CreateReverseEngineeringCommands() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); + rcCmdMgr.addCommand(new CmdApproxCurve()); rcCmdMgr.addCommand(new CmdApproxSurface()); rcCmdMgr.addCommand(new CmdApproxPlane()); rcCmdMgr.addCommand(new CmdApproxCylinder()); diff --git a/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.cpp b/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.cpp new file mode 100644 index 000000000000..d411ff4c348e --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.cpp @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2024 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#endif + +#include +#include + +#include "FitBSplineCurve.h" +#include "ui_FitBSplineCurve.h" + + +using namespace ReenGui; + +class FitBSplineCurveWidget::Private +{ +public: + Ui_FitBSplineCurve ui {}; + App::DocumentObjectT obj {}; +}; + +/* TRANSLATOR ReenGui::FitBSplineCurveWidget */ + +FitBSplineCurveWidget::FitBSplineCurveWidget(const App::DocumentObjectT& obj, QWidget* parent) + : d(new Private()) +{ + Q_UNUSED(parent); + d->ui.setupUi(this); + d->obj = obj; + + // clang-format off + connect(d->ui.checkBox, &QCheckBox::toggled, + this, &FitBSplineCurveWidget::toggleParametrizationType); + connect(d->ui.groupBoxSmooth, &QGroupBox::toggled, + this, &FitBSplineCurveWidget::toggleSmoothing); + // clang-format on +} + +FitBSplineCurveWidget::~FitBSplineCurveWidget() +{ + delete d; +} + +void FitBSplineCurveWidget::toggleParametrizationType(bool on) +{ + d->ui.paramType->setEnabled(on); + if (on) { + d->ui.groupBoxSmooth->setChecked(false); + } +} + +void FitBSplineCurveWidget::toggleSmoothing(bool on) +{ + if (on) { + d->ui.checkBox->setChecked(false); + d->ui.paramType->setEnabled(false); + } +} + +bool FitBSplineCurveWidget::accept() +{ + try { + tryAccept(); + } + catch (const Base::Exception& e) { + Gui::Command::abortCommand(); + QMessageBox::warning(this, tr("Input error"), QString::fromLatin1(e.what())); + return false; + } + + return true; +} + +void FitBSplineCurveWidget::tryAccept() +{ + QString document = QString::fromStdString(d->obj.getDocumentPython()); + QString object = QString::fromStdString(d->obj.getObjectPython()); + + QStringList arguments; + arguments.append( + QString::fromLatin1("Points=getattr(%1, %1.getPropertyNameOfGeometry())").arg(object)); + if (!d->ui.groupBoxSmooth->isChecked()) { + arguments.append(QString::fromLatin1("MinDegree = %1").arg(d->ui.degreeMin->value())); + } + arguments.append(QString::fromLatin1("MaxDegree = %1").arg(d->ui.degreeMax->value())); + arguments.append(QString::fromLatin1("Continuity = %1").arg(d->ui.continuity->currentIndex())); + if (d->ui.checkBoxClosed->isChecked()) { + arguments.append(QString::fromLatin1("Closed = True")); + } + else { + arguments.append(QString::fromLatin1("Closed = False")); + } + if (d->ui.checkBox->isChecked()) { + int index = d->ui.paramType->currentIndex(); + arguments.append(QString::fromLatin1("ParametrizationType = %1").arg(index)); + } + if (d->ui.groupBoxSmooth->isChecked()) { + arguments.append(QString::fromLatin1("Weight1 = %1").arg(d->ui.curveLength->value())); + arguments.append(QString::fromLatin1("Weight2 = %1").arg(d->ui.curvature->value())); + arguments.append(QString::fromLatin1("Weight3 = %1").arg(d->ui.torsion->value())); + } + + QString argument = arguments.join(QLatin1String(", ")); + QString command = QString::fromLatin1("%1.addObject(\"Part::Spline\", \"Spline\").Shape = " + "ReverseEngineering.approxCurve(%2).toShape()") + .arg(document, argument); + + tryCommand(command); +} + +void FitBSplineCurveWidget::exeCommand(const QString& cmd) +{ + Gui::WaitCursor wc; + Gui::Command::addModule(Gui::Command::App, "ReverseEngineering"); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Fit B-Spline")); + Gui::Command::runCommand(Gui::Command::Doc, cmd.toLatin1()); + Gui::Command::commitCommand(); + Gui::Command::updateActive(); +} + +void FitBSplineCurveWidget::tryCommand(const QString& cmd) +{ + try { + exeCommand(cmd); + } + catch (const Base::Exception& e) { + Gui::Command::abortCommand(); + e.ReportException(); + } +} + + +void FitBSplineCurveWidget::changeEvent(QEvent* e) +{ + QWidget::changeEvent(e); + if (e->type() == QEvent::LanguageChange) { + d->ui.retranslateUi(this); + } +} + + +/* TRANSLATOR ReenGui::TaskFitBSplineCurve */ + +TaskFitBSplineCurve::TaskFitBSplineCurve(const App::DocumentObjectT& obj) + : widget {new FitBSplineCurveWidget(obj)} +{ + addTaskBox(widget); +} + +void TaskFitBSplineCurve::open() +{} + +bool TaskFitBSplineCurve::accept() +{ + return widget->accept(); +} + +#include "moc_FitBSplineCurve.cpp" diff --git a/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.h b/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.h new file mode 100644 index 000000000000..ce340e45238c --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2024 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef REENGUI_FITBSPLINECURVE_H +#define REENGUI_FITBSPLINECURVE_H + +#include +#include + + +namespace ReenGui +{ + +class FitBSplineCurveWidget: public QWidget +{ + Q_OBJECT + +public: + explicit FitBSplineCurveWidget(const App::DocumentObjectT&, QWidget* parent = nullptr); + ~FitBSplineCurveWidget() override; + + bool accept(); + +protected: + void changeEvent(QEvent* e) override; + +private: + void toggleParametrizationType(bool on); + void toggleSmoothing(bool on); + void tryAccept(); + void exeCommand(const QString&); + void tryCommand(const QString&); + +private: + class Private; + Private* d; +}; + +class TaskFitBSplineCurve: public Gui::TaskView::TaskDialog +{ + Q_OBJECT + +public: + explicit TaskFitBSplineCurve(const App::DocumentObjectT&); + +public: + void open() override; + bool accept() override; + + QDialogButtonBox::StandardButtons getStandardButtons() const override + { + return QDialogButtonBox::Ok | QDialogButtonBox::Cancel; + } + +private: + FitBSplineCurveWidget* widget; +}; + +} // namespace ReenGui + +#endif // REENGUI_FITBSPLINECURVE_H diff --git a/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.ui b/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.ui new file mode 100644 index 000000000000..e2e7e51875a6 --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/FitBSplineCurve.ui @@ -0,0 +1,249 @@ + + + ReenGui::FitBSplineCurve + + + + 0 + 0 + 360 + 375 + + + + Fit B-spline surface + + + + + + Parameters + + + + + + Maximum degree + + + + + + + false + + + + Chord length + + + + + Centripetal + + + + + Iso-Parametric + + + + + + + + Continuity + + + + + + + 2 + + + 11 + + + 6 + + + + + + + 1 + + + 11 + + + 2 + + + + + + + Parametrization type + + + + + + + + C0 + + + + + G1 + + + + + C1 + + + + + G2 + + + + + C2 + + + + + C3 + + + + + CN + + + + + + + + Minimum degree + + + + + + + Closed curve + + + + + + + + + + Smoothing + + + true + + + + + + Torsion + + + + + + + Curve length + + + + + + + Curvature + + + + + + + 1.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + 1.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + 1.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + + + + Qt::Vertical + + + + 20 + 15 + + + + + + + + degreeMin + degreeMax + continuity + checkBox + paramType + checkBoxClosed + groupBoxSmooth + curveLength + curvature + torsion + + + + diff --git a/src/Mod/ReverseEngineering/Gui/Workbench.cpp b/src/Mod/ReverseEngineering/Gui/Workbench.cpp index fcc098aba7a7..662f9fee6dc0 100644 --- a/src/Mod/ReverseEngineering/Gui/Workbench.cpp +++ b/src/Mod/ReverseEngineering/Gui/Workbench.cpp @@ -74,7 +74,8 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Reen_ApproxSphere" << "Reen_ApproxPolynomial" << "Separator" - << "Reen_ApproxSurface"; + << "Reen_ApproxSurface" + << "Reen_ApproxCurve"; *reen << approx; return root; diff --git a/src/Mod/Robot/CMakeLists.txt b/src/Mod/Robot/CMakeLists.txt index b6605ac7d6a4..646b7320bb1e 100644 --- a/src/Mod/Robot/CMakeLists.txt +++ b/src/Mod/Robot/CMakeLists.txt @@ -63,4 +63,5 @@ INSTALL( ${CMAKE_INSTALL_DATADIR}/Mod/Robot PATTERN "Makefile*" EXCLUDE PATTERN "*.pdf" EXCLUDE + PATTERN "testprog.*" EXCLUDE ) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 91a11612ef93..2d1e1b764158 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -6508,9 +6508,7 @@ bool SketchObject::decreaseBSplineDegree(int GeoId, int degreedecrement /*= 1*/) int maxdegree = cdegree - degreedecrement; if (maxdegree == 0) return false; - bool ok = bspline->approximate(Precision::Confusion(), 20, maxdegree, 0); - if (!ok) - return false; + bspline->approximate(Precision::Confusion(), 20, maxdegree, GeomAbs_C0); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h index d581a446e669..18c854e0da39 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h @@ -249,7 +249,7 @@ class DrawSketchHandlerSymmetry: public DrawSketchHandlerSymmetryBase Obj->getSymmetric(listOfGeoIds, dummy1, dummy2, refGeoId, refPosId); for (auto* geo : symGeos) { - ShapeGeometry.emplace_back(std::move(std::unique_ptr(geo))); + ShapeGeometry.emplace_back(geo); } } } diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 2df37501bc99..2f50450ca1c5 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -22,6 +22,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include #include #include #include @@ -1747,6 +1748,8 @@ void ViewProviderSketch::moveConstraint(Sketcher::Constraint* Constr, int constN assert(int(geomlist.size()) == extGeoCount + intGeoCount); assert((Constr->First >= -extGeoCount && Constr->First < intGeoCount) || Constr->First != GeoEnum::GeoUndef); + boost::ignore_unused(intGeoCount); + boost::ignore_unused(extGeoCount); #endif if (Constr->Type == Distance || Constr->Type == DistanceX || Constr->Type == DistanceY diff --git a/src/Mod/Spreadsheet/importXLSX.py b/src/Mod/Spreadsheet/importXLSX.py index 6ed01a74b850..2b8cd5bd94a8 100644 --- a/src/Mod/Spreadsheet/importXLSX.py +++ b/src/Mod/Spreadsheet/importXLSX.py @@ -391,12 +391,12 @@ def handleWorkBook(theBook, sheetDict, Doc): aliasRef = getText(theAlias.childNodes) # aliasRef can be None if aliasRef and "$" in aliasRef: refList = aliasRef.split("!$") - adressList = refList[1].split("$") + addressList = refList[1].split("$") # print("aliasRef: ", aliasRef) # print('Sheet Name: ', refList[0]) - # print('Adress: ', adressList[0] + adressList[1]) + # print('Address: ', addressList[0] + addressList[1]) actSheet, sheetFile = sheetDict[refList[0]] - actSheet.setAlias(adressList[0] + adressList[1], aliasName) + actSheet.setAlias(addressList[0] + addressList[1], aliasName) def handleStrings(theStr, sList): diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index af497d6a1c03..1b4d83be629b 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -446,7 +446,7 @@ App::DocumentObjectExecReturn* DrawViewDimension::execute() { // Base::Console().Message("DVD::execute() - %s\n", getNameInDocument()); if (!okToProceed()) { - return App::DocumentObject::StdReturn; + return new App::DocumentObjectExecReturn("Dimension could not execute"); } resetLinear(); @@ -479,13 +479,6 @@ App::DocumentObjectExecReturn* DrawViewDimension::execute() // references are good, we can proceed m_referencesCorrect = true; - // is this check still relevant or is it replace by the autocorrect and - // validate methods? - if (References3D.getValues().empty() && !checkReferences2D()) { - Base::Console().Warning("%s has invalid 2D References\n", getNameInDocument()); - return new App::DocumentObjectExecReturn("Dimension object has invalid 2d references"); - } - // we have either or both valid References3D and References2D ReferenceVector references = getEffectiveReferences(); @@ -544,7 +537,8 @@ bool DrawViewDimension::okToProceed() if (!has2DReferences() && !has3DReferences()) { // no references, can't do anything - return App::DocumentObject::StdReturn; + Base::Console().Warning("Dimension object has no valid references\n"); + return false; } if (!getViewPart()->hasGeometry()) { @@ -552,7 +546,13 @@ bool DrawViewDimension::okToProceed() return false; } - return true; + // is this check still relevant or is it replaced by the autocorrect and + // validate methods? + if (References3D.getValues().empty() && !checkReferences2D()) { + Base::Console().Warning("%s has invalid 2D References\n", getNameInDocument()); + return false; + } + return validateReferenceForm(); } bool DrawViewDimension::isMultiValueSchema() const @@ -1451,8 +1451,8 @@ bool DrawViewDimension::hasBroken3dReferences() const void DrawViewDimension::updateSavedGeometry() { - // Base::Console().Message("DVD::updateSavedGeometry() - %s - savedGeometry: %d\n", - // getNameInDocument(), SavedGeometry.getValues().size()); + // Base::Console().Message("DVD::updateSavedGeometry() - %s - savedGeometry: %d\n", + // getNameInDocument(), SavedGeometry.getValues().size()); ReferenceVector references = getEffectiveReferences(); if (references.empty()) { // no references to save @@ -1624,6 +1624,108 @@ void DrawViewDimension::setAll3DMeasurement() } } +//! check the effective references have the correct number and type for this +//! dimension. +bool DrawViewDimension::validateReferenceForm() const +{ + // we have either or both valid References3D and References2D + ReferenceVector references = getEffectiveReferences(); + if (references.empty()) { + return false; + } + + if (Type.isValue("Distance") || Type.isValue("DistanceX") || Type.isValue("DistanceY")) { + if (getRefType() == oneEdge) { + if (references.size() != 1) { + return false; + } + std::string subGeom = DrawUtil::getGeomTypeFromName(references.front().getSubName()); + if (subGeom != "Edge") { + return false; + } + return true; + } + else if (getRefType() == twoEdge) { + if (references.size() != 2) { + return false; + } + std::string subGeom0 = DrawUtil::getGeomTypeFromName(references.front().getSubName()); + std::string subGeom1 = DrawUtil::getGeomTypeFromName(references.back().getSubName()); + if (subGeom0 != "Edge" || subGeom1 != "Edge") { + return false; + } + return true; + } + else if (getRefType() == twoVertex) { + if (references.size() != 2) { + return false; + } + std::string subGeom0 = DrawUtil::getGeomTypeFromName(references.front().getSubName()); + std::string subGeom1 = DrawUtil::getGeomTypeFromName(references.back().getSubName()); + if (subGeom0 != "Vertex" || subGeom1 != "Vertex") { + return false; + } + return true; + } + else if (getRefType() == vertexEdge) { + if (references.size() != 2) { + return false; + } + std::string subGeom0 = DrawUtil::getGeomTypeFromName(references.front().getSubName()); + std::string subGeom1 = DrawUtil::getGeomTypeFromName(references.back().getSubName()); + if ( (subGeom0 == "Vertex" && subGeom1 == "Edge") || + (subGeom0 == "Edge" && subGeom1 == "Vertex") ) { + return true; + } + return false; + } + } + else if (Type.isValue("Radius")) { + if (references.size() != 1) { + return false; + } + std::string subGeom = DrawUtil::getGeomTypeFromName(references.front().getSubName()); + if (subGeom != "Edge") { + return false; + } + return true; + } + else if (Type.isValue("Diameter")) { + if (references.size() != 1) { + return false; + } + std::string subGeom = DrawUtil::getGeomTypeFromName(references.front().getSubName()); + if (subGeom != "Edge") { + return false; + } + return true; + } + else if (Type.isValue("Angle")) { + if (references.size() != 2) { + return false; + } + std::string subGeom0 = DrawUtil::getGeomTypeFromName(references.front().getSubName()); + std::string subGeom1 = DrawUtil::getGeomTypeFromName(references.back().getSubName()); + if (subGeom0 != "Edge" || subGeom1 != "Edge") { + return false; + } + return true; + } + else if (Type.isValue("Angle3Pt")) { + if (references.size() != 3) { + return false; + } + std::string subGeom0 = DrawUtil::getGeomTypeFromName(references.at(0).getSubName()); + std::string subGeom1 = DrawUtil::getGeomTypeFromName(references.at(1).getSubName()); + std::string subGeom2 = DrawUtil::getGeomTypeFromName(references.at(2).getSubName()); + if (subGeom0 != "Vertex" || subGeom1 != "Vertex" || subGeom2 != "Vertex") { + return false; + } + return true; + } + return false; +} + // delete all previous measurements void DrawViewDimension::clear3DMeasurements() { @@ -1862,5 +1964,8 @@ Base::BoundBox3d DrawViewDimension::getSavedBox() Base::BoundBox3d DrawViewDimension::getFeatureBox() { - return getViewPart()->getBoundingBox(); + if (getViewPart() && getViewPart()->getBoundingBox().IsValid()) { + return getViewPart()->getBoundingBox(); + } + return Base::BoundBox3d(); } diff --git a/src/Mod/TechDraw/App/DrawViewDimension.h b/src/Mod/TechDraw/App/DrawViewDimension.h index 67987b8beb0c..babc4de683e4 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.h +++ b/src/Mod/TechDraw/App/DrawViewDimension.h @@ -237,6 +237,8 @@ class TechDrawExport DrawViewDimension: public TechDraw::DrawView bool okToProceed(); void updateSavedGeometry(); + bool validateReferenceForm() const; + private: static const char* TypeEnums[]; static const char* MeasureTypeEnums[]; diff --git a/src/Mod/TechDraw/Gui/DimensionValidators.cpp b/src/Mod/TechDraw/Gui/DimensionValidators.cpp index 38ccb649506e..e991a938a65d 100644 --- a/src/Mod/TechDraw/Gui/DimensionValidators.cpp +++ b/src/Mod/TechDraw/Gui/DimensionValidators.cpp @@ -151,7 +151,7 @@ DimensionGeometryType TechDraw::validateDimSelection( //check for wrong number of geometry GeomCountVector foundCounts; GeomCountMap minimumCountMap = loadRequiredCounts(acceptableGeometry, minimumCounts); - if (!checkGeometryOccurences(subNames, minimumCountMap)) { + if (!checkGeometryOccurrences(subNames, minimumCountMap)) { //too many or too few geometry descriptors. return isInvalid; } @@ -203,7 +203,7 @@ DimensionGeometryType TechDraw::validateDimSelection3d( //check for wrong number of geometry GeomCountMap minimumCountMap = loadRequiredCounts(acceptableGeometry, minimumCounts); - if (!checkGeometryOccurences(subNames, minimumCountMap)) { + if (!checkGeometryOccurrences(subNames, minimumCountMap)) { //too many or too few geometry descriptors. return isInvalid; } @@ -235,7 +235,7 @@ bool TechDraw::validateSubnameList(StringVector subNames, GeometrySet acceptable } //count how many of each "Edge", "Vertex, etc and compare totals to required minimum -bool TechDraw::checkGeometryOccurences(StringVector subNames, GeomCountMap keyedMinimumCounts) +bool TechDraw::checkGeometryOccurrences(StringVector subNames, GeomCountMap keyedMinimumCounts) { //how many of each geometry descriptor are input GeomCountMap foundCounts; diff --git a/src/Mod/TechDraw/Gui/DimensionValidators.h b/src/Mod/TechDraw/Gui/DimensionValidators.h index e5e71df9ca25..7f371670bd2c 100644 --- a/src/Mod/TechDraw/Gui/DimensionValidators.h +++ b/src/Mod/TechDraw/Gui/DimensionValidators.h @@ -84,7 +84,7 @@ DimensionGeometryType getGeometryConfiguration3d(DrawViewPart* dvp, GeomCountMap loadRequiredCounts(StringVector& acceptableGeometry, std::vector& minimumCouts); -bool checkGeometryOccurences(StringVector subNames, GeomCountMap keyedMinimumCounts); +bool checkGeometryOccurrences(StringVector subNames, GeomCountMap keyedMinimumCounts); DimensionGeometryType isValidVertexes(ReferenceVector refs); DimensionGeometryType isValidMultiEdge(ReferenceVector refs); diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.h b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.h index 6e7568f0732a..63c733dcd9c8 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.h +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.h @@ -59,8 +59,6 @@ public Q_SLOTS: private: std::unique_ptr ui; TechDraw::LineGenerator* m_lineGenerator; - - bool m_blockLineStandardOnChanged{false}; }; } // namespace TechDrawGui diff --git a/src/Tools/updatecrowdin.py b/src/Tools/updatecrowdin.py index f27af47361b1..c2f9fb60ccf6 100755 --- a/src/Tools/updatecrowdin.py +++ b/src/Tools/updatecrowdin.py @@ -141,8 +141,8 @@ ], [ "Material", - "../Mod/Material/Gui/Resources/translations", - "../Mod/Material/Gui/Resources/Material.qrc", + "../Mod/Material/Resources/translations", + "../Mod/Material/Resources/Material.qrc", ], [ "Mesh", diff --git a/tests/src/Misc/fmt.cpp b/tests/src/Misc/fmt.cpp index 53547039067b..2a6415e30f75 100644 --- a/tests/src/Misc/fmt.cpp +++ b/tests/src/Misc/fmt.cpp @@ -3,6 +3,7 @@ #include "gtest/gtest.h" #include +// NOLINTBEGIN TEST(fmt, fail) { EXPECT_NE("abc", fmt::format("{}{}", "a", "b")); @@ -18,9 +19,14 @@ TEST(fmt, print_pass) EXPECT_EQ("12", fmt::sprintf("%s%d", "1", 2)); EXPECT_EQ("x", fmt::sprintf("%c", 'x')); EXPECT_EQ("1.23 2", fmt::sprintf("%.2f %d", 1.23456, 2)); + EXPECT_EQ("0.123456789012", fmt::sprintf("%.12f", 0.123456789012)); + EXPECT_EQ("-1234", fmt::sprintf("%li", -1234L)); + EXPECT_EQ("-1234567890", fmt::sprintf("%li", -1234567890L)); + EXPECT_EQ("1234567890", fmt::sprintf("%li", 1234567890UL)); } TEST(fmt, print_fail) { EXPECT_THROW(fmt::printf("%s%d", 1, 2), std::exception); } +// NOLINTEND diff --git a/tests/src/Mod/Part/App/PartFeature.cpp b/tests/src/Mod/Part/App/PartFeature.cpp index 8b234e5bbf3d..7b0522acf67d 100644 --- a/tests/src/Mod/Part/App/PartFeature.cpp +++ b/tests/src/Mod/Part/App/PartFeature.cpp @@ -2,6 +2,7 @@ #include "gtest/gtest.h" +#include #include "Mod/Part/App/FeaturePartCommon.h" #include #include @@ -132,7 +133,6 @@ TEST_F(FeaturePartTest, create) TEST_F(FeaturePartTest, getElementHistory) { // Arrange - const char* name = "Part__Box"; const char* name2 = "Edge2"; // Edge, Vertex, or Face. will work here. // Act auto result = Feature::getElementHistory(_boxes[0], name2, true, false); @@ -156,6 +156,7 @@ TEST_F(FeaturePartTest, getRelatedElements) auto label1 = _common->Label.getValue(); auto label2 = _boxes[1]->Label.getValue(); const TopoShape& ts = _common->Shape.getShape(); + boost::ignore_unused(ts); auto result = Feature::getRelatedElements(_doc->getObject(label1), "Edge2", HistoryTraceType::followTypeChange, @@ -189,6 +190,9 @@ TEST_F(FeaturePartTest, getElementFromSource) auto label1 = _common->Label.getValue(); auto label2 = _boxes[1]->Label.getValue(); const TopoShape& ts = _common->Shape.getShape(); + boost::ignore_unused(label1); + boost::ignore_unused(label2); + boost::ignore_unused(ts); auto element = Feature::getElementFromSource(_common, "Part__Box001", // "Edge1", _boxes[0], @@ -204,7 +208,6 @@ TEST_F(FeaturePartTest, getSubObject) _common->Base.setValue(_boxes[0]); _common->Tool.setValue(_boxes[1]); App::DocumentObject sourceObject; - const char* sourceSubElement; PyObject* pyObj; // Act _common->execute();