Merge branch 'main' into bgbsww-toponamingMakeElementShape
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -621,6 +621,17 @@ PropertyStiffness::PropertyStiffness()
|
||||
setUnit(Base::Unit::Stiffness);
|
||||
}
|
||||
|
||||
//**************************************************************************
|
||||
// PropertyStiffnessDensity
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
TYPESYSTEM_SOURCE(App::PropertyStiffnessDensity, App::PropertyQuantity)
|
||||
|
||||
PropertyStiffnessDensity::PropertyStiffnessDensity()
|
||||
{
|
||||
setUnit(Base::Unit::StiffnessDensity);
|
||||
}
|
||||
|
||||
//**************************************************************************
|
||||
// PropertyTemperature
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"<pre>(.*?)</pre>", p.replace("\n", "--endl--"))
|
||||
|
||||
@@ -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 = "<html><body>"
|
||||
self.previous_state = WikiCleaner.State.BeforeMacroContent
|
||||
self.state = WikiCleaner.State.BeforeMacroContent
|
||||
|
||||
def handle_starttag(self, tag: str, attrs):
|
||||
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):
|
||||
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):
|
||||
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, unused):
|
||||
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 += "</body></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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<Base::Vector3d>());
|
||||
Normals.setValues(std::vector<Base::Vector3d>());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -28,31 +28,13 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_1">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::InputField" name="if_tolerance">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<widget class="Gui::QuantitySpinBox" name="spb_tolerance">
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0 mm</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<property name="value" stdset="0">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
@@ -60,9 +42,6 @@
|
||||
<property name="maximum">
|
||||
<double>1000000000.000000000000000</double>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
<property name="decimals" stdset="0">
|
||||
<number>2</number>
|
||||
</property>
|
||||
@@ -74,7 +53,14 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="l_tolerance">
|
||||
<property name="text">
|
||||
<string>Tolerance: </string>
|
||||
<string>Tolerance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="ckb_adjust">
|
||||
<property name="text">
|
||||
<string>Enable Adjust</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -87,9 +73,9 @@
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::InputField</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>Gui/InputField.h</header>
|
||||
<class>Gui::QuantitySpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Gui/QuantitySpinBox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
|
||||
@@ -91,14 +91,36 @@ TaskFemConstraintContact::TaskFemConstraintContact(ViewProviderFemConstraintCont
|
||||
|
||||
std::vector<App::DocumentObject*> Objects = pcConstraint->References.getValues();
|
||||
std::vector<std::string> 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<const char*>(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",
|
||||
|
||||
@@ -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_TaskFemConstraintContact> ui;
|
||||
};
|
||||
|
||||
@@ -135,56 +135,123 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Contact Stiffness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::InputField" name="spSlope">
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000000000.000000000000000</double>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">Pa</string>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>1000000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Friction coefficient</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="spFriction">
|
||||
<property name="decimals">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QGroupBox" name="gpbParameter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Parameters</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="fltParameters">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lblSlope">
|
||||
<property name="text">
|
||||
<string>Contact Stiffness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="spbSlope">
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000000000.000000000000000</double>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">GPa/m</string>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>100.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="lblAdjust">
|
||||
<property name="text">
|
||||
<string>Clearance Adjustment</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="spbAdjust">
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="ckbFriction">
|
||||
<property name="text">
|
||||
<string>Enable Friction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="lblFrictionCoeff">
|
||||
<property name="text">
|
||||
<string>Friction Coefficient</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="Gui::DoubleSpinBox" name="spbFrictionCoeff">
|
||||
<property name="decimals">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="lblStickSlope">
|
||||
<property name="text">
|
||||
<string>Stick Slope</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="spbStickSlope">
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000000000.000000000000000</double>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">GPa/m</string>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>lbl_info_2</zorder>
|
||||
@@ -194,9 +261,14 @@
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::InputField</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>Gui/InputField.h</header>
|
||||
<class>Gui::QuantitySpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Gui/QuantitySpinBox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::DoubleSpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Gui/SpinBox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
***********************************************************
|
||||
|
||||
@@ -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
|
||||
|
||||
***********************************************************
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -80,6 +80,7 @@ short MultiCommon::mustExecute() const
|
||||
|
||||
App::DocumentObjectExecReturn *MultiCommon::execute()
|
||||
{
|
||||
#ifndef FC_USE_TNP_FIX
|
||||
std::vector<TopoDS_Shape> s;
|
||||
std::vector<App::DocumentObject*> obj = Shapes.getValues();
|
||||
|
||||
@@ -194,4 +195,39 @@ App::DocumentObjectExecReturn *MultiCommon::execute()
|
||||
}
|
||||
|
||||
return App::DocumentObject::StdReturn;
|
||||
#else
|
||||
std::vector<TopoShape> 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<ParameterGrp> 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
|
||||
}
|
||||
|
||||
@@ -652,7 +652,6 @@ public:
|
||||
void mapSubElement(const std::vector<TopoShape> &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
|
||||
@@ -674,6 +673,24 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/** 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<TopoShape> &sources,
|
||||
const char *op=nullptr);
|
||||
|
||||
/** Make a compound shape
|
||||
*
|
||||
* @param shapes: input shapes
|
||||
@@ -690,6 +707,47 @@ public:
|
||||
*/
|
||||
TopoShape &makeElementCompound(const std::vector<TopoShape> &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<TopoShape>& 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<TopoShape>& shapes,
|
||||
const char* op = nullptr,
|
||||
const char* maker = nullptr,
|
||||
|
||||
@@ -25,21 +25,28 @@
|
||||
|
||||
#include "PreCompiled.h"
|
||||
#ifndef _PreComp_
|
||||
#include <BRep_Builder.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepCheck_Analyzer.hxx>
|
||||
#include <BRepFill_Generator.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <BRep_Builder.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <ShapeFix_Shape.hxx>
|
||||
#include <ShapeFix_ShapeTolerance.hxx>
|
||||
#include <ShapeUpgrade_ShellSewing.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
|
||||
#include <utility>
|
||||
#endif
|
||||
|
||||
#include "TopoShape.h"
|
||||
#include "TopoShapeCache.h"
|
||||
#include "TopoShapeOpCode.h"
|
||||
#include "FaceMaker.h"
|
||||
|
||||
#include "TopoShapeOpCode.h"
|
||||
#include <App/ElementNamingUtils.h>
|
||||
|
||||
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
|
||||
|
||||
@@ -336,6 +343,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,
|
||||
@@ -385,8 +394,8 @@ void TopoShape::mapSubElementTypeForShape(const TopoShape& other,
|
||||
}
|
||||
std::ostringstream ss;
|
||||
char elementType {shapeName(type)[0]};
|
||||
if ( ! elementMap() ) {
|
||||
FC_THROWM(NullShapeException, "No element map");
|
||||
if (!elementMap()) {
|
||||
FC_THROWM(NullShapeException, "No element map"); // NOLINT
|
||||
}
|
||||
elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag);
|
||||
elementMap()->setElementName(element, name, Tag, &sids);
|
||||
@@ -507,6 +516,743 @@ void TopoShape::mapSubElement(const std::vector<TopoShape>& 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(Data::MappedName n)
|
||||
: name(std::move(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(Data::POSTFIX_MOD);
|
||||
return postfix;
|
||||
}
|
||||
|
||||
const std::string& modgenPostfix()
|
||||
{
|
||||
static std::string postfix(Data::POSTFIX_MODGEN);
|
||||
return postfix;
|
||||
}
|
||||
|
||||
const std::string& genPostfix()
|
||||
{
|
||||
static std::string postfix(Data::POSTFIX_GEN);
|
||||
return postfix;
|
||||
}
|
||||
|
||||
const std::string& upperPostfix()
|
||||
{
|
||||
static std::string postfix(Data::POSTFIX_UPPER);
|
||||
return postfix;
|
||||
}
|
||||
|
||||
const std::string& lowerPostfix()
|
||||
{
|
||||
static std::string postfix(Data::POSTFIX_LOWER);
|
||||
return postfix;
|
||||
}
|
||||
|
||||
// TODO: Refactor checkForParallelOrCoplanar to reduce complexity
|
||||
void checkForParallelOrCoplanar(const TopoDS_Shape& newShape,
|
||||
const ShapeInfo& newInfo,
|
||||
std::vector<TopoDS_Shape>& 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,
|
||||
const std::vector<TopoShape>& 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 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<ShapeInfo*, 3> infos = {&vertexInfo, &edgeInfo, &faceInfo};
|
||||
|
||||
std::array<ShapeInfo*, TopAbs_SHAPE> 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;
|
||||
Data::MappedName newName;
|
||||
|
||||
std::map<Data::IndexedName, std::map<NameKey, NameInfo>> 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.at(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.at(newShape.ShapeType());
|
||||
std::vector<TopoDS_Shape> 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;
|
||||
}
|
||||
}
|
||||
checkForParallelOrCoplanar(newShape,
|
||||
newInfo,
|
||||
newShapes,
|
||||
pln,
|
||||
parallelFace,
|
||||
coplanarFace,
|
||||
checkParallel);
|
||||
}
|
||||
key.shapetype += shapeOffset;
|
||||
for (auto& workingShape : newShapes) {
|
||||
++newShapeCounter;
|
||||
int workingShapeIndex = newInfo.find(workingShape);
|
||||
if (workingShapeIndex == 0) {
|
||||
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
||||
// NOLINTNEXTLINE
|
||||
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<int>::min();
|
||||
}
|
||||
else if (newShapeCounter == coplanarFace) {
|
||||
name_info.index = std::numeric_limits<int>::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,
|
||||
other_name,
|
||||
ss2,
|
||||
&sids,
|
||||
Tag,
|
||||
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)) {
|
||||
FC_WARN("element is both generated and modified"); // NOLINT
|
||||
}
|
||||
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<Data::IndexedName,
|
||||
std::map<Data::MappedName, NameInfo, Data::ElementNameComparator>>
|
||||
names;
|
||||
auto& info = *infos.at(infoIndex);
|
||||
auto& next = *infos.at(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& [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& nameInfoMapEntry = *nameInfoMap.begin();
|
||||
{
|
||||
auto& nameInfo = nameInfoMapEntry.second;
|
||||
auto& sids = nameInfo.sids;
|
||||
newName = nameInfoMapEntry.first;
|
||||
ss.str("");
|
||||
ss << upperPostfix();
|
||||
if (nameInfo.index > 1) {
|
||||
ss << nameInfo.index;
|
||||
}
|
||||
elementMap()->encodeElementName(indexedName[0], newName, ss, &sids, Tag, op);
|
||||
elementMap()->setElementName(indexedName, 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.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)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Data::ElementIDRefs sids;
|
||||
std::map<Data::MappedName, Data::IndexedName, Data::ElementNameComparator> 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 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;
|
||||
}
|
||||
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)) {
|
||||
FC_WARN("unnamed lower element " << prevElement); // NOLINT
|
||||
}
|
||||
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<TopoShape>& shapes,
|
||||
@@ -768,7 +1514,8 @@ TopoShape& TopoShape::makeElementFace(const std::vector<TopoShape>& 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 <conditions>.
|
||||
* @param element The element name(type) that provides 1 one character suffix to the name IF
|
||||
* <conditions>.
|
||||
* @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.
|
||||
@@ -1015,6 +1762,29 @@ struct MapperSewing: Part::TopoShape::Mapper
|
||||
return _res;
|
||||
}
|
||||
};
|
||||
struct MapperFill: Part::TopoShape::Mapper
|
||||
{
|
||||
BRepFill_Generator& maker;
|
||||
explicit MapperFill(BRepFill_Generator& maker)
|
||||
: maker(maker)
|
||||
{}
|
||||
const std::vector<TopoDS_Shape>& 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;
|
||||
}
|
||||
};
|
||||
|
||||
const std::vector<TopoDS_Shape>& MapperMaker::modified(const TopoDS_Shape& s) const
|
||||
{
|
||||
@@ -1050,4 +1820,117 @@ const std::vector<TopoDS_Shape>& MapperMaker::generated(const TopoDS_Shape& s) c
|
||||
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<TopoShape>& 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
|
||||
|
||||
@@ -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<TechDraw::Circle>(bigCenter / scale, bigRadius / scale);
|
||||
std::make_shared<TechDraw::Circle>(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);
|
||||
}
|
||||
@@ -1475,10 +1490,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<TechDraw::Generic>(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 +1520,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;
|
||||
@@ -1519,7 +1539,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();
|
||||
@@ -2087,7 +2107,6 @@ void _createThreadLines(std::vector<std::string> 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")) {
|
||||
@@ -2103,10 +2122,22 @@ void _createThreadLines(std::vector<std::string> SubNames, TechDraw::DrawViewPar
|
||||
|
||||
TechDraw::GenericPtr line0 = std::static_pointer_cast<TechDraw::Generic>(geom0);
|
||||
TechDraw::GenericPtr line1 = std::static_pointer_cast<TechDraw::Generic>(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;
|
||||
@@ -2114,17 +2145,13 @@ void _createThreadLines(std::vector<std::string> 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);
|
||||
|
||||
@@ -15,6 +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}/TopoShapeMakeShapeWithElementMap.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShape.cpp
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <BRepPrimAPI_MakeBox.hxx>
|
||||
|
||||
#include "PartTestHelpers.h"
|
||||
|
||||
// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)
|
||||
@@ -89,6 +91,30 @@ void rectangle(double height, double width, char* name)
|
||||
ExecutePython(rectstring);
|
||||
}
|
||||
|
||||
std::tuple<TopoDS_Face, TopoDS_Wire, TopoDS_Edge, TopoDS_Edge, TopoDS_Edge, TopoDS_Edge>
|
||||
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<TopoDS_Face, TopoDS_Wire, TopoDS_Wire>
|
||||
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 +129,32 @@ boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec)
|
||||
<< b2.MinY << "," << b2.MinZ << " ; " << b2.MaxX << "," << b2.MaxY << "," << b2.MaxZ << ")";
|
||||
}
|
||||
|
||||
std::map<IndexedName, MappedName> elementMap(const TopoShape& shape)
|
||||
{
|
||||
std::map<IndexedName, MappedName> result {};
|
||||
auto elements = shape.getElementMap();
|
||||
for (auto const& entry : elements) {
|
||||
result[entry.index] = entry.name;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<TopoDS_Shape, TopoDS_Shape> 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)
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
#include "Mod/Part/App/FeaturePartFuse.h"
|
||||
#include "Mod/Part/App/FeatureFillet.h"
|
||||
#include <BRepGProp.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <GC_MakeCircle.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
|
||||
namespace PartTestHelpers
|
||||
{
|
||||
|
||||
using namespace Data;
|
||||
using namespace Part;
|
||||
|
||||
double getVolume(const TopoDS_Shape& shape);
|
||||
|
||||
double getArea(const TopoDS_Shape& shape);
|
||||
@@ -38,6 +46,17 @@ void executePython(const std::vector<std::string>& python);
|
||||
|
||||
void rectangle(double height, double width, char* name);
|
||||
|
||||
std::tuple<TopoDS_Face, TopoDS_Wire, TopoDS_Edge, TopoDS_Edge, TopoDS_Edge, TopoDS_Edge>
|
||||
CreateRectFace(float len = 2.0, float wid = 3.0);
|
||||
|
||||
std::tuple<TopoDS_Face, TopoDS_Wire, TopoDS_Wire>
|
||||
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<IndexedName, MappedName> elementMap(const TopoShape& shape);
|
||||
|
||||
std::pair<TopoDS_Shape, TopoDS_Shape> CreateTwoCubes();
|
||||
|
||||
} // namespace PartTestHelpers
|
||||
|
||||
@@ -8,16 +8,15 @@
|
||||
#include "PartTestHelpers.h"
|
||||
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepPrimAPI_MakeBox.hxx>
|
||||
#include <GC_MakeCircle.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
|
||||
// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)
|
||||
|
||||
using namespace PartTestHelpers;
|
||||
|
||||
class TopoShapeExpansionTest: public ::testing::Test
|
||||
{
|
||||
@@ -31,7 +30,6 @@ protected:
|
||||
{
|
||||
_docName = App::GetApplication().getUniqueDocumentName("test");
|
||||
App::GetApplication().newDocument(_docName.c_str(), "testUser");
|
||||
_sids = &_sid;
|
||||
_hasher = Base::Reference<App::StringHasher>(new App::StringHasher);
|
||||
ASSERT_EQ(_hasher.getRefCount(), 1);
|
||||
}
|
||||
@@ -45,7 +43,6 @@ protected:
|
||||
private:
|
||||
std::string _docName;
|
||||
Data::ElementIDRefs _sid;
|
||||
QVector<App::StringIDRef>* _sids = nullptr;
|
||||
App::StringHasherRef _hasher;
|
||||
};
|
||||
|
||||
@@ -125,26 +122,6 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoShapesGeneratesMap)
|
||||
EXPECT_EQ(4, topoShape.getMappedChildElements().size()); // two vertices and two edges
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::pair<TopoDS_Shape, TopoDS_Shape> 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
|
||||
@@ -169,42 +146,20 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes)
|
||||
// 26 subshapes each
|
||||
}
|
||||
|
||||
std::tuple<TopoDS_Face, TopoDS_Wire, TopoDS_Edge, TopoDS_Edge, TopoDS_Edge, TopoDS_Edge>
|
||||
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<TopoDS_Face, TopoDS_Wire, TopoDS_Wire>
|
||||
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);
|
||||
@@ -217,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()));
|
||||
@@ -239,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()));
|
||||
@@ -262,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()));
|
||||
@@ -286,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()));
|
||||
@@ -309,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()));
|
||||
@@ -414,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<Part::TopoShape> inner;
|
||||
@@ -424,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);
|
||||
@@ -434,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)
|
||||
{
|
||||
@@ -461,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);
|
||||
@@ -546,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});
|
||||
@@ -584,4 +557,111 @@ 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 float Len = 3;
|
||||
const float 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 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);
|
||||
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<Part::TopoShape> shapes;
|
||||
for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) {
|
||||
shapes.emplace_back(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<Part::TopoShape> shapes;
|
||||
for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) {
|
||||
shapes.emplace_back(face);
|
||||
}
|
||||
topoShape.setShape(cube2);
|
||||
for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) {
|
||||
shapes.emplace_back(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)
|
||||
|
||||
342
tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp
Normal file
342
tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
// 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 "PartTestHelpers.h"
|
||||
#include <Mod/Part/App/TopoShape.h>
|
||||
#include <Mod/Part/App/TopoShapeOpCode.h>
|
||||
// #include <MappedName.h>
|
||||
|
||||
#include <TopoDS_Vertex.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopoDS_Shell.hxx>
|
||||
#include <TopoDS_Solid.hxx>
|
||||
#include <TopoDS_CompSolid.hxx>
|
||||
#include <TopoDS_Compound.hxx>
|
||||
|
||||
using namespace Part;
|
||||
using namespace Data;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
App::GetApplication().closeDocument(_docName.c_str());
|
||||
}
|
||||
|
||||
Part::TopoShape* Shape()
|
||||
{
|
||||
return &_shape;
|
||||
}
|
||||
|
||||
Part::TopoShape::Mapper* Mapper()
|
||||
{
|
||||
return &_mapper;
|
||||
}
|
||||
|
||||
void testFindSourceSubShapesInElementMapForSource(const std::vector<TopoShape>& sources,
|
||||
const TopoShape& source);
|
||||
|
||||
private:
|
||||
std::string _docName;
|
||||
Data::ElementIDRefs _sid;
|
||||
Part::TopoShape _shape;
|
||||
Part::TopoShape::Mapper _mapper;
|
||||
};
|
||||
|
||||
TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows)
|
||||
{
|
||||
// Arrange
|
||||
auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<Part::TopoShape> sources {cube1, cube2};
|
||||
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(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);
|
||||
}
|
||||
|
||||
using Data::IndexedName, Data::MappedName;
|
||||
using Part::TopoShape;
|
||||
|
||||
std::map<IndexedName, MappedName> elementMap(const TopoShape& shape)
|
||||
{
|
||||
std::map<IndexedName, MappedName> 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)
|
||||
// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapFace)
|
||||
// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapShell)
|
||||
// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapSolid)
|
||||
|
||||
TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundCount)
|
||||
{
|
||||
// Arrange
|
||||
auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<TopoShape> 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] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<TopoShape> 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.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[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, emptySourceShapes)
|
||||
{
|
||||
// Arrange
|
||||
auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<Part::TopoShape> emptySources;
|
||||
std::vector<Part::TopoShape> 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] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<Part::TopoShape> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testFindSourceShapesInSingleShape(const Part::TopoShape& cmpdShape,
|
||||
const Part::TopoShape& source,
|
||||
const std::vector<Part::TopoShape>& sources,
|
||||
const TopoShape::Mapper& mapper)
|
||||
{
|
||||
std::vector<Part::TopoShape> 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] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<Part::TopoShape> 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) {
|
||||
testFindSourceShapesInSingleShape(cmpdShape, source, sources, *Mapper());
|
||||
}
|
||||
}
|
||||
|
||||
void testFindSubShapesForSourceWithTypeAndIndex(const std::string& shapeTypeStr,
|
||||
std::map<IndexedName, MappedName>& 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<IndexedName, MappedName>& 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<TopoShape>& 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<IndexedName, MappedName> 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] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<TopoShape> 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) {
|
||||
testFindSourceSubShapesInElementMapForSource(sources, source);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TopoShapeMakeShapeWithElementMapTests, findMakerOpInElementMap)
|
||||
{
|
||||
// Arrange
|
||||
auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes();
|
||||
std::vector<TopoShape> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user