Merge branch 'master' into gcode_apply_gcode_placement
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
347
src/Mod/AddonManager/AddonManagerTest/app/test_macro_parser.py
Normal file
347
src/Mod/AddonManager/AddonManagerTest/app/test_macro_parser.py
Normal 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"], "")
|
||||
@@ -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.")
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
249
src/Mod/AddonManager/addonmanager_macro_parser.py
Normal file
249
src/Mod/AddonManager/addonmanager_macro_parser.py
Normal 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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
-------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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><html><head/><body><p>Leave blank to use default Elmer elmer binary file</p><p><span style=" font-weight:600;">Note:</span> To use multithreading you must specify here<br> the executable variant with the suffix &quot;_mpi&quot;.</p></body></html></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><html><head/><body><p>Leave blank to use default Elmer elmer binary file</p><p><span style=" font-weight:600;">Note:</span> To use multithreading you must specify here<br> the executable variant with the suffix &quot;_mpi&quot;.</p></body></html></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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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_
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
70
src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterContours.svg
Normal file
70
src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterContours.svg
Normal 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
@@ -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
|
||||
|
||||
90
src/Mod/Fem/Gui/TaskPostContours.ui
Normal file
90
src/Mod/Fem/Gui/TaskPostContours.ui
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
## @}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -154,6 +154,7 @@ class TestObjectExistance(unittest.TestCase):
|
||||
|
||||
expected_vtk_obj_types = [
|
||||
"Fem::FemPostClipFilter",
|
||||
"Fem::FemPostContoursFilter",
|
||||
"Fem::FemPostCutFilter",
|
||||
"Fem::FemPostDataAlongLineFilter",
|
||||
"Fem::FemPostDataAtPointFilter",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -262,6 +262,8 @@ private:
|
||||
|
||||
void updateInventorNodeSizes();
|
||||
|
||||
void updateInventorColors();
|
||||
|
||||
/** @name coin nodes creation*/
|
||||
void createEditModeInventorNodes();
|
||||
//@}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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 |
164
src/Mod/TechDraw/Gui/TaskHoleShaftFit.ui
Normal file
164
src/Mod/TechDraw/Gui/TaskHoleShaftFit.ui
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
67
src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py
Normal file
67
src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py
Normal 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())
|
||||
184
src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py
Normal file
184
src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user