From d76db39662d30c461dfd70d5fc31c537b3b0c0b3 Mon Sep 17 00:00:00 2001 From: Roy-043 Date: Tue, 23 Jan 2024 18:05:11 +0100 Subject: [PATCH 01/27] Draft: Draft_Scale: make sure scale inputboxes show at least 6 decimals Fixes #7386. --- src/Mod/Draft/drafttaskpanels/task_scale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/drafttaskpanels/task_scale.py b/src/Mod/Draft/drafttaskpanels/task_scale.py index e23ec7a105..7afa3723d0 100644 --- a/src/Mod/Draft/drafttaskpanels/task_scale.py +++ b/src/Mod/Draft/drafttaskpanels/task_scale.py @@ -46,7 +46,7 @@ class ScaleTaskPanel: """The task panel for the Draft Scale tool.""" def __init__(self): - decimals = params.get_param("Decimals", path="Units") + decimals = max(6, params.get_param("Decimals", path="Units")) self.sourceCmd = None self.form = QtGui.QWidget() self.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_Scale.svg")) From 9aeb396d006ab1ddb8f54868e21736b8fdb00856 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 25 Jan 2024 11:00:01 -0500 Subject: [PATCH 02/27] Transfer in MakeElementShell --- src/Mod/Part/App/TopoShape.h | 41 +++++++ src/Mod/Part/App/TopoShapeExpansion.cpp | 139 ++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 06439746a9..573e4eb322 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -676,6 +676,47 @@ public: */ TopoShape &makeElementCompound(const std::vector &shapes, const char *op=nullptr, bool force=true); + /* Make a shell using this shape + * @param silent: whether to throw exception on failure + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a self reference so that multiple operations can be carried out + * for the same shape in the same line of code. + */ + TopoShape& makeElementShell(bool silent = true, const char* op = nullptr); + + /* Make a shell with input wires + * + * @param wires: input wires + * @param silent: whether to throw exception on failure + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a self reference so that multiple operations can be carried out + * for the same shape in the same line of code. + */ + // TopoShape& makeElementShellFromWires(const std::vector& wires, + // bool silent = true, + // const char* op = nullptr); + /* Make a shell with input wires + * + * @param wires: input wires + * @param silent: whether to throw exception on failure + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + // TopoShape& makeElementShellFromWires(bool silent = true, const char* op = nullptr) + // { + // return makeElementShellFromWires(getSubTopoShapes(TopAbs_WIRE), silent, op); + // } + TopoShape& makeElementFace(const std::vector& shapes, const char* op = nullptr, const char* maker = nullptr, diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index f6abf55423..e7554ebaa8 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -35,6 +35,9 @@ #endif +#include +#include +#include #include "TopoShape.h" #include "TopoShapeCache.h" #include "FaceMaker.h" @@ -796,5 +799,141 @@ TopoShape TopoShape::splitWires(std::vector* inner, SplitWireReorient return TopoShape(); } +struct MapperFill: Part::TopoShape::Mapper +{ + BRepFill_Generator& maker; + explicit MapperFill(BRepFill_Generator& maker) + : maker(maker) + {} + const std::vector& generated(const TopoDS_Shape& s) const override + { + _res.clear(); + try { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(maker.GeneratedShapes(s)); it.More(); it.Next()) { + _res.push_back(it.Value()); + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; + } +}; + +// topo naming counterpart of TopoShape::makeShell() +TopoShape& TopoShape::makeElementShell(bool silent, const char* op) +{ + if (silent) { + if (isNull()) { + return *this; + } + + if (shapeType(true) != TopAbs_COMPOUND) { + return *this; + } + + // we need a compound that consists of only faces + TopExp_Explorer it; + // no shells + if (hasSubShape(TopAbs_SHELL)) { + return *this; + } + + // no wires outside a face + it.Init(_Shape, TopAbs_WIRE, TopAbs_FACE); + if (it.More()) { + return *this; + } + + // no edges outside a wire + it.Init(_Shape, TopAbs_EDGE, TopAbs_WIRE); + if (it.More()) { + return *this; + } + + // no vertexes outside an edge + it.Init(_Shape, TopAbs_VERTEX, TopAbs_EDGE); + if (it.More()) { + return *this; + } + } + else if (!hasSubShape(TopAbs_FACE)) { + FC_THROWM(Base::CADKernelError, "Cannot make shell without face"); + } + + BRep_Builder builder; + TopoDS_Shape shape; + TopoDS_Shell shell; + builder.MakeShell(shell); + + try { + for (const auto& face : getSubShapes(TopAbs_FACE)) { + builder.Add(shell, face); + } + + TopoShape tmp(Tag, Hasher, shell); + tmp.resetElementMap(); + tmp.mapSubElement(*this, op); + + shape = shell; + BRepCheck_Analyzer check(shell); + if (!check.IsValid()) { + ShapeUpgrade_ShellSewing sewShell; + shape = sewShell.ApplySewing(shell); + // TODO confirm the above won't change OCCT topological naming + } + + if (shape.IsNull()) { + if (silent) { + return *this; + } + FC_THROWM(NullShapeException, "Failed to make shell"); + } + + if (shape.ShapeType() != TopAbs_SHELL) { + if (silent) { + return *this; + } + FC_THROWM(Base::CADKernelError, + "Failed to make shell: unexpected output shape type " + << shapeType(shape.ShapeType(), true)); + } + + setShape(shape); + resetElementMap(tmp.elementMap()); + } + catch (Standard_Failure& e) { + if (!silent) { + FC_THROWM(Base::CADKernelError, "Failed to make shell: " << e.GetMessageString()); + } + } + + return *this; +} + +// TopoShape& TopoShape::makeElementShellFromWires(const std::vector& wires, +// bool silent, +// const char* op) +// { +// BRepFill_Generator maker; +// for (auto& w : wires) { +// if (w.shapeType(silent) == TopAbs_WIRE) { +// maker.AddWire(TopoDS::Wire(w.getShape())); +// } +// } +// if (wires.empty()) { +// if (silent) { +// _Shape.Nullify(); +// return *this; +// } +// FC_THROWM(NullShapeException, "No input shapes"); +// } +// maker.Perform(); +// this->makeShapeWithElementMap(maker.Shell(), MapperFill(maker), wires, op); +// return *this; +// } } // namespace Part From ec545b4cecee1561ef7f677cdb04f2afe5b74c65 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 24 Jan 2024 21:14:45 -0600 Subject: [PATCH 03/27] Addon Manager: Improve macro readme rendering --- .../addonmanager_readme_viewer.py | 159 ++++++++++++++++-- src/Mod/AddonManager/package_details.py | 4 +- 2 files changed, 150 insertions(+), 13 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_readme_viewer.py b/src/Mod/AddonManager/addonmanager_readme_viewer.py index 5e8ec50464..af2a5d0752 100644 --- a/src/Mod/AddonManager/addonmanager_readme_viewer.py +++ b/src/Mod/AddonManager/addonmanager_readme_viewer.py @@ -25,6 +25,8 @@ import Addon from PySide import QtCore, QtGui, QtWidgets +from enum import Enum, auto +from html.parser import HTMLParser import addonmanager_freecad_interface as fci import addonmanager_utilities as utils @@ -43,18 +45,21 @@ class ReadmeViewer(QtWidgets.QTextBrowser): super().__init__(parent) NetworkManager.InitializeNetworkManager() NetworkManager.AM_NETWORK_MANAGER.completed.connect(self._download_completed) - self.request_index = 0 + self.readme_request_index = 0 + self.resource_requests = {} self.url = "" self.repo: Addon.Addon = None self.setOpenExternalLinks(True) self.setOpenLinks(True) self.image_map = {} + self.stop = True def set_addon(self, repo: Addon): """Set which Addon's information is displayed""" self.setPlainText(translate("AddonsInstaller", "Loading README data...")) self.repo = repo + self.stop = False if self.repo.repo_type == Addon.Addon.Kind.MACRO: self.url = self.repo.macro.wiki if not self.url: @@ -62,11 +67,13 @@ class ReadmeViewer(QtWidgets.QTextBrowser): else: self.url = utils.get_readme_url(repo) - self.request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(self.url) + self.readme_request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get( + self.url + ) def _download_completed(self, index: int, code: int, data: QtCore.QByteArray) -> None: """Callback for handling a completed README file download.""" - if index == self.request_index: + if index == self.readme_request_index: if code == 200: # HTTP success self._process_package_download(data.data().decode("utf-8")) else: @@ -76,34 +83,56 @@ class ReadmeViewer(QtWidgets.QTextBrowser): "Failed to download data from {} -- received response code {}.", ).format(self.url, code) ) + elif index in self.resource_requests: + if code == 200: + self._process_resource_download(self.resource_requests[index], data.data()) + else: + self.image_map[self.resource_requests[index]] = None + del self.resource_requests[index] + if not self.resource_requests: + self.set_addon(self.repo) # Trigger a reload of the page now with resources def _process_package_download(self, data: str): if self.repo.repo_type == Addon.Addon.Kind.MACRO: - self.setHtml(data) + parser = WikiCleaner() + parser.feed(data) + self.setHtml(parser.final_html) else: + # Check for recent Qt (e.g. Qt5.15 or later). Check can be removed when + # we no longer support Ubuntu 20.04LTS for compiling. if hasattr(self, "setMarkdown"): self.setMarkdown(data) else: self.setPlainText(data) + def _process_resource_download(self, resource_name: str, resource_data: bytes): + image = QtGui.QImage.fromData(resource_data) + if image: + self.image_map[resource_name] = self._ensure_appropriate_width(image) + else: + self.image_map[resource_name] = None + def loadResource(self, resource_type: int, name: QtCore.QUrl) -> object: """Callback for resource loading. Called automatically by underlying Qt code when external resources are needed for rendering. In particular, here it is used to download and cache (in RAM) the images needed for the README and Wiki pages.""" - if resource_type == QtGui.QTextDocument.ImageResource: + if resource_type == QtGui.QTextDocument.ImageResource and not self.stop: full_url = self._create_full_url(name.toString()) if full_url not in self.image_map: self.image_map[full_url] = None fci.Console.PrintMessage(f"Downloading image from {full_url}...\n") - data = NetworkManager.AM_NETWORK_MANAGER.blocking_get(full_url) - if data and data.data(): - image = QtGui.QImage.fromData(data.data()) - if image: - self.image_map[full_url] = self._ensure_appropriate_width(image) + index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url) + self.resource_requests[index] = full_url return self.image_map[full_url] return super().loadResource(resource_type, name) + def hideEvent(self, event: QtGui.QHideEvent): + self.stop = True + for request in self.resource_requests: + NetworkManager.AM_NETWORK_MANAGER.abort(request) + self.resource_requests.clear() + def _create_full_url(self, url: str) -> str: if url.startswith("http"): return url @@ -117,3 +146,113 @@ class ReadmeViewer(QtWidgets.QTextBrowser): if image.width() < ninety_seven_percent: return image return image.scaledToWidth(ninety_seven_percent) + + +class WikiCleaner(HTMLParser): + """This HTML parser cleans up FreeCAD Macro Wiki Page for display in a + QTextBrowser widget (which does not deal will with tables used as formatting, + etc.) It strips out any tables, and extracts the mw-parser-output div as the only + thing that actually gets displayed. It also discards anything inside the [edit] + spans that litter wiki output.""" + + class State(Enum): + BeforeMacroContent = auto() + InMacroContent = auto() + InTable = auto() + InEditSpan = auto() + AfterMacroContent = auto() + + def __init__(self): + super().__init__() + self.depth_in_div = 0 + self.depth_in_span = 0 + self.depth_in_table = 0 + self.final_html = "" + self.previous_state = WikiCleaner.State.BeforeMacroContent + self.state = WikiCleaner.State.BeforeMacroContent + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str]]): + if tag == "div": + self.handle_div_start(attrs) + elif tag == "span": + self.handle_span_start(attrs) + elif tag == "table": + self.handle_table_start(attrs) + else: + if self.state == WikiCleaner.State.InMacroContent: + self.add_tag_to_html(tag, attrs) + + def handle_div_start(self, attrs: list[tuple[str, str]]): + for name, value in attrs: + if name == "class" and value == "mw-parser-output": + self.previous_state = self.state + self.state = WikiCleaner.State.InMacroContent + if self.state == WikiCleaner.State.InMacroContent: + self.depth_in_div += 1 + self.add_tag_to_html("div", attrs) + + def handle_span_start(self, attrs: list[tuple[str, str]]): + for name, value in attrs: + if name == "class" and value == "mw-editsection": + self.previous_state = self.state + self.state = WikiCleaner.State.InEditSpan + break + if self.state == WikiCleaner.State.InEditSpan: + self.depth_in_span += 1 + elif WikiCleaner.State.InMacroContent: + self.add_tag_to_html("span", attrs) + + def handle_table_start(self, attrs: list[tuple[str, str]]): + if self.state != WikiCleaner.State.InTable: + self.previous_state = self.state + self.state = WikiCleaner.State.InTable + self.depth_in_table += 1 + + def add_tag_to_html(self, tag, attrs=None): + self.final_html += f"<{tag}" + if attrs: + self.final_html += " " + for attr, value in attrs: + self.final_html += f"{attr}='{value}'" + self.final_html += ">\n" + + def handle_endtag(self, tag): + if tag == "table": + self.handle_table_end() + elif tag == "span": + self.handle_span_end() + elif tag == "div": + self.handle_div_end() + else: + if self.state == WikiCleaner.State.InMacroContent: + self.add_tag_to_html(f"/{tag}") + + def handle_span_end(self): + if self.state == WikiCleaner.State.InEditSpan: + self.depth_in_span -= 1 + if self.depth_in_span <= 0: + self.depth_in_span = 0 + self.state = self.previous_state + else: + self.add_tag_to_html(f"/span") + + def handle_div_end(self): + if self.state == WikiCleaner.State.InMacroContent: + self.depth_in_div -= 1 + if self.depth_in_div <= 0: + self.depth_in_div = 0 + self.state = WikiCleaner.State.AfterMacroContent + self.final_html += "" + else: + self.add_tag_to_html(f"/div") + + def handle_table_end(self): + if self.state == WikiCleaner.State.InTable: + self.depth_in_table -= 1 + if self.depth_in_table <= 0: + self.depth_in_table = 0 + self.state = self.previous_state + + def handle_data(self, data): + if self.state == WikiCleaner.State.InMacroContent: + self.final_html += data diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py index 8310b5e903..a9e73a42c7 100644 --- a/src/Mod/AddonManager/package_details.py +++ b/src/Mod/AddonManager/package_details.py @@ -49,8 +49,6 @@ except ImportError: translate = fci.translate -show_javascript_console_output = False - class PackageDetails(QtWidgets.QWidget): """The PackageDetails QWidget shows package README information and provides @@ -90,7 +88,7 @@ class PackageDetails(QtWidgets.QWidget): # If this is the same repo we were already showing, we do not have to do the # expensive refetch unless reload is true - if self.repo != repo or reload: + if True or self.repo != repo or reload: self.repo = repo if self.worker is not None: From 1aff0ca7f5dec308e93670bf89062d662eb3a7c5 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Thu, 25 Jan 2024 11:13:00 -0500 Subject: [PATCH 04/27] Tests for makeElementShell --- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 95f5026bad..ff976589d6 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -584,4 +584,109 @@ TEST_F(TopoShapeExpansionTest, mapSubElementFindAncestors) EXPECT_TRUE(ancestorShapeList.back().IsEqual(topoShape6.getShape())); } +TEST_F(TopoShapeExpansionTest, makeElementShellInvalid) +{ + // Arrange + Part::TopoShape topoShape {1L}; + // Act / Assert + EXPECT_THROW(topoShape.makeElementShell(false, nullptr), Base::CADKernelError); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellSingle) +{ + // Arrange + const double Len = 3, Wid = 2; + auto [face1, wire1, edge1, edge2, edge3, _] = CreateRectFace(Len, Wid); + Part::TopoShape topoShape {face1, 1L}; + // Act + Part::TopoShape result = topoShape.makeElementShell(false, nullptr); + // Assert +#if OCC_VERSION_HEX >= 0x070400 + EXPECT_EQ(result.getShape().NbChildren(), 1); +#endif + EXPECT_EQ(result.countSubElements("Vertex"), 4); + EXPECT_EQ(result.countSubElements("Edge"), 4); + EXPECT_EQ(result.countSubElements("Face"), 1); + EXPECT_STREQ(result.shapeName().c_str(), "Shell"); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellOpen) +{ + // Arrange + const double Len = 3, Wid = 2; + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(Len, Wid); + auto transform {gp_Trsf()}; + transform.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), M_PI / 2); + auto face2 = face1; // Shallow copy + face2.Move(TopLoc_Location(transform)); + TopoDS_Compound compound1; + TopoDS_Builder builder {}; + builder.MakeCompound(compound1); + builder.Add(compound1, face1); + builder.Add(compound1, face2); + Part::TopoShape topoShape {compound1, 1L}; + // Act + Part::TopoShape result = topoShape.makeElementShell(true, nullptr); + // Assert +#if OCC_VERSION_HEX >= 0x070400 + EXPECT_EQ(result.getShape().NbChildren(), 2); +#endif + EXPECT_EQ(result.countSubElements("Vertex"), 6); + EXPECT_EQ(result.countSubElements("Edge"), 7); + EXPECT_EQ(result.countSubElements("Face"), 2); + EXPECT_STREQ(result.shapeName().c_str(), "Shell"); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellClosed) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + Part::TopoShape topoShape {cube1}; + std::vector shapes; + for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { + shapes.push_back(Part::TopoShape {face}); + } + // Act + Part::TopoShape topoShape1 {1L}; + topoShape1.makeElementCompound(shapes, "D"); + // Assert + Part::TopoShape result = topoShape1.makeElementShell(false, "SH1"); +#if OCC_VERSION_HEX >= 0x070400 + EXPECT_EQ(result.getShape().NbChildren(), 6); +#endif + EXPECT_EQ(result.countSubElements("Vertex"), 8); + EXPECT_EQ(result.countSubElements("Edge"), 12); + EXPECT_EQ(result.countSubElements("Face"), 6); + EXPECT_STREQ(result.shapeName().c_str(), "Shell"); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + auto transform {gp_Trsf()}; + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.5, 0.5, 0.0)); + cube2.Move(TopLoc_Location(transform)); + // Arrange + Part::TopoShape topoShape {cube1}; + std::vector shapes; + for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { + shapes.push_back(Part::TopoShape {face}); + } + topoShape.setShape(cube2); + for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { + shapes.push_back(Part::TopoShape {face}); + } + // Act + Part::TopoShape topoShape1 {1L}; + topoShape1.makeElementCompound(shapes, "D"); + // Act / Assert + EXPECT_THROW(topoShape1.makeElementShell(false, nullptr), Base::CADKernelError); +} + +// TEST_F(TopoShapeExpansionTest, makeElementShellFromWires) +// { +// // Arrange +// } + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) From f9c8c1034b7c38e8b3a912879885584208ecc38f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 25 Jan 2024 12:39:10 -0600 Subject: [PATCH 05/27] Addon Manager: Remove new-style type hints --- src/Mod/AddonManager/addonmanager_readme_viewer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_readme_viewer.py b/src/Mod/AddonManager/addonmanager_readme_viewer.py index af2a5d0752..fc3e9b19f6 100644 --- a/src/Mod/AddonManager/addonmanager_readme_viewer.py +++ b/src/Mod/AddonManager/addonmanager_readme_viewer.py @@ -171,7 +171,7 @@ class WikiCleaner(HTMLParser): self.previous_state = WikiCleaner.State.BeforeMacroContent self.state = WikiCleaner.State.BeforeMacroContent - def handle_starttag(self, tag: str, attrs: list[tuple[str, str]]): + def handle_starttag(self, tag: str, attrs): if tag == "div": self.handle_div_start(attrs) elif tag == "span": @@ -182,7 +182,7 @@ class WikiCleaner(HTMLParser): if self.state == WikiCleaner.State.InMacroContent: self.add_tag_to_html(tag, attrs) - def handle_div_start(self, attrs: list[tuple[str, str]]): + def handle_div_start(self, attrs): for name, value in attrs: if name == "class" and value == "mw-parser-output": self.previous_state = self.state @@ -191,7 +191,7 @@ class WikiCleaner(HTMLParser): self.depth_in_div += 1 self.add_tag_to_html("div", attrs) - def handle_span_start(self, attrs: list[tuple[str, str]]): + def handle_span_start(self, attrs): for name, value in attrs: if name == "class" and value == "mw-editsection": self.previous_state = self.state @@ -202,7 +202,7 @@ class WikiCleaner(HTMLParser): elif WikiCleaner.State.InMacroContent: self.add_tag_to_html("span", attrs) - def handle_table_start(self, attrs: list[tuple[str, str]]): + def handle_table_start(self, unused): if self.state != WikiCleaner.State.InTable: self.previous_state = self.state self.state = WikiCleaner.State.InTable From fba23029ff5a79e1e703d98b3daf1cff5a41e271 Mon Sep 17 00:00:00 2001 From: Roy-043 Date: Fri, 26 Jan 2024 10:43:42 +0100 Subject: [PATCH 06/27] Arch: BuildingPart fix setEdit and unsetEdit The Transform option from the Tree view context menu did not work. --- src/Mod/Arch/ArchBuildingPart.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Mod/Arch/ArchBuildingPart.py b/src/Mod/Arch/ArchBuildingPart.py index c7fa948cf0..7a1cf4a71d 100644 --- a/src/Mod/Arch/ArchBuildingPart.py +++ b/src/Mod/Arch/ArchBuildingPart.py @@ -883,8 +883,12 @@ class ViewProviderBuildingPart: return True def setEdit(self, vobj, mode): - # For some reason mode is always 0. - # Using FreeCADGui.getUserEditMode() as a workaround. + # mode == 1 if Transform is selected in the Tree view contex menu. + # mode == 2 has been added for consistency. + if mode == 1 or mode == 2: + return None + # For some reason mode is always 0 if the object is double-clicked in + # the Tree view. Using FreeCADGui.getUserEditMode() as a workaround. if FreeCADGui.getUserEditMode() in ("Transform", "Cutting"): return None @@ -892,8 +896,8 @@ class ViewProviderBuildingPart: return False # Return `False` as we don't want to enter edit mode. def unsetEdit(self, vobj, mode): - # For some reason mode is always 0. - # Using FreeCADGui.getUserEditMode() as a workaround. + if mode == 1 or mode == 2: + return None if FreeCADGui.getUserEditMode() in ("Transform", "Cutting"): return None From b1b0c48742c1974b4546f44c85bc83a07c10c33f Mon Sep 17 00:00:00 2001 From: Roy-043 Date: Fri, 26 Jan 2024 13:09:07 +0100 Subject: [PATCH 07/27] Draft: Scale widget did not handle FEM unit system The FEM UserSchema (8) was not handled by the code. --- src/Mod/Draft/draftutils/init_draft_statusbar.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Draft/draftutils/init_draft_statusbar.py b/src/Mod/Draft/draftutils/init_draft_statusbar.py index 7b5f4b3453..7f1148c87b 100644 --- a/src/Mod/Draft/draftutils/init_draft_statusbar.py +++ b/src/Mod/Draft/draftutils/init_draft_statusbar.py @@ -78,12 +78,12 @@ def get_scales(unit_system = 0): if unit_system == 0: scale_units_system = params.get_param("UserSchema", path="Units") - if scale_units_system in [0, 1, 4, 6]: - return draft_scales_metrics - elif scale_units_system in [2, 3, 5]: + if scale_units_system in [2, 3, 5]: return draft_scales_arch_imperial elif scale_units_system in [7]: return draft_scales_eng_imperial + else: + return draft_scales_metrics elif unit_system == 1: return draft_scales_metrics elif unit_system == 2: From 9e9cd1342af13020128b271c4b4b20833c753da2 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 25 Jan 2024 10:49:50 -0500 Subject: [PATCH 08/27] [TD]fix extension Extend function re scale/rotate --- src/Mod/TechDraw/Gui/CommandExtensionPack.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp index c3008c25c2..a35be20152 100644 --- a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp +++ b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp @@ -1475,10 +1475,16 @@ void execExtendShortenLine(Gui::Command* cmd, bool extend) TechDraw::BaseGeomPtr baseGeo = objFeat->getGeomByIndex(num); if (baseGeo) { if (baseGeo->getGeomType() == TechDraw::GENERIC) { - TechDraw::GenericPtr genLine = - std::static_pointer_cast(baseGeo); - Base::Vector3d P0 = genLine->points.at(0); - Base::Vector3d P1 = genLine->points.at(1); + // start and end points are scaled and rotated. invert the points + // so the canonicalPoint math works correctly. + Base::Vector3d P0 = DU::invertY(baseGeo->getStartPoint()); + Base::Vector3d P1 = DU::invertY(baseGeo->getEndPoint()); + // convert start and end to unscaled, unrotated. + P0 = CosmeticVertex::makeCanonicalPoint(objFeat, P0); + P1 = CosmeticVertex::makeCanonicalPoint(objFeat, P1); + // put the points back into weird Qt coord system. + P0 = DU::invertY(P0); + P1 = DU::invertY(P1); bool isCenterLine = false; TechDraw::CenterLine* centerEdge = nullptr; if (baseGeo->getCosmetic()) { @@ -1499,7 +1505,7 @@ void execExtendShortenLine(Gui::Command* cmd, bool extend) isCenterLine = true; centerEdge = objFeat->getCenterLine(uniTag); } - double scale = objFeat->getScale(); + // double scale = objFeat->getScale(); Base::Vector3d direction = (P1 - P0).Normalize(); Base::Vector3d delta = direction * activeDimAttributes.getLineStretch(); Base::Vector3d startPt, endPt; @@ -1519,7 +1525,7 @@ void execExtendShortenLine(Gui::Command* cmd, bool extend) } else { std::string lineTag = - objFeat->addCosmeticEdge(startPt / scale, endPt / scale); + objFeat->addCosmeticEdge(startPt, endPt); TechDraw::CosmeticEdge* lineEdge = objFeat->getCosmeticEdge(lineTag); _setLineAttributes(lineEdge, oldStyle, oldWeight, oldColor); objFeat->refreshCEGeoms(); From 9e080d13cea1f0425be79c12c02e70da3012b652 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 25 Jan 2024 12:52:24 -0500 Subject: [PATCH 09/27] [TD]fix extension hole thread re scale/rotate --- src/Mod/TechDraw/Gui/CommandExtensionPack.cpp | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp index a35be20152..ef51c387b9 100644 --- a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp +++ b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp @@ -1505,7 +1505,6 @@ void execExtendShortenLine(Gui::Command* cmd, bool extend) isCenterLine = true; centerEdge = objFeat->getCenterLine(uniTag); } - // double scale = objFeat->getScale(); Base::Vector3d direction = (P1 - P0).Normalize(); Base::Vector3d delta = direction * activeDimAttributes.getLineStretch(); Base::Vector3d startPt, endPt; @@ -2093,7 +2092,6 @@ void _createThreadLines(std::vector SubNames, TechDraw::DrawViewPar float factor) { // create symbolizing lines of a thread from the side seen - double scale = objFeat->getScale(); std::string GeoType0 = TechDraw::DrawUtil::getGeomTypeFromName(SubNames[0]); std::string GeoType1 = TechDraw::DrawUtil::getGeomTypeFromName(SubNames[1]); if ((GeoType0 == "Edge") && (GeoType1 == "Edge")) { @@ -2109,10 +2107,22 @@ void _createThreadLines(std::vector SubNames, TechDraw::DrawViewPar TechDraw::GenericPtr line0 = std::static_pointer_cast(geom0); TechDraw::GenericPtr line1 = std::static_pointer_cast(geom1); - Base::Vector3d start0 = line0->points.at(0); - Base::Vector3d end0 = line0->points.at(1); - Base::Vector3d start1 = line1->points.at(0); - Base::Vector3d end1 = line1->points.at(1); + // start and end points are scaled and rotated. invert the points + // so the canonicalPoint math works correctly. + Base::Vector3d start0 = DU::invertY(line0->getStartPoint()); + Base::Vector3d end0 = DU::invertY(line0->getEndPoint()); + Base::Vector3d start1 = DU::invertY(line1->getStartPoint()); + Base::Vector3d end1 = DU::invertY(line1->getEndPoint()); + // convert start and end to unscaled, unrotated. + start0 = CosmeticVertex::makeCanonicalPoint(objFeat, start0); + start1 = CosmeticVertex::makeCanonicalPoint(objFeat, start1); + end0 = CosmeticVertex::makeCanonicalPoint(objFeat, end0); + end1 = CosmeticVertex::makeCanonicalPoint(objFeat, end1); + // put the points back into weird Qt coord system. + start0 = DU::invertY(start0); + start1 = DU::invertY(start1); + end0 = DU::invertY(end0); + end1 = DU::invertY(end1); if (DrawUtil::circulation(start0, end0, start1) != DrawUtil::circulation(end0, end1, start1)) { Base::Vector3d help1 = start1; @@ -2120,17 +2130,13 @@ void _createThreadLines(std::vector SubNames, TechDraw::DrawViewPar start1 = help2; end1 = help1; } - start0.y = -start0.y; - end0.y = -end0.y; - start1.y = -start1.y; - end1.y = -end1.y; float kernelDiam = (start1 - start0).Length(); float kernelFactor = (kernelDiam * factor - kernelDiam) / 2; Base::Vector3d delta = (start1 - start0).Normalize() * kernelFactor; std::string line0Tag = - objFeat->addCosmeticEdge((start0 - delta) / scale, (end0 - delta) / scale); + objFeat->addCosmeticEdge(start0 - delta, end0 - delta); std::string line1Tag = - objFeat->addCosmeticEdge((start1 + delta) / scale, (end1 + delta) / scale); + objFeat->addCosmeticEdge(start1 + delta, end1 + delta); TechDraw::CosmeticEdge* cosTag0 = objFeat->getCosmeticEdge(line0Tag); TechDraw::CosmeticEdge* cosTag1 = objFeat->getCosmeticEdge(line1Tag); _setLineAttributes(cosTag0); From a671237073f7ef762e4136f950432a49f254791b Mon Sep 17 00:00:00 2001 From: marioalexis Date: Thu, 25 Jan 2024 19:00:54 -0300 Subject: [PATCH 10/27] Base: Add stiffness per unit area unit --- src/Base/Unit.cpp | 4 ++++ src/Base/Unit.h | 1 + src/Base/UnitsSchemaInternal.cpp | 20 +++++++++++++++++++- src/Base/UnitsSchemaMKS.cpp | 20 +++++++++++++++++++- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/Base/Unit.cpp b/src/Base/Unit.cpp index 64cefecf85..7aa14c7526 100644 --- a/src/Base/Unit.cpp +++ b/src/Base/Unit.cpp @@ -575,6 +575,9 @@ QString Unit::getTypeString() const if (*this == Unit::Stiffness) { return QString::fromLatin1("Stiffness"); } + if (*this == Unit::StiffnessDensity) { + return QString::fromLatin1("StiffnessDensity"); + } if (*this == Unit::Stress) { return QString::fromLatin1("Stress"); } @@ -667,6 +670,7 @@ const Unit Unit::ShearModulus (-1,1,-2); const Unit Unit::SpecificEnergy (2, 0, -2); const Unit Unit::SpecificHeat (2, 0, -2, 0, -1); const Unit Unit::Stiffness (0, 1, -2); +const Unit Unit::StiffnessDensity (-2, 1, -2); const Unit Unit::Stress (-1,1,-2); const Unit Unit::ThermalConductivity (1, 1, -3, 0, -1); const Unit Unit::ThermalExpansionCoefficient(0, 0, 0, 0, -1); diff --git a/src/Base/Unit.h b/src/Base/Unit.h index 8b4a4aec70..77b325a784 100644 --- a/src/Base/Unit.h +++ b/src/Base/Unit.h @@ -150,6 +150,7 @@ public: static const Unit YoungsModulus; static const Unit Stiffness; + static const Unit StiffnessDensity; static const Unit Force; static const Unit Work; diff --git a/src/Base/UnitsSchemaInternal.cpp b/src/Base/UnitsSchemaInternal.cpp index c3d13cd38e..355e8307c7 100644 --- a/src/Base/UnitsSchemaInternal.cpp +++ b/src/Base/UnitsSchemaInternal.cpp @@ -223,7 +223,7 @@ UnitsSchemaInternal::schemaTranslate(const Quantity& quant, double& factor, QStr unitString = QString::fromLatin1("mN/m"); factor = 1e-3; } - if (UnitValue < 1e3) { + else if (UnitValue < 1e3) { unitString = QString::fromLatin1("N/m"); factor = 1.0; } @@ -236,6 +236,24 @@ UnitsSchemaInternal::schemaTranslate(const Quantity& quant, double& factor, QStr factor = 1e6; } } + else if ((unit == Unit::StiffnessDensity)) { + if (UnitValue < 1e-3) { + unitString = QString::fromLatin1("Pa/m"); + factor = 1e-6; + } + else if (UnitValue < 1) { + unitString = QString::fromLatin1("kPa/m"); + factor = 1e-3; + } + else if (UnitValue < 1e3) { + unitString = QString::fromLatin1("MPa/m"); + factor = 1.0; + } + else { + unitString = QString::fromLatin1("GPa/m"); + factor = 1e3; + } + } else if (unit == Unit::Force) { if (UnitValue < 1e3) { unitString = QString::fromLatin1("mN"); diff --git a/src/Base/UnitsSchemaMKS.cpp b/src/Base/UnitsSchemaMKS.cpp index e15a721ad3..aba27295ab 100644 --- a/src/Base/UnitsSchemaMKS.cpp +++ b/src/Base/UnitsSchemaMKS.cpp @@ -174,7 +174,7 @@ QString UnitsSchemaMKS::schemaTranslate(const Quantity& quant, double& factor, Q unitString = QString::fromLatin1("mN/m"); factor = 1e-3; } - if (UnitValue < 1e3) { + else if (UnitValue < 1e3) { unitString = QString::fromLatin1("N/m"); factor = 1.0; } @@ -187,6 +187,24 @@ QString UnitsSchemaMKS::schemaTranslate(const Quantity& quant, double& factor, Q factor = 1e6; } } + else if ((unit == Unit::StiffnessDensity)) { + if (UnitValue < 1e-3) { + unitString = QString::fromLatin1("Pa/m"); + factor = 1e-6; + } + else if (UnitValue < 1) { + unitString = QString::fromLatin1("kPa/m"); + factor = 1e-3; + } + else if (UnitValue < 1e3) { + unitString = QString::fromLatin1("MPa/m"); + factor = 1.0; + } + else { + unitString = QString::fromLatin1("GPa/m"); + factor = 1e3; + } + } else if (unit == Unit::ThermalConductivity) { if (UnitValue > 1000000) { unitString = QString::fromLatin1("W/mm/K"); From f9a9ca2e36986d9441c42bfe47b71d363854da62 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Thu, 25 Jan 2024 19:02:08 -0300 Subject: [PATCH 11/27] App: Add StiffnessDensity property --- src/App/Application.cpp | 1 + src/App/PropertyUnits.cpp | 11 +++++++++++ src/App/PropertyUnits.h | 17 +++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 741cc97d36..c02e2ebbad 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -2027,6 +2027,7 @@ void Application::initTypes() App::PropertySpecificHeat ::init(); App::PropertySpeed ::init(); App::PropertyStiffness ::init(); + App::PropertyStiffnessDensity ::init(); App::PropertyStress ::init(); App::PropertyTemperature ::init(); App::PropertyThermalConductivity ::init(); diff --git a/src/App/PropertyUnits.cpp b/src/App/PropertyUnits.cpp index f7106d1061..6c0f60f8a8 100644 --- a/src/App/PropertyUnits.cpp +++ b/src/App/PropertyUnits.cpp @@ -621,6 +621,17 @@ PropertyStiffness::PropertyStiffness() setUnit(Base::Unit::Stiffness); } +//************************************************************************** +// PropertyStiffnessDensity +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE(App::PropertyStiffnessDensity, App::PropertyQuantity) + +PropertyStiffnessDensity::PropertyStiffnessDensity() +{ + setUnit(Base::Unit::StiffnessDensity); +} + //************************************************************************** // PropertyTemperature //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/App/PropertyUnits.h b/src/App/PropertyUnits.h index c10782ebe0..fc32e080b1 100644 --- a/src/App/PropertyUnits.h +++ b/src/App/PropertyUnits.h @@ -620,7 +620,7 @@ public: /** Stiffness property * This is a property for representing stiffness. It is basically a float - * property. On the Gui it has a quantity like m/s^2. + * property. On the Gui it has a quantity like N/m. */ class AppExport PropertyStiffness: public PropertyQuantity { @@ -631,8 +631,21 @@ public: ~PropertyStiffness() override = default; }; +/** StiffnessDensity property + * This is a property for representing stiffness per area unit. It is basically a float + * property. On the Gui it has a quantity like Pa/m. + */ +class AppExport PropertyStiffnessDensity: public PropertyQuantity +{ + TYPESYSTEM_HEADER_WITH_OVERRIDE(); + +public: + PropertyStiffnessDensity(); + ~PropertyStiffnessDensity() override = default; +}; + /** Stress property - * This is a property for representing . It is basically a float + * This is a property for representing stress. It is basically a float * property. On the Gui it has a quantity like Pa. */ class AppExport PropertyStress: public PropertyQuantity From 7e5bb7361a6e2b22e93f94b34d44d0959fcebe3e Mon Sep 17 00:00:00 2001 From: marioalexis Date: Fri, 26 Jan 2024 01:13:59 -0300 Subject: [PATCH 12/27] Fem: Improve constraint contact - partial fixes #11653 --- src/Mod/Fem/App/FemConstraintContact.cpp | 54 +++++- src/Mod/Fem/App/FemConstraintContact.h | 10 +- src/Mod/Fem/Gui/Command.cpp | 13 +- src/Mod/Fem/Gui/TaskFemConstraintContact.cpp | 89 +++++++-- src/Mod/Fem/Gui/TaskFemConstraintContact.h | 9 +- src/Mod/Fem/Gui/TaskFemConstraintContact.ui | 176 ++++++++++++------ .../constraint_contact_shell_shell.py | 5 +- .../constraint_contact_solid_solid.py | 4 +- .../calculix/write_constraint_contact.py | 22 ++- .../constraint_contact_shell_shell.inp | 6 +- .../constraint_contact_solid_solid.inp | 4 +- 11 files changed, 296 insertions(+), 96 deletions(-) diff --git a/src/Mod/Fem/App/FemConstraintContact.cpp b/src/Mod/Fem/App/FemConstraintContact.cpp index d49c67c262..f4d4fa5268 100644 --- a/src/Mod/Fem/App/FemConstraintContact.cpp +++ b/src/Mod/Fem/App/FemConstraintContact.cpp @@ -33,9 +33,31 @@ PROPERTY_SOURCE(Fem::ConstraintContact, Fem::Constraint) ConstraintContact::ConstraintContact() { /*Note: Initialise parameters here*/ - ADD_PROPERTY(Slope, (0.0)); - ADD_PROPERTY(Friction, (0.0)); - /* */ + ADD_PROPERTY_TYPE(Slope, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Contact stiffness"); + ADD_PROPERTY_TYPE(Adjust, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Node clearance adjustment limit"); + ADD_PROPERTY_TYPE(Friction, + (false), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Enable friction interaction"); + ADD_PROPERTY_TYPE(FrictionCoefficient, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Friction coefficient"); + ADD_PROPERTY_TYPE(StickSlope, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Stick slope"); ADD_PROPERTY_TYPE(Points, (Base::Vector3d()), @@ -47,6 +69,7 @@ ConstraintContact::ConstraintContact() "ConstraintContact", App::PropertyType(App::Prop_ReadOnly | App::Prop_Output), "Normals where symbols are drawn"); + /* */ Points.setValues(std::vector()); Normals.setValues(std::vector()); } @@ -77,3 +100,28 @@ void ConstraintContact::onChanged(const App::Property* prop) } } } + +void ConstraintContact::handleChangedPropertyType(Base::XMLReader& reader, + const char* typeName, + App::Property* prop) +{ + if (prop == &Slope && strcmp(typeName, "App::PropertyFloat") == 0) { + App::PropertyFloat oldSlope; + oldSlope.Restore(reader); + // old slope value stored as MPa/mm equivalent to 1e3 kg/(mm^2*s^2) + Slope.setValue(oldSlope.getValue() * 1000); + + // stick slope internally generated as slope/10 + StickSlope.setValue(Slope.getValue() / 10); + } + else if (prop == &Friction && strcmp(typeName, "App::PropertyFloat") == 0) { + App::PropertyFloat oldFriction; + oldFriction.Restore(reader); + FrictionCoefficient.setValue(oldFriction.getValue()); + + Friction.setValue(oldFriction.getValue() > 0 ? true : false); + } + else { + Constraint::handleChangedPropertyType(reader, typeName, prop); + } +} diff --git a/src/Mod/Fem/App/FemConstraintContact.h b/src/Mod/Fem/App/FemConstraintContact.h index 60c82cfbf9..68b17eef8b 100644 --- a/src/Mod/Fem/App/FemConstraintContact.h +++ b/src/Mod/Fem/App/FemConstraintContact.h @@ -50,8 +50,11 @@ public: * This is only the definitions of the variables ******/ // ex. - App::PropertyFloat Slope; - App::PropertyFloat Friction; + App::PropertyStiffnessDensity Slope; + App::PropertyLength Adjust; + App::PropertyBool Friction; + App::PropertyFloat FrictionCoefficient; + App::PropertyStiffnessDensity StickSlope; // etc /* */ @@ -64,6 +67,9 @@ public: protected: void onChanged(const App::Property* prop) override; + void handleChangedPropertyType(Base::XMLReader& reader, + const char* typeName, + App::Property* prop) override; }; } // namespace Fem diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp index 82f82e1a3f..0f5f1d8d1c 100644 --- a/src/Mod/Fem/Gui/Command.cpp +++ b/src/Mod/Fem/Gui/Command.cpp @@ -250,10 +250,19 @@ void CmdFemConstraintContact::activated(int) "App.activeDocument().addObject(\"Fem::ConstraintContact\",\"%s\")", FeatName.c_str()); doCommand(Doc, - "App.activeDocument().%s.Slope = 1000000.00", + "App.activeDocument().%s.Slope = \"1e6 GPa/m\"", FeatName.c_str()); // OvG: set default not equal to 0 doCommand(Doc, - "App.activeDocument().%s.Friction = 0.0", + "App.activeDocument().%s.Adjust = 0.0", + FeatName.c_str()); // OvG: set default equal to 0 + doCommand(Doc, + "App.activeDocument().%s.Friction = False", + FeatName.c_str()); // OvG: set default equal to 0 + doCommand(Doc, + "App.activeDocument().%s.FrictionCoefficient = 0.0", + FeatName.c_str()); // OvG: set default equal to 0 + doCommand(Doc, + "App.activeDocument().%s.StickSlope = \"1e4 GPa/m\"", FeatName.c_str()); // OvG: set default not equal to 0 doCommand(Doc, "App.activeDocument().%s.Scale = 1", diff --git a/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp b/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp index cc15da6dbb..d515c44438 100644 --- a/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp +++ b/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp @@ -91,14 +91,36 @@ TaskFemConstraintContact::TaskFemConstraintContact(ViewProviderFemConstraintCont std::vector Objects = pcConstraint->References.getValues(); std::vector SubElements = pcConstraint->References.getSubValues(); - double slope = pcConstraint->Slope.getValue(); - double friction = pcConstraint->Friction.getValue(); + + bool friction = pcConstraint->Friction.getValue(); // Fill data into dialog elements - ui->spSlope->setMinimum(1.0); - ui->spSlope->setValue(slope); - ui->spFriction->setValue(friction); + ui->spbSlope->setUnit(pcConstraint->Slope.getUnit()); + ui->spbSlope->setMinimum(0); + ui->spbSlope->setMaximum(FLOAT_MAX); + ui->spbSlope->setValue(pcConstraint->Slope.getQuantityValue()); + ui->spbSlope->bind(pcConstraint->Slope); + ui->spbAdjust->setUnit(pcConstraint->Adjust.getUnit()); + ui->spbAdjust->setMinimum(0); + ui->spbAdjust->setMaximum(FLOAT_MAX); + ui->spbAdjust->setValue(pcConstraint->Adjust.getQuantityValue()); + ui->spbAdjust->bind(pcConstraint->Adjust); + + ui->ckbFriction->setChecked(friction); + + ui->spbFrictionCoeff->setMinimum(0); + ui->spbFrictionCoeff->setMaximum(FLOAT_MAX); + ui->spbFrictionCoeff->setValue(pcConstraint->FrictionCoefficient.getValue()); + ui->spbFrictionCoeff->setEnabled(friction); + ui->spbFrictionCoeff->bind(pcConstraint->FrictionCoefficient); + + ui->spbStickSlope->setUnit(pcConstraint->StickSlope.getUnit()); + ui->spbStickSlope->setMinimum(0); + ui->spbStickSlope->setMaximum(FLOAT_MAX); + ui->spbStickSlope->setValue(pcConstraint->StickSlope.getQuantityValue()); + ui->spbStickSlope->setEnabled(friction); + ui->spbStickSlope->bind(pcConstraint->StickSlope); /* */ ui->lw_referencesMaster->clear(); @@ -136,6 +158,11 @@ TaskFemConstraintContact::TaskFemConstraintContact(ViewProviderFemConstraintCont this, &TaskFemConstraintContact::removeFromSelectionMaster); + connect(ui->ckbFriction, + &QCheckBox::toggled, + this, + &TaskFemConstraintContact::onFrictionChanged); + updateUI(); } @@ -428,6 +455,12 @@ void TaskFemConstraintContact::onReferenceDeletedMaster() TaskFemConstraintContact::removeFromSelectionMaster(); } +void TaskFemConstraintContact::onFrictionChanged(bool state) +{ + ui->spbFrictionCoeff->setEnabled(state); + ui->spbStickSlope->setEnabled(state); +} + const std::string TaskFemConstraintContact::getReferences() const { int rowsSlave = ui->lw_referencesSlave->model()->rowCount(); @@ -443,15 +476,29 @@ const std::string TaskFemConstraintContact::getReferences() const return TaskFemConstraint::getReferences(items); } -/* Note: */ -double TaskFemConstraintContact::get_Slope() const +const std::string TaskFemConstraintContact::getSlope() const { - return ui->spSlope->rawValue(); + return ui->spbSlope->value().getSafeUserString().toStdString(); } -double TaskFemConstraintContact::get_Friction() const +const std::string TaskFemConstraintContact::getAdjust() const { - return ui->spFriction->value(); + return ui->spbAdjust->value().getSafeUserString().toStdString(); +} + +bool TaskFemConstraintContact::getFriction() const +{ + return ui->ckbFriction->isChecked(); +} + +double TaskFemConstraintContact::getFrictionCoeff() const +{ + return ui->spbFrictionCoeff->value(); +} + +const std::string TaskFemConstraintContact::getStickSlope() const +{ + return ui->spbStickSlope->value().getSafeUserString().toStdString(); } void TaskFemConstraintContact::changeEvent(QEvent*) @@ -478,7 +525,7 @@ void TaskDlgFemConstraintContact::open() // a transaction is already open at creation time of the panel if (!Gui::Command::hasPendingCommand()) { QString msg = QObject::tr("Contact constraint"); - Gui::Command::openCommand((const char*)msg.toUtf8()); + Gui::Command::openCommand(static_cast(msg.toUtf8())); ConstraintView->setVisible(true); Gui::Command::runCommand( Gui::Command::Doc, @@ -497,13 +544,25 @@ bool TaskDlgFemConstraintContact::accept() try { Gui::Command::doCommand(Gui::Command::Doc, - "App.ActiveDocument.%s.Slope = %f", + "App.ActiveDocument.%s.Slope = \"%s\"", name.c_str(), - parameterContact->get_Slope()); + parameterContact->getSlope().c_str()); Gui::Command::doCommand(Gui::Command::Doc, - "App.ActiveDocument.%s.Friction = %f", + "App.ActiveDocument.%s.Adjust = \"%s\"", name.c_str(), - parameterContact->get_Friction()); + parameterContact->getAdjust().c_str()); + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.Friction = %s", + name.c_str(), + parameterContact->getFriction() ? "True" : "False"); + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.FrictionCoefficient = %f", + name.c_str(), + parameterContact->getFrictionCoeff()); + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.StickSlope = \"%s\"", + name.c_str(), + parameterContact->getStickSlope().c_str()); std::string scale = parameterContact->getScale(); // OvG: determine modified scale Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.Scale = %s", diff --git a/src/Mod/Fem/Gui/TaskFemConstraintContact.h b/src/Mod/Fem/Gui/TaskFemConstraintContact.h index cc7891a667..b5f78a6ec2 100644 --- a/src/Mod/Fem/Gui/TaskFemConstraintContact.h +++ b/src/Mod/Fem/Gui/TaskFemConstraintContact.h @@ -46,8 +46,11 @@ public: QWidget* parent = nullptr); ~TaskFemConstraintContact() override; const std::string getReferences() const override; - double get_Slope() const; - double get_Friction() const; + const std::string getAdjust() const; + const std::string getSlope() const; + bool getFriction() const; + const std::string getStickSlope() const; + double getFrictionCoeff() const; private Q_SLOTS: void onReferenceDeletedSlave(); @@ -56,12 +59,12 @@ private Q_SLOTS: void removeFromSelectionSlave(); void addToSelectionMaster(); void removeFromSelectionMaster(); + void onFrictionChanged(bool); protected: void changeEvent(QEvent* e) override; private: - // void onSelectionChanged(const Gui::SelectionChanges& msg); void updateUI(); std::unique_ptr ui; }; diff --git a/src/Mod/Fem/Gui/TaskFemConstraintContact.ui b/src/Mod/Fem/Gui/TaskFemConstraintContact.ui index 0d86b655fe..3fca07dde2 100644 --- a/src/Mod/Fem/Gui/TaskFemConstraintContact.ui +++ b/src/Mod/Fem/Gui/TaskFemConstraintContact.ui @@ -135,56 +135,123 @@ + - - - - - Contact Stiffness - - - - - - - 1.000000000000000 - - - 1000000000.000000000000000 - - - Pa - - - 1000000.000000000000000 - - - - - - - - - - - Friction coefficient - - - - - - - 2 - - - 1.000000000000000 - - - 0.100000000000000 - - - - + + + + 0 + 0 + + + + + + + Parameters + + + + + + Contact Stiffness + + + + + + + 1.000000000000000 + + + 1000000000.000000000000000 + + + GPa/m + + + 100.000000000000000 + + + + + + + Clearance Adjustment + + + + + + + 0.000000000000000 + + + mm + + + 0.000000000000000 + + + 0.100000000000000 + + + + + + + Enable Friction + + + + + + + Friction Coefficient + + + + + + + 2 + + + 0.000000000000000 + + + 0.000000000000000 + + + 0.100000000000000 + + + + + + + Stick Slope + + + + + + + 1.000000000000000 + + + 1000000000.000000000000000 + + + GPa/m + + + 1.000000000000000 + + + + + lbl_info_2 @@ -194,9 +261,14 @@ - Gui::InputField - QLineEdit -
Gui/InputField.h
+ Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+ + Gui::DoubleSpinBox + QWidget +
Gui/SpinBox.h
diff --git a/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py b/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py index 3e6464a041..2d2b42af80 100644 --- a/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py +++ b/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py @@ -193,9 +193,8 @@ def setup(doc=None, solvertype="ccxtools"): (lower_tube, "Face1"), (upper_tube, "Face1"), ] - con_contact.Friction = 0.0 - # con_contact.Slope = "1000000.0 kg/(mm*s^2)" # contact stiffness - con_contact.Slope = 1000000.0 # should be 1000000.0 kg/(mm*s^2) + con_contact.Friction = False + con_contact.Slope = "1000000.0 GPa/m" analysis.addObject(con_contact) # mesh diff --git a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py index c3efe9b189..d2392d1d2a 100644 --- a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py +++ b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py @@ -177,8 +177,8 @@ def setup(doc=None, solvertype="ccxtools"): (geom_obj, "Face7"), # first seams slave face, TODO proof in writer code! (geom_obj, "Face3"), # second seams master face, TODO proof in writer code! ] - con_contact.Friction = 0.0 - con_contact.Slope = 1000000.0 # contact stiffness 1000000.0 kg/(mm*s^2) + con_contact.Friction = False + con_contact.Slope = "1000000.0 GPa/m" analysis.addObject(con_contact) # mesh diff --git a/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py b/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py index c8527975f6..a9c0bcc9dd 100644 --- a/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py +++ b/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py @@ -68,20 +68,24 @@ def write_meshdata_constraint(f, femobj, contact_obj, ccxwriter): def write_constraint(f, femobj, contact_obj, ccxwriter): # floats read from ccx should use {:.13G}, see comment in writer module + adjust = "" + if contact_obj.Adjust.Value > 0: + adjust = ", ADJUST={:.13G}".format( + contact_obj.Adjust.getValueAs("mm").Value) f.write( - "*CONTACT PAIR, INTERACTION=INT{},TYPE=SURFACE TO SURFACE\n" - .format(contact_obj.Name) + "*CONTACT PAIR, INTERACTION=INT{}, TYPE=SURFACE TO SURFACE{}\n" + .format(contact_obj.Name, adjust) ) ind_surf = "IND" + contact_obj.Name dep_surf = "DEP" + contact_obj.Name - f.write("{},{}\n".format(dep_surf, ind_surf)) + f.write("{}, {}\n".format(dep_surf, ind_surf)) f.write("*SURFACE INTERACTION, NAME=INT{}\n".format(contact_obj.Name)) - f.write("*SURFACE BEHAVIOR,PRESSURE-OVERCLOSURE=LINEAR\n") - slope = contact_obj.Slope + f.write("*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR\n") + slope = contact_obj.Slope.getValueAs("MPa/mm").Value f.write("{:.13G}\n".format(slope)) - friction = contact_obj.Friction - if friction > 0: - f.write("*FRICTION \n") - stick = (slope / 10.0) + if contact_obj.Friction: + f.write("*FRICTION\n") + friction = contact_obj.FrictionCoefficient + stick = contact_obj.StickSlope.getValueAs("MPa/mm").Value f.write("{:.13G}, {:.13G}\n".format(friction, stick)) diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp b/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp index 437d5c7feb..4f696b5b7d 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp @@ -38365,10 +38365,10 @@ Efaces *********************************************************** ** Contact Constraints ** ConstraintContact -*CONTACT PAIR, INTERACTION=INTConstraintContact,TYPE=SURFACE TO SURFACE -DEPConstraintContact,INDConstraintContact +*CONTACT PAIR, INTERACTION=INTConstraintContact, TYPE=SURFACE TO SURFACE +DEPConstraintContact, INDConstraintContact *SURFACE INTERACTION, NAME=INTConstraintContact -*SURFACE BEHAVIOR,PRESSURE-OVERCLOSURE=LINEAR +*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR 1000000 *********************************************************** diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp b/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp index 883a37b530..5d315d93ad 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp @@ -5572,10 +5572,10 @@ Evolumes *********************************************************** ** Contact Constraints ** ConstraintContact -*CONTACT PAIR, INTERACTION=INTConstraintContact,TYPE=SURFACE TO SURFACE +*CONTACT PAIR, INTERACTION=INTConstraintContact, TYPE=SURFACE TO SURFACE DEPConstraintContact,INDConstraintContact *SURFACE INTERACTION, NAME=INTConstraintContact -*SURFACE BEHAVIOR,PRESSURE-OVERCLOSURE=LINEAR +*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR 1000000 *********************************************************** From 3dbb4b3dd3cc5fcaa136a4958dade972c225fcb7 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Fri, 26 Jan 2024 11:08:26 -0300 Subject: [PATCH 13/27] Fem: Improve constraint tie - partial fixes #11653 --- src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui | 46 +++++++------------ src/Mod/Fem/femobjects/constraint_tie.py | 11 ++++- .../calculix/write_constraint_tie.py | 9 ++-- .../Fem/femtaskpanels/task_constraint_tie.py | 17 +++++-- .../femtest/data/calculix/constraint_tie.inp | 2 +- 5 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui b/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui index 309fe91eca..c33c39b6eb 100644 --- a/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui +++ b/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui @@ -28,31 +28,13 @@ - - QFormLayout::AllNonFixedFieldsGrow - - - - - 0 - 0 - + + + mm - - - 80 - 20 - - - - Qt::LeftToRight - - - 0 mm - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0.000000000000000 1.000000000000000 @@ -60,9 +42,6 @@ 1000000000.000000000000000 - - mm - 2 @@ -74,7 +53,14 @@ - Tolerance: + Tolerance + + + + + + + Enable Adjust @@ -87,9 +73,9 @@ - Gui::InputField - QLineEdit -
Gui/InputField.h
+ Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
diff --git a/src/Mod/Fem/femobjects/constraint_tie.py b/src/Mod/Fem/femobjects/constraint_tie.py index ad36031163..c983656706 100644 --- a/src/Mod/Fem/femobjects/constraint_tie.py +++ b/src/Mod/Fem/femobjects/constraint_tie.py @@ -46,5 +46,14 @@ class ConstraintTie(base_fempythonobject.BaseFemPythonObject): "App::PropertyLength", "Tolerance", "Geometry", - "set max gap between tied faces" + "Set max gap between tied faces" ) + obj.Tolerance = "0.0 mm" + + obj.addProperty( + "App::PropertyBool", + "Adjust", + "Geometry", + "Adjust connected nodes" + ) + obj.Adjust = False diff --git a/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py b/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py index 8817fa9954..c75ab90be3 100644 --- a/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py +++ b/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py @@ -70,10 +70,13 @@ def write_constraint(f, femobj, tie_obj, ccxwriter): # floats read from ccx should use {:.13G}, see comment in writer module tolerance = tie_obj.Tolerance.getValueAs("mm").Value + adjust = "" + if not tie_obj.Adjust: + adjust = ", ADJUST=NO" f.write( - "*TIE, POSITION TOLERANCE={:.13G}, ADJUST=NO, NAME=TIE{}\n" - .format(tolerance, tie_obj.Name) + "*TIE, POSITION TOLERANCE={:.13G}{}, NAME=TIE{}\n" + .format(tolerance, adjust, tie_obj.Name) ) ind_surf = "TIE_IND{}".format(tie_obj.Name) dep_surf = "TIE_DEP{}".format(tie_obj.Name) - f.write("{},{}\n".format(dep_surf, ind_surf)) + f.write("{}, {}\n".format(dep_surf, ind_surf)) diff --git a/src/Mod/Fem/femtaskpanels/task_constraint_tie.py b/src/Mod/Fem/femtaskpanels/task_constraint_tie.py index 05b1369c13..f2e97ede3f 100644 --- a/src/Mod/Fem/femtaskpanels/task_constraint_tie.py +++ b/src/Mod/Fem/femtaskpanels/task_constraint_tie.py @@ -52,10 +52,15 @@ class _TaskPanel: FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/ConstraintTie.ui" ) QtCore.QObject.connect( - self.parameterWidget.if_tolerance, + self.parameterWidget.spb_tolerance, QtCore.SIGNAL("valueChanged(Base::Quantity)"), self.tolerance_changed ) + QtCore.QObject.connect( + self.parameterWidget.ckb_adjust, + QtCore.SIGNAL("toggled(bool)"), + self.adjust_changed + ) self.init_parameter_widget() # geometry selection widget @@ -67,7 +72,7 @@ class _TaskPanel: ) # form made from param and selection widget - self.form = [self.parameterWidget, self.selectionWidget] + self.form = [self.selectionWidget, self.parameterWidget] def accept(self): # check values @@ -94,6 +99,7 @@ class _TaskPanel: elif msgBox.clickedButton() == ignoreButton: pass self.obj.Tolerance = self.tolerance + self.obj.Adjust = self.adjust self.obj.References = self.selectionWidget.references self.recompute_and_set_back_all() return True @@ -112,7 +118,12 @@ class _TaskPanel: def init_parameter_widget(self): self.tolerance = self.obj.Tolerance - self.parameterWidget.if_tolerance.setText(self.tolerance.UserString) + self.adjust = self.obj.Adjust + self.parameterWidget.spb_tolerance.setProperty("value", self.tolerance) + self.parameterWidget.ckb_adjust.setChecked(self.adjust) def tolerance_changed(self, base_quantity_value): self.tolerance = base_quantity_value + + def adjust_changed(self, bool_value): + self.adjust = bool_value diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp index d028a0ba94..a201c5415f 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp @@ -18608,7 +18608,7 @@ Evolumes ** Tie Constraints ** ConstraintTie *TIE, POSITION TOLERANCE=25, ADJUST=NO, NAME=TIEConstraintTie -TIE_DEPConstraintTie,TIE_INDConstraintTie +TIE_DEPConstraintTie, TIE_INDConstraintTie *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD From e6341dd51aa69d558a97df20b4dcb93b17459016 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Fri, 26 Jan 2024 16:15:48 -0500 Subject: [PATCH 14/27] Add helper methods --- tests/src/Mod/Part/App/PartTestHelpers.cpp | 34 ++++++++++++++++++++++ tests/src/Mod/Part/App/PartTestHelpers.h | 17 +++++++++++ 2 files changed, 51 insertions(+) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index 24d106f9f8..8b0390b7e5 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -89,6 +89,30 @@ void rectangle(double height, double width, char* name) ExecutePython(rectstring); } +std::tuple +CreateRectFace(float len, float wid) +{ + auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(len, 0.0, 0.0)).Edge(); + auto edge2 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, 0.0, 0.0), gp_Pnt(len, wid, 0.0)).Edge(); + auto edge3 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, wid, 0.0), gp_Pnt(0.0, wid, 0.0)).Edge(); + auto edge4 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, wid, 0.0), gp_Pnt(0.0, 0.0, 0.0)).Edge(); + auto wire1 = BRepBuilderAPI_MakeWire({edge1, edge2, edge3, edge4}).Wire(); + auto face1 = BRepBuilderAPI_MakeFace(wire1).Face(); + return {face1, wire1, edge1, edge2, edge3, edge4}; +} + +std::tuple +CreateFaceWithRoundHole(float len, float wid, float radius) +{ + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(len, wid); + auto circ1 = + GC_MakeCircle(gp_Pnt(len / 2.0, wid / 2.0, 0), gp_Dir(0.0, 0.0, 1.0), radius).Value(); + auto edge5 = BRepBuilderAPI_MakeEdge(circ1).Edge(); + auto wire2 = BRepBuilderAPI_MakeWire(edge5).Wire(); + auto face2 = BRepBuilderAPI_MakeFace(face1, wire2).Face(); + return {face2, wire1, wire2}; +} + testing::AssertionResult boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec) { @@ -103,6 +127,16 @@ boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec) << b2.MinY << "," << b2.MinZ << " ; " << b2.MaxX << "," << b2.MaxY << "," << b2.MaxZ << ")"; } +std::map elementMap(const TopoShape& shape) +{ + std::map result {}; + auto elements = shape.getElementMap(); + for (auto const& entry : elements) { + result[entry.index] = entry.name; + } + return result; +} + } // namespace PartTestHelpers // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.h b/tests/src/Mod/Part/App/PartTestHelpers.h index 742eaf26bc..79147f6531 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.h +++ b/tests/src/Mod/Part/App/PartTestHelpers.h @@ -10,10 +10,18 @@ #include "Mod/Part/App/FeaturePartFuse.h" #include "Mod/Part/App/FeatureFillet.h" #include +#include +#include +#include +#include +#include namespace PartTestHelpers { +using namespace Data; +using namespace Part; + double getVolume(const TopoDS_Shape& shape); double getArea(const TopoDS_Shape& shape); @@ -38,6 +46,15 @@ void executePython(const std::vector& python); void rectangle(double height, double width, char* name); +std::tuple +CreateRectFace(float len = 2.0, float wid = 3.0); + +std::tuple +CreateFaceWithRoundHole(float len = 2.0, float wid = 3.0, float radius = 1.0); + testing::AssertionResult boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec = 1e-05); // NOLINT + +std::map elementMap(const TopoShape& shape); + } // namespace PartTestHelpers From 0ef7f3d0d8d87f9ce35338b63beeac26c6a0c171 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 26 Jan 2024 05:12:24 -0600 Subject: [PATCH 15/27] Addon Manager: Fix #11120 --- src/Mod/AddonManager/NetworkManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index 8c7a475512..8b1c1e6e1d 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -531,7 +531,7 @@ if HAVE_QTNETWORK: any notifications have been called.""" reply = self.sender() if not reply: - print("Network Manager Error: __reply_finished not called by a Qt signal") + # This can happen during a cancellation operation: silently do nothing return if reply.error() == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError: From bcb60d7d7e42a35c94c23aa9790490024a3a8c3f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 26 Jan 2024 06:46:09 -0600 Subject: [PATCH 16/27] Addon Manager: Allow .py filenames Also improve macro removal. --- .../AddonManager/addonmanager_installer.py | 22 +++++++++++++++++-- src/Mod/AddonManager/addonmanager_macro.py | 9 ++++++++ .../AddonManager/addonmanager_uninstaller.py | 22 ++++++++++++++++--- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_installer.py b/src/Mod/AddonManager/addonmanager_installer.py index a8a9e888e2..d35a3bc4b9 100644 --- a/src/Mod/AddonManager/addonmanager_installer.py +++ b/src/Mod/AddonManager/addonmanager_installer.py @@ -24,7 +24,7 @@ """ Contains the classes to manage Addon installation: intended as a stable API, safe for external code to call and to rely upon existing. See classes AddonInstaller and MacroInstaller for details. """ - +import json from datetime import datetime, timezone from enum import IntEnum, auto import os @@ -503,16 +503,34 @@ class MacroInstaller(QtCore.QObject): self.finished.emit() return False - # If it succeeded, move all of the files to the macro install location + # If it succeeded, move all the files to the macro install location, + # keeping a list of all the files we installed, so they can be removed later + # if this macro is uninstalled. + manifest = [] for item in os.listdir(temp_dir): src = os.path.join(temp_dir, item) dst = os.path.join(self.installation_path, item) shutil.move(src, dst) + manifest.append(dst) + self._write_installation_manifest(manifest) self.success.emit(self.addon_to_install) self.addon_to_install.set_status(Addon.Status.NO_UPDATE_AVAILABLE) self.finished.emit() return True + def _write_installation_manifest(self, manifest): + manifest_file = os.path.join( + self.installation_path, self.addon_to_install.macro.filename + ".manifest" + ) + try: + with open(manifest_file, "w", encoding="utf-8") as f: + f.write(json.dumps(manifest, indent=" ")) + except OSError as e: + FreeCAD.Console.PrintWarning( + translate("AddonsInstaller", "Failed to create installation manifest " "file:\n") + ) + FreeCAD.Console.PrintWarning(manifest_file) + @classmethod def _validate_object(cls, addon: object): """Make sure this object provides an attribute called "macro" with a method called diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index c133a0a9b3..b01a7fde78 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -69,6 +69,7 @@ class Macro: self.version = "" self.date = "" self.src_filename = "" + self.filename_from_url = "" self.author = "" self.icon = "" self.icon_source = None @@ -104,6 +105,8 @@ class Macro: """The filename of this macro""" if self.on_git: return os.path.basename(self.src_filename) + elif self.filename_from_url: + return self.filename_from_url return (self.name + ".FCMacro").replace(" ", "_") def is_installed(self): @@ -211,8 +214,14 @@ class Macro: ) return None code = u2.decode("utf8") + self._set_filename_from_url(self.raw_code_url) return code + def _set_filename_from_url(self, url: str): + lhs, slash, rhs = url.rpartition("/") + if rhs.endswith(".py") or rhs.lower().endswith(".fcmacro"): + self.filename_from_url = rhs + @staticmethod def _read_code_from_wiki(p: str) -> Optional[str]: code = re.findall(r"
(.*?)
", p.replace("\n", "--endl--")) diff --git a/src/Mod/AddonManager/addonmanager_uninstaller.py b/src/Mod/AddonManager/addonmanager_uninstaller.py index b83c8085f2..0c42f591dd 100644 --- a/src/Mod/AddonManager/addonmanager_uninstaller.py +++ b/src/Mod/AddonManager/addonmanager_uninstaller.py @@ -24,7 +24,7 @@ """ Contains the classes to manage Addon removal: intended as a stable API, safe for external code to call and to rely upon existing. See classes AddonUninstaller and MacroUninstaller for details.""" - +import json import os from typing import List @@ -228,7 +228,10 @@ class MacroUninstaller(QObject): directories = set() for f in self._get_files_to_remove(): normed = os.path.normpath(f) - full_path = os.path.join(self.installation_location, normed) + if os.path.isabs(normed): + full_path = normed + else: + full_path = os.path.join(self.installation_location, normed) if "/" in f: directories.add(os.path.dirname(full_path)) try: @@ -246,6 +249,10 @@ class MacroUninstaller(QObject): + str(e) ) success = False + except Exception: + # Generic catch-all, just in case (because failure to catch an exception + # here can break things pretty badly) + success = False self._cleanup_directories(directories) @@ -256,8 +263,17 @@ class MacroUninstaller(QObject): self.addon_to_remove.set_status(Addon.Status.NOT_INSTALLED) self.finished.emit() - def _get_files_to_remove(self) -> List[os.PathLike]: + def _get_files_to_remove(self) -> List[str]: """Get the list of files that should be removed""" + manifest_file = os.path.join( + self.installation_location, self.addon_to_remove.macro.filename + ".manifest" + ) + if os.path.exists(manifest_file): + with open(manifest_file, "r", encoding="utf-8") as f: + manifest_data = f.read() + manifest = json.loads(manifest_data) + manifest.append(manifest_file) # Remove the manifest itself as well + return manifest files_to_remove = [self.addon_to_remove.macro.filename] if self.addon_to_remove.macro.icon: files_to_remove.append(self.addon_to_remove.macro.icon) From c1b6a6e657cf690d509a35021726034e4254f5e7 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 26 Jan 2024 07:10:25 -0600 Subject: [PATCH 17/27] Addon Manager: Fix git detection on mac --- src/Mod/AddonManager/addonmanager_git.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Mod/AddonManager/addonmanager_git.py b/src/Mod/AddonManager/addonmanager_git.py index 9d44388948..e3a51b1ae1 100644 --- a/src/Mod/AddonManager/addonmanager_git.py +++ b/src/Mod/AddonManager/addonmanager_git.py @@ -322,6 +322,9 @@ class GitManager: if "Windows" in platform.system(): git_exe += ".exe" + if platform.system() == "Darwin" and not self._xcode_command_line_tools_are_installed(): + return + if not git_exe or not os.path.exists(git_exe): git_exe = shutil.which("git") @@ -331,6 +334,18 @@ class GitManager: prefs.SetString("GitExecutable", git_exe) self.git_exe = git_exe + def _xcode_command_line_tools_are_installed(self) -> bool: + """On Macs, there is *always* an executable called "git", but sometimes it's just a + script that tells the user to install XCode's Command Line tools. So the existence of git + on the Mac actually requires us to check for that installation.""" + try: + subprocess.check_output(["xcode-select", "-p"]) + fci.Console.PrintMessage("XCode command line tools are installed: git is available\n") + return True + except subprocess.CalledProcessError: + fci.Console.PrintMessage("XCode command line tools are not installed: not using git\n") + return False + def _synchronous_call_git(self, args: List[str]) -> str: """Calls git and returns its output.""" final_args = [self.git_exe] From f57c7a5aa5c7d86b9eebed813d1fe24e509bb956 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Fri, 26 Jan 2024 13:41:08 -0500 Subject: [PATCH 18/27] [TD]fix extension bolt hole circle re scale/rotate --- src/Mod/TechDraw/Gui/CommandExtensionPack.cpp | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp index ef51c387b9..346e8cb226 100644 --- a/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp +++ b/src/Mod/TechDraw/Gui/CommandExtensionPack.cpp @@ -123,24 +123,39 @@ void execHoleCircle(Gui::Command* cmd) return; } Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Bolt Circle Centerlines")); - double scale = objFeat->getScale(); + + // make the bolt hole circle from 3 scaled and rotated points Base::Vector3d bigCenter = _circleCenter(Circles[0]->center, Circles[1]->center, Circles[2]->center); - float bigRadius = (Circles[0]->center - bigCenter).Length(); + double bigRadius = (Circles[0]->center - bigCenter).Length(); + // now convert the center & radius to canonical form + bigCenter = DU::invertY(bigCenter); + bigCenter = CosmeticVertex::makeCanonicalPoint(objFeat, bigCenter); + bigCenter = DU::invertY(bigCenter); + bigRadius = bigRadius / objFeat->getScale(); TechDraw::BaseGeomPtr bigCircle = - std::make_shared(bigCenter / scale, bigRadius / scale); + std::make_shared(bigCenter, bigRadius); std::string bigCircleTag = objFeat->addCosmeticEdge(bigCircle); TechDraw::CosmeticEdge* ceCircle = objFeat->getCosmeticEdge(bigCircleTag); _setLineAttributes(ceCircle); + + // make the center lines for the individual bolt holes + constexpr double ExtendFactor{1.1}; for (const TechDraw::CirclePtr& oneCircle : Circles) { - Base::Vector3d oneCircleCenter = oneCircle->center; - float oneRadius = oneCircle->radius; - Base::Vector3d delta = (oneCircle->center - bigCenter).Normalize() * (oneRadius + 2); + // convert the center to canonical form + Base::Vector3d oneCircleCenter = DU::invertY(oneCircle->center); + oneCircleCenter = CosmeticVertex::makeCanonicalPoint(objFeat, oneCircleCenter); + oneCircleCenter = DU::invertY(oneCircleCenter); + // oneCircle->radius is scaled. + float oneRadius = oneCircle->radius / objFeat->getScale(); + // what is magic number 2 (now ExtendFactor)? just a fudge factor to extend the line beyond the bolt + // hole circle? should it be a function of hole diameter? maybe 110% of oneRadius? + Base::Vector3d delta = (oneCircleCenter - bigCenter).Normalize() * (oneRadius * ExtendFactor); Base::Vector3d startPt = oneCircleCenter + delta; Base::Vector3d endPt = oneCircleCenter - delta; - startPt.y = -startPt.y; - endPt.y = -endPt.y; - std::string oneLineTag = objFeat->addCosmeticEdge(startPt / scale, endPt / scale); + startPt = DU::invertY(startPt); + endPt = DU::invertY(endPt); + std::string oneLineTag = objFeat->addCosmeticEdge(startPt, endPt); TechDraw::CosmeticEdge* ceLine = objFeat->getCosmeticEdge(oneLineTag); _setLineAttributes(ceLine); } From f592b66ecbc2a1e89e39977c7c9c21816764cb4d Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 19 Jan 2024 10:32:28 -0600 Subject: [PATCH 19/27] Part/Toponaming: Add original code for makeShapeWithElementMap Called TopoShape::makESHAPE in the original Toponaming branch. The code here has been modified as little as possible to get it to compile in current main. --- src/Mod/Part/App/TopoShape.h | 38 ++ src/Mod/Part/App/TopoShapeExpansion.cpp | 753 +++++++++++++++++++++++- 2 files changed, 782 insertions(+), 9 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 573e4eb322..bf30436a82 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -638,6 +638,44 @@ public: void mapSubElement(const std::vector &shapes, const char *op=nullptr); bool hasPendingElementMap() const; + /** Helper class to return the generated and modified shape given an input shape + * + * Shape history information is extracted using OCCT APIs + * BRepBuilderAPI_MakeShape::Generated/Modified(). However, there is often + * some glitches in various derived class. So we use this class as an + * abstraction, and create various derived classes to deal with the glitches. + */ + struct PartExport Mapper { + /// Helper vector for temporary storage of both generated and modified shapes + mutable std::vector _res; + virtual ~Mapper() {} + /// Return a list of shape generated from the given input shape + virtual const std::vector &generated(const TopoDS_Shape &) const { + return _res; + } + /// Return a list of shape modified from the given input shape + virtual const std::vector &modified(const TopoDS_Shape &) const { + return _res; + } + }; + + /** Core function to generate mapped element names from shape history + * + * @param shape: the new shape + * @param mapper: for mapping input shapes to generated/modified shapes + * @param sources: list of source shapes. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The original content of this TopoShape is discarded and replaced + * with the given new shape. The function returns the TopoShape + * itself as a self reference so that multiple operations can be + * carried out for the same shape in the same line of code. + */ + TopoShape &makeShapeWithElementMap(const TopoDS_Shape &shape, + const Mapper &mapper, + const std::vector &sources, + const char *op=nullptr); /** Helper class to return the generated and modified shape given an input shape * diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index e7554ebaa8..be31389811 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -25,21 +25,23 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #endif -#include -#include -#include #include "TopoShape.h" #include "TopoShapeCache.h" +#include "TopoShapeOpCode.h" #include "FaceMaker.h" @@ -338,6 +340,8 @@ void checkAndMatchHasher(TopoShape& topoShape1, const TopoShape& topoShape2) } } // namespace + +// TODO: Refactor mapSubElementTypeForShape to reduce complexity void TopoShape::mapSubElementTypeForShape(const TopoShape& other, TopAbs_ShapeEnum type, const char* op, @@ -387,7 +391,8 @@ void TopoShape::mapSubElementTypeForShape(const TopoShape& other, } std::ostringstream ss; char elementType {shapeName(type)[0]}; - if ( ! elementMap() ) { + if (!elementMap()) { + // NOLINTNEXTLINE FC_THROWM(NullShapeException, "No element map"); } elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag); @@ -509,6 +514,735 @@ void TopoShape::mapSubElement(const std::vector& shapes, const char* } } +struct ShapeInfo +{ + const TopoDS_Shape& shape; + TopoShapeCache::Ancestry& cache; + TopAbs_ShapeEnum type; + const char* shapetype; + + ShapeInfo(const TopoDS_Shape& shape, TopAbs_ShapeEnum type, TopoShapeCache::Ancestry& cache) + : shape(shape) + , cache(cache) + , type(type) + , shapetype(TopoShape::shapeName(type).c_str()) + {} + + int count() const + { + return cache.count(); + } + + TopoDS_Shape find(int index) + { + return cache.find(shape, index); + } + + int find(const TopoDS_Shape& subshape) + { + return cache.find(shape, subshape); + } +}; + +//////////////////////////////////////// +// makESHAPE -> makeShapeWithElementMap +/////////////////////////////////////// + +struct NameKey +{ + Data::MappedName name; + long tag = 0; + int shapetype = 0; + + NameKey() + = default; + explicit NameKey(const Data::MappedName& n) + : name(n) + {} + NameKey(int type, Data::MappedName n) + : name(std::move(n)) + { + // Order the shape type from vertex < edge < face < other. We'll rely + // on this for sorting when we name the geometry element. + switch (type) { + case TopAbs_VERTEX: + shapetype = 0; + break; + case TopAbs_EDGE: + shapetype = 1; + break; + case TopAbs_FACE: + shapetype = 2; + break; + default: + shapetype = 3; + } + } + bool operator<(const NameKey& other) const + { + if (shapetype < other.shapetype) { + return true; + } + if (shapetype > other.shapetype) { + return false; + } + if (tag < other.tag) { + return true; + } + if (tag > other.tag) { + return false; + } + return name < other.name; + } +}; + +struct NameInfo +{ + int index{}; + Data::ElementIDRefs sids; + const char* shapetype{}; +}; + + +const std::string& modPostfix() +{ + static std::string postfix(TopoShape::elementMapPrefix() + ":M"); + return postfix; +} + +const std::string& modgenPostfix() +{ + static std::string postfix(modPostfix() + "G"); + return postfix; +} + +const std::string& genPostfix() +{ + static std::string postfix(TopoShape::elementMapPrefix() + ":G"); + return postfix; +} + +const std::string& upperPostfix() +{ + static std::string postfix(TopoShape::elementMapPrefix() + ":U"); + return postfix; +} + +const std::string& lowerPostfix() +{ + static std::string postfix(TopoShape::elementMapPrefix() + ":L"); + return postfix; +} + +// TODO: Refactor makeShapeWithElementMap to reduce complexity +TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, + const Mapper& mapper, + const std::vector& shapes, + const char* op) +{ + setShape(shape); + if (shape.IsNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + + if (shapes.empty()) { + return *this; + } + + size_t canMap = 0; + for (auto& incomingShape : shapes) { + if (canMapElement(incomingShape)) { + ++canMap; + } + } + if (canMap == 0U) { + return *this; + } + if (canMap != shapes.size() && FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Not all input shapes are mappable"); // NOLINT + } + + if (!op) { + op = Part::OpCodes::Maker; + } + std::string _op = op; + _op += '_'; + + initCache(); + ShapeInfo vinfo(_Shape, TopAbs_VERTEX, _cache->getAncestry(TopAbs_VERTEX)); + ShapeInfo einfo(_Shape, TopAbs_EDGE, _cache->getAncestry(TopAbs_EDGE)); + ShapeInfo finfo(_Shape, TopAbs_FACE, _cache->getAncestry(TopAbs_FACE)); + mapSubElement(shapes, op); + + std::array infos = {&vinfo, &einfo, &finfo}; + + std::array infoMap{}; + infoMap[TopAbs_VERTEX] = &vinfo; + infoMap[TopAbs_EDGE] = &einfo; + infoMap[TopAbs_WIRE] = &einfo; + infoMap[TopAbs_FACE] = &finfo; + infoMap[TopAbs_SHELL] = &finfo; + infoMap[TopAbs_SOLID] = &finfo; + infoMap[TopAbs_COMPOUND] = &finfo; + infoMap[TopAbs_COMPSOLID] = &finfo; + + std::ostringstream ss; + std::string postfix; + Data::MappedName newName; + + std::map> newNames; + + // First, collect names from other shapes that generates or modifies the + // new shape + for (auto& pinfo : infos) { + auto& info = *pinfo; + for (const auto & incomingShape : shapes) { + if (!canMapElement(incomingShape)) { + continue; + } + auto& otherMap = incomingShape._cache->getAncestry(info.type); + if (otherMap.count() == 0) { + continue; + } + + for (int i = 1; i <= otherMap.count(); i++) { + const auto& otherElement = otherMap.find(incomingShape._Shape, i); + // Find all new objects that are a modification of the old object + Data::ElementIDRefs sids; + NameKey key(info.type, + incomingShape.getMappedName(Data::IndexedName::fromConst(info.shapetype, i), + true, + &sids)); + + int newShapeCounter = 0; + for (auto& newShape : mapper.modified(otherElement)) { + ++newShapeCounter; + if (newShape.ShapeType() >= TopAbs_SHAPE) { + // NOLINTNEXTLINE + FC_ERR("unknown modified shape type " << newShape.ShapeType() << " from " + << info.shapetype << i); + continue; + } + auto& newInfo = *infoMap[newShape.ShapeType()]; + if (newInfo.type != newShape.ShapeType()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // TODO: it seems modified shape may report higher + // level shape type just like generated shape below. + // Maybe we shall do the same for name construction. + // NOLINTNEXTLINE + FC_WARN("modified shape type " << shapeName(newShape.ShapeType()) + << " mismatch with " << info.shapetype + << i); + } + continue; + } + int newShapeIndex = newInfo.find(newShape); + if (newShapeIndex == 0) { + // This warning occurs in makERevolve. It generates + // some shape from a vertex that never made into the + // final shape. There may be incomingShape cases there. + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE + FC_WARN("Cannot find " << op << " modified " << newInfo.shapetype + << " from " << info.shapetype << i); + } + continue; + } + + Data::IndexedName element = Data::IndexedName::fromConst(newInfo.shapetype, newShapeIndex); + if (getMappedName(element)) { + continue; + } + + key.tag = incomingShape.Tag; + auto& name_info = newNames[element][key]; + name_info.sids = sids; + name_info.index = newShapeCounter; + name_info.shapetype = info.shapetype; + } + + int checkParallel = -1; + gp_Pln pln; + + // Find all new objects that were generated from an old object + // (e.g. a face generated from an edge) + newShapeCounter = 0; + for (auto& newShape : mapper.generated(otherElement)) { + if (newShape.ShapeType() >= TopAbs_SHAPE) { + // NOLINTNEXTLINE + FC_ERR("unknown generated shape type " << newShape.ShapeType() << " from " + << info.shapetype << i); + continue; + } + + int parallelFace = -1; + int coplanarFace = -1; + auto& newInfo = *infoMap[newShape.ShapeType()]; + std::vector newShapes; + int shapeOffset = 0; + if (newInfo.type == newShape.ShapeType()) { + newShapes.push_back(newShape); + } + else { + // It is possible for the maker to report generating a + // higher level shape, such as shell or solid. For + // example, when extruding, OCC will report the + // extruding face generating the entire solid. However, + // it will also report the edges of the extruding face + // generating the side faces. In this case, too much + // information is bad for us. We don't want the name of + // the side face (and its edges) to be coupled with + // incomingShape (unrelated) edges in the extruding face. + // + // shapeOffset below is used to make sure the higher + // level mapped names comes late after sorting. We'll + // ignore those names if there are more precise mapping + // available. + shapeOffset = 3; + + if (info.type == TopAbs_FACE && checkParallel < 0) { + if (!TopoShape(otherElement).findPlane(pln)) { + checkParallel = 0; + } + else { + checkParallel = 1; + } + } + for (TopExp_Explorer xp(newShape, newInfo.type); xp.More(); xp.Next()) { + newShapes.push_back(xp.Current()); + + if ((parallelFace < 0 || coplanarFace < 0) && checkParallel > 0) { + // Specialized checking for high level mapped + // face that are either coplanar or parallel + // with the source face, which are common in + // operations like extrusion. Once found, the + // first coplanar face will assign an index of + // INT_MIN+1, and the first parallel face + // INT_MIN. The purpose of these special + // indexing is to make the name more stable for + // those generated faces. + // + // For example, the top or bottom face of an + // extrusion will be named using the extruding + // face. With a fixed index, the name is no + // longer affected by adding/removing of holes + // inside the extruding face/sketch. + gp_Pln plnOther; + if (TopoShape(newShapes.back()).findPlane(plnOther)) { + if (pln.Axis().IsParallel(plnOther.Axis(), + Precision::Angular())) { + if (coplanarFace < 0) { + gp_Vec vec(pln.Axis().Location(), + plnOther.Axis().Location()); + Standard_Real D1 = + gp_Vec(pln.Axis().Direction()).Dot(vec); + if (D1 < 0) { + D1 = -D1; + } + Standard_Real D2 = + gp_Vec(plnOther.Axis().Direction()).Dot(vec); + if (D2 < 0) { + D2 = -D2; + } + if (D1 <= Precision::Confusion() + && D2 <= Precision::Confusion()) { + coplanarFace = (int)newShapes.size(); + continue; + } + } + if (parallelFace < 0) { + parallelFace = (int)newShapes.size(); + } + } + } + } + } + } + key.shapetype += shapeOffset; + for (auto& workingShape : newShapes) { + ++newShapeCounter; + int workingShapeIndex = newInfo.find(workingShape); + if (!workingShapeIndex) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Cannot find " << op << " generated " << newInfo.shapetype + << " from " << info.shapetype << i); + } + continue; + } + + Data::IndexedName element = + Data::IndexedName::fromConst(newInfo.shapetype, workingShapeIndex); + auto mapped = getMappedName(element); + if (mapped) { + continue; + } + + key.tag = incomingShape.Tag; + auto& name_info = newNames[element][key]; + name_info.sids = sids; + if (newShapeCounter == parallelFace) { + name_info.index = std::numeric_limits::min(); + } + else if (newShapeCounter == coplanarFace) { + name_info.index = std::numeric_limits::min() + 1; + } + else { + name_info.index = -newShapeCounter; + } + name_info.shapetype = info.shapetype; + } + key.shapetype -= shapeOffset; + } + } + } + } + + // We shall first exclude those names generated from high level mapping. If + // there are still any unnamed elements left after we go through the process + // below, we set delayed=true, and start using those excluded names. + bool delayed = false; + + while (true) { + + // Construct the names for modification/generation info collected in + // the previous step + for (auto itName = newNames.begin(), itNext = itName; itNext != newNames.end(); + itName = itNext) { + // We treat the first modified/generated source shape name specially. + // If case there are more than one source shape. We hash the first + // source name separately, and then obtain the second string id by + // hashing all the source names together. We then use the second + // string id as the postfix for our name. + // + // In this way, we can associate the same source that are modified by + // multiple other shapes. + + ++itNext; + + auto& element = itName->first; + auto& names = itName->second; + const auto& first_key = names.begin()->first; + auto& first_info = names.begin()->second; + + if (!delayed && first_key.shapetype >= 3 && first_info.index > INT_MIN + 1) { + // This name is mapped from high level (shell, solid, etc.) + // Delay till next round. + // + // index>INT_MAX+1 is for checking generated coplanar and + // parallel face mapping, which has special fixed index to make + // name stable. These names are not delayed. + continue; + } + if (!delayed && getMappedName(element)) { + newNames.erase(itName); + continue; + } + + int name_type = + first_info.index > 0 ? 1 : 2; // index>0 means modified, or else generated + Data::MappedName first_name = first_key.name; + + Data::ElementIDRefs sids(first_info.sids); + + postfix.clear(); + if (names.size() > 1) { + ss.str(""); + ss << '('; + bool first = true; + auto it = names.begin(); + int count = 0; + for (++it; it != names.end(); ++it) { + auto& other_key = it->first; + if (other_key.shapetype >= 3 && first_key.shapetype < 3) { + // shapetype>=3 means it's a high level mapping (e.g. a face + // generates a solid). We don't want that if there are more + // precise low level mapping available. See comments above + // for more details. + break; + } + if (first) { + first = false; + } + else { + ss << '|'; + } + auto& other_info = it->second; + std::ostringstream ss2; + if (other_info.index != 1) { + // 'K' marks the additional source shape of this + // generate (or modified) shape. + ss2 << elementMapPrefix() << 'K'; + if (other_info.index == INT_MIN) { + ss2 << '0'; + } + else if (other_info.index == INT_MIN + 1) { + ss2 << "00"; + } + else { + // The same source shape may generate or modify + // more than one shape. The index here marks the + // position it is reported by OCC. Including the + // index here is likely to degrade name stablilty, + // but is unfortunately a necessity to avoid + // duplicate names. + ss2 << other_info.index; + } + } + Data::MappedName other_name = other_key.name; + elementMap()->encodeElementName(other_info.shapetype[0], + other_name, + ss2, + &sids, + Tag, + 0, + other_key.tag); + ss << other_name; + if ((name_type == 1 && other_info.index < 0) + || (name_type == 2 && other_info.index > 0)) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE + FC_WARN("element is both generated and modified"); + } + name_type = 0; + } + sids += other_info.sids; + // To avoid the name becoming to long, just put some limit here + if (++count == 4) { + break; + } + } + if (!first) { + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << sids.back().toString(); + } + postfix = ss.str(); + } + } + + ss.str(""); + if (name_type == 2) { + ss << genPostfix(); + } + else if (name_type == 1) { + ss << modPostfix(); + } + else { + ss << modgenPostfix(); + } + if (first_info.index == INT_MIN) { + ss << '0'; + } + else if (first_info.index == INT_MIN + 1) { + ss << "00"; + } + else if (abs(first_info.index) > 1) { + ss << abs(first_info.index); + } + ss << postfix; + elementMap() + ->encodeElementName(element[0], first_name, ss, &sids, Tag, op, first_key.tag); + elementMap()->setElementName(element, first_name, Tag, &sids); + + if (!delayed && first_key.shapetype < 3) { + newNames.erase(itName); + } + } + + // The reverse pass. Starting from the highest level element, i.e. + // Face, for any element that are named, assign names for its lower unnamed + // elements. For example, if Edge1 is named E1, and its vertexes are not + // named, then name them as E1;U1, E1;U2, etc. + // + // In order to make the name as stable as possible, we may assign multiple + // names (which must be sorted, because we may use the first one to name + // upper element in the final pass) to lower element if it appears in + // multiple higher elements, e.g. same edge in multiple faces. + + for (size_t infoIndex = infos.size() - 1; infoIndex != 0; --infoIndex) { + std::map> + names; + auto& info = *infos[infoIndex]; + auto& next = *infos[infoIndex - 1]; + int elementCounter = 1; + auto it = newNames.end(); + if (delayed) { + it = newNames.upper_bound(Data::IndexedName::fromConst(info.shapetype, 0)); + } + for (;; ++elementCounter) { + Data::IndexedName element; + if (!delayed) { + if (elementCounter > info.count()) { + break; + } + element = Data::IndexedName::fromConst(info.shapetype, elementCounter); + if (newNames.count(element) != 0U) { + continue; + } + } + else if (it == newNames.end() + || !boost::starts_with(it->first.getType(), info.shapetype)) { + break; + } + else { + element = it->first; + ++it; + elementCounter = element.getIndex(); + if (elementCounter == 0 || elementCounter > info.count()) { + continue; + } + } + Data::ElementIDRefs sids; + Data::MappedName mapped = getMappedName(element, false, &sids); + if (!mapped) { + continue; + } + + TopTools_IndexedMapOfShape submap; + TopExp::MapShapes(info.find(elementCounter), next.type, submap); + for (int submapIndex = 1, infoCounter = 1; submapIndex <= submap.Extent(); ++submapIndex) { + ss.str(""); + int elementIndex = next.find(submap(submapIndex)); + assert(elementIndex); + Data::IndexedName indexedName = Data::IndexedName::fromConst(next.shapetype, elementIndex); + if (getMappedName(indexedName)) { + continue; + } + auto& infoRef = names[indexedName][mapped]; + infoRef.index = infoCounter++; + infoRef.sids = sids; + } + } + // Assign the actual names + for (auto& actualName : names) { +#ifndef FC_ELEMENT_MAP_ALL + // Do we really want multiple names for an element in this case? + // If not, we just pick the name in the first sorting order here. + auto& name = *actualName.second.begin(); +#else + for (auto& name : actualName.second) +#endif + { + auto& infoRef = name.second; + auto& sids = infoRef.sids; + newName = name.first; + ss.str(""); + ss << upperPostfix(); + if (infoRef.index > 1) { + ss << infoRef.index; + } + elementMap()->encodeElementName(actualName.first[0], newName, ss, &sids, Tag, op); + elementMap()->setElementName(actualName.first, newName, Tag, &sids); + } + } + } + + // The forward pass. For any elements that are not named, try construct its + // name from the lower elements + bool hasUnnamed = false; + for (size_t ifo = 1; ifo < infos.size(); ++ifo) { + auto& info = *infos[ifo]; + auto& prev = *infos[ifo - 1]; + for (int i = 1; i <= info.count(); ++i) { + Data::IndexedName element = Data::IndexedName::fromConst(info.shapetype, i); + if (getMappedName(element)) { + continue; + } + + Data::ElementIDRefs sids; + std::map names; + TopExp_Explorer xp; + if (info.type == TopAbs_FACE) { + xp.Init(BRepTools::OuterWire(TopoDS::Face(info.find(i))), TopAbs_EDGE); + } + else { + xp.Init(info.find(i), prev.type); + } + for (; xp.More(); xp.Next()) { + int j = prev.find(xp.Current()); + assert(j); + Data::IndexedName prevElement = Data::IndexedName::fromConst(prev.shapetype, j); + if (!delayed && (newNames.count(prevElement) != 0U)) { + names.clear(); + break; + } + Data::ElementIDRefs sid; + Data::MappedName name = getMappedName(prevElement, false, &sid); + if (!name) { + // only assign name if all lower elements are named + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE + FC_WARN("unnamed lower element " << prevElement); + } + names.clear(); + break; + } + auto res = names.emplace(name, prevElement); + if (res.second) { + sids += sid; + } + else if (prevElement != res.first->second) { + // The seam edge will appear twice, which is normal. We + // only warn if the mapped element names are different. + // NOLINTNEXTLINE + FC_WARN("lower element " << prevElement << " and " << res.first->second + << " has duplicated name " << name << " for " + << info.shapetype << i); + } + } + if (names.empty()) { + hasUnnamed = true; + continue; + } + auto it = names.begin(); + newName = it->first; + if (names.size() == 1) { + ss << lowerPostfix(); + } + else { + bool first = true; + ss.str(""); + if (!Hasher) { + ss << lowerPostfix(); + } + ss << '('; + int count = 0; + for (++it; it != names.end(); ++it) { + if (first) { + first = false; + } + else { + ss << '|'; + } + ss << it->first; + + // To avoid the name becoming to long, just put some limit here + if (++count == 4) { + break; + } + } + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << lowerPostfix() << sids.back().toString(); + } + } + elementMap()->encodeElementName(element[0], newName, ss, &sids, Tag, op); + elementMap()->setElementName(element, newName, Tag, &sids); + } + } + if (!hasUnnamed || delayed || newNames.empty()) { + break; + } + delayed = true; + } + return *this; +} + namespace { void addShapesToBuilder(const std::vector& shapes, @@ -634,7 +1368,8 @@ TopoShape& TopoShape::makeElementFace(const std::vector& shapes, /** * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. * - * @param element The element name(type) that provides 1 one character suffix to the name IF . + * @param element The element name(type) that provides 1 one character suffix to the name IF + * . * @param names The subnames to build the name from. If empty, return the TopoShape MappedName. * @param marker The elementMap name or suffix to start the name with. If null, use the * elementMapPrefix. From ca6d9f9944fe68e6870eaa82c3199e0bf9ddecb6 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 19 Jan 2024 14:11:07 -0700 Subject: [PATCH 20/27] Part/Toponaming: Basic linter cleanup of makeShapeWithElementMap No major refactoring. --- src/Mod/Part/App/TopoShapeExpansion.cpp | 202 ++++++++++++------------ 1 file changed, 105 insertions(+), 97 deletions(-) diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index be31389811..a57780f7da 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -37,6 +37,7 @@ #include #include +#include #endif #include "TopoShape.h" @@ -392,8 +393,7 @@ void TopoShape::mapSubElementTypeForShape(const TopoShape& other, std::ostringstream ss; char elementType {shapeName(type)[0]}; if (!elementMap()) { - // NOLINTNEXTLINE - FC_THROWM(NullShapeException, "No element map"); + FC_THROWM(NullShapeException, "No element map"); // NOLINT } elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag); elementMap()->setElementName(element, name, Tag, &sids); @@ -556,8 +556,8 @@ struct NameKey NameKey() = default; - explicit NameKey(const Data::MappedName& n) - : name(n) + explicit NameKey(Data::MappedName n) + : name(std::move(n)) {} NameKey(int type, Data::MappedName n) : name(std::move(n)) @@ -598,9 +598,9 @@ struct NameKey struct NameInfo { - int index{}; + int index {}; Data::ElementIDRefs sids; - const char* shapetype{}; + const char* shapetype {}; }; @@ -634,6 +634,61 @@ const std::string& lowerPostfix() return postfix; } +// TODO: Refactor checkForParallelOrCoplanar to reduce complexity +void checkForParallelOrCoplanar(const TopoDS_Shape& newShape, + const ShapeInfo& newInfo, + std::vector& newShapes, + const gp_Pln& pln, + int parallelFace, + int& coplanarFace, + int& checkParallel) +{ + for (TopExp_Explorer xp(newShape, newInfo.type); xp.More(); xp.Next()) { + newShapes.push_back(xp.Current()); + + if ((parallelFace < 0 || coplanarFace < 0) && checkParallel > 0) { + // Specialized checking for high level mapped + // face that are either coplanar or parallel + // with the source face, which are common in + // operations like extrusion. Once found, the + // first coplanar face will assign an index of + // INT_MIN+1, and the first parallel face + // INT_MIN. The purpose of these special + // indexing is to make the name more stable for + // those generated faces. + // + // For example, the top or bottom face of an + // extrusion will be named using the extruding + // face. With a fixed index, the name is no + // longer affected by adding/removing of holes + // inside the extruding face/sketch. + gp_Pln plnOther; + if (TopoShape(newShapes.back()).findPlane(plnOther)) { + if (pln.Axis().IsParallel(plnOther.Axis(), Precision::Angular())) { + if (coplanarFace < 0) { + gp_Vec vec(pln.Axis().Location(), plnOther.Axis().Location()); + Standard_Real D1 = gp_Vec(pln.Axis().Direction()).Dot(vec); + if (D1 < 0) { + D1 = -D1; + } + Standard_Real D2 = gp_Vec(plnOther.Axis().Direction()).Dot(vec); + if (D2 < 0) { + D2 = -D2; + } + if (D1 <= Precision::Confusion() && D2 <= Precision::Confusion()) { + coplanarFace = (int)newShapes.size(); + continue; + } + } + if (parallelFace < 0) { + parallelFace = (int)newShapes.size(); + } + } + } + } + } +} + // TODO: Refactor makeShapeWithElementMap to reduce complexity TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, const Mapper& mapper, @@ -669,22 +724,22 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, _op += '_'; initCache(); - ShapeInfo vinfo(_Shape, TopAbs_VERTEX, _cache->getAncestry(TopAbs_VERTEX)); - ShapeInfo einfo(_Shape, TopAbs_EDGE, _cache->getAncestry(TopAbs_EDGE)); - ShapeInfo finfo(_Shape, TopAbs_FACE, _cache->getAncestry(TopAbs_FACE)); + ShapeInfo vertexInfo(_Shape, TopAbs_VERTEX, _cache->getAncestry(TopAbs_VERTEX)); + ShapeInfo edgeInfo(_Shape, TopAbs_EDGE, _cache->getAncestry(TopAbs_EDGE)); + ShapeInfo faceInfo(_Shape, TopAbs_FACE, _cache->getAncestry(TopAbs_FACE)); mapSubElement(shapes, op); - std::array infos = {&vinfo, &einfo, &finfo}; + std::array infos = {&vertexInfo, &edgeInfo, &faceInfo}; - std::array infoMap{}; - infoMap[TopAbs_VERTEX] = &vinfo; - infoMap[TopAbs_EDGE] = &einfo; - infoMap[TopAbs_WIRE] = &einfo; - infoMap[TopAbs_FACE] = &finfo; - infoMap[TopAbs_SHELL] = &finfo; - infoMap[TopAbs_SOLID] = &finfo; - infoMap[TopAbs_COMPOUND] = &finfo; - infoMap[TopAbs_COMPSOLID] = &finfo; + std::array infoMap {}; + infoMap[TopAbs_VERTEX] = &vertexInfo; + infoMap[TopAbs_EDGE] = &edgeInfo; + infoMap[TopAbs_WIRE] = &edgeInfo; + infoMap[TopAbs_FACE] = &faceInfo; + infoMap[TopAbs_SHELL] = &faceInfo; + infoMap[TopAbs_SOLID] = &faceInfo; + infoMap[TopAbs_COMPOUND] = &faceInfo; + infoMap[TopAbs_COMPSOLID] = &faceInfo; std::ostringstream ss; std::string postfix; @@ -723,7 +778,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, << info.shapetype << i); continue; } - auto& newInfo = *infoMap[newShape.ShapeType()]; + auto& newInfo = *infoMap.at(newShape.ShapeType()); if (newInfo.type != newShape.ShapeType()) { if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { // TODO: it seems modified shape may report higher @@ -777,7 +832,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, int parallelFace = -1; int coplanarFace = -1; - auto& newInfo = *infoMap[newShape.ShapeType()]; + auto& newInfo = *infoMap.at(newShape.ShapeType()); std::vector newShapes; int shapeOffset = 0; if (newInfo.type == newShape.ShapeType()) { @@ -808,62 +863,21 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, checkParallel = 1; } } - for (TopExp_Explorer xp(newShape, newInfo.type); xp.More(); xp.Next()) { - newShapes.push_back(xp.Current()); - - if ((parallelFace < 0 || coplanarFace < 0) && checkParallel > 0) { - // Specialized checking for high level mapped - // face that are either coplanar or parallel - // with the source face, which are common in - // operations like extrusion. Once found, the - // first coplanar face will assign an index of - // INT_MIN+1, and the first parallel face - // INT_MIN. The purpose of these special - // indexing is to make the name more stable for - // those generated faces. - // - // For example, the top or bottom face of an - // extrusion will be named using the extruding - // face. With a fixed index, the name is no - // longer affected by adding/removing of holes - // inside the extruding face/sketch. - gp_Pln plnOther; - if (TopoShape(newShapes.back()).findPlane(plnOther)) { - if (pln.Axis().IsParallel(plnOther.Axis(), - Precision::Angular())) { - if (coplanarFace < 0) { - gp_Vec vec(pln.Axis().Location(), - plnOther.Axis().Location()); - Standard_Real D1 = - gp_Vec(pln.Axis().Direction()).Dot(vec); - if (D1 < 0) { - D1 = -D1; - } - Standard_Real D2 = - gp_Vec(plnOther.Axis().Direction()).Dot(vec); - if (D2 < 0) { - D2 = -D2; - } - if (D1 <= Precision::Confusion() - && D2 <= Precision::Confusion()) { - coplanarFace = (int)newShapes.size(); - continue; - } - } - if (parallelFace < 0) { - parallelFace = (int)newShapes.size(); - } - } - } - } - } + checkForParallelOrCoplanar(newShape, + newInfo, + newShapes, + pln, + parallelFace, + coplanarFace, + checkParallel); } key.shapetype += shapeOffset; for (auto& workingShape : newShapes) { ++newShapeCounter; int workingShapeIndex = newInfo.find(workingShape); - if (!workingShapeIndex) { + if (workingShapeIndex == 0) { if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE FC_WARN("Cannot find " << op << " generated " << newInfo.shapetype << " from " << info.shapetype << i); } @@ -989,19 +1003,18 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, } } Data::MappedName other_name = other_key.name; - elementMap()->encodeElementName(other_info.shapetype[0], + elementMap()->encodeElementName(*other_info.shapetype, other_name, ss2, &sids, Tag, - 0, + nullptr, other_key.tag); ss << other_name; if ((name_type == 1 && other_info.index < 0) || (name_type == 2 && other_info.index > 0)) { if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { - // NOLINTNEXTLINE - FC_WARN("element is both generated and modified"); + FC_WARN("element is both generated and modified"); // NOLINT } name_type = 0; } @@ -1065,8 +1078,8 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, std::map> names; - auto& info = *infos[infoIndex]; - auto& next = *infos[infoIndex - 1]; + auto& info = *infos.at(infoIndex); + auto& next = *infos.at(infoIndex - 1); int elementCounter = 1; auto it = newNames.end(); if (delayed) { @@ -1117,25 +1130,21 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, } } // Assign the actual names - for (auto& actualName : names) { -#ifndef FC_ELEMENT_MAP_ALL + for (auto& [indexedName, nameInfoMap] : names) { // Do we really want multiple names for an element in this case? // If not, we just pick the name in the first sorting order here. - auto& name = *actualName.second.begin(); -#else - for (auto& name : actualName.second) -#endif + auto& nameInfoMapEntry = *nameInfoMap.begin(); { - auto& infoRef = name.second; - auto& sids = infoRef.sids; - newName = name.first; + auto& nameInfo = nameInfoMapEntry.second; + auto& sids = nameInfo.sids; + newName = nameInfoMapEntry.first; ss.str(""); ss << upperPostfix(); - if (infoRef.index > 1) { - ss << infoRef.index; + if (nameInfo.index > 1) { + ss << nameInfo.index; } - elementMap()->encodeElementName(actualName.first[0], newName, ss, &sids, Tag, op); - elementMap()->setElementName(actualName.first, newName, Tag, &sids); + elementMap()->encodeElementName(indexedName[0], newName, ss, &sids, Tag, op); + elementMap()->setElementName(indexedName, newName, Tag, &sids); } } } @@ -1144,8 +1153,8 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, // name from the lower elements bool hasUnnamed = false; for (size_t ifo = 1; ifo < infos.size(); ++ifo) { - auto& info = *infos[ifo]; - auto& prev = *infos[ifo - 1]; + auto& info = *infos.at(ifo); + auto& prev = *infos.at(ifo-1); for (int i = 1; i <= info.count(); ++i) { Data::IndexedName element = Data::IndexedName::fromConst(info.shapetype, i); if (getMappedName(element)) { @@ -1162,9 +1171,9 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, xp.Init(info.find(i), prev.type); } for (; xp.More(); xp.Next()) { - int j = prev.find(xp.Current()); - assert(j); - Data::IndexedName prevElement = Data::IndexedName::fromConst(prev.shapetype, j); + int previousElementIndex = prev.find(xp.Current()); + assert(previousElementIndex); + Data::IndexedName prevElement = Data::IndexedName::fromConst(prev.shapetype, previousElementIndex); if (!delayed && (newNames.count(prevElement) != 0U)) { names.clear(); break; @@ -1174,8 +1183,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, if (!name) { // only assign name if all lower elements are named if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { - // NOLINTNEXTLINE - FC_WARN("unnamed lower element " << prevElement); + FC_WARN("unnamed lower element " << prevElement); // NOLINT } names.clear(); break; From e9092bcc7c8216422f5348a4251364e018d95ce5 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Fri, 19 Jan 2024 14:55:45 -0700 Subject: [PATCH 21/27] Tests/Toponaming: Add test framework for makeShapeWithElementMap --- tests/src/Mod/Part/App/CMakeLists.txt | 2 + tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 23 +------ .../Part/App/TopoShapeExpansionHelpers.cpp | 26 ++++++++ .../Mod/Part/App/TopoShapeExpansionHelpers.h | 13 ++++ .../App/TopoShapeMakeShapeWithElementMap.cpp | 61 +++++++++++++++++++ 5 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp create mode 100644 tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h create mode 100644 tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index 9c642e65e2..4c5bedf30d 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -15,5 +15,7 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansionHelpers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShapeWithElementMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp ) diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index ff976589d6..dd72b48d20 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -2,6 +2,7 @@ #include "gtest/gtest.h" #include "src/App/InitApplication.h" +#include "TopoShapeExpansionHelpers.h" #include #include @@ -125,30 +126,10 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoShapesGeneratesMap) EXPECT_EQ(4, topoShape.getMappedChildElements().size()); // two vertices and two edges } -namespace -{ - -std::pair CreateTwoCubes() -{ - auto boxMaker1 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); - boxMaker1.Build(); - auto box1 = boxMaker1.Shape(); - - auto boxMaker2 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); - boxMaker2.Build(); - auto box2 = boxMaker2.Shape(); - auto transform = gp_Trsf(); - transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)); - box2.Location(TopLoc_Location(transform)); - - return {box1, box2}; -} -} // namespace - TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes) { // Arrange - auto [cube1, cube2] = CreateTwoCubes(); + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); Part::TopoShape cube1TS {cube1}; cube1TS.Tag = 1; Part::TopoShape cube2TS {cube2}; diff --git a/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp b/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp new file mode 100644 index 0000000000..ad401ee7b6 --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "TopoShapeExpansionHelpers.h" + +#include + +namespace TopoShapeExpansionHelpers +{ + +std::pair CreateTwoCubes() +{ + auto boxMaker1 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker1.Build(); + auto box1 = boxMaker1.Shape(); + + auto boxMaker2 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker2.Build(); + auto box2 = boxMaker2.Shape(); + auto transform = gp_Trsf(); + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)); + box2.Location(TopLoc_Location(transform)); + + return {box1, box2}; +} + +} // namespace TopoShapeExpansionHelpers diff --git a/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h b/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h new file mode 100644 index 0000000000..d92c18f4d4 --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef FREECAD_TOPOSHAPEEXPANSIONHELPERS_H +#define FREECAD_TOPOSHAPEEXPANSIONHELPERS_H + +#include + +namespace TopoShapeExpansionHelpers +{ +std::pair CreateTwoCubes(); +} + +#endif // FREECAD_TOPOSHAPEEXPANSIONHELPERS_H diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp new file mode 100644 index 0000000000..f135e23303 --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +// Tests for the makeShapeWithElementMap method, extracted from the main set of tests for TopoShape +// due to length and complexity. + +#include "gtest/gtest.h" +#include "src/App/InitApplication.h" +#include "TopoShapeExpansionHelpers.h" +#include + +#include + +class TopoShapeMakeShapeWithElementMapTests: public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + _docName = App::GetApplication().getUniqueDocumentName("test"); + App::GetApplication().newDocument(_docName.c_str(), "testUser"); + _sids = &_sid; + } + + void TearDown() override + { + App::GetApplication().closeDocument(_docName.c_str()); + } + + Part::TopoShape* Shape() + { + return &_shape; + } + + Part::TopoShape::Mapper* Mapper() + { + return &_mapper; + } + +private: + std::string _docName; + Data::ElementIDRefs _sid; + QVector* _sids = nullptr; + Part::TopoShape _shape; + Part::TopoShape::Mapper _mapper; +}; + +TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + TopoDS_Vertex nullShape; + + // Act and assert + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullShape, *Mapper(), sources), + Part::NullShapeException); +} From 2d30bad9aa6bd25d67866c9ec9c89c32d5f45454 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 24 Jan 2024 17:07:11 -0500 Subject: [PATCH 22/27] Element Map Test --- .../App/TopoShapeMakeShapeWithElementMap.cpp | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp index f135e23303..584652d5c5 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -7,7 +7,7 @@ #include "src/App/InitApplication.h" #include "TopoShapeExpansionHelpers.h" #include - +// #include #include class TopoShapeMakeShapeWithElementMapTests: public ::testing::Test @@ -48,6 +48,7 @@ private: Part::TopoShape::Mapper _mapper; }; + TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) { // Arrange @@ -59,3 +60,47 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) EXPECT_THROW(Shape()->makeShapeWithElementMap(nullShape, *Mapper(), sources), Part::NullShapeException); } + +using Data::IndexedName, Data::MappedName; +using Part::TopoShape; +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapVertex) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapEdge) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapWire) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapFace) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapShell) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapSolid) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompound) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompSolid) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapOffsetCubes) +TEST_F(TopoShapeMakeShapeWithElementMapTests, mapIntersectingCubes) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto transform {gp_Trsf()}; + transform.SetTranslation(gp_Vec(gp_XYZ(-0.5, -0.5, 0))); + cube2.Move(TopLoc_Location(transform)); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; + sources[1].Tag = 2; + // sources[0].makeShapeWithElementMap(sources[0].getShape(), *Mapper(), {sources[0]}); + // sources[1].makeShapeWithElementMap(sources[1].getShape(), *Mapper(), {sources[1]}); + + TopoShape compound; + compound.makeElementCompound(sources); + // Act + compound.makeShapeWithElementMap(compound.getShape(), *Mapper(), sources); + auto elements = compound.getElementMap(); + std::map mapped; + for (auto name : elements) { + mapped.emplace(name.index, name.name); + } + // Assert + EXPECT_EQ(elements.size(), 52); + EXPECT_EQ(mapped[IndexedName("Edge1")], MappedName("Edge1;MAK;:H1:4,E")); + EXPECT_EQ(mapped[IndexedName("Edge", 13)], MappedName("Edge1;MAK;:H2:4,E")); + // EXPECT_STREQ(mapped[IndexedName("Edge1")],MappedName("Edge1;MAK;:H:4,E;MAK;:H1:f,E")); + // EXPECT_STREQ(mapped[IndexedName("Edge",13)],MappedName("Edge1;MAK;:H:4,E;MAK;:H2:f,E")); + EXPECT_STREQ(sources[0].shapeName().c_str(), "Solid"); + EXPECT_STREQ(sources[1].shapeName().c_str(), "Solid"); + EXPECT_STREQ(compound.shapeName().c_str(), "Compound"); +} From 7e550c7e26dad7e408b1bf02b60e8b89c8d1e09d Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 24 Jan 2024 20:10:55 -0500 Subject: [PATCH 23/27] Rename test and add more assertions --- .../App/TopoShapeMakeShapeWithElementMap.cpp | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp index 584652d5c5..cc1bcf0916 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -63,16 +63,15 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) using Data::IndexedName, Data::MappedName; using Part::TopoShape; + // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapVertex) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapEdge) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapWire) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapFace) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapShell) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapSolid) -// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompound) -// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompSolid) -// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapOffsetCubes) -TEST_F(TopoShapeMakeShapeWithElementMapTests, mapIntersectingCubes) + +TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompound) { // Arrange auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); @@ -82,25 +81,40 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, mapIntersectingCubes) std::vector sources {cube1, cube2}; sources[0].Tag = 1; sources[1].Tag = 2; - // sources[0].makeShapeWithElementMap(sources[0].getShape(), *Mapper(), {sources[0]}); - // sources[1].makeShapeWithElementMap(sources[1].getShape(), *Mapper(), {sources[1]}); - + // Map only one of the two sources to test different names. + sources[0].makeShapeWithElementMap(sources[0].getShape(), *Mapper(), {sources[0]}); TopoShape compound; compound.makeElementCompound(sources); + auto preElements = compound.getElementMap(); // Map before mapping. + std::map preMap; + for (auto name : preElements) { + preMap.emplace(name.index, name.name); + } // Act compound.makeShapeWithElementMap(compound.getShape(), *Mapper(), sources); - auto elements = compound.getElementMap(); - std::map mapped; + auto elements = compound.getElementMap(); // Map after mapping + std::map map; for (auto name : elements) { - mapped.emplace(name.index, name.name); + map.emplace(name.index, name.name); } // Assert - EXPECT_EQ(elements.size(), 52); - EXPECT_EQ(mapped[IndexedName("Edge1")], MappedName("Edge1;MAK;:H1:4,E")); - EXPECT_EQ(mapped[IndexedName("Edge", 13)], MappedName("Edge1;MAK;:H2:4,E")); - // EXPECT_STREQ(mapped[IndexedName("Edge1")],MappedName("Edge1;MAK;:H:4,E;MAK;:H1:f,E")); - // EXPECT_STREQ(mapped[IndexedName("Edge",13)],MappedName("Edge1;MAK;:H:4,E;MAK;:H2:f,E")); + EXPECT_EQ(preElements.size(), 52); // Check the before map. + EXPECT_EQ(preMap[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;:H1:b,E")); + EXPECT_EQ(preMap[IndexedName("Edge", 13)], MappedName("Edge1;:H2,E")); + + EXPECT_EQ(elements.size(), 52); // 12 Edges, 8 Vertexes, 6 Faces per each Cube + EXPECT_EQ(map[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;MAK;:H1:f,E")); + EXPECT_EQ(map[IndexedName("Edge", 13)], MappedName("Edge1;MAK;:H2:4,E")); + EXPECT_EQ(map.count(IndexedName("Edge", 24)), 1); + EXPECT_EQ(map.count(IndexedName("Edge", 25)), 0); + EXPECT_EQ(map.count(IndexedName("Vertex", 16)), 1); + EXPECT_EQ(map.count(IndexedName("Vertex", 17)), 0); + EXPECT_EQ(map.count(IndexedName("Face", 12)), 1); + EXPECT_EQ(map.count(IndexedName("Face", 13)), 0); EXPECT_STREQ(sources[0].shapeName().c_str(), "Solid"); EXPECT_STREQ(sources[1].shapeName().c_str(), "Solid"); EXPECT_STREQ(compound.shapeName().c_str(), "Compound"); } +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompSolid) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapOffsetCubes) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapIntersectingCubes) From 1ecae13bca4022ee3c3fec7cb759eb0ed2034017 Mon Sep 17 00:00:00 2001 From: Vincenzo Calligaro Date: Sat, 27 Jan 2024 13:36:38 +0100 Subject: [PATCH 24/27] Tests for makeShapeWithElementMap (#12) * Tests for makeShapeWithElementMap * Added nullShapesThrows tests for the classes derived from * Added test passing as "sources" parameter of the tested method an empty vector of TopoShapes objects * Added test with unmappable sources * Added test to find shapes inside other shapes made from the first ones * Tests for makeShapeWithElementMap with simple shapes * Added test to find source shapes' sub-shapes name in the elements maps * Added test to find the "MAK" OpCode in the elements maps names --------- Signed-off-by: CalligaroV --- .../App/TopoShapeMakeShapeWithElementMap.cpp | 190 +++++++++++++++++- 1 file changed, 188 insertions(+), 2 deletions(-) diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp index cc1bcf0916..99548567be 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -7,8 +7,20 @@ #include "src/App/InitApplication.h" #include "TopoShapeExpansionHelpers.h" #include +#include // #include + #include +#include +#include +#include +#include +#include +#include +#include + +using namespace Part; +using namespace Data; class TopoShapeMakeShapeWithElementMapTests: public ::testing::Test { @@ -54,10 +66,31 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) // Arrange auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; - TopoDS_Vertex nullShape; + TopoDS_Vertex nullVertex; + TopoDS_Edge nullEdge; + TopoDS_Wire nullWire; + TopoDS_Face nullFace; + TopoDS_Shell nullShell; + TopoDS_Solid nullSolid; + TopoDS_CompSolid nullCompSolid; + TopoDS_Compound nullCompound; // Act and assert - EXPECT_THROW(Shape()->makeShapeWithElementMap(nullShape, *Mapper(), sources), + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullVertex, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullEdge, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullWire, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullFace, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullShell, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullSolid, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullCompSolid, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullCompound, *Mapper(), sources), Part::NullShapeException); } @@ -118,3 +151,156 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompound) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompSolid) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapOffsetCubes) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapIntersectingCubes) + +TEST_F(TopoShapeMakeShapeWithElementMapTests, emptySourceShapes) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + std::vector emptySources; + std::vector nonEmptySources {cube1, cube2}; + + // Act and assert + for (auto& source : nonEmptySources) { + Part::TopoShape& modifiedShape = source; + Part::TopoShape& originalShape = source; + + EXPECT_EQ( + &originalShape, + &modifiedShape.makeShapeWithElementMap(source.getShape(), *Mapper(), emptySources)); + } +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, nonMappableSources) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + + // Act and assert + for (auto& source : sources) { + size_t canMap = 0; + for (const auto& mappableSource : sources) { + if (source.canMapElement(mappableSource)) { + ++canMap; + } + } + + if (canMap == 0U) { + EXPECT_EQ(&source, + &source.makeShapeWithElementMap(source.getShape(), *Mapper(), sources)); + } + } +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceShapesInShape) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + sources[1].Tag = 2; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + Part::TopoShape cmpdShape; + cmpdShape.makeElementCompound(sources); + + // Act and assert + for (const auto& source : sources) { + std::vector tmpSources {source}; + for (const auto& subSource : sources) { + Part::TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), tmpSources); + if (&source == &subSource) { + EXPECT_NE(tmpShape.findShape(subSource.getShape()), + 0); // if tmpShape uses, for example, cube1 and we search for cube1 than + // we should find it + } + else { + EXPECT_EQ(tmpShape.findShape(subSource.getShape()), + 0); // if tmpShape uses, for example, cube1 and we search for cube2 than + // we shouldn't find it + } + } + EXPECT_NE(cmpdShape.findShape(source.getShape()), + 0); // as cmpdShape is made with cube1 and cube2 we should find both of them + } +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceSubShapesInElementMap) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + sources[1].Tag = 2; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + + // Act and assert + // Testing with all the source TopoShapes + for (const auto& source : sources) { + TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), sources); + + // First we create a map with the IndexedNames and MappedNames + std::map elementStdMap; + for (const auto& mappedElement : tmpShape.getElementMap()) { + elementStdMap.emplace(mappedElement.index, mappedElement.name); + } + + // Then for all the elements types (Vertex, Edge, Face) ... + for (const auto& shapeType : source.getElementTypes()) { + std::string shapeTypeStr {shapeType}; + + // ... and all the elements of the various types in the source TopoShape ... + for (unsigned long shapeIndex = 1U; shapeIndex <= source.countSubElements(shapeType); + shapeIndex++) { + std::string shapeIndexStr = std::to_string(shapeIndex); + std::string shapeName {shapeTypeStr + shapeIndexStr}; + + IndexedName indexedName {shapeTypeStr.c_str(), (int)shapeIndex}; + MappedName mappedName {elementStdMap[indexedName]}; + const char shapeTypePrefix[1] {indexedName.toString()[0]}; + + EXPECT_NO_THROW(elementStdMap.at(indexedName)); // .. we check that the IndexedName + // is one of the keys... + EXPECT_NE(mappedName.find(shapeName.c_str()), + -1); // ... that the element name is in the MappedName... + EXPECT_EQ( + mappedName.rfind(shapeTypePrefix), + mappedName.toString().length() + - 1); // ... that the element prefix is at the end of the MappedName ... + } + + // ... we also check that we don't find shapes that don't exist and therefor that don't + // have neither an IndexedName nor a MappedName + IndexedName fakeIndexedName {shapeTypeStr.c_str(), + (int)source.countSubElements(shapeType) + 1}; + EXPECT_THROW(elementStdMap.at(fakeIndexedName), std::out_of_range); + } + } +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, findMakerOpInElementMap) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + sources[1].Tag = 2; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + + // Act and assert + // Testing with all the source TopoShapes + for (const auto& source : sources) { + TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), sources); + + // For all the mappedElements ... + for (const auto& mappedElement : tmpShape.getElementMap()) { + EXPECT_NE(mappedElement.name.find(OpCodes::Maker), + -1); // ... we check that there's the "MAK" OpCode + } + } +} From 88838cfb4c9e776fe369db71a6406952754514ae Mon Sep 17 00:00:00 2001 From: bgbsww <120601209+bgbsww@users.noreply.github.com> Date: Sat, 27 Jan 2024 09:43:39 -0500 Subject: [PATCH 25/27] Cleaned up complex test (#15) * Element Map Test * Rename test and add more assertions * Reduce test complexity --- .../App/TopoShapeMakeShapeWithElementMap.cpp | 80 +++++++++++-------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp index 99548567be..7ee0f759ca 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -60,7 +60,6 @@ private: Part::TopoShape::Mapper _mapper; }; - TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) { // Arrange @@ -97,6 +96,16 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) using Data::IndexedName, Data::MappedName; using Part::TopoShape; +std::map elementMap(const TopoShape &shape) +{ + std::map result {}; + auto elements = shape.getElementMap(); + for (auto const & entry : elements) { + result[entry.index] = entry.name; + } + return result; +} + // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapVertex) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapEdge) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapWire) @@ -104,53 +113,54 @@ using Part::TopoShape; // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapShell) // TEST_F(TopoShapeMakeShapeWithElementMapTests, mapSolid) -TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompound) +TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundCount) +{ + // Arrange + auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; + sources[1].Tag = 2; + TopoShape compound = TopoShape(); + compound.makeElementCompound(sources); + auto preElements = elementMap(compound); // Map before mapping. + // Act + compound.makeShapeWithElementMap(compound.getShape(), *Mapper(), sources); + auto postElements = elementMap(compound); // Map after mapping + // Assert + EXPECT_EQ(preElements.size(), 52); // Check the before map. + EXPECT_EQ(postElements.size(), 52); // 12 Edges, 8 Vertexes, 6 Faces per each Cube + EXPECT_EQ(postElements.count(IndexedName("Edge", 24)), 1); + EXPECT_EQ(postElements.count(IndexedName("Edge", 25)), 0); + EXPECT_EQ(postElements.count(IndexedName("Vertex", 16)), 1); + EXPECT_EQ(postElements.count(IndexedName("Vertex", 17)), 0); + EXPECT_EQ(postElements.count(IndexedName("Face", 12)), 1); + EXPECT_EQ(postElements.count(IndexedName("Face", 13)), 0); + EXPECT_STREQ(sources[0].shapeName().c_str(), "Solid"); + EXPECT_STREQ(sources[1].shapeName().c_str(), "Solid"); + EXPECT_STREQ(compound.shapeName().c_str(), "Compound"); +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundMap) { // Arrange auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); - auto transform {gp_Trsf()}; - transform.SetTranslation(gp_Vec(gp_XYZ(-0.5, -0.5, 0))); - cube2.Move(TopLoc_Location(transform)); std::vector sources {cube1, cube2}; sources[0].Tag = 1; sources[1].Tag = 2; // Map only one of the two sources to test different names. sources[0].makeShapeWithElementMap(sources[0].getShape(), *Mapper(), {sources[0]}); - TopoShape compound; + TopoShape compound = TopoShape(); compound.makeElementCompound(sources); - auto preElements = compound.getElementMap(); // Map before mapping. - std::map preMap; - for (auto name : preElements) { - preMap.emplace(name.index, name.name); - } + auto preElements = elementMap(compound); // Map before mapping. // Act compound.makeShapeWithElementMap(compound.getShape(), *Mapper(), sources); - auto elements = compound.getElementMap(); // Map after mapping - std::map map; - for (auto name : elements) { - map.emplace(name.index, name.name); - } + auto postElements = elementMap(compound); // Map after mapping // Assert - EXPECT_EQ(preElements.size(), 52); // Check the before map. - EXPECT_EQ(preMap[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;:H1:b,E")); - EXPECT_EQ(preMap[IndexedName("Edge", 13)], MappedName("Edge1;:H2,E")); - - EXPECT_EQ(elements.size(), 52); // 12 Edges, 8 Vertexes, 6 Faces per each Cube - EXPECT_EQ(map[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;MAK;:H1:f,E")); - EXPECT_EQ(map[IndexedName("Edge", 13)], MappedName("Edge1;MAK;:H2:4,E")); - EXPECT_EQ(map.count(IndexedName("Edge", 24)), 1); - EXPECT_EQ(map.count(IndexedName("Edge", 25)), 0); - EXPECT_EQ(map.count(IndexedName("Vertex", 16)), 1); - EXPECT_EQ(map.count(IndexedName("Vertex", 17)), 0); - EXPECT_EQ(map.count(IndexedName("Face", 12)), 1); - EXPECT_EQ(map.count(IndexedName("Face", 13)), 0); - EXPECT_STREQ(sources[0].shapeName().c_str(), "Solid"); - EXPECT_STREQ(sources[1].shapeName().c_str(), "Solid"); - EXPECT_STREQ(compound.shapeName().c_str(), "Compound"); + EXPECT_EQ(preElements[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;:H1:b,E")); + EXPECT_EQ(preElements[IndexedName("Edge", 13)], MappedName("Edge1;:H2,E")); + EXPECT_EQ(postElements[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;MAK;:H1:f,E")); + EXPECT_EQ(postElements[IndexedName("Edge", 13)], MappedName("Edge1;MAK;:H2:4,E")); } -// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompSolid) -// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapOffsetCubes) -// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapIntersectingCubes) TEST_F(TopoShapeMakeShapeWithElementMapTests, emptySourceShapes) { From 4740ac88f27e46340d4731b6b35b8d11bd888aac Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 27 Jan 2024 10:49:59 -0600 Subject: [PATCH 26/27] Tests/Toponaming: Post-merge cleanup --- src/Mod/Part/App/TopoShape.h | 21 --- src/Mod/Part/App/TopoShapeExpansion.cpp | 12 +- tests/src/Mod/Part/App/CMakeLists.txt | 1 - tests/src/Mod/Part/App/PartTestHelpers.cpp | 18 ++ tests/src/Mod/Part/App/PartTestHelpers.h | 2 + tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 140 +++++++-------- .../Part/App/TopoShapeExpansionHelpers.cpp | 26 --- .../Mod/Part/App/TopoShapeExpansionHelpers.h | 13 -- .../App/TopoShapeMakeShapeWithElementMap.cpp | 166 ++++++++++-------- 9 files changed, 190 insertions(+), 209 deletions(-) delete mode 100644 tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp delete mode 100644 tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index bf30436a82..b04caf52ed 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -677,27 +677,6 @@ public: const std::vector &sources, const char *op=nullptr); - /** Helper class to return the generated and modified shape given an input shape - * - * Shape history information is extracted using OCCT APIs - * BRepBuilderAPI_MakeShape::Generated/Modified(). However, there is often - * some glitches in various derived class. So we use this class as an - * abstraction, and create various derived classes to deal with the glitches. - */ - struct PartExport Mapper { - /// Helper vector for temporary storage of both generated and modified shapes - mutable std::vector _res; - virtual ~Mapper() {} - /// Return a list of shape generated from the given input shape - virtual const std::vector &generated(const TopoDS_Shape &) const { - return _res; - } - /// Return a list of shape modified from the given input shape - virtual const std::vector &modified(const TopoDS_Shape &) const { - return _res; - } - }; - /** Make a compound shape * * @param shapes: input shapes diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index a57780f7da..eb6b03be29 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -45,6 +45,8 @@ #include "TopoShapeOpCode.h" #include "FaceMaker.h" +#include + FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -606,31 +608,31 @@ struct NameInfo const std::string& modPostfix() { - static std::string postfix(TopoShape::elementMapPrefix() + ":M"); + static std::string postfix(Data::POSTFIX_MOD); return postfix; } const std::string& modgenPostfix() { - static std::string postfix(modPostfix() + "G"); + static std::string postfix(Data::POSTFIX_MODGEN); return postfix; } const std::string& genPostfix() { - static std::string postfix(TopoShape::elementMapPrefix() + ":G"); + static std::string postfix(Data::POSTFIX_GEN); return postfix; } const std::string& upperPostfix() { - static std::string postfix(TopoShape::elementMapPrefix() + ":U"); + static std::string postfix(Data::POSTFIX_UPPER); return postfix; } const std::string& lowerPostfix() { - static std::string postfix(TopoShape::elementMapPrefix() + ":L"); + static std::string postfix(Data::POSTFIX_LOWER); return postfix; } diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index 4c5bedf30d..f239b3a27b 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -15,7 +15,6 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansion.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansionHelpers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShapeWithElementMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp ) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index 8b0390b7e5..36dc63f067 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -1,5 +1,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later +#include + #include "PartTestHelpers.h" // NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) @@ -137,6 +139,22 @@ std::map elementMap(const TopoShape& shape) return result; } +std::pair CreateTwoCubes() +{ + auto boxMaker1 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker1.Build(); + auto box1 = boxMaker1.Shape(); + + auto boxMaker2 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker2.Build(); + auto box2 = boxMaker2.Shape(); + auto transform = gp_Trsf(); + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)); + box2.Location(TopLoc_Location(transform)); + + return {box1, box2}; +} + } // namespace PartTestHelpers // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.h b/tests/src/Mod/Part/App/PartTestHelpers.h index 79147f6531..8b829e4ba6 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.h +++ b/tests/src/Mod/Part/App/PartTestHelpers.h @@ -57,4 +57,6 @@ boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec = std::map elementMap(const TopoShape& shape); +std::pair CreateTwoCubes(); + } // namespace PartTestHelpers diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index dd72b48d20..f1b82a9822 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -2,23 +2,21 @@ #include "gtest/gtest.h" #include "src/App/InitApplication.h" -#include "TopoShapeExpansionHelpers.h" #include #include #include "PartTestHelpers.h" #include -#include #include #include #include #include -#include #include // NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) +using namespace PartTestHelpers; class TopoShapeExpansionTest: public ::testing::Test { @@ -32,7 +30,6 @@ protected: { _docName = App::GetApplication().getUniqueDocumentName("test"); App::GetApplication().newDocument(_docName.c_str(), "testUser"); - _sids = &_sid; _hasher = Base::Reference(new App::StringHasher); ASSERT_EQ(_hasher.getRefCount(), 1); } @@ -46,7 +43,6 @@ protected: private: std::string _docName; Data::ElementIDRefs _sid; - QVector* _sids = nullptr; App::StringHasherRef _hasher; }; @@ -129,7 +125,7 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoShapesGeneratesMap) TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = CreateTwoCubes(); Part::TopoShape cube1TS {cube1}; cube1TS.Tag = 1; Part::TopoShape cube2TS {cube2}; @@ -150,42 +146,20 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes) // 26 subshapes each } -std::tuple -CreateRectFace(float len = 2.0, float wid = 3.0) -{ - auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(len, 0.0, 0.0)).Edge(); - auto edge2 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, 0.0, 0.0), gp_Pnt(len, wid, 0.0)).Edge(); - auto edge3 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, wid, 0.0), gp_Pnt(0.0, wid, 0.0)).Edge(); - auto edge4 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, wid, 0.0), gp_Pnt(0.0, 0.0, 0.0)).Edge(); - auto wire1 = BRepBuilderAPI_MakeWire({edge1, edge2, edge3, edge4}).Wire(); - auto face1 = BRepBuilderAPI_MakeFace(wire1).Face(); - return {face1, wire1, edge1, edge2, edge3, edge4}; -} - -std::tuple -CreateFaceWithRoundHole(float len = 2.0, float wid = 3.0, float radius = 1.0) -{ - auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(len, wid); - auto circ1 = - GC_MakeCircle(gp_Pnt(len / 2.0, wid / 2.0, 0), gp_Dir(0.0, 0.0, 1.0), radius).Value(); - auto edge5 = BRepBuilderAPI_MakeEdge(circ1).Edge(); - auto wire2 = BRepBuilderAPI_MakeWire(edge5).Wire(); - auto face2 = BRepBuilderAPI_MakeFace(face1, wire2).Face(); - return {face2, wire1, wire2}; -} - TEST_F(TopoShapeExpansionTest, makeElementFaceNull) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_FALSE(face1.IsEqual(newFace.getShape())); EXPECT_FLOAT_EQ(area, Len * Wid + M_PI * Rad * Rad); @@ -198,15 +172,17 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceNull) TEST_F(TopoShapeExpansionTest, makeElementFaceSimple) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire1); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -220,16 +196,18 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceSimple) TEST_F(TopoShapeExpansionTest, makeElementFaceParams) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire1, "Cut", "Part::FaceMakerBullseye", nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -243,16 +221,18 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceParams) TEST_F(TopoShapeExpansionTest, makeElementFaceFromFace) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(face1, "Cut", "Part::FaceMakerBullseye", nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -267,15 +247,17 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceFromFace) TEST_F(TopoShapeExpansionTest, makeElementFaceOpenWire) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {wire1, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire1, "Cut", nullptr, nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -290,16 +272,18 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceOpenWire) TEST_F(TopoShapeExpansionTest, makeElementFaceClosedWire) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {wire2, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire2, "Cut", "Part::FaceMakerBullseye", nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -395,7 +379,9 @@ TEST_F(TopoShapeExpansionTest, setElementComboNameCompound) TEST_F(TopoShapeExpansionTest, splitWires) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1, 1L}; std::vector inner; @@ -405,8 +391,8 @@ TEST_F(TopoShapeExpansionTest, splitWires) topoShape.splitWires(&inner, Part::TopoShape::SplitWireReorient::ReorientReversed); // Assert EXPECT_EQ(inner.size(), 1); - EXPECT_FLOAT_EQ(PartTestHelpers::getLength(wire.getShape()), 2 + 2 + 3 + 3); - EXPECT_FLOAT_EQ(PartTestHelpers::getLength(inner.front().getShape()), M_PI * Rad * 2); + EXPECT_FLOAT_EQ(getLength(wire.getShape()), 2 + 2 + 3 + 3); + EXPECT_FLOAT_EQ(getLength(inner.front().getShape()), M_PI * Rad * 2); EXPECT_EQ(wire.getShape().Orientation(), TopAbs_REVERSED); for (Part::TopoShape& shape : inner) { EXPECT_EQ(shape.getShape().Orientation(), TopAbs_FORWARD); @@ -415,8 +401,8 @@ TEST_F(TopoShapeExpansionTest, splitWires) // Possible future tests: // splitWires without inner Wires -// splitWires with allfour reorientation values NoReorient, ReOrient, ReorientForward, -// ReorientRevesed +// splitWires with all four reorientation values NoReorient, ReOrient, ReorientForward, +// ReorientReversed TEST_F(TopoShapeExpansionTest, mapSubElementInvalidParm) { @@ -442,7 +428,8 @@ TEST_F(TopoShapeExpansionTest, mapSubElementFindShapeByNames) Part::TopoShape cube2TS {cube2}; cube1TS.Tag = 1; cube2TS.Tag = 2; - Part::TopoShape topoShape, topoShape1; + Part::TopoShape topoShape; + Part::TopoShape topoShape1; // Act int fs1 = topoShape1.findShape(cube1); @@ -527,8 +514,13 @@ TEST_F(TopoShapeExpansionTest, mapSubElementFindAncestors) cube2TS.Tag = 2; cube3TS.Tag = 3; cube4TS.Tag = 4; - Part::TopoShape topoShape, topoShape1, topoShape2; - Part::TopoShape topoShape3, topoShape4, topoShape5, topoShape6; + Part::TopoShape topoShape; + Part::TopoShape topoShape1; + Part::TopoShape topoShape2; + Part::TopoShape topoShape3; + Part::TopoShape topoShape4; + Part::TopoShape topoShape5; + Part::TopoShape topoShape6; topoShape.makeElementCompound({cube1TS, cube2TS}); topoShape1.makeElementCompound({cube3TS, cube4TS}); topoShape2.makeElementCompound({cube1TS, cube3TS}); @@ -576,7 +568,8 @@ TEST_F(TopoShapeExpansionTest, makeElementShellInvalid) TEST_F(TopoShapeExpansionTest, makeElementShellSingle) { // Arrange - const double Len = 3, Wid = 2; + const float Len = 3; + const float Wid = 2; auto [face1, wire1, edge1, edge2, edge3, _] = CreateRectFace(Len, Wid); Part::TopoShape topoShape {face1, 1L}; // Act @@ -594,7 +587,8 @@ TEST_F(TopoShapeExpansionTest, makeElementShellSingle) TEST_F(TopoShapeExpansionTest, makeElementShellOpen) { // Arrange - const double Len = 3, Wid = 2; + const float Len = 3; + const float Wid = 2; auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(Len, Wid); auto transform {gp_Trsf()}; transform.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), M_PI / 2); @@ -625,7 +619,7 @@ TEST_F(TopoShapeExpansionTest, makeElementShellClosed) Part::TopoShape topoShape {cube1}; std::vector shapes; for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { - shapes.push_back(Part::TopoShape {face}); + shapes.emplace_back(face); } // Act Part::TopoShape topoShape1 {1L}; @@ -652,11 +646,11 @@ TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting) Part::TopoShape topoShape {cube1}; std::vector shapes; for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { - shapes.push_back(Part::TopoShape {face}); + shapes.emplace_back(face); } topoShape.setShape(cube2); for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { - shapes.push_back(Part::TopoShape {face}); + shapes.emplace_back(face); } // Act Part::TopoShape topoShape1 {1L}; diff --git a/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp b/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp deleted file mode 100644 index ad401ee7b6..0000000000 --- a/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "TopoShapeExpansionHelpers.h" - -#include - -namespace TopoShapeExpansionHelpers -{ - -std::pair CreateTwoCubes() -{ - auto boxMaker1 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); - boxMaker1.Build(); - auto box1 = boxMaker1.Shape(); - - auto boxMaker2 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); - boxMaker2.Build(); - auto box2 = boxMaker2.Shape(); - auto transform = gp_Trsf(); - transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)); - box2.Location(TopLoc_Location(transform)); - - return {box1, box2}; -} - -} // namespace TopoShapeExpansionHelpers diff --git a/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h b/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h deleted file mode 100644 index d92c18f4d4..0000000000 --- a/tests/src/Mod/Part/App/TopoShapeExpansionHelpers.h +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later - -#ifndef FREECAD_TOPOSHAPEEXPANSIONHELPERS_H -#define FREECAD_TOPOSHAPEEXPANSIONHELPERS_H - -#include - -namespace TopoShapeExpansionHelpers -{ -std::pair CreateTwoCubes(); -} - -#endif // FREECAD_TOPOSHAPEEXPANSIONHELPERS_H diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp index 7ee0f759ca..d7827ff781 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -5,7 +5,7 @@ #include "gtest/gtest.h" #include "src/App/InitApplication.h" -#include "TopoShapeExpansionHelpers.h" +#include "PartTestHelpers.h" #include #include // #include @@ -34,7 +34,6 @@ protected: { _docName = App::GetApplication().getUniqueDocumentName("test"); App::GetApplication().newDocument(_docName.c_str(), "testUser"); - _sids = &_sid; } void TearDown() override @@ -52,10 +51,12 @@ protected: return &_mapper; } + void testFindSourceSubShapesInElementMapForSource(const std::vector& sources, + const TopoShape& source); + private: std::string _docName; Data::ElementIDRefs _sid; - QVector* _sids = nullptr; Part::TopoShape _shape; Part::TopoShape::Mapper _mapper; }; @@ -63,7 +64,7 @@ private: TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; TopoDS_Vertex nullVertex; TopoDS_Edge nullEdge; @@ -96,11 +97,11 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) using Data::IndexedName, Data::MappedName; using Part::TopoShape; -std::map elementMap(const TopoShape &shape) +std::map elementMap(const TopoShape& shape) { std::map result {}; auto elements = shape.getElementMap(); - for (auto const & entry : elements) { + for (auto const& entry : elements) { result[entry.index] = entry.name; } return result; @@ -116,7 +117,7 @@ std::map elementMap(const TopoShape &shape) TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundCount) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; sources[0].Tag = 1; sources[1].Tag = 2; @@ -127,7 +128,7 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundCount) compound.makeShapeWithElementMap(compound.getShape(), *Mapper(), sources); auto postElements = elementMap(compound); // Map after mapping // Assert - EXPECT_EQ(preElements.size(), 52); // Check the before map. + EXPECT_EQ(preElements.size(), 52); // Check the before map. EXPECT_EQ(postElements.size(), 52); // 12 Edges, 8 Vertexes, 6 Faces per each Cube EXPECT_EQ(postElements.count(IndexedName("Edge", 24)), 1); EXPECT_EQ(postElements.count(IndexedName("Edge", 25)), 0); @@ -143,7 +144,7 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundCount) TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundMap) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; sources[0].Tag = 1; sources[1].Tag = 2; @@ -165,7 +166,7 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundMap) TEST_F(TopoShapeMakeShapeWithElementMapTests, emptySourceShapes) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector emptySources; std::vector nonEmptySources {cube1, cube2}; @@ -183,7 +184,7 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, emptySourceShapes) TEST_F(TopoShapeMakeShapeWithElementMapTests, nonMappableSources) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; // Act and assert @@ -202,10 +203,34 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, nonMappableSources) } } +void testFindSourceShapesInSingleShape(const Part::TopoShape& cmpdShape, + const Part::TopoShape& source, + const std::vector& sources, + const TopoShape::Mapper& mapper) +{ + std::vector tmpSources {source}; + for (const auto& subSource : sources) { + Part::TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), mapper, tmpSources); + if (&source == &subSource) { + EXPECT_NE(tmpShape.findShape(subSource.getShape()), + 0); // if tmpShape uses, for example, cube1 and we search for cube1 than + // we should find it + } + else { + EXPECT_EQ(tmpShape.findShape(subSource.getShape()), + 0); // if tmpShape uses, for example, cube1 and we search for cube2 than + // we shouldn't find it + } + } + EXPECT_NE(cmpdShape.findShape(source.getShape()), + 0); // as cmpdShape is made with cube1 and cube2 we should find both of them +} + TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceShapesInShape) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be // more or less the same of nonMappableSources @@ -216,30 +241,69 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceShapesInShape) // Act and assert for (const auto& source : sources) { - std::vector tmpSources {source}; - for (const auto& subSource : sources) { - Part::TopoShape tmpShape {source.getShape()}; - tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), tmpSources); - if (&source == &subSource) { - EXPECT_NE(tmpShape.findShape(subSource.getShape()), - 0); // if tmpShape uses, for example, cube1 and we search for cube1 than - // we should find it - } - else { - EXPECT_EQ(tmpShape.findShape(subSource.getShape()), - 0); // if tmpShape uses, for example, cube1 and we search for cube2 than - // we shouldn't find it - } - } - EXPECT_NE(cmpdShape.findShape(source.getShape()), - 0); // as cmpdShape is made with cube1 and cube2 we should find both of them + testFindSourceShapesInSingleShape(cmpdShape, source, sources, *Mapper()); + } +} + +void testFindSubShapesForSourceWithTypeAndIndex(const std::string& shapeTypeStr, + std::map& elementStdMap, + unsigned long shapeIndex) +{ + std::string shapeIndexStr = std::to_string(shapeIndex); + std::string shapeName {shapeTypeStr + shapeIndexStr}; + + IndexedName indexedName {shapeTypeStr.c_str(), (int)shapeIndex}; + MappedName mappedName {elementStdMap[indexedName]}; + const char shapeTypePrefix {indexedName.toString()[0]}; + + EXPECT_NO_THROW(elementStdMap.at(indexedName)); // We check that the IndexedName + // is one of the keys... + EXPECT_NE(mappedName.find(shapeName.c_str()), + -1); // ... that the element name is in the MappedName... + EXPECT_EQ(mappedName.toString().back(), shapeTypePrefix); +} + +void testFindSubShapesForSourceWithType(const TopoShape& source, + const char* shapeType, + std::map& elementStdMap) +{ + std::string shapeTypeStr {shapeType}; + + // ... and all the elements of the various types in the source TopoShape ... + for (unsigned long shapeIndex = 1U; shapeIndex <= source.countSubElements(shapeType); + shapeIndex++) { + testFindSubShapesForSourceWithTypeAndIndex(shapeTypeStr, elementStdMap, shapeIndex); + } + + // ... we also check that we don't find shapes that don't exist and therefore don't + // have either an IndexedName or a MappedName + IndexedName fakeIndexedName {shapeTypeStr.c_str(), (int)source.countSubElements(shapeType) + 1}; + EXPECT_THROW(elementStdMap.at(fakeIndexedName), std::out_of_range); +} + +void TopoShapeMakeShapeWithElementMapTests::testFindSourceSubShapesInElementMapForSource( + const std::vector& sources, + const TopoShape& source) +{ + TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), sources); + + // First we create a map with the IndexedNames and MappedNames + std::map elementStdMap; + for (const auto& mappedElement : tmpShape.getElementMap()) { + elementStdMap.emplace(mappedElement.index, mappedElement.name); + } + + // Then for all the elements types (Vertex, Edge, Face) ... + for (const auto& shapeType : source.getElementTypes()) { + testFindSubShapesForSourceWithType(source, shapeType, elementStdMap); } } TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceSubShapesInElementMap) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be // more or less the same of nonMappableSources @@ -249,52 +313,14 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceSubShapesInElementMap) // Act and assert // Testing with all the source TopoShapes for (const auto& source : sources) { - TopoShape tmpShape {source.getShape()}; - tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), sources); - - // First we create a map with the IndexedNames and MappedNames - std::map elementStdMap; - for (const auto& mappedElement : tmpShape.getElementMap()) { - elementStdMap.emplace(mappedElement.index, mappedElement.name); - } - - // Then for all the elements types (Vertex, Edge, Face) ... - for (const auto& shapeType : source.getElementTypes()) { - std::string shapeTypeStr {shapeType}; - - // ... and all the elements of the various types in the source TopoShape ... - for (unsigned long shapeIndex = 1U; shapeIndex <= source.countSubElements(shapeType); - shapeIndex++) { - std::string shapeIndexStr = std::to_string(shapeIndex); - std::string shapeName {shapeTypeStr + shapeIndexStr}; - - IndexedName indexedName {shapeTypeStr.c_str(), (int)shapeIndex}; - MappedName mappedName {elementStdMap[indexedName]}; - const char shapeTypePrefix[1] {indexedName.toString()[0]}; - - EXPECT_NO_THROW(elementStdMap.at(indexedName)); // .. we check that the IndexedName - // is one of the keys... - EXPECT_NE(mappedName.find(shapeName.c_str()), - -1); // ... that the element name is in the MappedName... - EXPECT_EQ( - mappedName.rfind(shapeTypePrefix), - mappedName.toString().length() - - 1); // ... that the element prefix is at the end of the MappedName ... - } - - // ... we also check that we don't find shapes that don't exist and therefor that don't - // have neither an IndexedName nor a MappedName - IndexedName fakeIndexedName {shapeTypeStr.c_str(), - (int)source.countSubElements(shapeType) + 1}; - EXPECT_THROW(elementStdMap.at(fakeIndexedName), std::out_of_range); - } + testFindSourceSubShapesInElementMapForSource(sources, source); } } TEST_F(TopoShapeMakeShapeWithElementMapTests, findMakerOpInElementMap) { // Arrange - auto [cube1, cube2] = TopoShapeExpansionHelpers::CreateTwoCubes(); + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); std::vector sources {cube1, cube2}; sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be // more or less the same of nonMappableSources From 05a7bae6cb9e8bf9fd3017caf796dbd45d9827d4 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 26 Jan 2024 16:40:05 -0500 Subject: [PATCH 27/27] FeaturePartCommon code move in --- src/Mod/Part/App/FeaturePartCommon.cpp | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/Mod/Part/App/FeaturePartCommon.cpp b/src/Mod/Part/App/FeaturePartCommon.cpp index 9832b8925c..2a4fbb9778 100644 --- a/src/Mod/Part/App/FeaturePartCommon.cpp +++ b/src/Mod/Part/App/FeaturePartCommon.cpp @@ -80,6 +80,7 @@ short MultiCommon::mustExecute() const App::DocumentObjectExecReturn *MultiCommon::execute() { +#ifndef FC_USE_TNP_FIX std::vector s; std::vector obj = Shapes.getValues(); @@ -194,4 +195,39 @@ App::DocumentObjectExecReturn *MultiCommon::execute() } return App::DocumentObject::StdReturn; +#else + std::vector shapes; + for (auto obj : Shapes.getValues()) { + TopoShape sh = Feature::getTopoShape(obj); + if (sh.isNull()) { + return new App::DocumentObjectExecReturn("Input shape is null"); + } + shapes.push_back(sh); + } + + TopoShape res {}; + res.makeElementBoolean(Part::OpCodes::Common, shapes); + if (res.isNull()) { + throw Base::RuntimeError("Resulting shape is null"); + } + + Base::Reference hGrp = App::GetApplication() + .GetUserParameter() + .GetGroup("BaseApp") + ->GetGroup("Preferences") + ->GetGroup("Mod/Part/Boolean"); + if (hGrp->GetBool("CheckModel", false)) { + BRepCheck_Analyzer aChecker(res.getShape()); + if (!aChecker.IsValid()) { + return new App::DocumentObjectExecReturn("Resulting shape is invalid"); + } + } + + if (this->Refine.getValue()) { + res = res.makeElementRefine(); + } + this->Shape.setValue(res); + + return Part::Feature::execute(); +#endif }