Merge branch 'master' into gcode_apply_gcode_placement

This commit is contained in:
Christian Mesh
2023-02-17 07:09:59 -05:00
committed by GitHub
77 changed files with 4076 additions and 1724 deletions

View File

@@ -22,17 +22,20 @@
"""Mock objects for use when testing the addon manager non-GUI code."""
# pylint: disable=too-few-public-methods,too-many-instance-attributes,missing-function-docstring
import os
from typing import Union, List
import xml.etree.ElementTree as ElemTree
class GitFailed (RuntimeError):
class GitFailed(RuntimeError):
pass
class MockConsole:
"""Mock for the FreeCAD.Console -- does NOT print anything out, just logs it."""
def __init__(self):
self.log = []
self.messages = []
@@ -71,6 +74,8 @@ class MockConsole:
class MockMetadata:
"""Minimal implementation of a Metadata-like object."""
def __init__(self):
self.Name = "MockMetadata"
self.Urls = {"repository": {"location": "file://localhost/", "branch": "main"}}
@@ -83,6 +88,8 @@ class MockMetadata:
"""Don't use the real metadata class, but try to read in the parameters we care about
from the given metadata file (or file-like object, as the case probably is). This
allows us to test whether the data is being passed around correctly."""
# pylint: disable=too-many-branches
xml = None
root = None
try:
@@ -120,12 +127,14 @@ class MockMetadata:
class MockAddon:
"""Minimal Addon class"""
# pylint: disable=too-many-instance-attributes
def __init__(
self,
name: str = None,
url: str = None,
status: object = None,
branch: str = "main",
self,
name: str = None,
url: str = None,
status: object = None,
branch: str = "main",
):
test_dir = os.path.join(os.path.dirname(__file__), "..", "data")
if name:
@@ -188,12 +197,13 @@ class MockMacro:
def install(self, location: os.PathLike):
"""Installer function for the mock macro object: creates a file with the src_filename
attribute, and optionally an icon, xpm, and other_files. The data contained in these files
is not usable and serves only as a placeholder for the existence of the files."""
is not usable and serves only as a placeholder for the existence of the files.
"""
with open(
os.path.join(location, self.filename),
"w",
encoding="utf-8",
os.path.join(location, self.filename),
"w",
encoding="utf-8",
) as f:
f.write("Test file for macro installation unit tests")
if self.icon:
@@ -201,7 +211,7 @@ class MockMacro:
f.write(b"Fake icon data - nothing to see here\n")
if self.xpm:
with open(
os.path.join(location, "MockMacro_icon.xpm"), "w", encoding="utf-8"
os.path.join(location, "MockMacro_icon.xpm"), "w", encoding="utf-8"
) as f:
f.write(self.xpm)
for name in self.other_files:
@@ -224,6 +234,15 @@ class MockMacro:
class SignalCatcher:
"""Object to track signals that it has caught.
Usage:
catcher = SignalCatcher()
my_signal.connect(catcher.catch_signal)
do_things_that_emit_the_signal()
self.assertTrue(catcher.caught)
"""
def __init__(self):
self.caught = False
self.killed = False
@@ -238,6 +257,8 @@ class SignalCatcher:
class AddonSignalCatcher:
"""Signal catcher specifically designed for catching emitted addons."""
def __init__(self):
self.addons = []
@@ -246,6 +267,10 @@ class AddonSignalCatcher:
class CallCatcher:
"""Generic call monitor -- use to override functions that are not themselves under
test so that you can detect when the function has been called, and how many times.
"""
def __init__(self):
self.called = False
self.call_count = 0
@@ -260,7 +285,8 @@ class CallCatcher:
class MockGitManager:
"""A mock git manager: does NOT require a git installation. Takes no actions, only records
which functions are called for instrumentation purposes. Can be forced to appear to fail as
needed. Various member variables can be set to emulate necessary return responses."""
needed. Various member variables can be set to emulate necessary return responses.
"""
def __init__(self):
self.called_methods = []
@@ -288,7 +314,9 @@ class MockGitManager:
self.called_methods.append("clone")
self._check_for_failure()
def async_clone(self, _remote, _local_path, _progress_monitor, _args: List[str] = None):
def async_clone(
self, _remote, _local_path, _progress_monitor, _args: List[str] = None
):
self.called_methods.append("async_clone")
self._check_for_failure()
@@ -380,7 +408,7 @@ class MockSignal:
class MockNetworkManager:
"""Instrumented mock for the NetworkManager. Does no network access, is not asynchronous, and
does not require a running event loop. No submitted requests ever complete."""
does not require a running event loop. No submitted requests ever complete."""
def __init__(self):
self.urls = []
@@ -418,8 +446,28 @@ class MockNetworkManager:
class MockByteArray:
"""Mock for QByteArray. Only provides the data() access member."""
def __init__(self, data_to_wrap="data".encode("utf-8")):
self.wrapped = data_to_wrap
def data(self) -> bytes:
return self.wrapped
class MockThread:
"""Mock for QThread for use when threading is not being used, but interruption
needs to be tested. Set interrupt_after_n_calls to the call number to stop at."""
def __init__(self):
self.interrupt_after_n_calls = 0
self.interrupt_check_counter = 0
def isInterruptionRequested(self):
self.interrupt_check_counter += 1
if (
self.interrupt_after_n_calls
and self.interrupt_check_counter >= self.interrupt_after_n_calls
):
return True
return False

View File

@@ -220,7 +220,7 @@ class TestAddon(unittest.TestCase):
self.assertEqual(addon.repo_type, Addon.Kind.MACRO)
self.assertEqual(addon.name, "DoNothing")
self.assertEqual(
addon.macro.comment, "Do absolutely nothing. For Addon Manager unit tests."
addon.macro.comment, "Do absolutely nothing. For Addon Manager integration tests."
)
self.assertEqual(addon.url, "https://github.com/FreeCAD/FreeCAD")
self.assertEqual(addon.macro.version, "1.0")
@@ -228,7 +228,7 @@ class TestAddon(unittest.TestCase):
self.assertEqual(addon.macro.author, "Chris Hennes")
self.assertEqual(addon.macro.date, "2022-02-28")
self.assertEqual(addon.macro.icon, "not_real.png")
self.assertEqual(addon.macro.xpm, "")
self.assertNotEqual(addon.macro.xpm, "")
def test_cache(self):
addon = Addon(

View File

@@ -0,0 +1,347 @@
# ***************************************************************************
# * *
# * Copyright (c) 2022-2023 FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
"""Tests for the MacroParser class"""
import io
import os
import sys
import unittest
sys.path.append("../../") # So the IDE can find the classes to run with
from addonmanager_macro_parser import MacroParser
from AddonManagerTest.app.mocks import MockConsole, CallCatcher, MockThread
# pylint: disable=protected-access, too-many-public-methods
class TestMacroParser(unittest.TestCase):
"""Test the MacroParser class"""
def setUp(self) -> None:
self.test_object = MacroParser("UnitTestMacro")
self.test_object.console = MockConsole()
self.test_object.current_thread = MockThread()
def tearDown(self) -> None:
pass
def test_fill_details_from_code_normal(self):
"""Test to make sure _process_line gets called as expected"""
catcher = CallCatcher()
self.test_object._process_line = catcher.catch_call
fake_macro_data = self.given_some_lines(20, 10)
self.test_object.fill_details_from_code(fake_macro_data)
self.assertEqual(catcher.call_count, 10)
def test_fill_details_from_code_too_many_lines(self):
"""Test to make sure _process_line gets limited as expected"""
catcher = CallCatcher()
self.test_object._process_line = catcher.catch_call
self.test_object.MAX_LINES_TO_SEARCH = 5
fake_macro_data = self.given_some_lines(20, 10)
self.test_object.fill_details_from_code(fake_macro_data)
self.assertEqual(catcher.call_count, 5)
def test_fill_details_from_code_thread_interrupted(self):
"""Test to make sure _process_line gets stopped as expected"""
catcher = CallCatcher()
self.test_object._process_line = catcher.catch_call
self.test_object.current_thread.interrupt_after_n_calls = 6 # Stop on the 6th
fake_macro_data = self.given_some_lines(20, 10)
self.test_object.fill_details_from_code(fake_macro_data)
self.assertEqual(catcher.call_count, 5)
@staticmethod
def given_some_lines(num_lines, num_dunder_lines) -> str:
"""Generate fake macro header data with the given number of lines and number of
lines beginning with a double-underscore."""
result = ""
for i in range(num_lines):
if i < num_dunder_lines:
result += f"__something_{i}__ = 'Test{i}' # A line to be scanned\n"
else:
result += f"# Nothing to see on line {i}\n"
return result
def test_process_line_known_lines(self):
"""Lines starting with keys are processed"""
test_lines = ["__known_key__ = 'Test'", "__another_known_key__ = 'Test'"]
for line in test_lines:
with self.subTest(line=line):
self.test_object.remaining_item_map = {
"__known_key__": "known_key",
"__another_known_key__": "another_known_key",
}
content_lines = io.StringIO(line)
read_in_line = content_lines.readline()
catcher = CallCatcher()
self.test_object._process_key = catcher.catch_call
self.test_object._process_line(read_in_line, content_lines)
self.assertTrue(
catcher.called, "_process_key was not called for a known key"
)
def test_process_line_unknown_lines(self):
"""Lines starting with non-keys are not processed"""
test_lines = [
"# Just a line with a comment",
"\n",
"__dont_know_this_one__ = 'Who cares?'",
"# __known_key__ = 'Aha, but it is commented out!'",
]
for line in test_lines:
with self.subTest(line=line):
self.test_object.remaining_item_map = {
"__known_key__": "known_key",
"__another_known_key__": "another_known_key",
}
content_lines = io.StringIO(line)
read_in_line = content_lines.readline()
catcher = CallCatcher()
self.test_object._process_key = catcher.catch_call
self.test_object._process_line(read_in_line, content_lines)
self.assertFalse(
catcher.called, "_process_key was called for an unknown key"
)
def test_process_key_standard(self):
"""Normal expected data is processed"""
self.test_object._reset_map()
in_memory_data = '__comment__ = "Test"'
content_lines = io.StringIO(in_memory_data)
line = content_lines.readline()
self.test_object._process_key("__comment__", line, content_lines)
self.assertTrue(self.test_object.parse_results["comment"], "Test")
def test_process_key_special(self):
"""Special handling for version = date is processed"""
self.test_object._reset_map()
self.test_object.parse_results["date"] = "2001-01-01"
in_memory_data = "__version__ = __date__"
content_lines = io.StringIO(in_memory_data)
line = content_lines.readline()
self.test_object._process_key("__version__", line, content_lines)
self.assertTrue(self.test_object.parse_results["version"], "2001-01-01")
def test_handle_backslash_continuation_no_backslashes(self):
"""The backslash handling code doesn't change a line with no backslashes"""
in_memory_data = '"Not a backslash in sight"'
content_lines = io.StringIO(in_memory_data)
line = content_lines.readline()
result = self.test_object._handle_backslash_continuation(line, content_lines)
self.assertEqual(result, in_memory_data)
def test_handle_backslash_continuation(self):
"""Lines ending in a backslash get stripped and concatenated"""
in_memory_data = '"Line1\\\nLine2\\\nLine3\\\nLine4"'
content_lines = io.StringIO(in_memory_data)
line = content_lines.readline()
result = self.test_object._handle_backslash_continuation(line, content_lines)
self.assertEqual(result, '"Line1Line2Line3Line4"')
def test_handle_triple_quoted_string_no_triple_quotes(self):
"""The triple-quote handler leaves alone lines without triple-quotes"""
in_memory_data = '"Line1"'
content_lines = io.StringIO(in_memory_data)
line = content_lines.readline()
result, was_triple_quoted = self.test_object._handle_triple_quoted_string(
line, content_lines
)
self.assertEqual(result, in_memory_data)
self.assertFalse(was_triple_quoted)
def test_handle_triple_quoted_string(self):
"""Data is extracted across multiple lines for a triple-quoted string"""
in_memory_data = '"""Line1\nLine2\nLine3\nLine4"""\nLine5\n'
content_lines = io.StringIO(in_memory_data)
line = content_lines.readline()
result, was_triple_quoted = self.test_object._handle_triple_quoted_string(
line, content_lines
)
self.assertEqual(result, '"""Line1\nLine2\nLine3\nLine4"""')
self.assertTrue(was_triple_quoted)
def test_strip_quotes_single(self):
"""Single quotes are stripped from the final string"""
expected = "test"
quoted = f"'{expected}'"
actual = self.test_object._strip_quotes(quoted)
self.assertEqual(actual, expected)
def test_strip_quotes_double(self):
"""Double quotes are stripped from the final string"""
expected = "test"
quoted = f'"{expected}"'
actual = self.test_object._strip_quotes(quoted)
self.assertEqual(actual, expected)
def test_strip_quotes_triple(self):
"""Triple quotes are stripped from the final string"""
expected = "test"
quoted = f'"""{expected}"""'
actual = self.test_object._strip_quotes(quoted)
self.assertEqual(actual, expected)
def test_strip_quotes_unquoted(self):
"""Unquoted data results in None"""
unquoted = "This has no quotation marks of any kind"
actual = self.test_object._strip_quotes(unquoted)
self.assertIsNone(actual)
def test_standard_extraction_string(self):
"""String variables are extracted and stored"""
string_keys = [
"comment",
"url",
"wiki",
"version",
"author",
"date",
"icon",
"xpm",
]
for key in string_keys:
with self.subTest(key=key):
self.test_object._standard_extraction(key, "test")
self.assertEqual(self.test_object.parse_results[key], "test")
def test_standard_extraction_list(self):
"""List variable is extracted and stored"""
key = "other_files"
self.test_object._standard_extraction(key, "test1, test2, test3")
self.assertIn("test1", self.test_object.parse_results[key])
self.assertIn("test2", self.test_object.parse_results[key])
self.assertIn("test3", self.test_object.parse_results[key])
def test_apply_special_handling_version(self):
"""If the tag is __version__, apply our special handling"""
self.test_object._reset_map()
self.test_object._apply_special_handling("__version__", 42)
self.assertNotIn("__version__", self.test_object.remaining_item_map)
self.assertEqual(self.test_object.parse_results["version"], "42")
def test_apply_special_handling_not_version(self):
"""If the tag is not __version__, raise an error"""
self.test_object._reset_map()
with self.assertRaises(SyntaxError):
self.test_object._apply_special_handling("__not_version__", 42)
self.assertIn("__version__", self.test_object.remaining_item_map)
def test_process_noncompliant_version_date(self):
"""Detect and allow __date__ for the __version__"""
self.test_object.parse_results["date"] = "1/2/3"
self.test_object._process_noncompliant_version("__date__")
self.assertEqual(
self.test_object.parse_results["version"],
self.test_object.parse_results["date"],
)
def test_process_noncompliant_version_float(self):
"""Detect and allow floats for the __version__"""
self.test_object._process_noncompliant_version(1.2)
self.assertEqual(self.test_object.parse_results["version"], "1.2")
def test_process_noncompliant_version_int(self):
"""Detect and allow integers for the __version__"""
self.test_object._process_noncompliant_version(42)
self.assertEqual(self.test_object.parse_results["version"], "42")
def test_detect_illegal_content_prefixed_string(self):
"""Detect and raise an error for various kinds of prefixed strings"""
illegal_strings = [
"f'Some fancy {thing}'",
'f"Some fancy {thing}"',
"r'Some fancy {thing}'",
'r"Some fancy {thing}"',
"u'Some fancy {thing}'",
'u"Some fancy {thing}"',
"fr'Some fancy {thing}'",
'fr"Some fancy {thing}"',
"rf'Some fancy {thing}'",
'rf"Some fancy {thing}"',
]
for test_string in illegal_strings:
with self.subTest(test_string=test_string):
with self.assertRaises(SyntaxError):
MacroParser._detect_illegal_content(test_string)
def test_detect_illegal_content_not_a_string(self):
"""Detect and raise an error for (some) non-strings"""
illegal_strings = [
"no quotes",
"do_stuff()",
'print("A function call sporting quotes!")',
"__name__",
"__version__",
"1.2.3",
]
for test_string in illegal_strings:
with self.subTest(test_string=test_string):
with self.assertRaises(SyntaxError):
MacroParser._detect_illegal_content(test_string)
def test_detect_illegal_content_no_failure(self):
"""Recognize strings of various kinds, plus ints, and floats"""
legal_strings = [
'"Some legal value in double quotes"',
"'Some legal value in single quotes'",
'"""Some legal value in triple quotes"""',
"__date__",
"42",
"4.2",
]
for test_string in legal_strings:
with self.subTest(test_string=test_string):
MacroParser._detect_illegal_content(test_string)
#####################
# INTEGRATION TESTS #
#####################
def test_macro_parser(self):
"""INTEGRATION TEST: Given "real" data, ensure the parsing yields the expected results."""
data_dir = os.path.join(os.path.dirname(__file__), "../data")
macro_file = os.path.join(data_dir, "DoNothing.FCMacro")
with open(macro_file, "r", encoding="utf-8") as f:
code = f.read()
self.test_object.fill_details_from_code(code)
self.assertEqual(len(self.test_object.console.errors), 0)
self.assertEqual(len(self.test_object.console.warnings), 0)
self.assertEqual(self.test_object.parse_results["author"], "Chris Hennes")
self.assertEqual(self.test_object.parse_results["version"], "1.0")
self.assertEqual(self.test_object.parse_results["date"], "2022-02-28")
self.assertEqual(
self.test_object.parse_results["comment"],
"Do absolutely nothing. For Addon Manager integration tests.",
)
self.assertEqual(
self.test_object.parse_results["url"], "https://github.com/FreeCAD/FreeCAD"
)
self.assertEqual(self.test_object.parse_results["icon"], "not_real.png")
self.assertListEqual(
self.test_object.parse_results["other_files"],
["file1.py", "file2.py", "file3.py"],
)
self.assertNotEqual(self.test_object.parse_results["xpm"], "")

View File

@@ -4,7 +4,7 @@ __Title__ = 'Do Nothing'
__Author__ = 'Chris Hennes'
__Version__ = '1.0'
__Date__ = '2022-02-28'
__Comment__ = 'Do absolutely nothing. For Addon Manager unit tests.'
__Comment__ = 'Do absolutely nothing. For Addon Manager integration tests.'
__Web__ = 'https://github.com/FreeCAD/FreeCAD'
__Wiki__ = ''
__Icon__ = 'not_real.png'
@@ -13,5 +13,18 @@ __Status__ = 'Very Stable'
__Requires__ = ''
__Communication__ = 'Shout into the void'
__Files__ = 'file1.py, file2.py, file3.py'
__Xpm__ = """/* XPM */
static char * blarg_xpm[] = {
"16 7 2 1",
"* c #000000",
". c #ffffff",
"**..*...........",
"*.*.*...........",
"**..*..**.**..**",
"*.*.*.*.*.*..*.*",
"**..*..**.*...**",
"...............*",
".............**."
};"""
print("Well, not quite *nothing*... it does print this line out.")

View File

@@ -23,6 +23,7 @@ SET(AddonManager_SRCS
addonmanager_installer.py
addonmanager_installer_gui.py
addonmanager_macro.py
addonmanager_macro_parser.py
addonmanager_update_all_gui.py
addonmanager_uninstaller.py
addonmanager_uninstaller_gui.py
@@ -85,6 +86,7 @@ SET(AddonManagerTestsApp_SRCS
AddonManagerTest/app/test_git.py
AddonManagerTest/app/test_installer.py
AddonManagerTest/app/test_macro.py
AddonManagerTest/app/test_macro_parser.py
AddonManagerTest/app/test_utilities.py
AddonManagerTest/app/test_uninstaller.py
)

View File

@@ -33,9 +33,8 @@ from typing import Dict, Tuple, List, Union, Optional
import FreeCAD
import NetworkManager
from PySide import QtCore
from addonmanager_utilities import is_float
from addonmanager_macro_parser import MacroParser
translate = FreeCAD.Qt.translate
@@ -109,7 +108,8 @@ class Macro:
def is_installed(self):
"""Returns True if this macro is currently installed (that is, if it exists in the
user macro directory), or False if it is not. Both the exact filename, as well as
the filename prefixed with "Macro", are considered an installation of this macro."""
the filename prefixed with "Macro", are considered an installation of this macro.
"""
if self.on_git and not self.src_filename:
return False
return os.path.exists(
@@ -125,144 +125,12 @@ class Macro:
self.fill_details_from_code(self.code)
def fill_details_from_code(self, code: str) -> None:
"""Reads in the macro code from the given string and parses it for its metadata."""
# Number of parsed fields of metadata. Overrides anything set previously (the code is
# considered authoritative).
# For now:
# __Comment__
# __Web__
# __Wiki__
# __Version__
# __Files__
# __Author__
# __Date__
# __Icon__
max_lines_to_search = 200
line_counter = 0
string_search_mapping = {
"__comment__": "comment",
"__web__": "url",
"__wiki__": "wiki",
"__version__": "version",
"__files__": "other_files",
"__author__": "author",
"__date__": "date",
"__icon__": "icon",
"__xpm__": "xpm",
}
string_search_regex = re.compile(r"\s*(['\"])(.*)\1")
f = io.StringIO(code)
while f and line_counter < max_lines_to_search:
line = f.readline()
if not line:
break
if QtCore.QThread.currentThread().isInterruptionRequested():
return
line_counter += 1
if not line.startswith("__"):
# Speed things up a bit... this comparison is very cheap
continue
lowercase_line = line.lower()
for key, value in string_search_mapping.items():
if lowercase_line.startswith(key):
_, _, after_equals = line.partition("=")
match = re.match(string_search_regex, after_equals)
# We do NOT support triple-quoted strings, except for the icon XPM data
# Most cases should be caught by this code
if match and '"""' not in after_equals:
self._standard_extraction(value, match.group(2))
string_search_mapping.pop(key)
break
# For cases where either there is no match, or we found a triple quote,
# more processing is needed
# Macro authors are supposed to be providing strings here, but in some
# cases they are not doing so. If this is the "__version__" tag, try
# to apply some special handling to accepts numbers, and "__date__"
if key == "__version__":
self._process_noncompliant_version(after_equals)
string_search_mapping.pop(key)
break
# Icon data can be actual embedded XPM data, inside a triple-quoted string
if key in ("__icon__", "__xpm__"):
self._process_icon(f, key, after_equals)
string_search_mapping.pop(key)
break
FreeCAD.Console.PrintError(
translate(
"AddonsInstaller",
"Syntax error while reading {} from macro {}",
).format(key, self.name)
+ "\n"
)
FreeCAD.Console.PrintError(line + "\n")
# Do some cleanup of the values:
if self.comment:
self.comment = re.sub("<.*?>", "", self.comment) # Strip any HTML tags
# Truncate long comments to speed up searches, and clean up display
if len(self.comment) > 512:
self.comment = self.comment[:511] + ""
# Make sure the icon is not an absolute path, etc.
self.clean_icon()
parser = MacroParser(self.name, code)
for key, value in parser.parse_results.items():
if value:
self.__dict__[key] = value
self.parsed = True
def _standard_extraction(self, value: str, match_group):
"""For most macro metadata values, this extracts the required data"""
if isinstance(self.__dict__[value], str):
self.__dict__[value] = match_group
elif isinstance(self.__dict__[value], list):
self.__dict__[value] = [of.strip() for of in match_group.split(",")]
else:
FreeCAD.Console.PrintError(
"Internal Error: bad type in addonmanager_macro class.\n"
)
def _process_noncompliant_version(self, after_equals):
if "__date__" in after_equals.lower():
self.version = self.date
elif is_float(after_equals):
self.version = str(after_equals).strip()
else:
FreeCAD.Console.PrintLog(
f"Unrecognized value for __version__ in macro {self.name}"
)
self.version = "(Unknown)"
def _process_icon(self, f, key, after_equals):
# If this is an icon, it's possible that the icon was actually directly
# specified in the file as XPM data. This data **must** be between
# triple double quotes in order for the Addon Manager to recognize it.
if '"""' in after_equals:
_, _, xpm_data = after_equals.partition('"""')
while True:
line = f.readline()
if not line:
FreeCAD.Console.PrintError(
translate(
"AddonsInstaller",
"Syntax error while reading {} from macro {}",
).format(key, self.name)
+ "\n"
)
break
if '"""' in line:
last_line, _, _ = line.partition('"""')
xpm_data += last_line
break
xpm_data += line
self.xpm = xpm_data
def fill_details_from_wiki(self, url):
"""For a given URL, download its data and attempt to get the macro's metadata out of
it. If the macro's code is hosted elsewhere, as specified by a "rawcodeurl" found on

View File

@@ -0,0 +1,249 @@
# ***************************************************************************
# * *
# * Copyright (c) 2023 FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
"""Contains the parser class for extracting metadata from a FreeCAD macro"""
# pylint: disable=too-few-public-methods
import io
import re
from typing import Any, Tuple
try:
from PySide import QtCore
except ImportError:
QtCore = None
try:
import FreeCAD
except ImportError:
FreeCAD = None
class DummyThread:
@classmethod
def isInterruptionRequested(cls):
return False
class MacroParser:
"""Extracts metadata information from a FreeCAD macro"""
MAX_LINES_TO_SEARCH = 200 # To speed up parsing: some files are VERY large
def __init__(self, name: str, code: str = ""):
"""Create a parser for the macro named "name". Note that the name is only
used as the context for error messages, it is not otherwise important."""
self.name = name
self.parse_results = {
"comment": "",
"url": "",
"wiki": "",
"version": "",
"other_files": [""],
"author": "",
"date": "",
"icon": "",
"xpm": "",
}
self.remaining_item_map = {}
self.console = None if FreeCAD is None else FreeCAD.Console
self.current_thread = (
DummyThread() if QtCore is None else QtCore.QThread.currentThread()
)
if code:
self.fill_details_from_code(code)
def _reset_map(self):
"""This map tracks which items we've already read. If the same parser is used
twice, it has to be reset."""
self.remaining_item_map = {
"__comment__": "comment",
"__web__": "url",
"__wiki__": "wiki",
"__version__": "version",
"__files__": "other_files",
"__author__": "author",
"__date__": "date",
"__icon__": "icon",
"__xpm__": "xpm",
}
def fill_details_from_code(self, code: str) -> None:
"""Reads in the macro code from the given string and parses it for its
metadata."""
self._reset_map()
line_counter = 0
content_lines = io.StringIO(code)
while content_lines and line_counter < self.MAX_LINES_TO_SEARCH:
line = content_lines.readline()
if not line:
break
if self.current_thread.isInterruptionRequested():
return
line_counter += 1
if not line.startswith("__"):
# Speed things up a bit... this comparison is very cheap
continue
try:
self._process_line(line, content_lines)
except SyntaxError as e:
err_string = f"Syntax error when parsing macro {self.name}:\n{str(e)}"
if self.console:
self.console.PrintWarning(err_string)
else:
print(err_string)
def _process_line(self, line: str, content_lines: io.StringIO):
"""Given a single line of the macro file, see if it matches one of our items,
and if so, extract the data."""
lowercase_line = line.lower()
for key in self.remaining_item_map:
if lowercase_line.startswith(key):
self._process_key(key, line, content_lines)
break
def _process_key(self, key: str, line: str, content_lines: io.StringIO):
"""Given a line that starts with a known key, extract the data for that key,
possibly reading in additional lines (if it contains a line continuation
character, or is a triple-quoted string)."""
line = self._handle_backslash_continuation(line, content_lines)
line, was_triple_quoted = self._handle_triple_quoted_string(line, content_lines)
_, _, line = line.partition("=")
if not was_triple_quoted:
line, _, _ = line.partition("#")
self._detect_illegal_content(line)
final_content_line = line.strip()
stripped_of_quotes = self._strip_quotes(final_content_line)
if stripped_of_quotes is not None:
self._standard_extraction(self.remaining_item_map[key], stripped_of_quotes)
self.remaining_item_map.pop(key)
else:
self._apply_special_handling(key, line)
@staticmethod
def _handle_backslash_continuation(line, content_lines) -> str:
while line.strip().endswith("\\"):
line = line.strip()[:-1]
concat_line = content_lines.readline()
line += concat_line.strip()
return line
@staticmethod
def _handle_triple_quoted_string(line, content_lines) -> Tuple[str, bool]:
result = line
was_triple_quoted = False
if '"""' in result:
was_triple_quoted = True
while True:
new_line = content_lines.readline()
if not new_line:
raise SyntaxError("Syntax error while reading macro")
if '"""' in new_line:
last_line, _, _ = new_line.partition('"""')
result += last_line + '"""'
break
result += new_line
return result, was_triple_quoted
@staticmethod
def _strip_quotes(line) -> str:
line = line.strip()
stripped_of_quotes = None
if line.startswith('"""') and line.endswith('"""'):
stripped_of_quotes = line[3:-3]
elif (line[0] == '"' and line[-1] == '"') or (
line[0] == "'" and line[-1] == "'"
):
stripped_of_quotes = line[1:-1]
return stripped_of_quotes
def _standard_extraction(self, value: str, match_group: str):
"""For most macro metadata values, this extracts the required data"""
if isinstance(self.parse_results[value], str):
self.parse_results[value] = match_group
if value == "comment":
self._cleanup_comment()
elif isinstance(self.parse_results[value], list):
self.parse_results[value] = [of.strip() for of in match_group.split(",")]
else:
raise SyntaxError(f"Conflicting data type for {value}")
def _cleanup_comment(self):
"""Remove HTML from the comment line, and truncate it at 512 characters."""
self.parse_results["comment"] = re.sub(
"<.*?>", "", self.parse_results["comment"]
)
if len(self.parse_results["comment"]) > 512:
self.parse_results["comment"] = self.parse_results["comment"][:511] + ""
def _apply_special_handling(self, key: str, line: str):
# Macro authors are supposed to be providing strings here, but in some
# cases they are not doing so. If this is the "__version__" tag, try
# to apply some special handling to accept numbers, and "__date__"
if key == "__version__":
self._process_noncompliant_version(line)
self.remaining_item_map.pop(key)
return
raise SyntaxError(f"Failed to process {key} from {line}")
def _process_noncompliant_version(self, after_equals):
if is_float(after_equals):
self.parse_results["version"] = str(after_equals).strip()
elif "__date__" in after_equals.lower() and self.parse_results["date"]:
self.parse_results["version"] = self.parse_results["date"]
else:
self.parse_results["version"] = "(Unknown)"
raise SyntaxError(f"Unrecognized version string {after_equals}")
@staticmethod
def _detect_illegal_content(line: str):
"""Raise a syntax error if this line contains something we can't handle"""
lower_line = line.strip().lower()
if lower_line.startswith("'") and lower_line.endswith("'"):
return
if lower_line.startswith('"') and lower_line.endswith('"'):
return
if is_float(lower_line):
return
if lower_line == "__date__":
return
raise SyntaxError(f"Metadata is expected to be a static string, but got {line}")
# Borrowed from Stack Overflow:
# https://stackoverflow.com/questions/736043/checking-if-a-string-can-be-converted-to-float
def is_float(element: Any) -> bool:
"""Determine whether a given item can be converted to a floating-point number"""
try:
float(element)
return True
except ValueError:
return False

View File

@@ -314,28 +314,28 @@ class Component(ArchIFC.IfcProduct):
if prop == "Placement":
if hasattr(self,"oldPlacement"):
if self.oldPlacement:
import DraftVecUtils
deltap = obj.Placement.Base.sub(self.oldPlacement.Base)
if deltap.Length == 0:
deltap = None
v = FreeCAD.Vector(0,0,1)
deltar = FreeCAD.Rotation(self.oldPlacement.Rotation.multVec(v),obj.Placement.Rotation.multVec(v))
#print "Rotation",deltar.Axis,deltar.Angle
deltar = obj.Placement.Rotation * self.oldPlacement.Rotation.inverted()
if deltar.Angle < 0.0001:
deltar = None
for child in self.getMovableChildren(obj):
#print "moving ",child.Label
if deltar:
#child.Placement.Rotation = child.Placement.Rotation.multiply(deltar) - not enough, child must also move
# use shape methods to obtain a correct placement
import Part,math
shape = Part.Shape()
shape.Placement = child.Placement
#print("angle before rotation:",shape.Placement.Rotation.Angle)
#print("rotation angle:",math.degrees(deltar.Angle))
shape.rotate(DraftVecUtils.tup(self.oldPlacement.Base), DraftVecUtils.tup(deltar.Axis), math.degrees(deltar.Angle))
#print("angle after rotation:",shape.Placement.Rotation.Angle)
child.Placement = shape.Placement
import math
# Code for V1.0:
# child.Placement.rotate(self.oldPlacement.Base,
# deltar.Axis,
# math.degrees(deltar.Angle),
# comp=True)
# Workaround solution for V0.20.3 backport:
# See: https://forum.freecadweb.org/viewtopic.php?p=613196#p613196
offset_rotation = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0),
FreeCAD.Rotation(deltar.Axis, math.degrees(deltar.Angle)),
self.oldPlacement.Base)
child.Placement = offset_rotation * child.Placement
# End workaround solution.
if deltap:
child.Placement.move(deltap)

View File

@@ -24,7 +24,8 @@
containers for Arch objects, and also define a terrain surface.
"""
import FreeCAD,Draft,ArchCommands,math,re,datetime,ArchIFC
import FreeCAD,Draft,ArchCommands,ArchComponent,math,re,datetime,ArchIFC
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtGui,QtCore
@@ -718,24 +719,16 @@ class _Site(ArchIFC.IfcProduct):
if vobj.Proxy is not None:
vobj.Proxy.updateDisplaymodeTerrainSwitches(vobj)
def onChanged(self,obj,prop):
"""Method called when the object has a property changed.
def onBeforeChange(self, obj, prop):
ArchComponent.Component.onBeforeChange(self, obj, prop)
If Terrain has changed, hide the base object terrain.
def onChanged(self, obj, prop):
ArchComponent.Component.onChanged(self, obj, prop)
if prop == "Terrain" and obj.Terrain and FreeCAD.GuiUp:
obj.Terrain.ViewObject.hide()
Also call ArchIFC.IfcProduct.onChanged().
Parameters
----------
prop: string
The name of the property that has changed.
"""
ArchIFC.IfcProduct.onChanged(self, obj, prop)
if prop == "Terrain":
if obj.Terrain:
if FreeCAD.GuiUp:
obj.Terrain.ViewObject.hide()
def getMovableChildren(self, obj):
return obj.Additions + obj.Subtractions
def computeAreas(self,obj):
"""Compute the area, perimeter length, and volume of the terrain shape.

View File

@@ -161,28 +161,18 @@ def makeRailing(stairs):
if side == "L":
outlineLR = stair.OutlineLeft
outlineLRAll = stair.OutlineLeftAll
stairs0RailingLR = "RailingLeft" # stairs0OutlineWireLR = "OutlineWireLeft"
stairRailingLR = "RailingLeft" # stairOutlineWireLR = "OutlineWireLeft"
stairRailingLR = "RailingLeft"
elif side == "R":
outlineLR = stair.OutlineRight
outlineLRAll = stair.OutlineRightAll
stairs0RailingLR = "RailingRight" # stairs0OutlineWireLR = "OutlineWireRight"
stairRailingLR = "RailingRight" # stairOutlineWireLR = "OutlineWireRight"
stairRailingLR = "RailingRight"
if outlineLR or outlineLRAll:
lrRail = ArchPipe.makePipe(baseobj=None,diameter=0,length=0,placement=None,name="Rail")
if outlineLRAll:
#lrRail.Base = lrRailWire # no need to set here as _Stairs will do
setattr(stair, stairRailingLR, lrRail.Name) # setattr(stair, stairOutlineWireLR, lrRailWire.Name)
railList = stairs[0].Additions
railList.append(lrRail)
stairs[0].Additions = railList
setattr(stair, stairRailingLR, lrRail)
break
elif outlineLR:
#lrRail.Base = lrRailWire # no need to set here as _Stairs will do
setattr(stair, stairRailingLR, lrRail.Name) # setattr(stair, stairOutlineWireLR, lrRailWire.Name)
railList = stair.Additions
railList.append(lrRail)
stair.Additions = railList
setattr(stair, stairRailingLR, lrRail)
if stairs is None:
sel = FreeCADGui.Selection.getSelection()
@@ -246,7 +236,7 @@ class _CommandStairs:
FreeCADGui.addModule("Draft")
for obj in stairs:
Draft.autogroup(obj) # seems not working?
Draft.autogroup(obj) # seems not working?
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
@@ -342,25 +332,9 @@ class _Stairs(ArchComponent.Component):
self.OutlineRailArcRight = []
if not hasattr(obj,"RailingLeft"):
obj.addProperty("App::PropertyString","RailingLeft","Segment and Parts","Name of Railing object (left) created")
# Migration
if hasattr(obj,"OutlineWireLeft"):
outlineWireLeftObject = FreeCAD.ActiveDocument.getObject(obj.OutlineWireLeft)
try:
obj.RailingLeft = outlineWireLeftObject.InList[0].Name
obj.removeProperty("OutlineWireLeft")
except Exception:
pass
obj.addProperty("App::PropertyLinkHidden","RailingLeft","Segment and Parts","Name of Railing object (left) created")
if not hasattr(obj,"RailingRight"):
obj.addProperty("App::PropertyString","RailingRight","Segment and Parts","Name of Railing object (right) created")
# Migration
if hasattr(obj,"OutlineWireRight"):
outlineWireRightObject = FreeCAD.ActiveDocument.getObject(obj.OutlineWireRight)
try:
obj.RailingRight = outlineWireRightObject.InList[0].Name
obj.removeProperty("OutlineWireRight")
except Exception:
pass
obj.addProperty("App::PropertyLinkHidden","RailingRight","Segment and Parts","Name of Railing object (right) created")
if not hasattr(obj,"OutlineLeftAll"):
obj.addProperty("App::PropertyVectorList","OutlineLeftAll","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'left outline' of all segments of stairs"))
@@ -426,6 +400,57 @@ class _Stairs(ArchComponent.Component):
ArchComponent.Component.onDocumentRestored(self,obj)
self.setProperties(obj)
if hasattr(obj,"OutlineWireLeft"):
self.update_properties_0v18_to_0v20(obj)
if obj.getTypeIdOfProperty("RailingLeft") == "App::PropertyString":
self.update_properties_0v19_to_0v20(obj)
def update_properties_0v18_to_0v20(self, obj):
doc = FreeCAD.ActiveDocument
outlineWireLeftObject = doc.getObject(obj.OutlineWireLeft)
outlineWireRightObject = doc.getObject(obj.OutlineWireRight)
try:
obj.RailingLeft = outlineWireLeftObject.InList[0]
except Exception:
pass
try:
obj.RailingRight = outlineWireRightObject.InList[0]
except Exception:
pass
obj.removeProperty("OutlineWireLeft")
obj.removeProperty("OutlineWireRight")
self.update_properties_to_0v20(obj)
from draftutils.messages import _wrn
_wrn("v0.20.3, " + obj.Label + ", "
+ translate("Arch", "removed properties 'OutlineWireLeft' and 'OutlineWireRight', and added properties 'RailingLeft' and 'RailingRight'"))
def update_properties_0v19_to_0v20(self, obj):
doc = FreeCAD.ActiveDocument
railingLeftObject = doc.getObject(obj.RailingLeft)
railingRightObject = doc.getObject(obj.RailingRight)
obj.removeProperty("RailingLeft")
obj.removeProperty("RailingRight")
self.setProperties(obj)
obj.RailingLeft = railingLeftObject
obj.RailingRight = railingRightObject
self.update_properties_to_0v20(obj)
from draftutils.messages import _wrn
_wrn("v0.20.3, " + obj.Label + ", "
+ translate("Arch", "changed the type of properties 'RailingLeft' and 'RailingRight'"))
def update_properties_to_0v20(self, obj):
additions = obj.Additions
if obj.RailingLeft in additions:
additions.remove(obj.RailingLeft)
if obj.RailingRight in additions:
additions.remove(obj.RailingRight)
obj.Additions = additions
if obj.RailingLeft is not None:
obj.RailingLeft.Visibility = True
if obj.RailingRight is not None:
obj.RailingRight.Visibility = True
def execute(self,obj):
"constructs the shape of the stairs"
@@ -481,7 +506,7 @@ class _Stairs(ArchComponent.Component):
else:
if obj.Landings == "At center":
landings = 1
self.makeCurvedStairsWithLandings(obj,edge)
self.makeCurvedStairsWithLanding(obj,edge)
else:
self.makeCurvedStairs(obj,edge)
@@ -515,23 +540,22 @@ class _Stairs(ArchComponent.Component):
railingLeftObject, railWireL = None, None
railingRightObject, railWireR = None, None
doc = FreeCAD.ActiveDocument
if obj.RailingLeft:
railingLeftObject = FreeCAD.ActiveDocument.getObject(obj.RailingLeft)
if railingLeftObject: # TODO - need to update if railing is deleted by user? This become None if deleted.
railingLeftObject = obj.RailingLeft
if obj.OutlineLeftAll:
railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeftAll, self.OutlineRailArcLeftAll, mode = "notFaceAlso") #(outlinePoints, pArc, mode="wire or faceAlso")
railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeftAll, self.OutlineRailArcLeftAll, mode = "notFaceAlso")
elif obj.OutlineLeft:
railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeft, self.OutlineRailArcLeft, mode = "notFaceAlso")
else:
print (" No obj.OutlineLeftAll or obj.OutlineLeft")
if railWireL:
# Migration
if Draft.getType(railingLeftObject.Base) != "Part": # None or not "Part"
railingLeftWireObject = FreeCAD.ActiveDocument.addObject("Part::Feature","RailingWire")
if railingLeftObject.Base: # if has railingLeftObject.Base but that != "Part"
railingLeftObject.Document.removeObject(railingLeftObject.Base.Name) # Delete the previous Base object... # Not Using FreeCAD.ActiveDocument...
if Draft.getType(railingLeftObject.Base) != "Part::Feature": # Base can have wrong type or be None.
if railingLeftObject.Base:
doc.removeObject(railingLeftObject.Base.Name)
railingLeftWireObject = doc.addObject("Part::Feature","RailingWire")
railingLeftObject.Base = railingLeftWireObject
# update the Base object shape
railingLeftObject.Base.Shape = railWireL
@@ -539,23 +563,19 @@ class _Stairs(ArchComponent.Component):
print (" No railWireL created ")
if obj.RailingRight:
railingRightObject = FreeCAD.ActiveDocument.getObject(obj.RailingRight)
if railingRightObject: # TODO - need to update if railing is deleted by user? This become None if deleted.
railingRightObject = obj.RailingRight
if obj.OutlineRightAll:
print (" DEBUG - has obj.OutlineRightAll ")
railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineRightAll, self.OutlineRailArcRightAll, mode = "notFaceAlso") #(outlinePoints, pArc, mode="wire or faceAlso")
railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineRightAll, self.OutlineRailArcRightAll, mode = "notFaceAlso")
elif obj.OutlineLeft:
print (" DEBUG - has obj.OutlineLeft ")
railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeft, self.OutlineRailArcRight, mode = "notFaceAlso")
else:
print (" No obj.OutlineRightAll or obj.OutlineLeft")
if railWireR:
# Migration
if Draft.getType(railingRightObject.Base) != "Part":
railingRightWireObject = FreeCAD.ActiveDocument.addObject("Part::Feature","RailingWire")
if Draft.getType(railingRightObject.Base) != "Part::Feature": # Base can have wrong type or be None.
if railingRightObject.Base:
railingRightObject.Document.removeObject(railingRightObject.Base.Name)
doc.removeObject(railingRightObject.Base.Name)
railingRightWireObject = doc.addObject("Part::Feature","RailingWire")
railingRightObject.Base = railingRightWireObject
# update the Base object shape
railingRightObject.Base.Shape = railWireR
@@ -1308,24 +1328,25 @@ class _Stairs(ArchComponent.Component):
"builds a straight staircase with/without a landing in the middle"
if obj.NumberOfSteps < 3:
if obj.NumberOfSteps < 2:
print("Fewer than 2 steps, unable to create/update stairs")
return
v = DraftGeomUtils.vec(edge)
landing = 0
if obj.TreadDepthEnforce == 0:
if obj.Landings == "At center":
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
if obj.LandingDepth:
reslength = edge.Length - obj.LandingDepth.Value
else:
reslength = edge.Length - obj.Width.Value
treadDepth = float(reslength)/(obj.NumberOfSteps-2) # why needs 'float'?
treadDepth = reslength/(obj.NumberOfSteps-2)
obj.TreadDepth = treadDepth
vLength = DraftVecUtils.scaleTo(v,treadDepth)
else:
reslength = edge.Length
treadDepth = float(reslength)/(obj.NumberOfSteps-1) # why needs 'float'?
treadDepth = reslength/(obj.NumberOfSteps-1)
obj.TreadDepth = treadDepth
vLength = DraftVecUtils.scaleTo(v,treadDepth)
else:
@@ -1348,7 +1369,7 @@ class _Stairs(ArchComponent.Component):
h = obj.RiserHeightEnforce.Value * (obj.NumberOfSteps)
hstep = obj.RiserHeightEnforce.Value
obj.RiserHeight = hstep
if obj.Landings == "At center":
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
landing = int(obj.NumberOfSteps/2)
else:
landing = obj.NumberOfSteps
@@ -1360,7 +1381,7 @@ class _Stairs(ArchComponent.Component):
obj.AbsTop = p1.add(Vector(0,0,h))
p2 = p1.add(DraftVecUtils.scale(vLength,landing-1).add(Vector(0,0,landing*hstep)))
if obj.Landings == "At center":
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
if obj.LandingDepth:
p3 = p2.add(DraftVecUtils.scaleTo(vLength,obj.LandingDepth.Value))
else:
@@ -1392,13 +1413,16 @@ class _Stairs(ArchComponent.Component):
self.makeStraightStairs(obj,Part.LineSegment(p1,p2).toShape(),obj.DownSlabThickness.Value,obj.RiserHeight.Value,landing,None,'toSlabThickness')
else:
if obj.Landings == "At center":
print("Fewer than 4 steps, unable to create landing")
self.makeStraightStairs(obj,Part.LineSegment(p1,p2).toShape(),obj.DownSlabThickness.Value,obj.UpSlabThickness.Value,landing,None,None)
print (p1, p2)
if obj.Landings == "At center" and obj.Flight not in ["HalfTurnLeft", "HalfTurnRight"]:
print (p3, p4)
elif obj.Landings == "At center" and obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]:
print (p3r, p4r)
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
if obj.Flight not in ["HalfTurnLeft", "HalfTurnRight"]:
print (p3, p4)
elif obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]:
print (p3r, p4r)
edge = Part.LineSegment(p1,p2).toShape()
@@ -1475,6 +1499,26 @@ class _ViewProviderStairs(ArchComponent.ViewProviderComponent):
import Arch_rc
return ":/icons/Arch_Stairs_Tree.svg"
def claimChildren(self):
"Define which objects will appear as children in the tree view"
if hasattr(self, "Object"):
obj = self.Object
lst = []
if hasattr(obj, "Base"):
lst.append(obj.Base)
if hasattr(obj, "RailingLeft"):
lst.append(obj.RailingLeft)
if hasattr(obj, "RailingRight"):
lst.append(obj.RailingRight)
if hasattr(obj, "Additions"):
lst.extend(obj.Additions)
if hasattr(obj, "Subtractions"):
lst.extend(obj.Subtractions)
return lst
return []
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Arch_Stairs',_CommandStairs())

View File

@@ -2237,6 +2237,11 @@ def getRepresentation(
loops = []
verts = [v.Point for v in fcface.OuterWire.OrderedVertexes]
c = fcface.CenterOfMass
if len(verts) < 1:
print("Warning: OuterWire returned no ordered Vertexes in ", obj.Label)
# Part.show(fcface)
# Part.show(fcsolid)
continue
v1 = verts[0].sub(c)
v2 = verts[1].sub(c)
try:

View File

@@ -109,8 +109,8 @@ def mirror(objlist, p1, p2):
result = []
for obj in objlist:
mir = App.ActiveDocument.addObject("Part::Mirroring", "mirror")
mir.Label = obj.Label + " (" + translate("draft","mirrored") + ") "
mir = App.ActiveDocument.addObject("Part::Mirroring", "Mirror")
mir.Label = obj.Label + " (" + translate("draft", "mirrored") + ")"
mir.Source = obj
mir.Base = p1
mir.Normal = pnorm

View File

@@ -195,7 +195,7 @@ def make_linear_dimension(p1, p2, dim_line=None):
positioned from the measured segment that goes from `p1` to `p2`.
If it is `None`, this point will be calculated from the intermediate
distance betwwen `p1` and `p2`.
distance between `p1` and `p2`.
Returns
-------
@@ -288,7 +288,7 @@ def make_linear_dimension_obj(edge_object, i1=1, i2=2, dim_line=None):
positioned from the measured segment in `edge_object`.
If it is `None`, this point will be calculated from the intermediate
distance betwwen the vertices defined by `i1` and `i2`.
distance between the vertices defined by `i1` and `i2`.
Returns
-------

View File

@@ -67,7 +67,7 @@ draft_scales_eng_imperial = ["1in=10ft", "1in=20ft", "1in=30ft",
def get_scales(unit_system = 0):
"""
returns the list of preset scales accordin to unit system.
returns the list of preset scales according to unit system.
Parameters:
unit_system = 0 : default from user preferences

View File

@@ -178,6 +178,7 @@ PyMOD_INIT_FUNC(Fem)
Fem::FemPostPipeline ::init();
Fem::FemPostFilter ::init();
Fem::FemPostClipFilter ::init();
Fem::FemPostContoursFilter ::init();
Fem::FemPostCutFilter ::init();
Fem::FemPostDataAlongLineFilter ::init();
Fem::FemPostDataAtPointFilter ::init();

View File

@@ -24,6 +24,7 @@
#ifndef _PreComp_
# include <Python.h>
# include <vtkDoubleArray.h>
# include <vtkPointData.h>
#endif
@@ -45,51 +46,50 @@ FemPostFilter::FemPostFilter()
}
FemPostFilter::~FemPostFilter()
{}
void FemPostFilter::addFilterPipeline(const FemPostFilter::FilterPipeline& p, std::string name)
{
}
void FemPostFilter::addFilterPipeline(const FemPostFilter::FilterPipeline& p, std::string name) {
m_pipelines[name] = p;
}
FemPostFilter::FilterPipeline& FemPostFilter::getFilterPipeline(std::string name) {
FemPostFilter::FilterPipeline& FemPostFilter::getFilterPipeline(std::string name)
{
return m_pipelines[name];
}
void FemPostFilter::setActiveFilterPipeline(std::string name) {
void FemPostFilter::setActiveFilterPipeline(std::string name)
{
if (m_activePipeline != name && isValid()) {
m_activePipeline = name;
}
}
DocumentObjectExecReturn* FemPostFilter::execute() {
DocumentObjectExecReturn* FemPostFilter::execute()
{
if (!m_pipelines.empty() && !m_activePipeline.empty()) {
FemPostFilter::FilterPipeline& pipe = m_pipelines[m_activePipeline];
if (m_activePipeline.length() >= 11) {
std::string LineClip = m_activePipeline.substr(0, 13);
std::string PointClip = m_activePipeline.substr(0, 11);
if ((LineClip == "DataAlongLine") || (PointClip == "DataAtPoint")) {
vtkSmartPointer<vtkDataObject> data = getInputData();
if (!data || !data->IsA("vtkDataSet"))
return StdReturn;
if ((m_activePipeline == "DataAlongLine") || (m_activePipeline == "DataAtPoint")) {
pipe.filterSource->SetSourceData(getInputData());
pipe.filterTarget->Update();
Data.setValue(pipe.filterTarget->GetOutputDataObject(0));
}
}
else {
pipe.source->SetInputDataObject(getInputData());
pipe.source->SetInputDataObject(data);
pipe.target->Update();
Data.setValue(pipe.target->GetOutputDataObject(0));
}
}
return StdReturn;
}
vtkDataObject* FemPostFilter::getInputData() {
vtkDataObject* FemPostFilter::getInputData()
{
if (Input.getValue()) {
if (Input.getValue()->getTypeId().isDerivedFrom(Base::Type::fromName("Fem::FemPostObject")))
return Input.getValue<FemPostObject*>()->Data.getValue();
@@ -111,100 +111,18 @@ vtkDataObject* FemPostFilter::getInputData() {
return nullptr;
}
// ***************************************************************************
// clip filter
PROPERTY_SOURCE(Fem::FemPostClipFilter, Fem::FemPostFilter)
FemPostClipFilter::FemPostClipFilter() : FemPostFilter() {
ADD_PROPERTY_TYPE(Function,
(nullptr),
"Clip",
App::Prop_None,
"The function object which defines the clip regions");
ADD_PROPERTY_TYPE(InsideOut, (false), "Clip", App::Prop_None, "Invert the clip direction");
ADD_PROPERTY_TYPE(
CutCells,
(false),
"Clip",
App::Prop_None,
"Decides if cells are cuttet and interpolated or if the cells are kept as a whole");
FilterPipeline clip;
m_clipper = vtkSmartPointer<vtkTableBasedClipDataSet>::New();
clip.source = m_clipper;
clip.target = m_clipper;
addFilterPipeline(clip, "clip");
FilterPipeline extr;
m_extractor = vtkSmartPointer<vtkExtractGeometry>::New();
extr.source = m_extractor;
extr.target = m_extractor;
addFilterPipeline(extr, "extract");
m_extractor->SetExtractInside(0);
setActiveFilterPipeline("extract");
}
FemPostClipFilter::~FemPostClipFilter() {
}
void FemPostClipFilter::onChanged(const Property* prop) {
if (prop == &Function) {
if (Function.getValue()
&& Function.getValue()->isDerivedFrom(FemPostFunction::getClassTypeId())) {
m_clipper->SetClipFunction(
static_cast<FemPostFunction*>(Function.getValue())->getImplicitFunction());
m_extractor->SetImplicitFunction(
static_cast<FemPostFunction*>(Function.getValue())->getImplicitFunction());
}
}
else if (prop == &InsideOut) {
m_clipper->SetInsideOut(InsideOut.getValue());
m_extractor->SetExtractInside((InsideOut.getValue()) ? 1 : 0);
}
else if (prop == &CutCells) {
if (!CutCells.getValue())
setActiveFilterPipeline("extract");
else
setActiveFilterPipeline("clip");
};
Fem::FemPostFilter::onChanged(prop);
}
short int FemPostClipFilter::mustExecute() const {
if (Function.isTouched() ||
InsideOut.isTouched() ||
CutCells.isTouched()) {
return 1;
}
else return App::DocumentObject::mustExecute();
}
DocumentObjectExecReturn* FemPostClipFilter::execute() {
if (!m_extractor->GetImplicitFunction())
return StdReturn;
return Fem::FemPostFilter::execute();
}
// in the following, the different filters sorted alphabetically
// ***************************************************************************
// ***************************************************************************
// data along a line
// data along line filter
PROPERTY_SOURCE(Fem::FemPostDataAlongLineFilter, Fem::FemPostFilter)
FemPostDataAlongLineFilter::FemPostDataAlongLineFilter() : FemPostFilter() {
FemPostDataAlongLineFilter::FemPostDataAlongLineFilter()
: FemPostFilter()
{
ADD_PROPERTY_TYPE(Point1,
(Base::Vector3d(0.0, 0.0, 0.0)),
"DataAlongLine",
@@ -258,12 +176,11 @@ FemPostDataAlongLineFilter::FemPostDataAlongLineFilter() : FemPostFilter() {
setActiveFilterPipeline("DataAlongLine");
}
FemPostDataAlongLineFilter::~FemPostDataAlongLineFilter() {
}
DocumentObjectExecReturn* FemPostDataAlongLineFilter::execute() {
FemPostDataAlongLineFilter::~FemPostDataAlongLineFilter()
{}
DocumentObjectExecReturn* FemPostDataAlongLineFilter::execute()
{
//recalculate the filter
return Fem::FemPostFilter::execute();
}
@@ -288,7 +205,8 @@ void FemPostDataAlongLineFilter::handleChangedPropertyType(Base::XMLReader& read
}
}
void FemPostDataAlongLineFilter::onChanged(const Property* prop) {
void FemPostDataAlongLineFilter::onChanged(const Property* prop)
{
if (prop == &Point1) {
const Base::Vector3d& vec1 = Point1.getValue();
m_line->SetPoint1(vec1.x, vec1.y, vec1.z);
@@ -306,24 +224,23 @@ void FemPostDataAlongLineFilter::onChanged(const Property* prop) {
Fem::FemPostFilter::onChanged(prop);
}
short int FemPostDataAlongLineFilter::mustExecute() const {
if (Point1.isTouched() ||
Point2.isTouched() ||
Resolution.isTouched()) {
short int FemPostDataAlongLineFilter::mustExecute() const
{
if (Point1.isTouched() || Point2.isTouched() || Resolution.isTouched())
return 1;
}
else return App::DocumentObject::mustExecute();
else
return App::DocumentObject::mustExecute();
}
void FemPostDataAlongLineFilter::GetAxisData() {
void FemPostDataAlongLineFilter::GetAxisData()
{
std::vector<double> coords;
std::vector<double> values;
vtkSmartPointer<vtkDataObject> data = m_probe->GetOutputDataObject(0);
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return;
vtkDataArray* pdata = dset->GetPointData()->GetArray(PlotData.getValue());
// VTK cannot deliver data when the filer relies e.g. on a scalar clip filter
// whose value is set so that all data are clipped
@@ -363,11 +280,12 @@ void FemPostDataAlongLineFilter::GetAxisData() {
// ***************************************************************************
// data point filter
// data at point filter
PROPERTY_SOURCE(Fem::FemPostDataAtPointFilter, Fem::FemPostFilter)
FemPostDataAtPointFilter::FemPostDataAtPointFilter() : FemPostFilter() {
FemPostDataAtPointFilter::FemPostDataAtPointFilter()
: FemPostFilter()
{
ADD_PROPERTY_TYPE(Center,
(Base::Vector3d(0.0, 0.0, 0.0)),
"DataAtPoint",
@@ -408,17 +326,17 @@ FemPostDataAtPointFilter::FemPostDataAtPointFilter() : FemPostFilter() {
setActiveFilterPipeline("DataAtPoint");
}
FemPostDataAtPointFilter::~FemPostDataAtPointFilter() {
}
DocumentObjectExecReturn* FemPostDataAtPointFilter::execute() {
FemPostDataAtPointFilter::~FemPostDataAtPointFilter()
{}
DocumentObjectExecReturn* FemPostDataAtPointFilter::execute()
{
//recalculate the filter
return Fem::FemPostFilter::execute();
}
void FemPostDataAtPointFilter::onChanged(const Property* prop) {
void FemPostDataAtPointFilter::onChanged(const Property* prop)
{
if (prop == &Center) {
const Base::Vector3d& vec = Center.getValue();
m_point->SetCenter(vec.x, vec.y, vec.z);
@@ -427,20 +345,22 @@ void FemPostDataAtPointFilter::onChanged(const Property* prop) {
Fem::FemPostFilter::onChanged(prop);
}
short int FemPostDataAtPointFilter::mustExecute() const {
short int FemPostDataAtPointFilter::mustExecute() const
{
if (Center.isTouched())
return 1;
else
return App::DocumentObject::mustExecute();
}
void FemPostDataAtPointFilter::GetPointData() {
void FemPostDataAtPointFilter::GetPointData()
{
std::vector<double> values;
vtkSmartPointer<vtkDataObject> data = m_probe->GetOutputDataObject(0);
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return;
vtkDataArray* pdata = dset->GetPointData()->GetArray(FieldName.getValue());
// VTK cannot deliver data when the filer relies e.g. on a scalar clip filter
// whose value is set so that all data are clipped
@@ -467,197 +387,342 @@ void FemPostDataAtPointFilter::GetPointData() {
// ***************************************************************************
// scalar clip filter
PROPERTY_SOURCE(Fem::FemPostScalarClipFilter, Fem::FemPostFilter)
// clip filter
PROPERTY_SOURCE(Fem::FemPostClipFilter, Fem::FemPostFilter)
FemPostScalarClipFilter::FemPostScalarClipFilter() : FemPostFilter() {
ADD_PROPERTY_TYPE(
Value, (0), "Clip", App::Prop_None, "The scalar value used to clip the selected field");
ADD_PROPERTY_TYPE(Scalars, (long(0)), "Clip", App::Prop_None, "The field used to clip");
FemPostClipFilter::FemPostClipFilter()
: FemPostFilter()
{
ADD_PROPERTY_TYPE(Function,
(nullptr),
"Clip",
App::Prop_None,
"The function object which defines the clip regions");
ADD_PROPERTY_TYPE(InsideOut, (false), "Clip", App::Prop_None, "Invert the clip direction");
Value.setConstraints(&m_constraints);
ADD_PROPERTY_TYPE(
CutCells,
(false),
"Clip",
App::Prop_None,
"Decides if cells are cut and interpolated or if the cells are kept as a whole");
FilterPipeline clip;
m_clipper = vtkSmartPointer<vtkTableBasedClipDataSet>::New();
m_clipper = vtkSmartPointer<vtkTableBasedClipDataSet>::New();
clip.source = m_clipper;
clip.target = m_clipper;
addFilterPipeline(clip, "clip");
setActiveFilterPipeline("clip");
FilterPipeline extr;
m_extractor = vtkSmartPointer<vtkExtractGeometry>::New();
extr.source = m_extractor;
extr.target = m_extractor;
addFilterPipeline(extr, "extract");
m_extractor->SetExtractInside(0);
setActiveFilterPipeline("extract");
}
FemPostScalarClipFilter::~FemPostScalarClipFilter() {
FemPostClipFilter::~FemPostClipFilter()
{}
}
void FemPostClipFilter::onChanged(const Property* prop)
{
if (prop == &Function) {
DocumentObjectExecReturn* FemPostScalarClipFilter::execute() {
std::string val;
if (Scalars.getValue() >= 0)
val = Scalars.getValueAsString();
std::vector<std::string> ScalarsArray;
vtkSmartPointer<vtkDataObject> data = getInputData();
if (!data || !data->IsA("vtkDataSet"))
return StdReturn;
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
vtkPointData* pd = dset->GetPointData();
// get all scalar fields
for (int i = 0; i < pd->GetNumberOfArrays(); ++i) {
if (pd->GetArray(i)->GetNumberOfComponents() == 1)
ScalarsArray.emplace_back(pd->GetArrayName(i));
}
App::Enumeration empty;
Scalars.setValue(empty);
m_scalarFields.setEnums(ScalarsArray);
Scalars.setValue(m_scalarFields);
// search if the current field is in the available ones and set it
std::vector<std::string>::iterator it =
std::find(ScalarsArray.begin(), ScalarsArray.end(), val);
if (!val.empty() && it != ScalarsArray.end())
Scalars.setValue(val.c_str());
//recalculate the filter
return Fem::FemPostFilter::execute();
}
void FemPostScalarClipFilter::onChanged(const Property* prop) {
if (prop == &Value) {
m_clipper->SetValue(Value.getValue());
if (Function.getValue()
&& Function.getValue()->isDerivedFrom(FemPostFunction::getClassTypeId())) {
m_clipper->SetClipFunction(
static_cast<FemPostFunction*>(Function.getValue())->getImplicitFunction());
m_extractor->SetImplicitFunction(
static_cast<FemPostFunction*>(Function.getValue())->getImplicitFunction());
}
}
else if (prop == &InsideOut) {
m_clipper->SetInsideOut(InsideOut.getValue());
m_extractor->SetExtractInside((InsideOut.getValue()) ? 1 : 0);
}
else if (prop == &Scalars && (Scalars.getValue() >= 0)) {
m_clipper->SetInputArrayToProcess(0, 0, 0,
vtkDataObject::FIELD_ASSOCIATION_POINTS, Scalars.getValueAsString());
setConstraintForField();
}
else if (prop == &CutCells) {
if (!CutCells.getValue())
setActiveFilterPipeline("extract");
else
setActiveFilterPipeline("clip");
};
Fem::FemPostFilter::onChanged(prop);
}
short int FemPostScalarClipFilter::mustExecute() const {
short int FemPostClipFilter::mustExecute() const
{
if (Function.isTouched() || InsideOut.isTouched() || CutCells.isTouched())
if (Value.isTouched() ||
InsideOut.isTouched() ||
Scalars.isTouched())
return 1;
else
return App::DocumentObject::mustExecute();
}
void FemPostScalarClipFilter::setConstraintForField() {
vtkSmartPointer<vtkDataObject> data = getInputData();
if (!data || !data->IsA("vtkDataSet"))
return;
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
vtkDataArray* pdata = dset->GetPointData()->GetArray(Scalars.getValueAsString());
// VTK cannot deliver data when the filer relies e.g. on a cut clip filter
// whose value is set so that all data are cut
if (!pdata)
return;
double p[2];
pdata->GetRange(p);
m_constraints.LowerBound = p[0];
m_constraints.UpperBound = p[1];
m_constraints.StepSize = (p[1] - p[0]) / 100.;
}
// ***************************************************************************
// warp vector filter
PROPERTY_SOURCE(Fem::FemPostWarpVectorFilter, Fem::FemPostFilter)
FemPostWarpVectorFilter::FemPostWarpVectorFilter() : FemPostFilter() {
ADD_PROPERTY_TYPE(Factor,
(0),
"Warp",
App::Prop_None,
"The factor by which the vector is added to the node positions");
ADD_PROPERTY_TYPE(
Vector, (long(0)), "Warp", App::Prop_None, "The field added to the node position");
FilterPipeline warp;
m_warp = vtkSmartPointer<vtkWarpVector>::New();
warp.source = m_warp;
warp.target = m_warp;
addFilterPipeline(warp, "warp");
setActiveFilterPipeline("warp");
}
FemPostWarpVectorFilter::~FemPostWarpVectorFilter() {
}
DocumentObjectExecReturn* FemPostWarpVectorFilter::execute() {
std::string val;
if (Vector.getValue() >= 0)
val = Vector.getValueAsString();
std::vector<std::string> VectorArray;
vtkSmartPointer<vtkDataObject> data = getInputData();
if (!data || !data->IsA("vtkDataSet"))
DocumentObjectExecReturn* FemPostClipFilter::execute()
{
if (!m_extractor->GetImplicitFunction())
return StdReturn;
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
vtkPointData* pd = dset->GetPointData();
// get all vector fields
for (int i = 0; i < pd->GetNumberOfArrays(); ++i) {
if (pd->GetArray(i)->GetNumberOfComponents() == 3)
VectorArray.emplace_back(pd->GetArrayName(i));
}
App::Enumeration empty;
Vector.setValue(empty);
m_vectorFields.setEnums(VectorArray);
Vector.setValue(m_vectorFields);
// search if the current field is in the available ones and set it
std::vector<std::string>::iterator it =
std::find(VectorArray.begin(), VectorArray.end(), val);
if (!val.empty() && it != VectorArray.end())
Vector.setValue(val.c_str());
//recalculate the filter
return Fem::FemPostFilter::execute();
}
void FemPostWarpVectorFilter::onChanged(const Property* prop) {
// ***************************************************************************
// contours filter
PROPERTY_SOURCE(Fem::FemPostContoursFilter, Fem::FemPostFilter)
if (prop == &Factor)
// since our mesh is in mm, we must scale the factor
m_warp->SetScaleFactor(1000 * Factor.getValue());
else if (prop == &Vector && (Vector.getValue() >= 0))
m_warp->SetInputArrayToProcess(0, 0, 0,
vtkDataObject::FIELD_ASSOCIATION_POINTS, Vector.getValueAsString());
FemPostContoursFilter::FemPostContoursFilter()
: FemPostFilter()
{
ADD_PROPERTY_TYPE(NumberOfContours, (10), "Contours", App::Prop_None, "The number of contours");
ADD_PROPERTY_TYPE(Field, (long(0)), "Clip", App::Prop_None, "The field used to clip");
ADD_PROPERTY_TYPE(
VectorMode, ((long)0), "Contours", App::Prop_None, "Select what vector field");
ADD_PROPERTY_TYPE(NoColor, (false), "Contours",
PropertyType(Prop_Hidden), "Don't color the contours");
m_contourConstraints.LowerBound = 1;
m_contourConstraints.UpperBound = 1000;
m_contourConstraints.StepSize = 1;
NumberOfContours.setConstraints(&m_contourConstraints);
FilterPipeline contours;
m_contours = vtkSmartPointer<vtkContourFilter>::New();
m_contours->ComputeScalarsOn();
contours.source = m_contours;
contours.target = m_contours;
addFilterPipeline(contours, "contours");
setActiveFilterPipeline("contours");
}
FemPostContoursFilter::~FemPostContoursFilter()
{}
DocumentObjectExecReturn* FemPostContoursFilter::execute()
{
// update list of available fields and their vectors
if (!m_blockPropertyChanges) {
refreshFields();
refreshVectors();
}
// recalculate the filter
auto returnObject = Fem::FemPostFilter::execute();
// delete contour field
vtkSmartPointer<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return returnObject;
dset->GetPointData()->RemoveArray(contourFieldName.c_str());
// refresh fields to reflect the deletion
if (!m_blockPropertyChanges)
refreshFields();
return returnObject;
}
void FemPostContoursFilter::onChanged(const Property* prop)
{
if (m_blockPropertyChanges)
return;
if (prop == &Field && (Field.getValue() >= 0))
refreshVectors();
// note that we need to calculate also in case of a Data change
// otherwise the contours output would be empty and the ViewProviderFemPostObject
// would not get any data
if ((prop == &Field || prop == &VectorMode || prop == &NumberOfContours || prop == &Data)
&& (Field.getValue() >= 0)) {
double p[2];
// get the field and its data
vtkSmartPointer<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return;
vtkDataArray* pdata = dset->GetPointData()->GetArray(Field.getValueAsString());
if (!pdata)
return;
if (pdata->GetNumberOfComponents() == 1) {
// if we have a scalar, we can directly use the array
m_contours->SetInputArrayToProcess(
0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, Field.getValueAsString());
pdata->GetRange(p);
recalculateContours(p[0], p[1]);
}
else {
// The contour filter handles vectors by taking always its first component.
// There is no other solution than to make the desired vectorn component a
// scalar array and append this temporarily to the data. (vtkExtractVectorComponents
// does not work because our data is an unstructured data set.)
int component = -1;
if (VectorMode.getValue() == 1)
component = 0;
else if (VectorMode.getValue() == 2)
component = 1;
else if (VectorMode.getValue() == 3)
component = 2;
// extract the component to a new array
vtkSmartPointer<vtkDoubleArray> componentArray = vtkSmartPointer<vtkDoubleArray>::New();
componentArray->SetNumberOfComponents(1);
vtkIdType numTuples = pdata->GetNumberOfTuples();
componentArray->SetNumberOfTuples(numTuples);
if (component >= 0) {
for (vtkIdType tupleIdx = 0; tupleIdx < numTuples; ++tupleIdx) {
componentArray->SetComponent(
tupleIdx, 0, pdata->GetComponent(tupleIdx, component));
}
}
else {
for (vtkIdType tupleIdx = 0; tupleIdx < numTuples; ++tupleIdx) {
componentArray->SetComponent(
tupleIdx,
0,
std::sqrt(
pdata->GetComponent(tupleIdx, 0) * pdata->GetComponent(tupleIdx, 0)
+ pdata->GetComponent(tupleIdx, 1) * pdata->GetComponent(tupleIdx, 1)
+ pdata->GetComponent(tupleIdx, 2) * pdata->GetComponent(tupleIdx, 2)));
}
}
// name the array
contourFieldName = std::string(Field.getValueAsString()) + "_contour";
componentArray->SetName(contourFieldName.c_str());
// add the array as new field and use it for the contour filter
dset->GetPointData()->AddArray(componentArray);
m_contours->SetInputArrayToProcess(
0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, contourFieldName.c_str());
componentArray->GetRange(p);
recalculateContours(p[0], p[1]);
if (prop == &Data) {
// we must recalculate to pass the new created contours field
// to ViewProviderFemPostObject
m_blockPropertyChanges = true;
execute();
m_blockPropertyChanges = false;
}
}
}
Fem::FemPostFilter::onChanged(prop);
}
short int FemPostWarpVectorFilter::mustExecute() const {
if (Factor.isTouched() ||
Vector.isTouched())
short int FemPostContoursFilter::mustExecute() const
{
if (Field.isTouched() || VectorMode.isTouched() || NumberOfContours.isTouched()
|| Data.isTouched())
return 1;
else
return App::DocumentObject::mustExecute();
}
void FemPostContoursFilter::recalculateContours(double min, double max)
{
// As the min and max contours are not visible, an input of "3" leads
// to 1 visible contour. To not confuse the user, take the visible contours
// for NumberOfContours
int visibleNum = NumberOfContours.getValue() + 2;
m_contours->GenerateValues(visibleNum, min, max);
}
void FemPostContoursFilter::refreshFields()
{
m_blockPropertyChanges = true;
std::string fieldName;
if (Field.getValue() >= 0)
fieldName = Field.getValueAsString();
std::vector<std::string> FieldsArray;
vtkSmartPointer<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset) {
m_blockPropertyChanges = false;
return;
}
vtkPointData* pd = dset->GetPointData();
// get all fields
for (int i = 0; i < pd->GetNumberOfArrays(); ++i) {
FieldsArray.emplace_back(pd->GetArrayName(i));
}
App::Enumeration empty;
Field.setValue(empty);
m_fields.setEnums(FieldsArray);
Field.setValue(m_fields);
// search if the current field is in the available ones and set it
std::vector<std::string>::iterator it =
std::find(FieldsArray.begin(), FieldsArray.end(), fieldName);
if (!fieldName.empty() && it != FieldsArray.end()) {
Field.setValue(fieldName.c_str());
}
else {
m_blockPropertyChanges = false;
// select the first field
Field.setValue(long(0));
fieldName = Field.getValueAsString();
}
m_blockPropertyChanges = false;
}
void FemPostContoursFilter::refreshVectors()
{
// refreshes the list of available vectors for the current Field
m_blockPropertyChanges = true;
vtkSmartPointer<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset) {
m_blockPropertyChanges = false;
return;
}
vtkDataArray* fieldArray = dset->GetPointData()->GetArray(Field.getValueAsString());
if (!fieldArray) {
m_blockPropertyChanges = false;
return;
}
// store name if already set
std::string vectorName;
if (VectorMode.hasEnums() && VectorMode.getValue() >= 0)
vectorName = VectorMode.getValueAsString();
std::vector<std::string> vectorArray;
if (fieldArray->GetNumberOfComponents() == 1)
vectorArray.emplace_back("Not a vector");
else {
vectorArray.emplace_back("Magnitude");
if (fieldArray->GetNumberOfComponents() >= 2) {
vectorArray.emplace_back("X");
vectorArray.emplace_back("Y");
}
if (fieldArray->GetNumberOfComponents() >= 3) {
vectorArray.emplace_back("Z");
}
}
App::Enumeration empty;
VectorMode.setValue(empty);
m_vectors.setEnums(vectorArray);
VectorMode.setValue(m_vectors);
// apply stored name
auto it = std::find(vectorArray.begin(), vectorArray.end(), vectorName);
if (!vectorName.empty() && it != vectorArray.end())
VectorMode.setValue(vectorName.c_str());
m_blockPropertyChanges = false;
}
// ***************************************************************************
// cut filter
@@ -673,7 +738,7 @@ FemPostCutFilter::FemPostCutFilter()
"The function object which defines the cut function");
FilterPipeline cut;
m_cutter = vtkSmartPointer<vtkCutter>::New();
m_cutter = vtkSmartPointer<vtkCutter>::New();
cut.source = m_cutter;
cut.target = m_cutter;
addFilterPipeline(cut, "cut");
@@ -711,3 +776,191 @@ DocumentObjectExecReturn* FemPostCutFilter::execute()
return Fem::FemPostFilter::execute();
}
// ***************************************************************************
// scalar clip filter
PROPERTY_SOURCE(Fem::FemPostScalarClipFilter, Fem::FemPostFilter)
FemPostScalarClipFilter::FemPostScalarClipFilter() : FemPostFilter() {
ADD_PROPERTY_TYPE(
Value, (0), "Clip", App::Prop_None, "The scalar value used to clip the selected field");
ADD_PROPERTY_TYPE(Scalars, (long(0)), "Clip", App::Prop_None, "The field used to clip");
ADD_PROPERTY_TYPE(InsideOut, (false), "Clip", App::Prop_None, "Invert the clip direction");
Value.setConstraints(&m_constraints);
FilterPipeline clip;
m_clipper = vtkSmartPointer<vtkTableBasedClipDataSet>::New();
clip.source = m_clipper;
clip.target = m_clipper;
addFilterPipeline(clip, "clip");
setActiveFilterPipeline("clip");
}
FemPostScalarClipFilter::~FemPostScalarClipFilter() {
}
DocumentObjectExecReturn* FemPostScalarClipFilter::execute()
{
std::string val;
if (Scalars.getValue() >= 0)
val = Scalars.getValueAsString();
std::vector<std::string> ScalarsArray;
vtkSmartPointer<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return StdReturn;
vtkPointData* pd = dset->GetPointData();
// get all scalar fields
for (int i = 0; i < pd->GetNumberOfArrays(); ++i) {
if (pd->GetArray(i)->GetNumberOfComponents() == 1)
ScalarsArray.emplace_back(pd->GetArrayName(i));
}
App::Enumeration empty;
Scalars.setValue(empty);
m_scalarFields.setEnums(ScalarsArray);
Scalars.setValue(m_scalarFields);
// search if the current field is in the available ones and set it
std::vector<std::string>::iterator it =
std::find(ScalarsArray.begin(), ScalarsArray.end(), val);
if (!val.empty() && it != ScalarsArray.end())
Scalars.setValue(val.c_str());
//recalculate the filter
return Fem::FemPostFilter::execute();
}
void FemPostScalarClipFilter::onChanged(const Property* prop)
{
if (prop == &Value) {
m_clipper->SetValue(Value.getValue());
}
else if (prop == &InsideOut) {
m_clipper->SetInsideOut(InsideOut.getValue());
}
else if (prop == &Scalars && (Scalars.getValue() >= 0)) {
m_clipper->SetInputArrayToProcess(0, 0, 0,
vtkDataObject::FIELD_ASSOCIATION_POINTS, Scalars.getValueAsString());
setConstraintForField();
}
Fem::FemPostFilter::onChanged(prop);
}
short int FemPostScalarClipFilter::mustExecute() const
{
if (Value.isTouched() ||
InsideOut.isTouched() ||
Scalars.isTouched())
return 1;
else
return App::DocumentObject::mustExecute();
}
void FemPostScalarClipFilter::setConstraintForField()
{
vtkSmartPointer<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return;
vtkDataArray* pdata = dset->GetPointData()->GetArray(Scalars.getValueAsString());
// VTK cannot deliver data when the filer relies e.g. on a cut clip filter
// whose value is set so that all data are cut
if (!pdata)
return;
double p[2];
pdata->GetRange(p);
m_constraints.LowerBound = p[0];
m_constraints.UpperBound = p[1];
m_constraints.StepSize = (p[1] - p[0]) / 100.;
}
// ***************************************************************************
// warp vector filter
PROPERTY_SOURCE(Fem::FemPostWarpVectorFilter, Fem::FemPostFilter)
FemPostWarpVectorFilter::FemPostWarpVectorFilter() : FemPostFilter()
{
ADD_PROPERTY_TYPE(Factor,
(0),
"Warp",
App::Prop_None,
"The factor by which the vector is added to the node positions");
ADD_PROPERTY_TYPE(
Vector, (long(0)), "Warp", App::Prop_None, "The field added to the node position");
FilterPipeline warp;
m_warp = vtkSmartPointer<vtkWarpVector>::New();
warp.source = m_warp;
warp.target = m_warp;
addFilterPipeline(warp, "warp");
setActiveFilterPipeline("warp");
}
FemPostWarpVectorFilter::~FemPostWarpVectorFilter()
{}
DocumentObjectExecReturn* FemPostWarpVectorFilter::execute()
{
std::string val;
if (Vector.getValue() >= 0)
val = Vector.getValueAsString();
std::vector<std::string> VectorArray;
vtkSmartPointer<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return StdReturn;
vtkPointData* pd = dset->GetPointData();
// get all vector fields
for (int i = 0; i < pd->GetNumberOfArrays(); ++i) {
if (pd->GetArray(i)->GetNumberOfComponents() == 3)
VectorArray.emplace_back(pd->GetArrayName(i));
}
App::Enumeration empty;
Vector.setValue(empty);
m_vectorFields.setEnums(VectorArray);
Vector.setValue(m_vectorFields);
// search if the current field is in the available ones and set it
std::vector<std::string>::iterator it =
std::find(VectorArray.begin(), VectorArray.end(), val);
if (!val.empty() && it != VectorArray.end())
Vector.setValue(val.c_str());
//recalculate the filter
return Fem::FemPostFilter::execute();
}
void FemPostWarpVectorFilter::onChanged(const Property* prop)
{
if (prop == &Factor)
// since our mesh is in mm, we must scale the factor
m_warp->SetScaleFactor(1000 * Factor.getValue());
else if (prop == &Vector && (Vector.getValue() >= 0))
m_warp->SetInputArrayToProcess(0, 0, 0,
vtkDataObject::FIELD_ASSOCIATION_POINTS, Vector.getValueAsString());
Fem::FemPostFilter::onChanged(prop);
}
short int FemPostWarpVectorFilter::mustExecute() const
{
if (Factor.isTouched() || Vector.isTouched())
return 1;
else
return App::DocumentObject::mustExecute();
}

View File

@@ -23,9 +23,12 @@
#ifndef Fem_FemPostFilter_H
#define Fem_FemPostFilter_H
#include <vtkContourFilter.h>
#include <vtkCutter.h>
#include <vtkExtractGeometry.h>
#include <vtkExtractVectorComponents.h>
#include <vtkLineSource.h>
#include <vtkVectorNorm.h>
#include <vtkPointSource.h>
#include <vtkProbeFilter.h>
#include <vtkSmartPointer.h>
@@ -72,33 +75,15 @@ private:
std::string m_activePipeline;
};
class FemExport FemPostClipFilter : public FemPostFilter {
// ***************************************************************************
// in the following, the different filters sorted alphabetically
// ***************************************************************************
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostClipFilter);
public:
FemPostClipFilter();
~FemPostClipFilter() override;
App::PropertyLink Function;
App::PropertyBool InsideOut;
App::PropertyBool CutCells;
const char* getViewProviderName() const override {
return "FemGui::ViewProviderFemPostClip";
}
short int mustExecute() const override;
App::DocumentObjectExecReturn* execute() override;
protected:
void onChanged(const App::Property* prop) override;
private:
vtkSmartPointer<vtkTableBasedClipDataSet> m_clipper;
vtkSmartPointer<vtkExtractGeometry> m_extractor;
};
class FemExport FemPostDataAlongLineFilter : public FemPostFilter {
// ***************************************************************************
// data along line filter
class FemExport FemPostDataAlongLineFilter: public FemPostFilter
{
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostDataAlongLineFilter);
@@ -113,8 +98,9 @@ public:
App::PropertyFloatList YAxisData;
App::PropertyString PlotData;
const char* getViewProviderName() const override {
return "FemGui::ViewProviderFemPostDataAlongLine";
const char* getViewProviderName() const override
{
return "FemGui::ViewProviderFemPostDataAlongLine";
}
short int mustExecute() const override;
void GetAxisData();
@@ -126,13 +112,15 @@ protected:
App::Property* prop) override;
private:
vtkSmartPointer<vtkLineSource> m_line;
vtkSmartPointer<vtkProbeFilter> m_probe;
vtkSmartPointer<vtkLineSource> m_line;
vtkSmartPointer<vtkProbeFilter> m_probe;
};
class FemExport FemPostDataAtPointFilter : public FemPostFilter {
// ***************************************************************************
// data at point filter
class FemExport FemPostDataAtPointFilter: public FemPostFilter
{
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostDataAtPointFilter);
@@ -146,8 +134,9 @@ public:
App::PropertyFloatList PointData;
App::PropertyString Unit;
const char* getViewProviderName() const override {
return "FemGui::ViewProviderFemPostDataAtPoint";
const char* getViewProviderName() const override
{
return "FemGui::ViewProviderFemPostDataAtPoint";
}
short int mustExecute() const override;
@@ -157,13 +146,115 @@ protected:
void GetPointData();
private:
vtkSmartPointer<vtkPointSource> m_point;
vtkSmartPointer<vtkProbeFilter> m_probe;
vtkSmartPointer<vtkPointSource> m_point;
vtkSmartPointer<vtkProbeFilter> m_probe;
};
class FemExport FemPostScalarClipFilter : public FemPostFilter {
// ***************************************************************************
// clip filter
class FemExport FemPostClipFilter: public FemPostFilter
{
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostClipFilter);
public:
FemPostClipFilter();
~FemPostClipFilter() override;
App::PropertyLink Function;
App::PropertyBool InsideOut;
App::PropertyBool CutCells;
const char* getViewProviderName() const override
{
return "FemGui::ViewProviderFemPostClip";
}
short int mustExecute() const override;
App::DocumentObjectExecReturn* execute() override;
protected:
void onChanged(const App::Property* prop) override;
private:
vtkSmartPointer<vtkTableBasedClipDataSet> m_clipper;
vtkSmartPointer<vtkExtractGeometry> m_extractor;
};
// ***************************************************************************
// contours filter
class FemExport FemPostContoursFilter: public FemPostFilter
{
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostContoursFilter);
public:
FemPostContoursFilter();
~FemPostContoursFilter() override;
App::PropertyEnumeration Field;
App::PropertyIntegerConstraint NumberOfContours;
App::PropertyEnumeration VectorMode;
App::PropertyBool NoColor;
const char* getViewProviderName() const override
{
return "FemGui::ViewProviderFemPostContours";
}
short int mustExecute() const override;
protected:
App::DocumentObjectExecReturn* execute() override;
void onChanged(const App::Property* prop) override;
void recalculateContours(double min, double max);
void refreshFields();
void refreshVectors();
bool m_blockPropertyChanges = false;
std::string contourFieldName;
private:
vtkSmartPointer<vtkContourFilter> m_contours;
vtkSmartPointer<vtkExtractVectorComponents> m_extractor;
vtkSmartPointer<vtkVectorNorm> m_norm;
App::Enumeration m_fields;
App::Enumeration m_vectors;
App::PropertyIntegerConstraint::Constraints m_contourConstraints;
};
// ***************************************************************************
// cut filter
class FemExport FemPostCutFilter: public FemPostFilter
{
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostCutFilter);
public:
FemPostCutFilter();
~FemPostCutFilter() override;
App::PropertyLink Function;
const char* getViewProviderName() const override
{
return "FemGui::ViewProviderFemPostCut";
}
short int mustExecute() const override;
App::DocumentObjectExecReturn* execute() override;
protected:
void onChanged(const App::Property* prop) override;
private:
vtkSmartPointer<vtkCutter> m_cutter;
};
// ***************************************************************************
// scalar clip filter
class FemExport FemPostScalarClipFilter: public FemPostFilter
{
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostScalarClipFilter);
@@ -171,11 +262,12 @@ public:
FemPostScalarClipFilter();
~FemPostScalarClipFilter() override;
App::PropertyBool InsideOut;
App::PropertyBool InsideOut;
App::PropertyFloatConstraint Value;
App::PropertyEnumeration Scalars;
App::PropertyEnumeration Scalars;
const char* getViewProviderName() const override {
const char* getViewProviderName() const override
{
return "FemGui::ViewProviderFemPostScalarClip";
}
short int mustExecute() const override;
@@ -186,11 +278,14 @@ protected:
void setConstraintForField();
private:
vtkSmartPointer<vtkTableBasedClipDataSet> m_clipper;
App::Enumeration m_scalarFields;
App::PropertyFloatConstraint::Constraints m_constraints;
vtkSmartPointer<vtkTableBasedClipDataSet> m_clipper;
App::Enumeration m_scalarFields;
App::PropertyFloatConstraint::Constraints m_constraints;
};
// ***************************************************************************
// warp vector filter
class FemExport FemPostWarpVectorFilter : public FemPostFilter {
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostWarpVectorFilter);
@@ -216,29 +311,6 @@ private:
App::Enumeration m_vectorFields;
};
class FemExport FemPostCutFilter : public FemPostFilter {
PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostCutFilter);
public:
FemPostCutFilter();
~FemPostCutFilter() override;
App::PropertyLink Function;
const char* getViewProviderName() const override {
return "FemGui::ViewProviderFemPostCut";
}
short int mustExecute() const override;
App::DocumentObjectExecReturn* execute() override;
protected:
void onChanged(const App::Property* prop) override;
private:
vtkSmartPointer<vtkCutter> m_cutter;
};
} //namespace Fem

View File

@@ -48,8 +48,9 @@ vtkBoundingBox FemPostObject::getBoundingBox() {
vtkBoundingBox box;
if (Data.getValue() && Data.getValue()->IsA("vtkDataSet"))
box.AddBounds(vtkDataSet::SafeDownCast(Data.getValue())->GetBounds());
vtkDataSet* dset = vtkDataSet::SafeDownCast(Data.getValue());
if (dset)
box.AddBounds(dset->GetBounds());
// TODO: add calculation of multiblock and Multipiece datasets

View File

@@ -152,6 +152,7 @@ PyMOD_INIT_FUNC(FemGui)
FemGui::ViewProviderFemPostObject ::init();
FemGui::ViewProviderFemPostPipeline ::init();
FemGui::ViewProviderFemPostClip ::init();
FemGui::ViewProviderFemPostContours ::init();
FemGui::ViewProviderFemPostCut ::init();
FemGui::ViewProviderFemPostDataAlongLine ::init();
FemGui::ViewProviderFemPostDataAtPoint ::init();

View File

@@ -93,6 +93,7 @@ if(BUILD_FEM_VTK)
${FemGui_UIC_SRCS}
TaskPostDisplay.ui
TaskPostClip.ui
TaskPostContours.ui
TaskPostDataAlongLine.ui
TaskPostDataAtPoint.ui
TaskPostScalarClip.ui
@@ -271,6 +272,7 @@ if(BUILD_FEM_VTK)
PlaneWidget.ui
SphereWidget.ui
TaskPostClip.ui
TaskPostContours.ui
TaskPostDataAlongLine.ui
TaskPostDataAtPoint.ui
TaskPostScalarClip.ui

View File

@@ -1534,13 +1534,14 @@ void setupFilter(Gui::Command* cmd, std::string Name) {
// issue error if no post object
if (!((selObject->getTypeId() == Base::Type::fromName("Fem::FemPostPipeline"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostWarpVectorFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostScalarClipFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostCutFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostClipFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostDataAlongLineFilter")) )
) {
QMessageBox::warning(Gui::getMainWindow(),
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostClipFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostContoursFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostCutFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostDataAlongLineFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostScalarClipFilter"))
|| (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostWarpVectorFilter")))) {
QMessageBox::warning(
Gui::getMainWindow(),
qApp->translate("setupFilter", "Error: no post processing object selected."),
qApp->translate("setupFilter", "The filter could not be set up."));
return;
@@ -1721,20 +1722,21 @@ bool CmdFemPostClipFilter::isActive()
// only allow one object
if (getSelection().getSelection().size() > 1)
return false;
// only activate if a result is either a post pipeline, scalar, cut or warp filter,
// itself or along line filter
// only activate if a result is either a post pipeline or a possible filter
if (getSelection().getObjectsOfType<Fem::FemPostPipeline>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostDataAlongLineFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostContoursFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
else
return false;
}
@@ -1765,20 +1767,21 @@ bool CmdFemPostCutFilter::isActive()
// only allow one object
if (getSelection().getSelection().size() > 1)
return false;
// only activate if a result is either a post pipeline, scalar, clip or warp filter,
// itself, or along line filter
// only activate if a result is either a post pipeline or a possible filter
if (getSelection().getObjectsOfType<Fem::FemPostPipeline>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
else if (getSelection().getObjectsOfType<Fem::FemPostContoursFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostDataAlongLineFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
else
return false;
}
@@ -1809,14 +1812,16 @@ bool CmdFemPostDataAlongLineFilter::isActive()
// only allow one object
if (getSelection().getSelection().size() > 1)
return false;
// only activate if a result is either a post pipeline, scalar, cut, clip or warp filter
// only activate if a result is either a post pipeline or a possible filter
if (getSelection().getObjectsOfType<Fem::FemPostPipeline>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostContoursFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
@@ -1852,20 +1857,19 @@ bool CmdFemPostDataAtPointFilter::isActive()
// only allow one object
if (getSelection().getSelection().size() > 1)
return false;
// only activate if a result is either a post pipeline, scalar, cut, clip,
// warp or along line filter
// only activate if a result is either a post pipeline or a possible filter
if (getSelection().getObjectsOfType<Fem::FemPostPipeline>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostDataAlongLineFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
else
return false;
}
@@ -1970,17 +1974,19 @@ bool CmdFemPostScalarClipFilter::isActive()
// only allow one object
if (getSelection().getSelection().size() > 1)
return false;
// only activate if a result is either a post pipeline, clip, cut, warp or along line filter
// only activate if a result is either a post pipeline or a possible other filter
if (getSelection().getObjectsOfType<Fem::FemPostPipeline>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostContoursFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostDataAlongLineFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
else
return false;
}
@@ -2011,17 +2017,63 @@ bool CmdFemPostWarpVectorFilter::isActive()
// only allow one object
if (getSelection().getSelection().size() > 1)
return false;
// only activate if a result is either a post pipeline, scalar, clip, cut or along line filter
// only activate if a result is either a post pipeline or a possible other filter
if (getSelection().getObjectsOfType<Fem::FemPostPipeline>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostContoursFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostDataAlongLineFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else
return false;
}
//================================================================================================
DEF_STD_CMD_A(CmdFemPostContoursFilter)
CmdFemPostContoursFilter::CmdFemPostContoursFilter()
: Command("FEM_PostFilterContours")
{
sAppModule = "Fem";
sGroup = QT_TR_NOOP("Fem");
sMenuText = QT_TR_NOOP("Contours filter");
sToolTipText =
QT_TR_NOOP("Define/create a contours filter which displays iso contours");
sWhatsThis = "FEM_PostFilterContours";
sStatusTip = sToolTipText;
sPixmap = "FEM_PostFilterContours";
}
void CmdFemPostContoursFilter::activated(int)
{
setupFilter(this, "Contours");
}
bool CmdFemPostContoursFilter::isActive()
{
// only allow one object
if (getSelection().getSelection().size() > 1)
return false;
// only activate if a result is either a post pipeline or a possible other filter
if (getSelection().getObjectsOfType<Fem::FemPostPipeline>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostCutFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostDataAlongLineFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostScalarClipFilter>().size() == 1)
return true;
else if (getSelection().getObjectsOfType<Fem::FemPostWarpVectorFilter>().size() == 1)
return true;
else
return false;
}
@@ -2370,15 +2422,16 @@ void CreateFemCommands()
// vtk post processing
#ifdef FC_USE_VTK
rcCmdMgr.addCommand(new CmdFemPostApllyChanges);
rcCmdMgr.addCommand(new CmdFemPostClipFilter);
rcCmdMgr.addCommand(new CmdFemPostContoursFilter);
rcCmdMgr.addCommand(new CmdFemPostCutFilter);
rcCmdMgr.addCommand(new CmdFemPostDataAlongLineFilter);
rcCmdMgr.addCommand(new CmdFemPostDataAtPointFilter);
rcCmdMgr.addCommand(new CmdFemPostLinearizedStressesFilter);
rcCmdMgr.addCommand(new CmdFemPostFunctions);
rcCmdMgr.addCommand(new CmdFemPostPipelineFromResult);
rcCmdMgr.addCommand(new CmdFemPostScalarClipFilter);
rcCmdMgr.addCommand(new CmdFemPostWarpVectorFilter);
rcCmdMgr.addCommand(new CmdFemPostFunctions);
rcCmdMgr.addCommand(new CmdFemPostApllyChanges);
rcCmdMgr.addCommand(new CmdFemPostPipelineFromResult);
#endif
}

View File

@@ -6,15 +6,15 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>203</height>
<width>350</width>
<height>259</height>
</rect>
</property>
<property name="windowTitle">
<string>Elmer</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QGroupBox" name="gb_gmsh_param">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@@ -39,6 +39,46 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::PrefFileChooser" name="fc_elmer_binary_path">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Leave blank to use default Elmer elmer binary file&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note:&lt;/span&gt; To use multithreading you must specify here&lt;br&gt; the executable variant with the suffix &amp;quot;_mpi&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>elmerBinaryPath</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Fem/Elmer</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::PrefCheckBox" name="cb_grid_binary_std">
<property name="text">
@@ -55,22 +95,6 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="l_grid_binary_path">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>ElmerGrid binary path</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::PrefFileChooser" name="fc_grid_binary_path">
<property name="enabled">
@@ -134,6 +158,22 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="l_grid_binary_path">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>ElmerGrid binary path</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="l_elmer_binary_path">
<property name="enabled">
@@ -150,54 +190,23 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::PrefFileChooser" name="fc_elmer_binary_path">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Leave blank to use default Elmer elmer binary file&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note:&lt;/span&gt; To use multithreading you must specify here&lt;br&gt; the executable variant with the suffix &amp;quot;_mpi&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>elmerBinaryPath</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Fem/Elmer</cstring>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="l_elmer_binary_std_2">
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="gb_elmer_options">
<property name="title">
<string>Options</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="l_elmer_multithreading">
<property name="text">
<string>Multithreading:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_cores">
@@ -246,10 +255,37 @@
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="l_elmer_multiCPU">
<property name="text">
<string>Multi-CPU core support:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::PrefCheckBox" name="cb_elmer_filtering">
<property name="toolTip">
<string>The mesh volume regions processed by each CPU core
will be merged to make the volume boundaries invisible.</string>
</property>
<property name="text">
<string>Filter results</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>FilterMultiCPUResults</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Fem/Elmer</cstring>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>

View File

@@ -70,6 +70,7 @@ void DlgSettingsFemElmerImp::saveSettings()
ui->fc_grid_binary_path->onSave();
ui->sb_elmer_num_cores->onSave();
ui->cb_elmer_filtering->onSave();
}
void DlgSettingsFemElmerImp::loadSettings()
@@ -81,6 +82,7 @@ void DlgSettingsFemElmerImp::loadSettings()
ui->fc_grid_binary_path->onRestore();
ui->sb_elmer_num_cores->onRestore();
ui->cb_elmer_filtering->onRestore();
}
/**

View File

@@ -171,10 +171,11 @@
#include <SMDSAbs_ElementType.hxx>
// VTK
#include <vtkPointData.h>
#include <vtkCellArray.h>
#include <vtkCellData.h>
#include <vtkDoubleArray.h>
#include <vtkLookupTable.h>
#include <vtkPointData.h>
#endif //_PreComp_

View File

@@ -70,6 +70,7 @@
<!-- gui command icons: post processing -->
<file>icons/FEM_PostFilterClipRegion.svg</file>
<file>icons/FEM_PostFilterClipScalar.svg</file>
<file>icons/FEM_PostFilterContours.svg</file>
<file>icons/FEM_PostFilterCutFunction.svg</file>
<file>icons/FEM_PostFilterDataAlongLine.svg</file>
<file>icons/FEM_PostFilterDataAtPoint.svg</file>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg2" version="1.1" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[Alexander Gryson]</dc:title>
</cc:Agent>
</dc:creator>
<dc:date>2017-03-11</dc:date>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6">
<linearGradient id="linearGradient4937">
<stop style="stop-color:#4e9a06;stop-opacity:1" offset="0" id="stop4933" />
<stop style="stop-color:#73d216;stop-opacity:1" offset="1" id="stop4935" />
</linearGradient>
<linearGradient id="linearGradient4929">
<stop style="stop-color:#4e9a06;stop-opacity:1" offset="0" id="stop4925" />
<stop style="stop-color:#73d216;stop-opacity:1" offset="1" id="stop4927" />
</linearGradient>
<linearGradient id="linearGradient4921">
<stop style="stop-color:#4e9a06;stop-opacity:1" offset="0" id="stop4917" />
<stop style="stop-color:#73d216;stop-opacity:1" offset="1" id="stop4919" />
</linearGradient>
<linearGradient id="linearGradient3826">
<stop style="stop-color:#4e9a06;stop-opacity:1" offset="0" id="stop3828" />
<stop style="stop-color:#73d216;stop-opacity:1" offset="1" id="stop3830" />
</linearGradient>
<linearGradient id="linearGradient3816">
<stop style="stop-color:#73d216;stop-opacity:1" offset="0" id="stop3818" />
<stop style="stop-color:#8ae234;stop-opacity:1" offset="1" id="stop3820" />
</linearGradient>
<linearGradient xlink:href="#linearGradient3816" id="linearGradient3841" gradientUnits="userSpaceOnUse" x1="95" y1="47" x2="91" y2="15" />
<linearGradient xlink:href="#linearGradient3826" id="linearGradient3843" gradientUnits="userSpaceOnUse" x1="120" y1="46" x2="109" y2="15" />
<linearGradient xlink:href="#linearGradient4929" id="linearGradient4931" x1="117.5" y1="50.5" x2="117.5" y2="50.5" gradientUnits="userSpaceOnUse" />
<linearGradient xlink:href="#linearGradient4937" id="linearGradient4939" x1="100.60143" y1="9.4661894" x2="117.5" y2="50.5" gradientUnits="userSpaceOnUse" />
<linearGradient xlink:href="#linearGradient4921" id="linearGradient4949" gradientUnits="userSpaceOnUse" x1="100.60143" y1="9.4661894" x2="117.5" y2="50.5" />
</defs>
<g id="g3834" transform="matrix(1.5077443,0,0,1.2007124,-134.53727,-4.0213696)" style="stroke-width:0.743219">
<path id="path3812" d="M 117,4 L 131,45 L 104,56 L 90,13 Z" style="fill:none;fill-opacity:1;stroke:#172a04;stroke-width:1.48643;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path style="fill:none;stroke:url(#linearGradient4939);stroke-width:1.11483;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M 115.09277,8.1017279 C 115.09277,8.1017279 111.03315,16.682926 114.5164,25.024389 C 117.99965,33.365852 121.44094,39.334366 123.38943,40.657696 L 128,43.789004" id="path1048" />
<path style="fill:none;stroke:url(#linearGradient4931);stroke-width:1.11483;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M 107.86441,9.8386331 C 107.86441,9.8386331 102.54878,16.007596 105.02439,25.503784 C 107.5,34.999971 110.94492,40.843691 116.171,43.789004 L 121.3238,46.693017" id="path1050" />
<path style="fill:none;stroke:url(#linearGradient4949);stroke-width:1.11483;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M 99.999951,11.793103 C 99.999951,11.793103 95.928545,17.853922 99.053812,26.962416 C 102.17908,36.07091 106.80942,47.780649 110.5,50.196728" id="path1052" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@
class QComboBox;
class Ui_TaskPostDisplay;
class Ui_TaskPostClip;
class Ui_TaskPostContours;
class Ui_TaskPostDataAlongLine;
class Ui_TaskPostDataAtPoint;
class Ui_TaskPostScalarClip;
@@ -50,8 +51,11 @@ class SoEventCallback;
class SoMarkerSet;
namespace FemGui {
namespace FemGui
{
// ***************************************************************************
// point marker
class ViewProviderPointMarker;
class PointMarker : public QObject
{
@@ -77,7 +81,6 @@ private:
std::string ObjectInvisible();
};
class FemGuiExport ViewProviderPointMarker : public Gui::ViewProviderDocumentObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderPointMarker);
@@ -92,6 +95,8 @@ protected:
};
// ***************************************************************************
// data marker
class ViewProviderDataMarker;
class DataMarker : public QObject
{
@@ -132,8 +137,11 @@ protected:
friend class DataMarker;
};
class TaskPostBox : public Gui::TaskView::TaskBox {
// ***************************************************************************
// main task dialog
class TaskPostBox : public Gui::TaskView::TaskBox
{
Q_OBJECT
public:
@@ -175,7 +183,8 @@ private:
};
/// simulation dialog for the TaskView
// ***************************************************************************
// simulation dialog for the TaskView
class TaskDlgPost : public Gui::TaskView::TaskDialog
{
Q_OBJECT
@@ -216,6 +225,8 @@ protected:
};
// ***************************************************************************
// box to set the coloring
class TaskPostDisplay : public TaskPostBox
{
Q_OBJECT
@@ -240,8 +251,10 @@ private:
};
class TaskPostFunction : public TaskPostBox {
// ***************************************************************************
// functions
class TaskPostFunction : public TaskPostBox
{
Q_OBJECT
public:
@@ -252,8 +265,76 @@ public:
};
class TaskPostClip : public TaskPostBox {
// ***************************************************************************
// in the following, the different filters sorted alphabetically
// ***************************************************************************
// ***************************************************************************
// data along line filter
class TaskPostDataAlongLine: public TaskPostBox
{
Q_OBJECT
public:
explicit TaskPostDataAlongLine(Gui::ViewProviderDocumentObject* view,
QWidget* parent = nullptr);
~TaskPostDataAlongLine() override;
void applyPythonCode() override;
static void pointCallback(void* ud, SoEventCallback* n);
private Q_SLOTS:
void on_SelectPoints_clicked();
void on_CreatePlot_clicked();
void on_Representation_activated(int i);
void on_Field_activated(int i);
void on_VectorMode_activated(int i);
void point2Changed(double);
void point1Changed(double);
void resolutionChanged(int val);
void onChange(double x1, double y1, double z1, double x2, double y2, double z2);
private:
std::string Plot();
std::string ObjectVisible();
QWidget* proxy;
std::unique_ptr<Ui_TaskPostDataAlongLine> ui;
};
// ***************************************************************************
// data at point filter
class TaskPostDataAtPoint: public TaskPostBox
{
Q_OBJECT
public:
explicit TaskPostDataAtPoint(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr);
~TaskPostDataAtPoint() override;
void applyPythonCode() override;
static void pointCallback(void* ud, SoEventCallback* n);
private Q_SLOTS:
void on_SelectPoint_clicked();
void on_Field_activated(int i);
void centerChanged(double);
void onChange(double x, double y, double z);
private:
std::string toString(double val) const;
void showValue(double value, const char* unit);
std::string ObjectVisible();
QWidget* proxy;
std::unique_ptr<Ui_TaskPostDataAtPoint> ui;
};
// ***************************************************************************
// clip filter
class TaskPostClip : public TaskPostBox
{
Q_OBJECT
public:
@@ -282,69 +363,66 @@ private:
};
class TaskPostDataAlongLine: public TaskPostBox {
// ***************************************************************************
// contours filter
class TaskPostContours: public TaskPostBox
{
Q_OBJECT
public:
explicit TaskPostDataAlongLine(Gui::ViewProviderDocumentObject* view,
QWidget* parent = nullptr);
~TaskPostDataAlongLine() override;
explicit TaskPostContours(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr);
~TaskPostContours() override;
void applyPythonCode() override;
static void pointCallback(void * ud, SoEventCallback * n);
private Q_SLOTS:
void on_SelectPoints_clicked();
void on_CreatePlot_clicked();
void on_Representation_activated(int i);
void on_Field_activated(int i);
void on_VectorMode_activated(int i);
void point2Changed(double);
void point1Changed(double);
void resolutionChanged(int val);
void onChange(double x1, double y1, double z1, double x2, double y2, double z2);
void onFieldsChanged(int idx);
void onVectorModeChanged(int idx);
void onNumberOfContoursChanged(int number);
void onNoColorChanged(bool state);
private:
std::string Plot();
std::string ObjectVisible();
QWidget* proxy;
std::unique_ptr<Ui_TaskPostDataAlongLine> ui;
std::unique_ptr<Ui_TaskPostContours> ui;
bool blockVectorUpdate = false;
void updateFields();
};
class TaskPostDataAtPoint: public TaskPostBox {
// ***************************************************************************
// cut filter
class TaskPostCut: public TaskPostBox
{
Q_OBJECT
public:
explicit TaskPostDataAtPoint(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr);
~TaskPostDataAtPoint() override;
TaskPostCut(Gui::ViewProviderDocumentObject* view, App::PropertyLink* function,
QWidget* parent = nullptr);
~TaskPostCut() override;
void applyPythonCode() override;
static void pointCallback(void * ud, SoEventCallback * n);
private Q_SLOTS:
void on_SelectPoint_clicked();
void on_Field_activated(int i);
void centerChanged(double);
void onChange(double x, double y, double z);
void on_CreateButton_triggered(QAction*);
void on_FunctionBox_currentIndexChanged(int idx);
Q_SIGNALS:
void emitAddedFunction();
private:
std::string toString(double val) const;
void showValue(double value, const char* unit);
void collectImplicitFunctions();
private:
std::string ObjectVisible();
//App::PropertyLink* m_functionProperty;
QWidget* proxy;
std::unique_ptr<Ui_TaskPostDataAtPoint> ui;
std::unique_ptr<Ui_TaskPostCut> ui;
FunctionWidget* fwidget;
};
class TaskPostScalarClip : public TaskPostBox {
// ***************************************************************************
// scalar clip filter
class TaskPostScalarClip : public TaskPostBox
{
Q_OBJECT
public:
@@ -365,8 +443,10 @@ private:
};
class TaskPostWarpVector : public TaskPostBox {
// ***************************************************************************
// warp vector filter
class TaskPostWarpVector : public TaskPostBox
{
Q_OBJECT
public:
@@ -387,34 +467,6 @@ private:
std::unique_ptr<Ui_TaskPostWarpVector> ui;
};
class TaskPostCut : public TaskPostBox {
Q_OBJECT
public:
TaskPostCut(Gui::ViewProviderDocumentObject* view, App::PropertyLink* function,
QWidget* parent = nullptr);
~TaskPostCut() override;
void applyPythonCode() override;
private Q_SLOTS:
void on_CreateButton_triggered(QAction*);
void on_FunctionBox_currentIndexChanged(int idx);
Q_SIGNALS:
void emitAddedFunction();
private:
void collectImplicitFunctions();
//App::PropertyLink* m_functionProperty;
QWidget* proxy;
std::unique_ptr<Ui_TaskPostCut> ui;
FunctionWidget* fwidget;
};
} //namespace FemGui
#endif // GUI_TASKVIEW_TaskPostDisplay_H

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TaskPostContours</class>
<widget class="QWidget" name="TaskPostContours">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>250</width>
<height>115</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="fieldsCB"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="vectorL">
<property name="text">
<string>Vector:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="vectorsCB"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="fieldL">
<property name="text">
<string>Field:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="numberContoursL">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Number of contours:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="numberContoursSB">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="keyboardTracking">
<bool>false</bool>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="noColorCB">
<property name="toolTip">
<string>Contour lines will not be colored</string>
</property>
<property name="text">
<string>No color</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -30,6 +30,63 @@
using namespace FemGui;
// ***************************************************************************
// in the following, the different filters sorted alphabetically
// ***************************************************************************
// ***************************************************************************
// data along line filter
PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAlongLine, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostDataAlongLine::ViewProviderFemPostDataAlongLine()
{
sPixmap = "FEM_PostFilterDataAlongLine";
}
ViewProviderFemPostDataAlongLine::~ViewProviderFemPostDataAlongLine()
{}
void ViewProviderFemPostDataAlongLine::setupTaskDialog(TaskDlgPost* dlg)
{
//add the function box
dlg->appendBox(new TaskPostDataAlongLine(dlg->getView()));
}
// ***************************************************************************
// data at point filter
PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAtPoint, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostDataAtPoint::ViewProviderFemPostDataAtPoint()
{
sPixmap = "FEM_PostFilterDataAtPoint";
}
void ViewProviderFemPostDataAtPoint::show()
{
Gui::ViewProviderDocumentObject::show();
}
void ViewProviderFemPostDataAtPoint::onSelectionChanged(const Gui::SelectionChanges&)
{
// do not do anything here
// For DataAtPoint the color bar must not be refreshed when it is selected
// because a single point does not make sense with a color range.
}
ViewProviderFemPostDataAtPoint::~ViewProviderFemPostDataAtPoint()
{}
void ViewProviderFemPostDataAtPoint::setupTaskDialog(TaskDlgPost* dlg)
{
//add the function box
dlg->appendBox(new TaskPostDataAtPoint(dlg->getView()));
}
// ***************************************************************************
// clip filter
PROPERTY_SOURCE(FemGui::ViewProviderFemPostClip, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostClip::ViewProviderFemPostClip() {
@@ -37,9 +94,8 @@ ViewProviderFemPostClip::ViewProviderFemPostClip() {
sPixmap = "FEM_PostFilterClipRegion";
}
ViewProviderFemPostClip::~ViewProviderFemPostClip() {
}
ViewProviderFemPostClip::~ViewProviderFemPostClip()
{}
void ViewProviderFemPostClip::setupTaskDialog(TaskDlgPost* dlg) {
@@ -51,59 +107,56 @@ void ViewProviderFemPostClip::setupTaskDialog(TaskDlgPost* dlg) {
FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg);
}
PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAlongLine, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostDataAlongLine::ViewProviderFemPostDataAlongLine() {
// ***************************************************************************
// contours filter
PROPERTY_SOURCE(FemGui::ViewProviderFemPostContours, FemGui::ViewProviderFemPostObject)
sPixmap = "FEM_PostFilterDataAlongLine";
}
ViewProviderFemPostDataAlongLine::~ViewProviderFemPostDataAlongLine() {
}
void ViewProviderFemPostDataAlongLine::setupTaskDialog(TaskDlgPost* dlg) {
//add the function box
dlg->appendBox(new TaskPostDataAlongLine(dlg->getView()));
}
PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAtPoint, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostDataAtPoint::ViewProviderFemPostDataAtPoint() {
sPixmap = "FEM_PostFilterDataAtPoint";
}
void ViewProviderFemPostDataAtPoint::show()
ViewProviderFemPostContours::ViewProviderFemPostContours()
{
Gui::ViewProviderDocumentObject::show();
sPixmap = "FEM_PostFilterContours";
}
void ViewProviderFemPostDataAtPoint::onSelectionChanged(const Gui::SelectionChanges &)
ViewProviderFemPostContours::~ViewProviderFemPostContours()
{}
void ViewProviderFemPostContours::setupTaskDialog(TaskDlgPost* dlg)
{
// do not do anything here
// For DataAtPoint the color bar must not be refreshed when it is selected
// because a single point does not make sense with a color range.
// the filter-specific task panel
dlg->appendBox(new TaskPostContours(dlg->getView()));
}
ViewProviderFemPostDataAtPoint::~ViewProviderFemPostDataAtPoint() {
// ***************************************************************************
// cut filter
PROPERTY_SOURCE(FemGui::ViewProviderFemPostCut, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostCut::ViewProviderFemPostCut()
{
sPixmap = "FEM_PostFilterCutFunction";
}
void ViewProviderFemPostDataAtPoint::setupTaskDialog(TaskDlgPost* dlg) {
ViewProviderFemPostCut::~ViewProviderFemPostCut()
{}
void ViewProviderFemPostCut::setupTaskDialog(TaskDlgPost* dlg)
{
//add the function box
dlg->appendBox(new TaskPostDataAtPoint(dlg->getView()));
dlg->appendBox(new TaskPostCut(
dlg->getView(),
&static_cast<Fem::FemPostCutFilter*>(dlg->getView()->getObject())->Function));
//add the display options
FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg);
}
// ***************************************************************************
// scalar clip filter
PROPERTY_SOURCE(FemGui::ViewProviderFemPostScalarClip, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostScalarClip::ViewProviderFemPostScalarClip() {
ViewProviderFemPostScalarClip::ViewProviderFemPostScalarClip()
{
sPixmap = "FEM_PostFilterClipScalar";
}
@@ -111,8 +164,8 @@ ViewProviderFemPostScalarClip::~ViewProviderFemPostScalarClip() {
}
void ViewProviderFemPostScalarClip::setupTaskDialog(TaskDlgPost* dlg) {
void ViewProviderFemPostScalarClip::setupTaskDialog(TaskDlgPost* dlg)
{
//add the function box
dlg->appendBox(new TaskPostScalarClip(dlg->getView()));
@@ -120,44 +173,24 @@ void ViewProviderFemPostScalarClip::setupTaskDialog(TaskDlgPost* dlg) {
FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg);
}
// ***************************************************************************
// warp vector filter
PROPERTY_SOURCE(FemGui::ViewProviderFemPostWarpVector, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostWarpVector::ViewProviderFemPostWarpVector() {
ViewProviderFemPostWarpVector::ViewProviderFemPostWarpVector()
{
sPixmap = "FEM_PostFilterWarp";
}
ViewProviderFemPostWarpVector::~ViewProviderFemPostWarpVector() {
}
void ViewProviderFemPostWarpVector::setupTaskDialog(TaskDlgPost* dlg) {
ViewProviderFemPostWarpVector::~ViewProviderFemPostWarpVector()
{}
void ViewProviderFemPostWarpVector::setupTaskDialog(TaskDlgPost* dlg)
{
//add the function box
dlg->appendBox(new TaskPostWarpVector(dlg->getView()));
//add the display options
FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg);
}
PROPERTY_SOURCE(FemGui::ViewProviderFemPostCut, FemGui::ViewProviderFemPostObject)
ViewProviderFemPostCut::ViewProviderFemPostCut() {
sPixmap = "FEM_PostFilterCutFunction";
}
ViewProviderFemPostCut::~ViewProviderFemPostCut() {
}
void ViewProviderFemPostCut::setupTaskDialog(TaskDlgPost* dlg) {
//add the function box
dlg->appendBox(new TaskPostCut(dlg->getView(),
&static_cast<Fem::FemPostCutFilter*>(dlg->getView()->getObject())->Function));
//add the display options
FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg);
}

View File

@@ -20,30 +20,24 @@
* *
***************************************************************************/
#ifndef FEM_VIEWPROVIDERFEMPOSTFILTER_H
#define FEM_VIEWPROVIDERFEMPOSTFILTER_H
#include "ViewProviderFemPostObject.h"
namespace FemGui
{
class FemGuiExport ViewProviderFemPostClip : public ViewProviderFemPostObject {
// ***************************************************************************
// in the following, the different filters sorted alphabetically
// ***************************************************************************
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostClip);
public:
/// constructor.
ViewProviderFemPostClip();
~ViewProviderFemPostClip() override;
protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
class FemGuiExport ViewProviderFemPostDataAlongLine : public ViewProviderFemPostObject {
// ***************************************************************************
// data along line filter
class FemGuiExport ViewProviderFemPostDataAlongLine : public ViewProviderFemPostObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostDataAlongLine);
public:
@@ -55,8 +49,11 @@ protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
class FemGuiExport ViewProviderFemPostDataAtPoint: public ViewProviderFemPostObject {
// ***************************************************************************
// data at point filter
class FemGuiExport ViewProviderFemPostDataAtPoint: public ViewProviderFemPostObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostDataAtPoint);
public:
@@ -69,34 +66,44 @@ public:
protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
class FemGuiExport ViewProviderFemPostScalarClip : public ViewProviderFemPostObject {
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostScalarClip);
// ***************************************************************************
// clip filter
class FemGuiExport ViewProviderFemPostClip: public ViewProviderFemPostObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostClip);
public:
/// constructor.
ViewProviderFemPostScalarClip();
~ViewProviderFemPostScalarClip() override;
ViewProviderFemPostClip();
~ViewProviderFemPostClip() override;
protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
class FemGuiExport ViewProviderFemPostWarpVector : public ViewProviderFemPostObject {
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostWarpVector);
// ***************************************************************************
// contours filter
class FemGuiExport ViewProviderFemPostContours: public ViewProviderFemPostObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostContours);
public:
/// constructor.
ViewProviderFemPostWarpVector();
~ViewProviderFemPostWarpVector() override;
ViewProviderFemPostContours();
~ViewProviderFemPostContours() override;
protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
class FemGuiExport ViewProviderFemPostCut : public ViewProviderFemPostObject {
// ***************************************************************************
// cut filter
class FemGuiExport ViewProviderFemPostCut: public ViewProviderFemPostObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostCut);
public:
@@ -108,6 +115,38 @@ protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
// ***************************************************************************
// scalar clip filter
class FemGuiExport ViewProviderFemPostScalarClip: public ViewProviderFemPostObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostScalarClip);
public:
/// constructor.
ViewProviderFemPostScalarClip();
~ViewProviderFemPostScalarClip() override;
protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
// ***************************************************************************
// warp vector filter
class FemGuiExport ViewProviderFemPostWarpVector : public ViewProviderFemPostObject
{
PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostWarpVector);
public:
/// constructor.
ViewProviderFemPostWarpVector();
~ViewProviderFemPostWarpVector() override;
protected:
void setupTaskDialog(TaskDlgPost* dlg) override;
};
} //namespace FemGui

View File

@@ -38,6 +38,7 @@
# include <vtkCellArray.h>
# include <vtkCellData.h>
# include <vtkDoubleArray.h>
# include <vtkImplicitFunction.h>
# include <vtkPointData.h>
# include <QApplication>
@@ -55,6 +56,8 @@
#include <Gui/SelectionObject.h>
#include <Gui/SoFCColorBar.h>
#include <Gui/TaskView/TaskDialog.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Mod/Fem/App/FemPostFilter.h>
#include "ViewProviderFemPostObject.h"
@@ -265,8 +268,8 @@ void ViewProviderFemPostObject::attach(App::DocumentObject* pcObj)
(void)setupPipeline();
}
SoSeparator* ViewProviderFemPostObject::getFrontRoot() const {
SoSeparator* ViewProviderFemPostObject::getFrontRoot() const
{
return m_colorRoot;
}
@@ -359,8 +362,9 @@ void ViewProviderFemPostObject::updateProperties() {
colorArrays.emplace_back("Not a vector");
else {
int array = Field.getValue() - 1; //0 is none
vtkPolyData* pd = m_currentAlgorithm->GetOutput();
vtkDataArray* data = pd->GetPointData()->GetArray(array);
vtkDataArray* data = point->GetArray(array);
if (!data)
return;
if (data->GetNumberOfComponents() == 1)
colorArrays.emplace_back("Not a vector");
@@ -491,7 +495,6 @@ void ViewProviderFemPostObject::update3D() {
void ViewProviderFemPostObject::WritePointData(vtkPoints* points, vtkDataArray* normals,
vtkDataArray* tcoords)
{
Q_UNUSED(tcoords);
if (!points)
@@ -539,14 +542,15 @@ void ViewProviderFemPostObject::updateMaterial()
WriteColorData(true);
}
void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) {
void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange)
{
if (!setupPipeline())
return;
if (Field.getEnumVector().empty() || Field.getValue() == 0) {
m_material->diffuseColor.setValue(SbColor(0.8, 0.8, 0.8));
m_material->transparency.setValue(0.);
float trans = float(Transparency.getValue()) / 100.0;
m_material->transparency.setValue(trans);
m_materialBinding->value = SoMaterialBinding::OVERALL;
m_materialBinding->touch();
// since there is no field, set the range to the default
@@ -558,6 +562,8 @@ void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) {
int array = Field.getValue() - 1; // 0 is none
vtkPolyData* pd = m_currentAlgorithm->GetOutput();
vtkDataArray* data = pd->GetPointData()->GetArray(array);
if (!data)
return;
int component = VectorMode.getValue() - 1; // 0 is either "Not a vector" or magnitude,
// for -1 is correct for magnitude.
@@ -606,8 +612,8 @@ void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) {
m_triangleStrips->touch();
}
void ViewProviderFemPostObject::WriteTransparency() {
void ViewProviderFemPostObject::WriteTransparency()
{
float trans = float(Transparency.getValue()) / 100.0;
m_material->transparency.setValue(trans);
@@ -616,17 +622,73 @@ void ViewProviderFemPostObject::WriteTransparency() {
m_triangleStrips->touch();
}
void ViewProviderFemPostObject::updateData(const App::Property* p) {
void ViewProviderFemPostObject::updateData(const App::Property* p)
{
if (strcmp(p->getName(), "Data") == 0) {
updateVtk();
}
}
void ViewProviderFemPostObject::filterArtifacts(vtkDataObject* data)
{
// The problem is that in the surface view the boundary regions of the volumes
// calculated by the different CPU cores is always visible, independent of the
// transparency setting. Elmer is not to blame because this is a property of the
// partial VTK file reader. So this can happen with various inputs
// since FreeCAD can also be used to view VTK files without the need to perform
// an analysis. Therefore it is impossible to know in advance when a filter
// is necessary or not.
// Only for pure CCX analyses we know that no filtering is necessary. However,
// the effort to catch this case is not worth it since the filtering is
// only as time-consuming as enabling the surface filter. In fact, it is like
// performing the surface filter twice.
// We need to set the filter clipping plane below the z-minimum of the data.
// We can either do this by checking the VTK data or by getting the info from
// the 3D view. We use here the latter because this is much faster.
// since we will set the filter according to the visible bounding box
// assure the object is visible
bool visibility = this->Visibility.getValue();
this->Visibility.setValue(false);
Gui::Document* doc = this->getDocument();
Gui::View3DInventor* view =
qobject_cast<Gui::View3DInventor*>(doc->getViewOfViewProvider(this));
if (view) {
Gui::View3DInventorViewer* viewer = view->getViewer();
SbBox3f boundingBox;
boundingBox = viewer->getBoundingBox();
if (boundingBox.hasVolume()) {
// setup
vtkSmartPointer<vtkImplicitFunction> m_implicit;
auto m_plane = vtkSmartPointer<vtkPlane>::New();
m_implicit = m_plane;
m_plane->SetNormal(0., 0., 1.);
auto extractor = vtkSmartPointer<vtkTableBasedClipDataSet>::New();
float dx, dy, dz;
boundingBox.getSize(dx, dy, dz);
// set plane slightly below the minimum to assure there are
// no boundary cells (touching the function
m_plane->SetOrigin(0., 0., -1 * dz - 1);
extractor->SetClipFunction(m_implicit);
extractor->SetInputData(data);
extractor->Update();
m_surface->SetInputData(extractor->GetOutputDataObject(0));
}
else {
// for e.g. DataAtPoint filter
m_surface->SetInputData(data);
}
}
// restore initial vsibility
this->Visibility.setValue(visibility);
}
bool ViewProviderFemPostObject::setupPipeline()
{
vtkDataObject* data = static_cast<Fem::FemPostObject*>(getObject())->Data.getValue();
if (!data)
return false;
@@ -634,6 +696,8 @@ bool ViewProviderFemPostObject::setupPipeline()
// add a field with an absolute value
vtkSmartPointer<vtkDataObject> SPdata = data;
vtkDataSet* dset = vtkDataSet::SafeDownCast(SPdata);
if (!dset)
return false;
std::string FieldName;
auto numFields = dset->GetPointData()->GetNumberOfArrays();
for (int i = 0; i < numFields; ++i) {
@@ -642,15 +706,23 @@ bool ViewProviderFemPostObject::setupPipeline()
}
m_outline->SetInputData(data);
m_surface->SetInputData(data);
m_wireframe->SetInputData(data);
m_points->SetInputData(data);
// filtering artifacts is only necessary for the surface filter
auto hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Fem/Elmer");
bool FilterMultiCPUResults = hGrp->GetBool("FilterMultiCPUResults", 1);
if (FilterMultiCPUResults)
filterArtifacts(data);
else
m_surface->SetInputData(data);
return true;
}
void ViewProviderFemPostObject::onChanged(const App::Property* prop) {
void ViewProviderFemPostObject::onChanged(const App::Property* prop)
{
if (m_blockPropertyChanges)
return;
@@ -679,7 +751,8 @@ void ViewProviderFemPostObject::onChanged(const App::Property* prop) {
ViewProviderDocumentObject::onChanged(prop);
}
bool ViewProviderFemPostObject::doubleClicked() {
bool ViewProviderFemPostObject::doubleClicked()
{
// work around for a problem in VTK implementation:
// https://forum.freecadweb.org/viewtopic.php?t=10587&start=130#p125688
// check if backlight is enabled
@@ -695,8 +768,8 @@ bool ViewProviderFemPostObject::doubleClicked() {
return true;
}
bool ViewProviderFemPostObject::setEdit(int ModNum) {
bool ViewProviderFemPostObject::setEdit(int ModNum)
{
if (ModNum == ViewProvider::Default || ModNum == 1) {
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
@@ -733,8 +806,8 @@ bool ViewProviderFemPostObject::setEdit(int ModNum) {
}
}
void ViewProviderFemPostObject::setupTaskDialog(TaskDlgPost* dlg) {
void ViewProviderFemPostObject::setupTaskDialog(TaskDlgPost* dlg)
{
dlg->appendBox(new TaskPostDisplay(this));
}
@@ -752,7 +825,8 @@ void ViewProviderFemPostObject::unsetEdit(int ModNum) {
}
}
void ViewProviderFemPostObject::hide() {
void ViewProviderFemPostObject::hide()
{
Gui::ViewProviderDocumentObject::hide();
m_colorStyle->style = SoDrawStyle::INVISIBLE;
// The object is now hidden but the color bar is wrong
@@ -787,7 +861,8 @@ void ViewProviderFemPostObject::hide() {
}
}
void ViewProviderFemPostObject::show() {
void ViewProviderFemPostObject::show()
{
Gui::ViewProviderDocumentObject::show();
m_colorStyle->style = SoDrawStyle::FILLED;
// we must update the color bar except for data point filters
@@ -795,7 +870,8 @@ void ViewProviderFemPostObject::show() {
WriteColorData(true);
}
void ViewProviderFemPostObject::OnChange(Base::Subject< int >& /*rCaller*/, int /*rcReason*/) {
void ViewProviderFemPostObject::OnChange(Base::Subject< int >& /*rCaller*/, int /*rcReason*/)
{
bool ResetColorBarRange = false;
WriteColorData(ResetColorBarRange);
}

View File

@@ -28,6 +28,7 @@
#include <Mod/Fem/FemGlobal.h>
#include <vtkAppendPolyData.h>
#include <vtkDataObject.h>
#include <vtkExtractEdges.h>
#include <vtkGeometryFilter.h>
#include <vtkOutlineCornerFilter.h>
@@ -150,6 +151,7 @@ protected:
vtkSmartPointer<vtkVertexGlyphFilter> m_points, m_pointsSurface;
private:
void filterArtifacts(vtkDataObject* data);
void updateProperties();
void update3D();
void WritePointData(vtkPoints *points, vtkDataArray *normals,

View File

@@ -155,10 +155,9 @@ void ViewProviderFemPostPipeline::transformField(char *FieldName, double FieldFa
Fem::FemPostPipeline *obj = static_cast<Fem::FemPostPipeline *>(getObject());
vtkSmartPointer<vtkDataObject> data = obj->Data.getValue();
if (!data || !data->IsA("vtkDataSet"))
return;
vtkDataSet *dset = vtkDataSet::SafeDownCast(data);
if (!dset)
return;
vtkDataArray *pdata = dset->GetPointData()->GetArray(FieldName);
if (!pdata)
return;

View File

@@ -203,6 +203,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
<< "FEM_PostFilterClipScalar"
<< "FEM_PostFilterCutFunction"
<< "FEM_PostFilterClipRegion"
<< "FEM_PostFilterContours"
<< "FEM_PostFilterDataAlongLine"
<< "FEM_PostFilterLinearizedStresses"
<< "FEM_PostFilterDataAtPoint"
@@ -371,6 +372,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const
<< "FEM_PostFilterClipScalar"
<< "FEM_PostFilterCutFunction"
<< "FEM_PostFilterClipRegion"
<< "FEM_PostFilterContours"
<< "FEM_PostFilterDataAlongLine"
<< "FEM_PostFilterLinearizedStresses"
<< "FEM_PostFilterDataAtPoint"

View File

@@ -720,6 +720,21 @@ def makePostVtkFilterWarp(
return obj
def makePostVtkFilterContours(
doc,
base_vtk_result,
name="VtkFilterContours"
):
"""makePostVtkFilterContours(document, base_vtk_result, [name]):
creates a FEM post processing contours filter object (vtk based)"""
obj = doc.addObject("Fem::FemPostContoursFilter", name)
tmp_filter_list = base_vtk_result.Filter
tmp_filter_list.append(obj)
base_vtk_result.Filter = tmp_filter_list
del tmp_filter_list
return obj
def makePostVtkResult(
doc,
base_result,

View File

@@ -394,11 +394,11 @@ class GmshTools():
self.group_elements[ge] = new_group_elements[ge]
else:
Console.PrintError(" A group with this name exists already.\n")
else:
Console.PrintMessage(" No Group meshing for analysis.\n")
#else:
# Console.PrintMessage(" No Group meshing for analysis.\n")
if self.group_elements:
Console.PrintMessage(" {}\n".format(self.group_elements))
#if self.group_elements:
# Console.PrintMessage(" {}\n".format(self.group_elements))
def get_gmsh_version(self):
self.get_gmsh_command()
@@ -447,7 +447,7 @@ class GmshTools():
# print(" No mesh regions.")
pass
else:
Console.PrintMessage(" Mesh regions, we need to get the elements.\n")
#Console.PrintMessage(" Mesh regions, we need to get the elements.\n")
# by the use of MeshRegion object and a BooleanSplitCompound
# there could be problems with node numbers see
# http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467
@@ -461,17 +461,7 @@ class GmshTools():
or femutils.is_of_type(part, "FeatureXOR")
)
):
error_message = (
" The mesh to shape is a boolean split tools Compound "
"and the mesh has mesh region list. "
"Gmsh could return unexpected meshes in such circumstances. "
"It is strongly recommended to extract the shape to mesh "
"from the Compound and use this one."
)
Console.PrintError(error_message + "\n")
# TODO: no gui popup because FreeCAD will be in a endless output loop
# as long as the pop up is on --> maybe find a better solution for
# either of both --> thus the pop up is in task panel
self.outputCompoundWarning
for mr_obj in self.mesh_obj.MeshRegionList:
# print(mr_obj.Name)
# print(mr_obj.CharacteristicLength)
@@ -540,8 +530,8 @@ class GmshTools():
ele_shape = geomtools.get_element(self.part_obj, eleml)
ele_vertexes = geomtools.get_vertexes_by_element(self.part_obj.Shape, ele_shape)
self.ele_node_map[eleml] = ele_vertexes
Console.PrintMessage(" {}\n".format(self.ele_length_map))
Console.PrintMessage(" {}\n".format(self.ele_node_map))
#Console.PrintMessage(" {}\n".format(self.ele_length_map))
#Console.PrintMessage(" {}\n".format(self.ele_node_map))
def get_boundary_layer_data(self):
# mesh boundary layer
@@ -553,16 +543,11 @@ class GmshTools():
# print(" No mesh boundary layer setting document object.")
pass
else:
Console.PrintMessage(" Mesh boundary layers, we need to get the elements.\n")
#Console.PrintMessage(" Mesh boundary layers, we need to get the elements.\n")
if self.part_obj.Shape.ShapeType == "Compound":
# see http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467 and
# http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&p=149520#p149520
err = (
"Gmsh could return unexpected meshes for a boolean split tools Compound. "
"It is strongly recommended to extract the shape to mesh "
"from the Compound and use this one."
)
Console.PrintError(err + "\n")
self.outputCompoundWarning
for mr_obj in self.mesh_obj.MeshBoundaryLayerList:
if mr_obj.MinimumThickness and Units.Quantity(mr_obj.MinimumThickness).Value > 0:
if mr_obj.References:
@@ -960,6 +945,17 @@ class GmshTools():
else:
Console.PrintError("No mesh was created.\n")
def outputCompoundWarning(self):
error_message = (
"The mesh to shape is a Boolean Split Tools compound "
"and the mesh has mesh region list.\n"
"Gmsh could return unexpected meshes in such circumstances.\n"
"If this is the case, use the part workbench and "
"apply a Compound Filter on the compound.\n"
"Use the Compound Filter as input for the mesh."
)
Console.PrintWarning(error_message + "\n")
## @}

View File

@@ -199,6 +199,8 @@ class _TaskPanel:
)
def run_gmsh(self):
from femmesh import gmshtools
gmsh_mesh = gmshtools.GmshTools(self.mesh_obj, self.analysis)
QApplication.setOverrideCursor(Qt.WaitCursor)
part = self.mesh_obj.Part
if (
@@ -209,43 +211,26 @@ class _TaskPanel:
or is_of_type(part, "FeatureXOR")
)
):
error_message = (
"The shape to mesh is a boolean split tools Compound "
"and the mesh has mesh region list. "
"Gmsh could return unexpected meshes in such circumstances. "
"It is strongly recommended to extract the shape "
"to mesh from the Compound and use this one."
)
qtbox_title = (
"Shape to mesh is a BooleanFragmentsCompound "
"and mesh regions are defined"
)
QtGui.QMessageBox.critical(
None,
qtbox_title,
error_message
)
gmsh_mesh.outputCompoundWarning()
self.Start = time.time()
self.form.l_time.setText("Time: {0:4.1f}: ".format(time.time() - self.Start))
self.console_message_gmsh = ""
self.gmsh_runs = True
self.console_log("We are going to start ...")
self.get_active_analysis()
from femmesh import gmshtools
gmsh_mesh = gmshtools.GmshTools(self.mesh_obj, self.analysis)
self.console_log("Start Gmsh ...")
error = ""
try:
error = gmsh_mesh.create_mesh()
except Exception:
error = sys.exc_info()[1]
FreeCAD.Console.PrintMessage(
FreeCAD.Console.PrintError(
"Unexpected error when creating mesh: {}\n"
.format(error)
)
if error:
FreeCAD.Console.PrintMessage("Gmsh had warnings ...\n")
FreeCAD.Console.PrintMessage("{}\n".format(error))
FreeCAD.Console.PrintWarning("Gmsh had warnings:\n")
FreeCAD.Console.PrintWarning("{}\n".format(error))
self.console_log("Gmsh had warnings ...")
self.console_log(error, "#FF0000")
else:
@@ -265,7 +250,7 @@ class _TaskPanel:
else:
for m in analysis.Group:
if m.Name == self.mesh_obj.Name:
FreeCAD.Console.PrintMessage(
FreeCAD.Console.PrintLog(
"Active analysis found: {}\n"
.format(analysis.Name)
)

View File

@@ -154,6 +154,7 @@ class TestObjectExistance(unittest.TestCase):
expected_vtk_obj_types = [
"Fem::FemPostClipFilter",
"Fem::FemPostContoursFilter",
"Fem::FemPostCutFilter",
"Fem::FemPostDataAlongLineFilter",
"Fem::FemPostDataAtPointFilter",

View File

@@ -87,11 +87,11 @@ class TestObjectCreate(unittest.TestCase):
# solver children: equations --> 8
# gmsh mesh children: group, region, boundary layer --> 3
# resule children: mesh result --> 1
# post pipeline children: region, scalar, cut, wrap --> 4
# post pipeline children: region, scalar, cut, wrap --> 5
# analysis itself is not in analysis group --> 1
# thus: -17
# thus: -18
self.assertEqual(len(doc.Analysis.Group), count_defmake - 17)
self.assertEqual(len(doc.Analysis.Group), count_defmake - 18)
self.assertEqual(len(doc.Objects), count_defmake)
fcc_print("doc objects count: {}, method: {}".format(
@@ -1858,6 +1858,7 @@ def create_all_fem_objects_doc(
vres = analysis.addObject(ObjectsFem.makePostVtkResult(doc, res))[0]
ObjectsFem.makePostVtkFilterClipRegion(doc, vres)
ObjectsFem.makePostVtkFilterClipScalar(doc, vres)
ObjectsFem.makePostVtkFilterContours(doc, vres)
ObjectsFem.makePostVtkFilterCutFunction(doc, vres)
ObjectsFem.makePostVtkFilterWarp(doc, vres)

View File

@@ -36,7 +36,6 @@ import FemGui
from PySide import QtGui
from femtaskpanels import task_mesh_gmsh
from femtools.femutils import is_of_type
# from . import view_base_femobject
# TODO use VPBaseFemObject from view_base_femobject
@@ -64,21 +63,21 @@ class VPMeshGmsh:
def setEdit(self, vobj, mode):
# hide all FEM meshes and VTK FemPost* objects
for o in vobj.Object.Document.Objects:
for obj in vobj.Object.Document.Objects:
if (
o.isDerivedFrom("Fem::FemMeshObject")
or o.isDerivedFrom("Fem::FemPostPipeline")
or o.isDerivedFrom("Fem::FemPostClipFilter")
or o.isDerivedFrom("Fem::FemPostScalarClipFilter")
or o.isDerivedFrom("Fem::FemPostWarpVectorFilter")
or o.isDerivedFrom("Fem::FemPostDataAlongLineFilter")
or o.isDerivedFrom("Fem::FemPostDataAtPointFilter")
or o.isDerivedFrom("Fem::FemPostCutFilter")
or o.isDerivedFrom("Fem::FemPostDataAlongLineFilter")
or o.isDerivedFrom("Fem::FemPostPlaneFunction")
or o.isDerivedFrom("Fem::FemPostSphereFunction")
obj.isDerivedFrom("Fem::FemMeshObject")
or obj.isDerivedFrom("Fem::FemPostClipFilter")
or obj.isDerivedFrom("Fem::FemPostContoursFilter")
or obj.isDerivedFrom("Fem::FemPostCutFilter")
or obj.isDerivedFrom("Fem::FemPostDataAlongLineFilter")
or obj.isDerivedFrom("Fem::FemPostDataAtPointFilter")
or obj.isDerivedFrom("Fem::FemPostPipeline")
or obj.isDerivedFrom("Fem::FemPostPlaneFunction")
or obj.isDerivedFrom("Fem::FemPostScalarClipFilter")
or obj.isDerivedFrom("Fem::FemPostSphereFunction")
or obj.isDerivedFrom("Fem::FemPostWarpVectorFilter")
):
o.ViewObject.hide()
obj.ViewObject.hide()
# show the mesh we like to edit
self.ViewObject.show()
# show task panel

View File

@@ -2244,29 +2244,23 @@ GeomBSplineCurve* GeomCircle::toNurbs(double first, double last) const
return GeomConic::toNurbs(first, last);
}
double radius = getRadius();
Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(handle());
gp_Ax1 axis = conic->Axis();
//gp_Dir xdir = conic->XAxis().Direction();
//Standard_Real angle = gp_Dir(1,0,0).Angle(xdir) + first;
Standard_Real angle = first;
const gp_Pnt& loc = axis.Location();
//Note: If the matching this way doesn't work reliably then we must compute the
//angle so that the point of the curve for 'first' matches the first pole
//gp_Pnt pnt = conic->Value(first);
Handle(Geom_Circle) conic = Handle(Geom_Circle)::DownCast(handle());
double radius = conic->Radius();
TColgp_Array1OfPnt poles(1, 7);
poles(1) = loc.Translated(gp_Vec(radius, 0, 0));
poles(2) = loc.Translated(gp_Vec(radius, 2*radius, 0));
poles(3) = loc.Translated(gp_Vec(-radius, 2*radius, 0));
poles(4) = loc.Translated(gp_Vec(-radius, 0, 0));
poles(5) = loc.Translated(gp_Vec(-radius, -2*radius, 0));
poles(6) = loc.Translated(gp_Vec(radius, -2*radius, 0));
poles(7) = loc.Translated(gp_Vec(radius, 0, 0));
poles(1) = gp_Pnt(radius, 0, 0);
poles(2) = gp_Pnt(radius, 2*radius, 0);
poles(3) = gp_Pnt(-radius, 2*radius, 0);
poles(4) = gp_Pnt(-radius, 0, 0);
poles(5) = gp_Pnt(-radius, -2*radius, 0);
poles(6) = gp_Pnt(radius, -2*radius, 0);
poles(7) = gp_Pnt(radius, 0, 0);
gp_Trsf trsf;
trsf.SetTransformation(conic->Position(), gp_Ax3());
TColStd_Array1OfReal weights(1,7);
for (int i=1; i<=7; i++) {
poles(i).Rotate(axis, angle);
poles(i).Transform(trsf);
weights(i) = 1;
}
weights(1) = 3;
@@ -2285,7 +2279,6 @@ GeomBSplineCurve* GeomCircle::toNurbs(double first, double last) const
Handle(Geom_BSplineCurve) spline = new Geom_BSplineCurve(poles, weights,knots, mults, 3,
Standard_False, Standard_True);
spline->Segment(0, last-first);
return new GeomBSplineCurve(spline);
}
@@ -2671,25 +2664,23 @@ GeomBSplineCurve* GeomEllipse::toNurbs(double first, double last) const
}
Handle(Geom_Ellipse) conic = Handle(Geom_Ellipse)::DownCast(handle());
gp_Ax1 axis = conic->Axis();
Standard_Real majorRadius = conic->MajorRadius();
Standard_Real minorRadius = conic->MinorRadius();
gp_Dir xdir = conic->XAxis().Direction();
Standard_Real angle = atan2(xdir.Y(), xdir.X());
const gp_Pnt& loc = axis.Location();
TColgp_Array1OfPnt poles(1, 7);
poles(1) = loc.Translated(gp_Vec(majorRadius, 0, 0));
poles(2) = loc.Translated(gp_Vec(majorRadius, 2*minorRadius, 0));
poles(3) = loc.Translated(gp_Vec(-majorRadius, 2*minorRadius, 0));
poles(4) = loc.Translated(gp_Vec(-majorRadius, 0, 0));
poles(5) = loc.Translated(gp_Vec(-majorRadius, -2*minorRadius, 0));
poles(6) = loc.Translated(gp_Vec(majorRadius, -2*minorRadius, 0));
poles(7) = loc.Translated(gp_Vec(majorRadius, 0, 0));
poles(1) = gp_Pnt(majorRadius, 0, 0);
poles(2) = gp_Pnt(majorRadius, 2*minorRadius, 0);
poles(3) = gp_Pnt(-majorRadius, 2*minorRadius, 0);
poles(4) = gp_Pnt(-majorRadius, 0, 0);
poles(5) = gp_Pnt(-majorRadius, -2*minorRadius, 0);
poles(6) = gp_Pnt(majorRadius, -2*minorRadius, 0);
poles(7) = gp_Pnt(majorRadius, 0, 0);
gp_Trsf trsf;
trsf.SetTransformation(conic->Position(), gp_Ax3());
TColStd_Array1OfReal weights(1,7);
for (int i=1; i<=7; i++) {
poles(i).Rotate(axis, angle);
poles(i).Transform(trsf);
weights(i) = 1;
}
weights(1) = 3;

View File

@@ -175,6 +175,48 @@ class PartTestBSplineCurve(unittest.TestCase):
#closing doc
FreeCAD.closeDocument("PartTest")
class PartTestCurveToNurbs(unittest.TestCase):
def testCircleToNurbs(self):
mat = Base.Matrix()
mat.rotateX(1)
mat.rotateY(1)
mat.rotateZ(1)
circle = Part.Circle()
circle.Radius = 5
circle.transform(mat)
nurbs = circle.toNurbs()
self.assertEqual(circle.value(0), nurbs.value(0))
arc = circle.trim(0, 2)
nurbs = arc.toNurbs()
self.assertEqual(circle.value(0), nurbs.value(0))
spline = circle.toBSpline()
self.assertAlmostEqual(circle.value(0).distanceToPoint(spline.value(0)), 0)
def testEllipseToNurbs(self):
mat = Base.Matrix()
mat.rotateX(1)
mat.rotateY(1)
mat.rotateZ(1)
ellipse = Part.Ellipse()
ellipse.MajorRadius = 5
ellipse.MinorRadius = 3
ellipse.transform(mat)
nurbs = ellipse.toNurbs()
self.assertEqual(ellipse.value(0), nurbs.value(0))
arc = ellipse.trim(0, 2)
nurbs = arc.toNurbs()
self.assertEqual(ellipse.value(0), nurbs.value(0))
spline = ellipse.toBSpline()
self.assertAlmostEqual(ellipse.value(0).distanceToPoint(spline.value(0)), 0)
class PartTestBSplineSurface(unittest.TestCase):
def testTorusToSpline(self):
to = Part.Toroid()

View File

@@ -148,6 +148,8 @@ void EditModeCoinManager::ParameterObserver::initParameters()
[this, drawingParameters = Client.drawingParameters](const std::string & param){updateColor(drawingParameters.PreselectColor, param);}},
{"SelectionColor",
[this, drawingParameters = Client.drawingParameters](const std::string & param){updateColor(drawingParameters.SelectColor, param);}},
{"CursorTextColor",
[this, drawingParameters = Client.drawingParameters](const std::string & param){updateColor(drawingParameters.CursorTextColor, param);}},
};
for( auto & val : str2updatefunction){
@@ -230,12 +232,12 @@ void EditModeCoinManager::ParameterObserver::updateElementSizeParameters(const s
// simple scaling factor for hardcoded pixel values in the Sketcher
Client.drawingParameters.pixelScalingFactor = viewScalingFactor * dpi / 96; // 96 ppi is the standard pixel density for which pixel quantities were calculated
// Coin documentation indicates the size of a font is:
// SoSFFloat SoFont::size Size of font. Defaults to 10.0.
// About sizes:
// SoDatumLabel takes the size in points, not in pixels. This is because it uses QFont internally.
// Coin, at least our coin at this time, takes pixels, not points.
//
// For 2D rendered bitmap fonts (like for SoText2), this value is the height of a character in screen pixels. For 3D text, this value is the world-space coordinates height of a character in the current units setting (see documentation for SoUnits node).
//
// However, with hdpi monitors, the coin font labels do not respect the size passed in pixels:
// DPI considerations:
// With hdpi monitors, the coin font labels do not respect the size passed in pixels:
// https://forum.freecadweb.org/viewtopic.php?f=3&t=54347&p=467610#p467610
// https://forum.freecadweb.org/viewtopic.php?f=10&t=49972&start=40#p467471
//
@@ -247,7 +249,8 @@ void EditModeCoinManager::ParameterObserver::updateElementSizeParameters(const s
// This means that the following correction does not have a documented basis, but appears necessary so that the Sketcher is usable in
// HDPI monitors.
Client.drawingParameters.coinFontSize = std::lround(sketcherfontSize * 96.0f / dpi);
Client.drawingParameters.coinFontSize = std::lround(sketcherfontSize * 96.0f / dpi); // this is in pixels
Client.drawingParameters.labelFontSize = std::lround(sketcherfontSize * 72.0f / dpi); // this is in points, as SoDatumLabel uses points
Client.drawingParameters.constraintIconSize = std::lround(0.8 * sketcherfontSize);
// For marker size the global default is used.
@@ -269,6 +272,8 @@ void EditModeCoinManager::ParameterObserver::updateColor(SbColor &sbcolor, const
unsigned long color = (unsigned long)(sbcolor.getPackedValue());
color = hGrp->GetUnsigned(parametername.c_str(), color);
sbcolor.setPackedValue((uint32_t)color, transparency);
Client.updateInventorColors();
}
void EditModeCoinManager::ParameterObserver::subscribeToParameters()
@@ -705,11 +710,6 @@ void EditModeCoinManager::createEditModeInventorNodes()
editModeScenegraphNodes.EditCurveSet->setName("EditCurveLineSet");
editCurvesRoot->addChild(editModeScenegraphNodes.EditCurveSet);
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
float transparency;
SbColor cursorTextColor(0,0,1);
cursorTextColor.setPackedValue((uint32_t)hGrp->GetUnsigned("CursorTextColor", cursorTextColor.getPackedValue()), transparency);
// stuff for the EditMarkers +++++++++++++++++++++++++++++++++++++++
SoSeparator* editMarkersRoot = new SoSeparator;
editModeScenegraphNodes.EditRoot->addChild(editMarkersRoot);
@@ -740,15 +740,16 @@ void EditModeCoinManager::createEditModeInventorNodes()
// no caching for frequently-changing data structures
Coordsep->renderCaching = SoSeparator::OFF;
SoMaterial *CoordTextMaterials = new SoMaterial;
CoordTextMaterials->setName("CoordTextMaterials");
CoordTextMaterials->diffuseColor = cursorTextColor;
Coordsep->addChild(CoordTextMaterials);
editModeScenegraphNodes.textMaterial = new SoMaterial;
editModeScenegraphNodes.textMaterial->setName("CoordTextMaterials");
editModeScenegraphNodes.textMaterial->diffuseColor = drawingParameters.CursorTextColor;
Coordsep->addChild(editModeScenegraphNodes.textMaterial);
SoFont *font = new SoFont();
font->size.setValue(drawingParameters.coinFontSize);
editModeScenegraphNodes.textFont = new SoFont();
editModeScenegraphNodes.textFont->name.setValue("Helvetica");
editModeScenegraphNodes.textFont->size.setValue(drawingParameters.coinFontSize);
Coordsep->addChild(font);
Coordsep->addChild(editModeScenegraphNodes.textFont);
editModeScenegraphNodes.textPos = new SoTranslation();
Coordsep->addChild(editModeScenegraphNodes.textPos);
@@ -828,9 +829,18 @@ void EditModeCoinManager::updateInventorNodeSizes()
editModeScenegraphNodes.ConstraintDrawStyle->lineWidth = 1 * drawingParameters.pixelScalingFactor;
editModeScenegraphNodes.InformationDrawStyle->lineWidth = 1 * drawingParameters.pixelScalingFactor;
editModeScenegraphNodes.textFont->size.setValue(drawingParameters.coinFontSize);
pEditModeConstraintCoinManager->rebuildConstraintNodes();
}
void EditModeCoinManager::updateInventorColors()
{
editModeScenegraphNodes.RootCrossMaterials->diffuseColor.set1Value(0, drawingParameters.CrossColorH);
editModeScenegraphNodes.RootCrossMaterials->diffuseColor.set1Value(1, drawingParameters.CrossColorV);
editModeScenegraphNodes.textMaterial->diffuseColor = drawingParameters.CursorTextColor;
}
/************************ Edit node access ************************/
SoSeparator* EditModeCoinManager::getRootEditNode()

View File

@@ -262,6 +262,8 @@ private:
void updateInventorNodeSizes();
void updateInventorColors();
/** @name coin nodes creation*/
void createEditModeInventorNodes();
//@}

View File

@@ -51,5 +51,6 @@ SbColor DrawingParameters::ConstrIcoColor (1.0f, 0.14
SbColor DrawingParameters::NonDrivingConstrDimColor (0.0f, 0.149f, 1.0f); // #0026FF -> ( 0, 38,255)
SbColor DrawingParameters::ExprBasedConstrDimColor (1.0f, 0.5f, 0.149f); // #FF7F26 -> (255, 127,38)
SbColor DrawingParameters::DeactivatedConstrDimColor (0.8f, 0.8f, 0.8f); // #CCCCCC -> (204,204,204)
SbColor DrawingParameters::CursorTextColor (0.0f, 0.0f, 1.0f); // #0000FF -> (0,0,255)
const MultiFieldId MultiFieldId::Invalid = MultiFieldId();

View File

@@ -31,6 +31,7 @@
#include <Inventor/SbColor.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoFont.h>
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/nodes/SoLineSet.h>
#include <Inventor/nodes/SoMarkerSet.h>
@@ -39,6 +40,7 @@
#include <Inventor/nodes/SoText2.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Gui/Inventor/SmSwitchboard.h>
#include <Mod/Sketcher/App/GeoList.h>
@@ -112,12 +114,14 @@ struct DrawingParameters {
static SbColor NonDrivingConstrDimColor; // Color used for non-driving (reference) dimensional constraints
static SbColor ExprBasedConstrDimColor; // Color used for expression based dimensional constraints
static SbColor DeactivatedConstrDimColor; // Color used for deactivated dimensional constraints
static SbColor CursorTextColor; // Color used by the edit mode cursor
//@}
/** @name Rendering sizes (also to support HDPI monitors) **/
//@{
double pixelScalingFactor = 1.0; // Scaling factor to be used for pixels
int coinFontSize = 17; // Font size to be used by coin
int labelFontSize = 17; // Font size to be used by SoDatumLabel, which uses a QPainter and a QFont internally
int constraintIconSize = 15; // Size of constraint icons
int markerSize = 7; // Size used for markers
//@}
@@ -326,6 +330,8 @@ struct EditModeScenegraphNodes {
//@{
SoText2 *textX;
SoTranslation *textPos;
SoFont *textFont;
SoMaterial *textMaterial;
//@}
/** @name Constraint nodes*/

View File

@@ -1440,7 +1440,7 @@ void EditModeConstraintCoinManager::rebuildConstraintNodes(const GeoListFacade &
drawingParameters.ConstrDimColor
:drawingParameters.NonDrivingConstrDimColor)
:drawingParameters.DeactivatedConstrDimColor;
text->size.setValue(drawingParameters.coinFontSize);
text->size.setValue(drawingParameters.labelFontSize);
text->lineWidth = 2 * drawingParameters.pixelScalingFactor;
text->useAntialiasing = false;
SoAnnotation *anno = new SoAnnotation();

View File

@@ -365,9 +365,9 @@ void EditModeInformationOverlayCoinConverter::addNode(const Result & result) {
else
setText(result.strings[i], text);
sep->addChild(translate);
sep->addChild(mat);
sep->addChild(font);
sep->addChild(translate);
sep->addChild(text);
sw->addChild(sep);

View File

@@ -253,7 +253,7 @@ def getDefaultIcon():
def buildCard(filename,method,arg=None):
"""builds an html <li> element representing a file.
"""builds an html <li> element representing a file.
method is a script + a keyword, for ex. url.py?key="""
result = ""

View File

@@ -21,14 +21,17 @@ set(TechDraw_ToolsScripts
TechDrawTools/CommandMoveView.py
TechDrawTools/CommandShareView.py
TechDrawTools/CommandAxoLengthDimension.py
TechDrawTools/CommandHoleShaftFit.py
TechDrawTools/TaskMoveView.py
TechDrawTools/TaskShareView.py
TechDrawTools/TaskHoleShaftFit.py
TechDrawTools/TDToolsUtil.py
TechDrawTools/TDToolsMovers.py
)
set(TechDraw_ToolsGui
Gui/TaskMoveView.ui
Gui/TaskHoleShaftFit.ui
Gui/DlgPageChooser.ui
)

View File

@@ -29,6 +29,7 @@
<file>icons/actions/TechDraw_Midpoints.svg</file>
<file>icons/actions/TechDraw_MoveView.svg</file>
<file>icons/actions/TechDraw_AxoLengthDimension.svg</file>
<file>icons/actions/TechDraw_HoleShaftFit.svg</file>
<file>icons/actions/TechDraw_Multiview.svg</file>
<file>icons/actions/TechDraw_PageDefault.svg</file>
<file>icons/actions/TechDraw_PageTemplate.svg</file>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TechDrawGui::TaskSurfaceFinishSymbols</class>
<widget class="QWidget" name="TechDrawGui::TaskSurfaceFinishSymbols">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>274</width>
<height>162</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Hole /Shaft Fit ISO 286</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QRadioButton" name="rbHoleBase">
<property name="text">
<string>shaft fit</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="rbShaftBase">
<property name="text">
<string>hole fit</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="2">
<widget class="QComboBox" name="cbField">
<property name="enabled">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>c11</string>
</property>
</item>
<item>
<property name="text">
<string>f7</string>
</property>
</item>
<item>
<property name="text">
<string>h6</string>
</property>
</item>
<item>
<property name="text">
<string>h7</string>
</property>
</item>
<item>
<property name="text">
<string>h9</string>
</property>
</item>
<item>
<property name="text">
<string>h9</string>
</property>
</item>
<item>
<property name="text">
<string>h9</string>
</property>
</item>
<item>
<property name="text">
<string>h6</string>
</property>
</item>
<item>
<property name="text">
<string>h6</string>
</property>
</item>
<item>
<property name="text">
<string>h6</string>
</property>
</item>
<item>
<property name="text">
<string>h6</string>
</property>
</item>
<item>
<property name="text">
<string>h6</string>
</property>
</item>
<item>
<property name="text">
<string>k6</string>
</property>
</item>
<item>
<property name="text">
<string>n6</string>
</property>
</item>
<item>
<property name="text">
<string>r6</string>
</property>
</item>
<item>
<property name="text">
<string>s6</string>
</property>
</item>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="lbFitType">
<property name="text">
<string>loose fit</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lbBaseField">
<property name="text">
<string> H11/</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="Resources/TechDraw.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -226,6 +226,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const
*draw << "TechDraw_ShowAll";
*draw << "TechDraw_WeldSymbol";
*draw << "TechDraw_SurfaceFinishSymbols";
*draw << "TechDraw_HoleShaftFit";
*draw << "Separator";
*draw << "TechDraw_ProjectShape";
return root;
@@ -376,6 +377,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
*anno << "TechDraw_ShowAll";
*anno << "TechDraw_WeldSymbol";
*anno << "TechDraw_SurfaceFinishSymbols";
*anno << "TechDraw_HoleShaftFit";
return root;
}
@@ -525,6 +527,7 @@ Gui::ToolBarItem* Workbench::setupCommandBars() const
*anno << "TechDraw_ShowAll";
*anno << "TechDraw_WeldSymbol";
*anno << "TechDraw_SurfaceFinishSymbols";
*anno << "TechDraw_HoleShaftFit";
return root;
}

View File

@@ -0,0 +1,67 @@
# ***************************************************************************
# * Copyright (c) 2023 edi <edi271@a1.net> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides the TechDraw HoleShaftFit GuiCommand."""
__title__ = "TechDrawTools.CommandHoleShaftFit"
__author__ = "edi"
__url__ = "https://www.freecadweb.org"
__version__ = "00.01"
__date__ = "2023/02/07"
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
import TechDrawTools
class CommandHoleShaftFit:
"""Adds a hole or shaft fit to a selected dimension."""
def GetResources(self):
"""Return a dictionary with data that will be used by the button or menu item."""
return {'Pixmap': 'actions/TechDraw_HoleShaftFit.svg',
'Accel': "",
'MenuText': QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "Add hole or shaft fit"),
'ToolTip': QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "Add a hole or shaft fit to a dimension<br>\
- select one length dimension or diameter dimension<br>\
- click the tool button, a panel openes<br>\
- select shaft fit / hole fit<br>\
- select the desired ISO 286 fit field using the combo box")}
def Activated(self):
"""Run the following code when the command is activated (button press)."""
sel = Gui.Selection.getSelectionEx()
#if sel and sel[0].Object.TypeId == 'TechDraw::DrawViewDimension':
if sel[0].Object.TypeId == 'TechDraw::DrawViewDimension':
self.ui = TechDrawTools.TaskHoleShaftFit(sel)
Gui.Control.showDialog(self.ui)
def IsActive(self):
"""Return True when the command should be active or False when it should be disabled (greyed)."""
if App.ActiveDocument:
return TechDrawTools.TDToolsUtil.havePage() and TechDrawTools.TDToolsUtil.haveView()
else:
return False
#
# The command must be "registered" with a unique name by calling its class.
Gui.addCommand('TechDraw_HoleShaftFit', CommandHoleShaftFit())

View File

@@ -0,0 +1,184 @@
# ***************************************************************************
# * Copyright (c) 2023 edi <edi271@a1.net> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides the TechDraw HoleShaftFit Task Dialog."""
__title__ = "TechDrawTools.TaskHoleShaftFit"
__author__ = "edi"
__url__ = "https://www.freecadweb.org"
__version__ = "00.01"
__date__ = "2023/02/07"
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD as App
import FreeCADGui as Gui
from functools import partial
import os
class TaskHoleShaftFit:
def __init__(self,sel):
loose = QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "loose")
snug = QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "snug")
press = QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "press")
self.isHole = True
self.sel = sel
self.holeValues = [["h9","D10",loose],["h9","E9",loose],["h9","F8",loose],["h6","G7",loose],
["c11","H11",loose],["f7","H8",loose],["h6","H7",loose],["h7","H8",loose],
["k6","H7",snug],["n6","H7",snug],["r6","H7",press],["s6","H7",press],
["h6","K7",snug],["h6","N7",snug],["h6","R7",press],["h6","S7",press]]
self.shaftValues = [["H11","c11",loose],["H8","f7",loose],["H7","h6",loose],["H8","h7",loose],
["D10","h9",loose],["E9","h9",loose],["F8","h9",loose],["G7","h6",loose],
["K7","h6",snug],["N7","h6",snug],["R7","h6",press],["S7","h6",press],
["H7","k6",snug],["H7","n6",snug],["H7","r6",press],["H7","s6",press]]
self._uiPath = App.getHomePath()
self._uiPath = os.path.join(self._uiPath, "Mod/TechDraw/TechDrawTools/Gui/TaskHoleShaftFit.ui")
self.form = Gui.PySideUic.loadUi(self._uiPath)
self.form.setWindowTitle(QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "Hole / Shaft Fit ISO 286"))
self.form.rbHoleBase.clicked.connect(partial(self.on_HoleShaftChanged,True))
self.form.rbShaftBase.clicked.connect(partial(self.on_HoleShaftChanged,False))
self.form.cbField.currentIndexChanged.connect(self.on_FieldChanged)
def setHoleFields(self):
'''set hole fields in the combo box'''
for i in range(self.form.cbField.count()):
self.form.cbField.removeItem(0)
for value in self.holeValues:
self.form.cbField.addItem(value[1])
self.form.lbBaseField.setText(' '+self.holeValues[0][0]+" /")
self.form.lbFitType.setText(self.holeValues[0][2]+" fit")
def setShaftFields(self):
'''set shaft fields in the combo box'''
for i in range(self.form.cbField.count()):
self.form.cbField.removeItem(0)
for value in self.shaftValues:
self.form.cbField.addItem(value[1])
self.form.lbBaseField.setText(' '+self.shaftValues[0][0]+" /")
self.form.lbFitType.setText(self.shaftValues[0][2]+" fit")
def on_HoleShaftChanged(self,isHole):
'''slot: change the used base fit hole/shaft'''
if isHole:
self.isHole = isHole
self.setShaftFields()
else:
self.isHole = isHole
self.setHoleFields()
def on_FieldChanged(self):
'''slot: change of the desired field'''
currentIndex = self.form.cbField.currentIndex()
if self.isHole:
self.form.lbBaseField.setText(' '+self.shaftValues[currentIndex][0]+" /")
self.form.lbFitType.setText(self.shaftValues[currentIndex][2]+" fit")
else:
self.form.lbBaseField.setText(' '+self.holeValues[currentIndex][0]+" /")
self.form.lbFitType.setText(self.holeValues[currentIndex][2]+" fit")
def accept(self):
'''slot: OK pressed'''
currentIndex = self.form.cbField.currentIndex()
if self.isHole:
selectedField = self.shaftValues[currentIndex][1]
else:
selectedField = self.holeValues[currentIndex][1]
fieldChar = selectedField[0]
quality = int(selectedField[1:])
dim = self.sel[0].Object
value = dim.getRawValue()
iso = ISO286()
iso.calculate(value,fieldChar,quality)
rangeValues = iso.getValues()
mainFormat = dim.FormatSpec
dim.FormatSpec = mainFormat+selectedField
dim.EqualTolerance = False
dim.FormatSpecOverTolerance = '(%+.3f)'
dim.OverTolerance = rangeValues[0]
dim.UnderTolerance = rangeValues[1]
Gui.Control.closeDialog()
def reject(self):
return True
class ISO286:
'''This class represents a subset of the ISO 286 standard'''
def getNominalRange(self,measureValue):
'''return index of selected nominal range field, 0 < measureValue < 500 mm'''
measureRanges = [0,3,6,10,14,18,24,30,40,50,65,80,100,120,140,160,180,200,225,250,280,315,355,400,450,500]
index = 1
while measureValue > measureRanges[index]:
index = index+1
return index-1
def getITValue(self,valueQuality,valueNominalRange):
'''return IT-value (value of quality in micrometers)'''
'''tables IT6 to IT11 from 0 to 500 mm'''
IT6 = [6,8,9,11,11,13,13,16,16,19,19,22,22,25,25,25,29,29,29,32,32,36,36,40,40]
IT7 = [10,12,15,18,18,21,21,25,25,30,30,35,35,40,40,40,46,46,46,52,52,57,57,63,63]
IT8 = [14,18,22,27,27,33,33,39,39,46,46,54,54,63,63,63,72,72,72,81,81,89,89,97,97]
IT9 = [25,30,36,43,43,52,52,62,62,74,74,87,87,100,100,100,115,115,115,130,130,140,140,155,155]
IT10 = [40,48,58,70,70,84,84,100,100,120,120,140,140,160,160,160,185,185,185,210,210,230,230,250,250]
IT11 = [60,75,90,110,110,130,130,160,160,190,190,220,220,250,250,250,290,290,290,320,320,360,360,400,400]
qualityTable = [IT6,IT7,IT8,IT9,IT10,IT11]
return qualityTable[valueQuality-6][valueNominalRange]
def getFieldValue(self,fieldCharacter,valueNominalRange):
'''return es or ES value of the field in micrometers'''
cField = [-60,-70,-80,-95,-95,-110,-110,-120,-130,-140,-150,-170,-180,-200,-210,-230,-240,-260,-280,-300,-330,-360,-400,-440,-480]
fField = [-6,-10,-13,-16,-16,-20,-20,-25,-25,-30,-30,-36,-36,-43,-43,-43,-50,-50,-50,-56,-56,-62,-62,-68,-68]
gField = [-2,-4,-5,-6,-6,-7,-7,-9,-9,-10,-10,-12,-12,-14,-14,-14,-15,-15,-15,-17,-17,-18,-18,-20,-20]
hField = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
kField = [6,9,10,12,12,15,15,18,18,21,21,25,25,28,28,28,33,33,33,36,36,40,40,45,45]
nField = [10,16,19,23,23,28,28,33,33,39,39,45,45,52,52,60,60,66,66,73,73,80,80]
rField = [16,23,28,34,34,41,41,50,50,60,62,73,76,88,90,93,106,109,113,126,130,144,150,166,172]
sField = [20,27,32,39,39,48,48,59,59,72,78,93,101,117,125,133,151,159,169,190,202,226,244,272,292]
DField = [60,78,98,120,120,149,149,180,180,220,220,260,260,305,305,305,355,355,355,400,400,440,440,480,480]
EField = [39,50,61,75,75,92,92,112,112,134,134,159,159,185,185,185,215,215,215,240,240,265,265,290,290]
FField = [20,28,35,43,43,53,53,64,64,76,76,90,90,106,106,106,122,122,122,137,137,151,151,165,165]
GField = [12,16,20,24,24,28,28,34,34,40,40,47,47,54,54,54,61,61,61,69,69,75,75,83,83]
HField = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
KField = [0,3,5,6,6,6,6,7,7,9,9,10,10,12,12,12,13,13,13,16,16,17,17,18,18]
NField = [-4,-4,-4,-5,-5,-7,-7,-8,-8,-9,-9,-10,-10,-12,-12,-12,-14,-14,-14,-14,-14,-16,-16,-17,-17]
RField = [-10,-11,-13,-16,-16,-20,-20,-25,-25,-30,-32,-38,-41,-48,-50,-53,-60,-63,-67,-74,-78,-87,-93,-103,-109]
SField = [-14,-15,-17,-21,-21,-27,-27,-34,-34,-42,-48,-58,-66,-77,-85,-93,-105,-113,-123,-138,-150,-169,-187,-209,-229]
fieldDict = {'c':cField,'f':fField,'g':gField,'h':hField,'k':kField,'n':nField,'r':rField,'s':sField,
'D':DField,'E':EField,'F':FField,'G':GField,'H':HField,'K':KField,'N':NField,'R':RField,'S':SField}
return fieldDict[fieldCharacter][valueNominalRange]
def calculate(self,value,fieldChar,quality):
'''calculate upper and lower field values'''
self.nominalRange = self. getNominalRange(value)
self.upperValue = self.getFieldValue(fieldChar,self.nominalRange)
self.lowerValue = self.upperValue-self.getITValue(quality,self.nominalRange)
if fieldChar == 'H':
self.upperValue = -self.lowerValue
self.lowerValue = 0
def getValues(self):
'''return range values in mm'''
return (self.upperValue/1000,self.lowerValue/1000)

View File

@@ -34,5 +34,7 @@ from .TDToolsUtil import *
from .CommandShareView import CommandShareView
from .CommandMoveView import CommandMoveView
from .CommandAxoLengthDimension import CommandAxoLengthDimension
from .CommandHoleShaftFit import CommandHoleShaftFit
from .TaskShareView import TaskShareView
from .TaskMoveView import TaskMoveView
from .TaskHoleShaftFit import TaskHoleShaftFit