File size: 34,493 Bytes
985c397 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 | # ***************************************************************************
# * Copyright (c) 2015 Przemo Firszt <przemo@firszt.eu> *
# * Copyright (c) 2016 Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * 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 *
# * *
# ***************************************************************************
__title__ = "FemToolsCcx"
__author__ = "Przemo Firszt, Bernd Hahnebach"
__url__ = "https://www.freecad.org"
## \addtogroup FEM
# @{
import os
import sys
import subprocess
import shutil
import FreeCAD
from femtools import femutils
from femtools import membertools
from PySide import QtCore # there might be a special reason this is not guarded ?!?
if FreeCAD.GuiUp:
from PySide import QtGui
import FemGui
class FemToolsCcx(QtCore.QRunnable, QtCore.QObject):
"""
Attributes
----------
analysis : Fem::FemAnalysis
FEM group analysis object
has to be present, will be set in __init__
solver : Fem::FemSolverObjectPython
FEM solver object
has to be present, will be set in __init__
base_name : str
name of .inp/.frd file (without extension)
It is used to construct .inp file path that is passed to CalculiX ccx
ccx_binary : str
working_dir : str
results_present : bool
indicating if there are calculation results ready for us
members : class femtools/membertools/AnalysisMember
contains references to all analysis member except solvers and mesh
Updated with update_objects
"""
finished = QtCore.Signal(int)
def __init__(self, analysis=None, solver=None, test_mode=False):
"""The constructor
Parameters
----------
analysis : Fem::FemAnalysis, optional
analysis group as a container for all objects needed for the analysis
solver : Fem::FemSolverObjectPython, optional
solver object to be used for this solve
test_mode : bool, optional
mainly used in unit tests
"""
QtCore.QRunnable.__init__(self)
QtCore.QObject.__init__(self)
self.ccx_binary_present = False
self.analysis = None
self.solver = None
# TODO if something will go wrong in __init__ do not continue,
# but do not raise a exception, break in a smarter way
if analysis:
self.analysis = analysis
if solver:
# analysis and solver given
self.solver = solver
else:
# analysis given, search for the solver
self.find_solver()
if not self.solver:
raise Exception("FEM: No solver found!")
else:
if solver:
# solver given, search for the analysis
self.solver = solver
self.find_solver_analysis()
if not self.analysis:
raise Exception(
"FEM: The solver was given as parameter, "
"but no analysis for this solver was found!"
)
else:
# neither analysis nor solver given, search both
self.find_analysis()
if not self.analysis:
raise Exception(
"FEM: No solver was given and either no active analysis "
"or no analysis at all or more than one analysis found!"
)
self.find_solver()
if not self.solver:
raise Exception("FEM: No solver found!")
if self.analysis.Document is not self.solver.Document:
raise Exception("FEM: The analysis and solver are not in the same document!")
if self.solver not in self.analysis.Group:
raise Exception("FEM: The solver is not part of the analysis Group!")
# print(self.solver)
# print(self.analysis)
if self.analysis and self.solver:
self.working_dir = ""
self.ccx_binary = ""
self.base_name = ""
self.results_present = False
if test_mode:
self.test_mode = True
self.ccx_binary_present = True
else:
self.test_mode = False
self.ccx_binary_present = False
self.result_object = None
else:
raise Exception(
"FEM: Something went wrong, the exception should have been raised earlier!"
)
def purge_results(self):
"""Remove all result objects and result meshes from an analysis group"""
from femresult.resulttools import purge_results as pr
pr(self.analysis)
def reset_mesh_purge_results_checked(self):
"""Reset mesh color, deformation and removes all result objects
if preferences to keep them is not set.
"""
self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General")
keep_results_on_rerun = self.fem_prefs.GetBool("KeepResultsOnReRun", False)
if not keep_results_on_rerun:
# we remove the result objects only, not the postprocessing ones.
# Reason: "Not keep results" means for the user override the data. For postprocessing
# this means keeping all filters, just change the data.
from femresult.resulttools import purge_result_objects as purge
purge(self.analysis)
def reset_all(self):
"""Reset mesh color, deformation and removes all result objects"""
self.purge_results()
def _get_several_member(self, obj_type):
return membertools.get_several_member(self.analysis, obj_type)
def find_analysis(self):
if FreeCAD.GuiUp:
self.analysis = FemGui.getActiveAnalysis()
if self.analysis:
return
found_analysis = False
# search in the active document
for m in FreeCAD.activeDocument().Objects:
if femutils.is_of_type(m, "Fem::FemAnalysis"):
if not found_analysis:
self.analysis = m
found_analysis = True
else:
self.analysis = None # more than one analysis
if self.analysis:
if FreeCAD.GuiUp:
FemGui.setActiveAnalysis(self.analysis)
def find_solver_analysis(self):
"""get the analysis group the solver belongs to"""
if self.solver.getParentGroup():
obj = self.solver.getParentGroup()
if femutils.is_of_type(obj, "Fem::FemAnalysis"):
self.analysis = obj
if FreeCAD.GuiUp:
FemGui.setActiveAnalysis(self.analysis)
def find_solver(self):
found_solver_for_use = False
for m in self.analysis.Group:
if femutils.is_of_type(m, "Fem::SolverCcxTools"):
# we are going to explicitly check for the ccx tools solver type only,
# thus it is possible to have lots of framework solvers inside the analysis anyway
# for some methods no solver is needed (purge_results) --> solver could be none
# analysis has one solver and no solver was set --> use the one solver
# analysis has more than one solver and no solver was set --> use solver none
# analysis has no solver --> use solver none
if not found_solver_for_use:
# no solver was found before
self.solver = m
found_solver_for_use = True
else:
# another solver was found --> We have more than one solver
# we do not know which one to use, so we use none !
self.solver = None
FreeCAD.Console.PrintLog(
"FEM: More than one solver in the analysis "
"and no solver given to analyze. "
"No solver is set!\n"
)
def update_objects(self):
## @var mesh
# mesh for the analysis
self.mesh = None
mesh, message = membertools.get_mesh_to_solve(self.analysis)
if mesh is not None:
self.mesh = mesh
else:
# the prerequisites will run anyway and they will print a message box anyway
# thus do not print one here, but print a console warning
FreeCAD.Console.PrintWarning(f"{message} The prerequisite check will fail.\n")
## @var members
# members of the analysis. All except the solver and the mesh
self.member = membertools.AnalysisMember(self.analysis)
def check_prerequisites(self):
FreeCAD.Console.PrintMessage("\n") # because of time print in separate line
FreeCAD.Console.PrintMessage("Check prerequisites...\n")
message = ""
# analysis
if not self.analysis:
message += "No active Analysis\n"
# solver
if not self.solver:
message += "No solver object defined in the analysis\n"
if not self.working_dir:
message += "Working directory not set\n"
if not os.path.isdir(self.working_dir):
message += f"Working directory '{self.working_dir}' doesn't exist."
from femtools.checksanalysis import check_member_for_solver_calculix
message += check_member_for_solver_calculix(
self.analysis, self.solver, self.mesh, self.member
)
return message
def set_base_name(self, base_name=None):
"""
Set base_name
Parameters
----------
base_name : str, optional
base_name base name of .inp/.frd file (without extension).
It is used to construct .inp file path that is passed to CalculiX ccx
"""
if base_name is None:
self.base_name = ""
else:
self.base_name = base_name
# Update inp file name
self.set_inp_file_name()
def set_inp_file_name(self, inp_file_name=None):
"""
Set inp file name. Normally inp file name is set by write_inp_file.
That name is also used to determine location and name of frd result file.
Parameters
----------
inp_file_name : str, optional
input file name path
"""
if inp_file_name is not None:
self.inp_file_name = inp_file_name
else:
self.inp_file_name = os.path.join(self.working_dir, (self.base_name + ".inp"))
def setup_working_dir(self, param_working_dir=None, create=False):
"""Set working dir for solver execution.
Parameters
----------
param_working_dir : str, optional
directory to be used for writing
create : bool, optional
Should the working directory be created if it does not exist
"""
self.working_dir = ""
# try to use given working dir or overwrite with solver working dir
fem_general_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General")
if param_working_dir is not None:
self.working_dir = param_working_dir
if femutils.check_working_dir(self.working_dir) is not True:
if create is True:
FreeCAD.Console.PrintMessage(
f"Dir given as parameter '{self.working_dir}' doesn't exist.\n"
)
else:
FreeCAD.Console.PrintError(
"Dir given as parameter '{}' doesn't exist "
"and create parameter is set to False.\n".format(self.working_dir)
)
self.working_dir = femutils.get_pref_working_dir(self.solver)
FreeCAD.Console.PrintMessage(
f"Dir '{self.working_dir}' will be used instead.\n"
)
elif fem_general_prefs.GetBool("OverwriteSolverWorkingDirectory", True) is False:
self.working_dir = self.solver.WorkingDir
if femutils.check_working_dir(self.working_dir) is not True:
if self.working_dir == "":
FreeCAD.Console.PrintError(
"Working Dir is set to be used from solver object "
"but Dir from solver object '{}' is empty.\n".format(self.working_dir)
)
else:
FreeCAD.Console.PrintError(
f"Dir from solver object '{self.working_dir}' doesn't exist.\n"
)
self.working_dir = femutils.get_pref_working_dir(self.solver)
FreeCAD.Console.PrintMessage(f"Dir '{self.working_dir}' will be used instead.\n")
else:
self.working_dir = femutils.get_pref_working_dir(self.solver)
# check working_dir exist, if not use a tmp dir and inform the user
if femutils.check_working_dir(self.working_dir) is not True:
FreeCAD.Console.PrintError(
f"Dir '{self.working_dir}' doesn't exist or cannot be created.\n"
)
self.working_dir = femutils.get_temp_dir(self.solver)
FreeCAD.Console.PrintMessage(f"Dir '{self.working_dir}' will be used instead.\n")
# Update inp file name
self.set_inp_file_name()
def write_inp_file(self):
# get mesh set data
# TODO use separate method for getting the mesh set data
from femmesh import meshsetsgetter
meshdatagetter = meshsetsgetter.MeshSetsGetter(
self.analysis,
self.solver,
self.mesh,
membertools.AnalysisMember(self.analysis),
)
# save the sets into the member objects of the instanz meshdatagetter
meshdatagetter.get_mesh_sets()
# write input file
import femsolver.calculix.writer as iw
self.inp_file_name = ""
try:
inp_writer = iw.FemInputWriterCcx(
self.analysis,
self.solver,
self.mesh,
meshdatagetter.member,
self.working_dir,
meshdatagetter.mat_geo_sets,
)
self.inp_file_name = inp_writer.write_solver_input()
except Exception:
FreeCAD.Console.PrintError(
f"Unexpected error when writing CalculiX input file: {sys.exc_info()[1]}\n"
)
raise
def setup_ccx(self, ccx_binary=None, ccx_binary_sig="CalculiX"):
"""Set Calculix binary path and validate its execution.
Parameters
----------
ccx_binary : str, optional
It defaults to `None`. The path to the `ccx` binary. If it is `None`,
the path is guessed.
ccx_binary_sig : str, optional
Defaults to 'CalculiX'. Expected output from `ccx` when run empty.
"""
error_title = self.tr("No or wrong CalculiX binary ccx")
self.ccx_binary = ccx_binary
if self.ccx_binary is None:
self.ccx_binary = FreeCAD.ParamGet(
"User parameter:BaseApp/Preferences/Mod/Fem/Ccx"
).GetString("ccxBinaryPath", "")
if not self.ccx_binary:
# search in system
self.ccx_binary = shutil.which("ccx")
else:
# check user defined path
self.ccx_binary = shutil.which(self.ccx_binary)
if self.ccx_binary is None:
raise FileNotFoundError(
"CalculiX binary not found\n"
"Install CalculiX or set path to binary in FEM user preferences"
)
ccx_stdout = None
ccx_stderr = None
try:
p = subprocess.Popen(
[self.ccx_binary],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
startupinfo=femutils.startProgramInfo(""),
)
ccx_stdout, ccx_stderr = p.communicate()
if ccx_binary_sig in str(ccx_stdout):
self.ccx_binary_present = True
else:
error_message = self.tr("FEM: wrong ccx binary")
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, error_title, error_message)
FreeCAD.Console.PrintError(error_message)
# TODO: I'm still able to break it.
# If user doesn't give a file but a path without a file or
# a file which is not a binary no exception at all is raised.
except OSError as e:
FreeCAD.Console.PrintError(f"{e}\n")
if e.errno == 2:
error_message = self.tr(
"FEM: CalculiX binary ccx '{}' not found. "
"Please set the CalculiX binary ccx path in "
"FEM preferences tab CalculiX."
).format(self.ccx_binary)
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, error_title, error_message)
FreeCAD.Console.PrintError(error_message)
except Exception as e:
FreeCAD.Console.PrintError(f"{e}\n")
error_message = self.tr(
"FEM: CalculiX ccx '{}' output '{}' doesn't "
"contain expected phrase '{}'. "
"There are some problems when running the ccx binary. "
"Check if ccx runs standalone without FreeCAD."
).format(self.ccx_binary, ccx_stdout, ccx_binary_sig)
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, error_title, error_message)
FreeCAD.Console.PrintError(error_message)
def start_ccx(self):
import multiprocessing
self.ccx_stdout = ""
self.ccx_stderr = ""
ont_backup = os.environ.get("OMP_NUM_THREADS")
self.ccx_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Ccx")
# If number of CPU's specified
num_cpu_pref = self.ccx_prefs.GetInt("AnalysisNumCPUs", 1)
if not ont_backup:
ont_backup = str(num_cpu_pref)
if num_cpu_pref > 1:
# If user picked a number use that instead
_env = os.putenv("OMP_NUM_THREADS", str(num_cpu_pref))
else:
_env = os.putenv("OMP_NUM_THREADS", str(multiprocessing.cpu_count()))
# change cwd because ccx may crash if directory has no write permission
# there is also a limit of the length of file names so jump to the document directory
cwd = QtCore.QDir.currentPath()
f = QtCore.QFileInfo(self.inp_file_name)
QtCore.QDir.setCurrent(f.path())
p = subprocess.Popen(
[self.ccx_binary, "-i ", f.baseName()],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
env=_env,
)
self.ccx_stdout, self.ccx_stderr = p.communicate()
self.ccx_stdout = self.ccx_stdout.decode()
self.ccx_stderr = self.ccx_stderr.decode()
os.putenv("OMP_NUM_THREADS", ont_backup)
QtCore.QDir.setCurrent(cwd)
return p.returncode
def get_ccx_version(self):
self.setup_ccx()
import re
from platform import system
ccx_stdout = None
ccx_stderr = None
# Now extract the version number
p = subprocess.Popen(
[self.ccx_binary, "-v"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
startupinfo=femutils.startProgramInfo(""),
)
ccx_stdout, ccx_stderr = p.communicate()
ccx_stdout = ccx_stdout.decode()
m = re.search(r"(\d+).(\d+)", ccx_stdout)
return (int(m.group(1)), int(m.group(2)))
def ccx_run(self):
ret_code = None
FreeCAD.Console.PrintMessage("\n") # because of time print in separate line
FreeCAD.Console.PrintMessage("CalculiX solver run...\n")
if self.test_mode:
FreeCAD.Console.PrintError("CalculiX can not be run if test_mode is True.\n")
return
self.setup_ccx()
if self.ccx_binary_present is False:
error_message = (
"FEM: CalculiX binary ccx '{}' not found. "
"Please set the CalculiX binary ccx path in FEM preferences tab CalculiX.\n".format(
self.ccx_binary
)
)
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, "No CalculiX binary ccx", error_message)
return
progress_bar = FreeCAD.Base.ProgressIndicator()
progress_bar.start("Everything seems fine. CalculiX ccx will be executed ...", 0)
ret_code = self.start_ccx()
self.finished.emit(ret_code)
progress_bar.stop()
if ret_code or self.ccx_stderr:
if ret_code == 201 and self.solver.AnalysisType == "check":
FreeCAD.Console.PrintMessage(
"It seems we run into NOANALYSIS problem, "
"thus workaround for wrong exit code for *NOANALYSIS check "
"and set ret_code to 0.\n"
)
# https://forum.freecad.org/viewtopic.php?f=18&t=31303&start=10#p260743
ret_code = 0
else:
FreeCAD.Console.PrintError(f"CalculiX failed with exit code {ret_code}\n")
FreeCAD.Console.PrintMessage("--------start of stderr-------\n")
FreeCAD.Console.PrintMessage(self.ccx_stderr)
FreeCAD.Console.PrintMessage("--------end of stderr---------\n")
FreeCAD.Console.PrintMessage("--------start of stdout-------\n")
FreeCAD.Console.PrintMessage(self.ccx_stdout)
FreeCAD.Console.PrintMessage("\n--------end of stdout---------\n")
FreeCAD.Console.PrintMessage("--------start problems---------\n")
self.has_no_material_assigned()
self.has_nonpositive_jacobians()
FreeCAD.Console.PrintMessage("\n--------end problems---------\n")
else:
# remove highlighted nodes, if any
if FreeCAD.GuiUp:
self.mesh.ViewObject.HighlightedNodes = []
FreeCAD.Console.PrintMessage("CalculiX finished without error.\n")
return ret_code
def run(self):
self.update_objects()
self.setup_working_dir()
message = self.check_prerequisites()
if message:
text = "CalculiX can not be started due to missing prerequisites:\n"
error_app = f"{text}{message}"
error_gui = f"{text}\n{message}"
FreeCAD.Console.PrintError(error_app)
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, "Missing prerequisite", error_gui)
return False
else:
self.write_inp_file()
if self.inp_file_name == "":
error_message = "Error on writing CalculiX input file.\n"
FreeCAD.Console.PrintError(error_message)
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, "Error", error_message)
return False
else:
FreeCAD.Console.PrintLog("Writing CalculiX input file completed.\n")
ret_code = self.ccx_run()
if ret_code is None:
error_message = "CalculiX has not been run. The CalculiX binary search returned: {}.\n".format(
self.ccx_binary_present
)
FreeCAD.Console.PrintError(error_message)
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, "Error", error_message)
return False
if ret_code != 0:
error_message = f"CalculiX finished with error {ret_code}.\n"
FreeCAD.Console.PrintError(error_message)
if FreeCAD.GuiUp:
QtGui.QMessageBox.critical(None, "Error", error_message)
return False
else:
FreeCAD.Console.PrintLog("Try to read result files\n")
self.load_results()
# TODO: output an error message if there where problems reading the results
return True
def has_no_material_assigned(self):
if " *ERROR in calinput: no material was assigned" in self.ccx_stdout:
without_material_elements = []
without_material_elemnodes = []
for line in self.ccx_stdout.splitlines():
if "to element" in line:
# print(line)
# print(line.split())
non_mat_ele = int(line.split()[2])
# print(non_mat_ele)
if non_mat_ele not in without_material_elements:
without_material_elements.append(non_mat_ele)
for e in without_material_elements:
for n in self.mesh.FemMesh.getElementNodes(e):
without_material_elemnodes.append(n)
without_material_elements = sorted(without_material_elements)
without_material_elemnodes = sorted(without_material_elemnodes)
command_for_withoutmatnodes = "without_material_elemnodes = {}".format(
without_material_elemnodes
)
command_to_highlight = (
"Gui.ActiveDocument.{}.HighlightedNodes = without_material_elemnodes".format(
self.mesh.Name
)
)
# some output for the user
FreeCAD.Console.PrintError(
"\n\nCalculiX returned an error due to elements without materials.\n"
)
FreeCAD.Console.PrintMessage(
f"without_material_elements = {without_material_elements}\n"
)
FreeCAD.Console.PrintMessage(command_for_withoutmatnodes + "\n")
if FreeCAD.GuiUp:
import FreeCADGui
# with this the list without_material_elemnodes
# will be available for further user interaction
FreeCADGui.doCommand(command_for_withoutmatnodes)
FreeCAD.Console.PrintMessage("\n")
FreeCADGui.doCommand(command_to_highlight)
FreeCAD.Console.PrintMessage(
"\nFollowing some commands to copy. "
"They will highlight the elements without materials "
"or to reset the highlighted nodes:\n"
)
FreeCAD.Console.PrintMessage(command_to_highlight + "\n")
# command to reset the Highlighted Nodes
FreeCAD.Console.PrintMessage(
f"Gui.ActiveDocument.{self.mesh.Name}.HighlightedNodes = []\n\n"
)
return True
else:
return False
def has_nonpositive_jacobians(self):
if "*ERROR in e_c3d: nonpositive jacobian" in self.ccx_stdout:
nonpositive_jacobian_elements = []
nonpositive_jacobian_elenodes = []
for line in self.ccx_stdout.splitlines():
if "determinant in element" in line:
# print(line)
# print(line.split())
non_posjac_ele = int(line.split()[3])
# print(non_posjac_ele)
if non_posjac_ele not in nonpositive_jacobian_elements:
nonpositive_jacobian_elements.append(non_posjac_ele)
for e in nonpositive_jacobian_elements:
for n in self.mesh.FemMesh.getElementNodes(e):
nonpositive_jacobian_elenodes.append(n)
nonpositive_jacobian_elements = sorted(nonpositive_jacobian_elements)
nonpositive_jacobian_elenodes = sorted(nonpositive_jacobian_elenodes)
command_for_nonposjacnodes = "nonpositive_jacobian_elenodes = {}".format(
nonpositive_jacobian_elenodes
)
command_to_highlight = (
"Gui.ActiveDocument.{}.HighlightedNodes = nonpositive_jacobian_elenodes".format(
self.mesh.Name
)
)
# some output for the user
FreeCAD.Console.PrintError(
"\n\nCalculiX returned an error due to nonpositive jacobian elements.\n"
)
FreeCAD.Console.PrintMessage(
f"nonpositive_jacobian_elements = {nonpositive_jacobian_elements}\n"
)
FreeCAD.Console.PrintMessage(command_for_nonposjacnodes + "\n")
if FreeCAD.GuiUp:
import FreeCADGui
# with this the list nonpositive_jacobian_elenodes
# will be available for further user interaction
FreeCADGui.doCommand(command_for_nonposjacnodes)
FreeCAD.Console.PrintMessage("\n")
FreeCADGui.doCommand(command_to_highlight)
FreeCAD.Console.PrintMessage(
"\nFollowing some commands to copy. "
"They highlight the nonpositive jacobians "
"or to reset the highlighted nodes:\n"
)
FreeCAD.Console.PrintMessage(command_to_highlight + "\n")
# command to reset the Highlighted Nodes
FreeCAD.Console.PrintMessage(
f"Gui.ActiveDocument.{self.mesh.Name}.HighlightedNodes = []\n\n"
)
return True
else:
return False
def load_results(self):
FreeCAD.Console.PrintMessage("\n") # because of time print in separate line
FreeCAD.Console.PrintMessage("CalculiX read results...\n")
self.results_present = False
self.load_results_ccxfrd()
self.load_results_ccxdat()
self.analysis.Document.recompute()
def load_results_ccxfrd(self):
"""Load results of ccx calculations from .frd file."""
import feminout.importCcxFrdResults as importCcxFrdResults
frd_result_file = os.path.splitext(self.inp_file_name)[0] + ".frd"
if os.path.isfile(frd_result_file):
importCcxFrdResults.importFrd(
frd_result_file, self.analysis, "CCX_", self.solver.AnalysisType
)
for m in self.analysis.Group:
if m.isDerivedFrom("Fem::FemResultObject"):
self.results_present = True
break
else:
if self.solver.AnalysisType == "check":
for m in self.analysis.Group:
if m.isDerivedFrom("Fem::FemMeshObjectPython"):
# we have no result object but a mesh object
# this happens in NOANALYSIS mode
break
else:
FreeCAD.Console.PrintError("FEM: No result object in active Analysis.\n")
else:
FreeCAD.Console.PrintError(f"FEM: No frd result file found at {frd_result_file}\n")
def load_results_ccxdat(self):
"""Load results of ccx calculations from .dat file."""
import feminout.importCcxDatResults as importCcxDatResults
dat_result_file = os.path.splitext(self.inp_file_name)[0] + ".dat"
mode_frequencies = None
dat_content = None
if os.path.isfile(dat_result_file):
mode_frequencies = importCcxDatResults.import_dat(dat_result_file, self.analysis)
dat_file = open(dat_result_file)
dat_content = dat_file.read()
dat_file.close()
else:
FreeCAD.Console.PrintError(f"FEM: No dat result file found at {dat_result_file}\n")
if mode_frequencies:
# print(mode_frequencies)
for m in self.analysis.Group:
if m.isDerivedFrom("Fem::FemResultObject") and m.Eigenmode > 0:
for mf in mode_frequencies:
if m.Eigenmode == mf["eigenmode"]:
m.EigenmodeFrequency = mf["frequency"]
if dat_content:
# print(dat_content)
dat_text_obj = self.analysis.Document.addObject("App::TextDocument", "ccx_dat_file")
dat_text_obj.Text = dat_content
dat_text_obj.setPropertyStatus("Text", "ReadOnly") # set property editor readonly
if FreeCAD.GuiUp:
dat_text_obj.ViewObject.ReadOnly = True # set editor view readonly
self.analysis.addObject(dat_text_obj)
class CcxTools(FemToolsCcx):
def __init__(self, solver=None):
FemToolsCcx.__init__(self, None, solver)
## @}
|