From: gdd Date: Mon, 27 Sep 2010 15:17:08 +0000 (+0000) Subject: Merge from branch BR_pypkg (python packaging) X-Git-Tag: V5_1_5a1~5 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=d47a7b9c3a7eda90c2288e578e2d2b39dc6e6c97;p=modules%2Fgeom.git Merge from branch BR_pypkg (python packaging) --- diff --git a/configure.ac b/configure.ac index ef4855a21..e5cd6f127 100644 --- a/configure.ac +++ b/configure.ac @@ -316,6 +316,13 @@ echo CHECK_HTML_GENERATORS +echo +echo --------------------------------------------- +echo testing sphinx +echo --------------------------------------------- +echo +CHECK_SPHINX + echo echo --------------------------------------------- echo Testing Kernel @@ -333,7 +340,7 @@ echo echo Configure if test "${gui_ok}" = "yes"; then - variables="cc_ok lex_yacc_ok python_ok swig_ok threads_ok OpenGL_ok qt_ok vtk_ok hdf5_ok omniORB_ok boost_ok occ_ok doxygen_ok graphviz_ok Kernel_ok gui_ok" + variables="cc_ok lex_yacc_ok python_ok swig_ok threads_ok OpenGL_ok qt_ok vtk_ok hdf5_ok omniORB_ok boost_ok occ_ok doxygen_ok graphviz_ok sphinx_ok Kernel_ok gui_ok" elif test "${SalomeGUI_need}" != "no"; then variables="cc_ok lex_yacc_ok python_ok swig_ok threads_ok vtk_ok hdf5_ok omniORB_ok boost_ok occ_ok doxygen_ok graphviz_ok Kernel_ok gui_ok" else @@ -381,6 +388,7 @@ AC_OUTPUT([ \ bin/Makefile \ GEOM_version.h \ doc/Makefile \ + doc/docutils/Makefile \ doc/salome/Makefile \ doc/salome/gui/Makefile \ doc/salome/gui/GEOM/Makefile \ @@ -415,6 +423,8 @@ AC_OUTPUT([ \ src/GEOM_I_Superv/Makefile \ src/GEOM_SWIG/Makefile \ src/GEOM_SWIG_WITHIHM/Makefile \ + src/GEOM_PY/Makefile \ + src/GEOM_PY/structelem/Makefile \ src/GenerationGUI/Makefile \ src/GroupGUI/Makefile \ src/IGESExport/Makefile \ diff --git a/doc/Makefile.am b/doc/Makefile.am index f40fb81a5..6cf4c8ea2 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -24,7 +24,7 @@ # $Header$ # source path # -SUBDIRS = salome +SUBDIRS = salome docutils usr_docs: (cd salome && $(MAKE) $(AM_MAKEFLAGS) usr_docs) diff --git a/doc/docutils/Makefile.am b/doc/docutils/Makefile.am new file mode 100644 index 000000000..a85de6cef --- /dev/null +++ b/doc/docutils/Makefile.am @@ -0,0 +1,92 @@ +# -*- coding: iso-8859-1 -*- +# Copyright (C) 2007-2010 CEA/DEN, EDF R&D, OPEN CASCADE +# +# Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +# CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +# +# This library 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. +# +# This library 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 this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com + +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +pydocdir = $(docdir)/tui/GEOM/docutils + +.PHONY : latex + +if SPHINX_IS_OK + +pydoc_DATA=html/index.html +html/index.html:$(RSTFILES) + make htm + +endif + +EXTRA_DIST+= html + +SPHINXOPTS = +SOURCEDIR = $(srcdir) +SPHINXBUILD = sphinx-build +PAPEROPT_a4 = -D latex_paper_size=a4 +ALLSPHINXOPTS = -d doctrees $(PAPEROPT_a4) $(SPHINXOPTS) $(SOURCEDIR) + +SPHINX_PYTHONPATH = $(prefix)/lib/python$(PYTHON_VERSION)/site-packages/salome:$(KERNEL_ROOT_DIR)/bin/salome:$(KERNEL_ROOT_DIR)/lib/python$(PYTHON_VERSION)/site-packages/salome:$(OMNIORB_ROOT)/lib/python$(PYTHON_VERSION)/site-packages + +SPHINX_LD_LIBRARY_PATH = $(OMNIORB_ROOT)/lib + +htm: + mkdir -p html doctrees + PYTHONPATH=$(SPHINX_PYTHONPATH):${PYTHONPATH}; \ + LD_LIBRARY_PATH=$(SPHINX_LD_LIBRARY_PATH):${LD_LIBRARY_PATH}; \ + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) html + @echo + @echo "Build finished. The HTML pages are in html." + +latex: + mkdir -p latex doctrees + PYTHONPATH=$(SPHINX_PYTHONPATH):${PYTHONPATH}; \ + LD_LIBRARY_PATH=$(SPHINX_LD_LIBRARY_PATH):${LD_LIBRARY_PATH}; \ + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) latex + @echo + @echo "Build finished; the LaTeX files are in latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +html: + mkdir -p $@ + +RSTFILES= \ + index.rst \ + overview.rst \ + docapi.rst + +EXTRA_DIST+= $(RSTFILES) + +EXTRA_DIST+= \ + conf.py + +install-data-local: + $(INSTALL) -d $(pydocdir) + if test -d "html"; then b=; else b="$(srcdir)/"; fi; \ + cp -rf $$b"html"/* $(pydocdir) ; \ + if test -f $$b"latex"/geompy.pdf; then cp -f $$b"latex"/geompy.pdf $(pydocdir) ; fi; + +uninstall-local: + chmod -R +w $(pydocdir) + rm -rf $(pydocdir)/* + +clean-local: + -rm -rf html latex doctrees + if test -d "html"; then rm -rf html ; fi diff --git a/doc/docutils/conf.py b/doc/docutils/conf.py new file mode 100644 index 000000000..5afa71b2f --- /dev/null +++ b/doc/docutils/conf.py @@ -0,0 +1,200 @@ +# -*- coding: iso-8859-1 -*- +# +# yacs documentation build configuration file, created by +# sphinx-quickstart on Fri Aug 29 09:57:25 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Uncomment the following line to build the links with Python documentation +# (you might need to set http_proxy environment variable for this to work) +#extensions += ['sphinx.ext.intersphinx'] + +# Intersphinx mapping to add links to modules and objects in the Python +# standard library documentation +intersphinx_mapping = {'http://docs.python.org': None} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'GEOM python packages' +copyright = '2010 EDF R&D' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '5.1.4' +# The full version, including alpha/beta/rc tags. +release = '5.1.4' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['.build','ref','images','CVS','.svn'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' +#html_theme = 'nature' +#html_theme = 'agogo' +#html_theme = 'sphinxdoc' +#html_theme = 'omadoc' + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = ['themes'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +html_use_modindex = False + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/. +html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'geompydoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +latex_paper_size = 'a4' + +# The font size ('10pt', '11pt' or '12pt'). +latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', 'geompy.tex', 'Documentation of the GEOM python packages', 'EDF R\&D', 'manual') +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = '../salome/tui/images/head.png' + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = True + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +latex_use_modindex = False diff --git a/doc/docutils/docapi.rst b/doc/docutils/docapi.rst new file mode 100644 index 000000000..20a52ca80 --- /dev/null +++ b/doc/docutils/docapi.rst @@ -0,0 +1,43 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + Documentation of the programming interface (API) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +This section describes the python packages and modules of the +``salome.geom`` python package. The main part is generated from the +code documentation included in source python files. + +:mod:`salome.geom` -- Package containing the GEOM python utilities +================================================================== + +:mod:`geomtools` -- Tools to access GEOM engine and objects +----------------------------------------------------------- + +.. automodule:: salome.geom.geomtools + :members: + +:mod:`structelem` -- Structural elements package +------------------------------------------------ + +.. automodule:: salome.geom.structelem + +.. autoclass:: StructuralElementManager + :members: + +.. autoclass:: StructuralElement + :members: + +:mod:`structelem.parts` -- Structural element parts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: salome.geom.structelem.parts + :members: + :undoc-members: + :show-inheritance: + +:mod:`structelem.orientation` -- Structural element orientation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: salome.geom.structelem.orientation + :members: + :undoc-members: diff --git a/doc/docutils/images/salome-geom-structuralelements.png b/doc/docutils/images/salome-geom-structuralelements.png new file mode 100644 index 000000000..6ed41b12f Binary files /dev/null and b/doc/docutils/images/salome-geom-structuralelements.png differ diff --git a/doc/docutils/index.rst b/doc/docutils/index.rst new file mode 100644 index 000000000..81e4f24aa --- /dev/null +++ b/doc/docutils/index.rst @@ -0,0 +1,14 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + Documentation of the GEOM python packages +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +Main documentation +================== + +.. toctree:: + :maxdepth: 3 + + overview.rst + docapi.rst + diff --git a/doc/docutils/overview.rst b/doc/docutils/overview.rst new file mode 100644 index 000000000..3ca3a4b20 --- /dev/null +++ b/doc/docutils/overview.rst @@ -0,0 +1,38 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +General presentation of the GEOM python package +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +The GEOM python package essentially contains: + +* The visualization of structural elements: a function to create + geometrical 3D representations of mechanical models called + "structural elements". + +Note that these functions either encapsulate the python programming +interface of GEOM core (the CORBA or SWIG interfaces for example) or +extend existing utilities as the ``geompy.py`` module. + +The functions are distributed in the python package +``salome.geom``. For example, the usage of the visualization of +structural elements can be appreciated with this set of instructions: + +.. code-block:: python + + from salome.geom.structelem import TEST_StructuralElement + TEST_StructuralElement() + +This creates the geometrical objects displayed in the study below: + +.. image:: /images/salome-geom-structuralelements.png + :align: center + +The specification of the programming interface of this package is +detailled in the part :doc:`Documentation of the programming interface +(API)` of this documentation. + +.. note:: + The main package ``salome`` contains other sub-packages that are + distributed with the other SALOME modules. For example, the KERNEL + module provides the python package ``salome.kernel`` and SMESH the + package ``salome.smesh``. diff --git a/doc/salome/gui/GEOM/images/salome-geom-structuralelements.png b/doc/salome/gui/GEOM/images/salome-geom-structuralelements.png new file mode 100644 index 000000000..6ed41b12f Binary files /dev/null and b/doc/salome/gui/GEOM/images/salome-geom-structuralelements.png differ diff --git a/doc/salome/gui/GEOM/input/geompypkg.doc b/doc/salome/gui/GEOM/input/geompypkg.doc new file mode 100644 index 000000000..bd507959a --- /dev/null +++ b/doc/salome/gui/GEOM/input/geompypkg.doc @@ -0,0 +1,44 @@ +/*! + +\page geompypkg_page Programming Interface of GEOM python package + +Sorry, but the documentation is not available yet in doxygen format. + +Fortunately, a documentation exists in restructured format and then +can be generated here using sphinx, in the expectative of the doxygen +version. This documentation is available +here. + +Here is a sample generated from the doxygen format: + +The GEOM python package essentially contains: + +
    +
  • The visualization of structural elements: a function to create + geometrical 3D representations of mechanical models called + "structural elements". +
+ +For details, you should refer to the complete + documentation of the GEOM +python packages generated with sphinx from rst text files. + +Note that these functions either encapsulate the python programming +interface of GEOM core (the CORBA or SWIG interfaces for example) or +extend existing utilities as the ``geompy.py`` module. + +The functions are distributed in the python package +``salome.geom``. For example, the usage of the visualization of +structural elements can be appreciated with this set of instructions: + +\code + + from salome.geom.structelem import TEST_StructuralElement + TEST_StructuralElement() +\endcode + +This creates the geometrical objects displayed in the study below: + +\image html salome-geom-structuralelements.png "Example of Geometry created from structural elements" + +*/ diff --git a/doc/salome/gui/GEOM/input/index.doc b/doc/salome/gui/GEOM/input/index.doc index d28e8c343..f612e7108 100644 --- a/doc/salome/gui/GEOM/input/index.doc +++ b/doc/salome/gui/GEOM/input/index.doc @@ -23,6 +23,9 @@ various algorithms; Almost all geometry module functionalities are accessible via \subpage geompy_page "Geometry module Python Interface" +\n Have a look also at the + documentation of the GEOM python packages + \image html image3.png "Example of Geometry module usage for engineering tasks" diff --git a/src/GEOM_PY/Makefile.am b/src/GEOM_PY/Makefile.am new file mode 100644 index 000000000..a3b6695e7 --- /dev/null +++ b/src/GEOM_PY/Makefile.am @@ -0,0 +1,27 @@ +# Copyright (C) 2007-2010 CEA/DEN, EDF R&D, OPEN CASCADE +# +# This library 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. +# +# This library 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 this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +SUBDIRS= structelem + +salomepypkgdir = $(salomepythondir)/salome/geom +salomepypkg_PYTHON = \ + __init__.py \ + geomtools.py diff --git a/src/GEOM_PY/__init__.py b/src/GEOM_PY/__init__.py new file mode 100644 index 000000000..5d3edfa55 --- /dev/null +++ b/src/GEOM_PY/__init__.py @@ -0,0 +1 @@ +# -*- coding: iso-8859-1 -*- diff --git a/src/GEOM_PY/geomtools.py b/src/GEOM_PY/geomtools.py new file mode 100644 index 000000000..9693525f8 --- /dev/null +++ b/src/GEOM_PY/geomtools.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +""" +This module provides tools to facilitate the use of geom engine and geom +objects in Salome. +""" + +import salome +GEOM = None # GEOM module is loaded only when needed + +from salome.kernel.logger import Logger +from salome.kernel import termcolor +logger = Logger("salome.geom.geomtools", color = termcolor.RED) + +from salome.kernel.studyedit import getActiveStudyId, getStudyEditor + +_geompys = {} + +def getGeompy(studyId = None): + """ + Return an object behaving exactly like geompy module, except that it is + associated with the study `studyId`. If `studyId` is :const:`None`, return + a pseudo geompy object for the current study. + """ + # We can't use geompy module because it initializes GEOM with + # salome.myStudy, which may not exist. So we use this trick to create + # a pseudo geompy module. + salome.salome_init() + if studyId is None: + studyId = getActiveStudyId() + if not _geompys.has_key(studyId): + import geompyDC + _geompys[studyId] = salome.lcc.FindOrLoadComponent("FactoryServer", + "GEOM") + _geompys[studyId].ShapeType = geompyDC.ShapeType + _geompys[studyId].GEOM = geompyDC.GEOM + _geompys[studyId].kind = geompyDC.kind + _geompys[studyId].info = geompyDC.info + _geompys[studyId].PackData = geompyDC.PackData + _geompys[studyId].ReadTexture = geompyDC.ReadTexture + study = salome.myStudyManager.GetStudyByID(studyId) + _geompys[studyId].init_geom(study) + return _geompys[studyId] + + +class GeomStudyTools: + """ + This class provides several methods to manipulate geom objects in Salome + study. The parameter `studyEditor` defines a + :class:`~salome.kernel.studyedit.StudyEditor` object used to access the study. If + :const:`None`, the method returns a :class:`~salome.kernel.studyedit.StudyEditor` + object on the current study. + + .. attribute:: editor + + This instance attribute contains the underlying + :class:`~salome.kernel.studyedit.StudyEditor` object. It can be used to access + the study but the attribute itself should not be modified. + + """ + + def __init__(self, studyEditor = None): + global GEOM + if GEOM is None: + GEOM = __import__("GEOM") + if studyEditor is None: + studyEditor = getStudyEditor() + self.editor = studyEditor + + def displayShapeByName(self, shapeName, color = None): + """ + Display the geometrical shape whose name in the study is `shapeName`. + + :type shapeName: string + :param shapeName: name of the geometrical shape + + :type color: tuple (triplet) + :param color: RGB components of the color of the shape + + :return: True if the shape was found, False otherwise + """ + logger.debug("displayShapeByName in PAL: %s with color %s" % + (shapeName, color)) + listSO = self.editor.study.FindObjectByName(shapeName, "GEOM") + for sObj in listSO: + entry = sObj.GetID() + geomObj = self.editor.getOrLoadObject(sObj) + if geomObj: + shape = geomObj._narrow(GEOM.GEOM_Object) + if shape: + geomgui = salome.ImportComponentGUI("GEOM") + geomgui.createAndDisplayGO(entry) + geomgui.setDisplayMode(entry, 1) + if color is not None: + geomgui.setColor(entry, color[0], color[1], color[2]) + return True + return False diff --git a/src/GEOM_PY/structelem/Makefile.am b/src/GEOM_PY/structelem/Makefile.am new file mode 100644 index 000000000..cea34db04 --- /dev/null +++ b/src/GEOM_PY/structelem/Makefile.am @@ -0,0 +1,26 @@ +# Copyright (C) 2007-2010 CEA/DEN, EDF R&D, OPEN CASCADE +# +# This library 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. +# +# This library 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 this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +include $(top_srcdir)/adm_local/unix/make_common_starter.am + +salomepypkgdir = $(salomepythondir)/salome/geom/structelem +salomepypkg_PYTHON = \ + __init__.py \ + parts.py \ + orientation.py diff --git a/src/GEOM_PY/structelem/__init__.py b/src/GEOM_PY/structelem/__init__.py new file mode 100644 index 000000000..e713832a5 --- /dev/null +++ b/src/GEOM_PY/structelem/__init__.py @@ -0,0 +1,486 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +""" +This package is used to create and visualize structural elements. It contains +three modules: + +* This module :mod:`salome.geom.structelem` defines the main classes + :class:`StructuralElement` and :class:`StructuralElementManager` that can be + directly used to build structural elements. +* The module :mod:`salome.geom.structelem.parts` defines the classes corresponding to + the different parts (beams, grids, etc.) that make up a structural element. + It is used to build the geometric shapes in the structural element. +* The module :mod:`salome.geom.structelem.orientation` defines the classes that are + used to compute the orientation of the structural element parts and to build + the corresponding markers. + +A structural element is a set of geometric shapes (beams, grids, etc.) that +are built semi-automatically along a set of geometric primitives (edges for +instance). They are visualized with the same color as their base primitives in +the geom viewer. + +Structural elements are generally created by the +:class:`StructuralElementManager` class, from a list of commands describing +the element to create. + +Example:: + + commandList = [('VisuPoutreGenerale', {'Group_Maille': 'Edge_1'}), + ('VisuBarreCercle', + {'R': 30, 'Group_Maille': 'Edge_1', 'EP': 15}), + ] + + structElemManager = StructuralElementManager() + elem = structElemManager.createElement(commandList) + elem.display() + salome.sg.updateObjBrowser(True) + +""" + +import types + +import salome + +from salome.kernel.logger import Logger +from salome.kernel import termcolor +logger = Logger("salome.geom.structelem", color = termcolor.RED) +from salome.kernel.studyedit import getStudyEditor + +__all__ = ["parts", "orientation"] + +from salome.geom.structelem import parts +from salome.geom.structelem.parts import InvalidParameterError + +class StructuralElementManager: + """ + This class manages the structural elements in the study. It is used to + create a new structural element from a list of commands. The parameter + `studyId` defines the ID of the study in which the manager will create + structural elements. If it is :const:`None` or not specified, it will use + the ID of the current study as defined by + :func:`salome.kernel.studyedit.getActiveStudyId` function. + """ + def __init__(self, studyId = None): + self._studyEditor = getStudyEditor(studyId) + + def createElement(self, commandList): + """ + Create a structural element from the list of commands `commandList`. + Each command in this list represent a part of the structural element, + that is a specific kind of shape (circular beam, grid, etc.) + associated with one or several geometrical primitives. A command must + be a tuple. The first element is the structural element part class + name or alias name. The second element is a dictionary containing the + parameters describing the part. Valid class names are all the classes + defined in the module :mod:`~salome.geom.structelem.parts` and inheriting + class :class:`~parts.StructuralElementPart`. There are also several + aliases for backward compatibility. Here is the complete list: + + * :class:`~parts.GeneralBeam` + * :class:`~parts.CircularBeam` + * :class:`~parts.RectangularBeam` + * :class:`~parts.ThickShell` + * :class:`~parts.Grid` + + * :func:`~parts.VisuPoutreGenerale` (alias for + :class:`~parts.GeneralBeam`) + * :func:`~parts.VisuPoutreCercle` (alias for + :class:`~parts.CircularBeam`) + * :func:`~parts.VisuPoutreRectangle` (alias for + :class:`~parts.RectangularBeam`) + * :func:`~parts.VisuBarreGenerale` (alias for + :class:`~parts.GeneralBeam`) + * :func:`~parts.VisuBarreRectangle` (alias for + :class:`~parts.RectangularBeam`) + * :func:`~parts.VisuBarreCercle` (alias for + :class:`~parts.CircularBeam`) + * :func:`~parts.VisuCable` (alias for :class:`~parts.CircularBeam`) + * :func:`~parts.VisuCoque` (alias for :class:`~parts.ThickShell`) + * :func:`~parts.VisuGrille` (alias for :class:`~parts.Grid`) + + * ``Orientation``: This identifier is used to specify the orientation + of one or several 1D structural element parts (i.e. beams). The + parameters are described in class + :class:`~orientation.Orientation1D`. + + The valid parameters in the dictionary depend on the type of the + structural element part, and are detailed in the documentation of + the corresponding class. The only parameter that is common to all the + classes is "MeshGroups" (that can also be named "Group_Maille"). It + defines the name of the geometrical object(s) in the study that will + be used as primitives to build the structural element part. This + parameter can be either a list of strings or a single string with + comma separated names. + """ + logger.debug("StructuralElementManager.createElement: START") + logger.debug("Command list: %s" % commandList) + + element = StructuralElement(self._studyEditor.studyId) + orientationCmdList = [] + for command in commandList: + (parttype, parameters) = command + if parttype == "Orientation": + orientationCmdList += [command] + elif parttype not in dir(parts): + logger.warning('Invalid structural element part name "%s"' + ' in command %s, this command will be ' + 'ignored.' % (parttype, command)) + else: + (meshGroupList, newparams) = self._extractMeshGroups(command) + for meshGroup in meshGroupList: + # Get the geometrical primitive object + groupSObj = self._studyEditor.study.FindObject(meshGroup) + groupGeomObj = None + if groupSObj is not None: + groupGeomObj = \ + self._studyEditor.getOrLoadObject(groupSObj) + if groupGeomObj is None: + logger.error("Can't get geom object corresponding to " + 'mesh group "%s", structural element ' + "part %s will not be built." % + (groupName, part)) + continue + + # Create the part + try: + part = parts.__dict__[parttype]( + self._studyEditor.studyId, meshGroup, + groupGeomObj, newparams) + element.addPart(part) + except InvalidParameterError, e: + logger.error("Invalid parameter error: %s" % e) + raise + except: + logger.exception("Can't create structural element" + " part with command %s." % + str(command)) + + # Orientations are parsed after the parts because they must be + # associated with existing parts. + for command in orientationCmdList: + (parttype, parameters) = command + (meshGroupList, orientParams) = self._extractMeshGroups(command) + for meshGroup in meshGroupList: + element.addOrientation(meshGroup, orientParams) + + element.build() + logger.debug("StructuralElementManager.createElement: END") + return element + + def _extractMeshGroups(self, command): + """ + This method extracts the names of the mesh groups (i.e. the + geometrical objects used to build the structural element part) in the + command in parameter. It returns a tuple containing the mesh groups as + a list of strings and the other parameters of the command as a new + dictionary. + """ + (parttype, parameters) = command + newparams = parameters.copy() + groupMailleParam = newparams.pop("Group_Maille", None) + meshGroupParam = newparams.pop("MeshGroups", None) + if groupMailleParam is None and meshGroupParam is None: + logger.warning("No mesh group specified in command %s, this " + "command will be ignored." % command) + return ([], newparams) + elif groupMailleParam is not None and meshGroupParam is not None: + logger.warning('Both "MeshGroups" and "Group_Maille" specified in' + ' command %s, only "MeshGroups" will be used.' % + command) + elif groupMailleParam is not None and meshGroupParam is None: + meshGroupParam = groupMailleParam + + meshGroupList = [] + if type(meshGroupParam) == types.StringType: + meshGroupList = self._getMeshGroupListFromString(meshGroupParam) + else: + for item in meshGroupParam: + meshGroupList += self._getMeshGroupListFromString(item) + + if len(meshGroupList) == 0: + logger.warning("Mesh group list is empty in command %s, this " + "command will be ignored." % command) + + return (meshGroupList, newparams) + + def _getMeshGroupListFromString(self, meshString): + """ + This method splits the string in parameter to extract comma separated + names. Those names are returned as a list of strings. + """ + meshGroupList = [] + list = meshString.split(",") + for item in list: + strippedItem = item.strip() + if len(strippedItem) > 0: + meshGroupList.append(strippedItem) + return meshGroupList + + +class StructuralElement: + """ + This class represents a structural element, i.e. a set of geometrical + objects built along geometrical primitives. The parameter `studyId` + defines the ID of the study that will contain the structural element. If + it is :const:`None` or not specified, the constructor will use the ID of + the active study as defined by :func:`salome.kernel.studyedit.getActiveStudyId` + function. Structural elements are normally created by the class + :class:`StructuralElementManager`, so this class should not be + instantiated directly in the general case. + """ + _counter = 1 + _mainFolderTag = 14725 + + def __init__(self, studyId = None): + # _parts is the dictionary mapping group name to structural element + # part. _shapeDict is the dictionary mapping SubShapeID objects to + # structural element parts. Both are used to avoid duplicate shapes + # in structural elements. + self._parts = {} + self._shapeDict = {} + self._id = StructuralElement._counter + StructuralElement._counter += 1 + self._studyEditor = getStudyEditor(studyId) + logger.debug("Creating structural element in study %s" % + self._studyEditor.studyId) + self._SObject = None + + def _getSObject(self): + """ + Find or create the study object corresponding to the structural + element. This object is named "SE_N" where N is a numerical ID. + """ + if self._SObject is None: + geomComponent = self._studyEditor.study.FindComponent("GEOM") + mainFolder = self._studyEditor.setItemAtTag(geomComponent, + StructuralElement._mainFolderTag, + name = "Structural Elements") + self._SObject = self._studyEditor.findOrCreateItem(mainFolder, + name = "SE_" + str(self._id)) + return self._SObject + + def addPart(self, newpart): + """ + Add a part to the structural element. + + :type newpart: :class:`~parts.StructuralElementPart` + :param newpart: the part to add to the structural element. + + """ + newshapes = newpart.baseShapesSet + + # Check duplicate groups + if self._parts.has_key(newpart.groupName): + logger.warning('Mesh group "%s" is used several times in the ' + 'structural element. Only the last definition ' + 'will be used.' % newpart.groupName) + else: + # Check duplicate shapes + intersect = newshapes.intersection(self._shapeDict.keys()) + while len(intersect) > 0: + shape, = intersect + oldpartwithshape = self._shapeDict[shape] + oldpartshapes = oldpartwithshape.baseShapesSet + intersectwitholdpart = intersect.intersection(oldpartshapes) + logger.warning('Some shapes are common to groups "%s" and ' + '"%s". For those, the parameters defined for ' + '"%s" will be used.' % + (oldpartwithshape.groupName, newpart.groupName, + newpart.groupName)) + oldpartwithshape.baseShapesSet = \ + oldpartshapes.difference(intersectwitholdpart) + intersect = intersect.difference(intersectwitholdpart) + + # Finally add the new part in the structural element + self._parts[newpart.groupName] = newpart + for shape in newshapes: + self._shapeDict[shape] = newpart + + def addOrientation(self, meshGroup, orientParams): + """ + Add orientation information to a part in the structural element. This + information will be used to build the corresponding markers. + + :type meshGroup: string + :param meshGroup: the name of a geometrical primitive. The orientation + information will apply to the structural element + part built along this primitive. + + :type orientParams: dictionary + :param orientParams: parameters defining the orientation of the + structural element part. Those parameters are + detailed in class + :class:`~orientation.Orientation1D`. + + """ + if self._parts.has_key(meshGroup): + self._parts[meshGroup].addOrientation(orientParams) + else: + logger.warning('Mesh group "%s" not found in structural element, ' + 'cannot set orientation.' % meshGroup) + + def build(self): + """ + Build the geometric shapes and the markers corresponding to the + different parts of the structural element, and add them to the study. + """ + gg = salome.ImportComponentGUI("GEOM") + for part in self._parts.itervalues(): + # Build the structural element part + logger.debug("Building %s" % part) + try: + (shape, markers) = part.build() + if shape is None: + logger.error("Part %s has not been built" % part) + continue + except: + logger.exception("Couldn't build part %s" % part) + continue + + # Add the new objects to the study + IOR = self._studyEditor.study.ConvertObjectToIOR(shape) + shapeSObjName = part.name + "_" + part.groupName + icon = None + if salome.hasDesktop(): + icon = gg.getShapeTypeIcon(IOR) + shapeSObj = self._studyEditor.createItem(self._getSObject(), + name = shapeSObjName, IOR = IOR, + icon = icon) + if markers is not None and len(markers) > 0: + i = 1 + for marker in markers: + markerIOR = \ + self._studyEditor.study.ConvertObjectToIOR(marker) + markerSObjName = "Orient_" + shapeSObjName + if len(markers) > 1: + markerSObjName += "_%d" % i + markerSObj = self._studyEditor.createItem( + self._getSObject(), + name = markerSObjName, + IOR = markerIOR, + icon = "ICON_OBJBROWSER_LCS") + i += 1 + + def display(self): + """ + Display the structural element in the geom view. + """ + StructuralElement.showElement(self._SObject) + + @staticmethod + def showElement(theSObject): + """ + Display the structural element corresponding to the study object + `theSObject` + """ + if theSObject is not None: + gg = salome.ImportComponentGUI("GEOM") + aStudy = theSObject.GetStudy() + editor = getStudyEditor(aStudy._get_StudyId()) + aIterator = aStudy.NewChildIterator(theSObject) + aIterator.Init() + while aIterator.More(): + sobj = aIterator.Value() + icon = editor.getIcon(sobj) + if icon != "ICON_OBJBROWSER_LCS": + entry = aIterator.Value().GetID() + gg.createAndDisplayGO(entry) + gg.setDisplayMode(entry, 1) + aIterator.Next() + + +def TEST_CreateGeometry(): + import geompy + import SALOMEDS + geompy.init_geom(salome.myStudy) + Box_1 = geompy.MakeBoxDXDYDZ(200, 200, 200) + edges = geompy.SubShapeAllSorted(Box_1, geompy.ShapeType["EDGE"]) + edges[0].SetColor(SALOMEDS.Color(1.0,0.0,0.0)) + edges[1].SetColor(SALOMEDS.Color(0.0,1.0,0.0)) + edges[2].SetColor(SALOMEDS.Color(0.0,0.0,1.0)) + edges[3].SetColor(SALOMEDS.Color(1.0,0.0,1.0)) + edges[4].SetColor(SALOMEDS.Color(0.0,1.0,1.0)) + edges[5].SetColor(SALOMEDS.Color(0.5,0.0,0.0)) + edges[6].SetColor(SALOMEDS.Color(0.0,0.5,0.0)) + edges[7].SetColor(SALOMEDS.Color(0.0,0.0,0.5)) + geompy.addToStudy(Box_1, "Box_1") + for i in range(len(edges)): + geompy.addToStudyInFather(Box_1, edges[i], "Edge_%d" % i) + faces = geompy.SubShapeAllSorted(Box_1, geompy.ShapeType["FACE"]) + faces[3].SetColor(SALOMEDS.Color(1.0,0.5,0.0)) + faces[4].SetColor(SALOMEDS.Color(0.0,1.0,0.5)) + for i in range(len(faces)): + geompy.addToStudyInFather(Box_1, faces[i], "Face_%d" % i) + Cylinder_1 = geompy.MakeCylinderRH(50, 200) + geompy.TranslateDXDYDZ(Cylinder_1, 300, 300, 0) + cyl_faces = geompy.SubShapeAllSorted(Cylinder_1, geompy.ShapeType["FACE"]) + geompy.addToStudy(Cylinder_1, "Cylinder_1") + for i in range(len(cyl_faces)): + geompy.addToStudyInFather(Cylinder_1, cyl_faces[i], "CylFace_%d" % i) + Cylinder_2 = geompy.MakeTranslation(Cylinder_1, 100, 100, 0) + cyl_faces2 = geompy.SubShapeAllSorted(Cylinder_2, + geompy.ShapeType["FACE"]) + geompy.addToStudy(Cylinder_2, "Cylinder_2") + for i in range(len(cyl_faces2)): + geompy.addToStudyInFather(Cylinder_2, cyl_faces2[i], + "CylFace2_%d" % i) + + +def TEST_StructuralElement(): + salome.salome_init() + TEST_CreateGeometry() + liste_commandes = [('Orientation', {'MeshGroups': 'Edge_4', + 'VECT_Y': (1.0, 0.0, 1.0)}), + ('Orientation', {'MeshGroups': 'Edge_5', + 'ANGL_VRIL': 45.0}), + ('GeneralBeam', {'MeshGroups': 'Edge_1, Edge_7'}), + ('VisuPoutreCercle', {'MeshGroups': ['Edge_6'], + 'R1': 30, 'R2': 20}), + ('CircularBeam', {'MeshGroups': ['Edge_2', 'Edge_3'], + 'R': 40, 'EP': 20}), + ('RectangularBeam', {'MeshGroups': 'Edge_4, Edge_5', + 'HZ1': 60, 'HY1': 40, + 'EPZ1': 15, 'EPY1': 10, + 'HZ2': 40, 'HY2': 60, + 'EPZ2': 10, 'EPY2': 15}), + ('VisuCable', {'MeshGroups': 'Edge_7', 'R': 5}), + ('VisuCoque', {'MeshGroups': 'Face_4', + 'Epais': 10, 'Excentre': 5, + 'angleAlpha': 45, 'angleBeta': 60}), + ('VisuCoque', {'MeshGroups': 'CylFace_2', 'Epais': 5}), + ('VisuGrille', {'MeshGroups': 'Face_5', 'Excentre': 5, + 'angleAlpha': 45, 'angleBeta': 60}), + ('VisuGrille', {'MeshGroups': 'CylFace2_2', + 'Excentre': 5, 'origAxeX': 400, + 'origAxeY': 400, 'origAxeZ': 0, + 'axeX': 0, 'axeY': 0, 'axeZ': 100}), + ] + + structElemManager = StructuralElementManager() + elem = structElemManager.createElement(liste_commandes) + if salome.hasDesktop(): + elem.display() + salome.sg.updateObjBrowser(True) + + +# Main function only used to test the module +if __name__ == "__main__": + TEST_StructuralElement() diff --git a/src/GEOM_PY/structelem/orientation.py b/src/GEOM_PY/structelem/orientation.py new file mode 100644 index 000000000..1bf111791 --- /dev/null +++ b/src/GEOM_PY/structelem/orientation.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +""" +This module is used to compute the orientation of the different parts in a +structural element and to build the corresponding markers (trihedrons). +""" + +import math + +from salome.kernel.logger import Logger +from salome.kernel import termcolor +logger = Logger("__PAL_GEOM__.structelem.orientation", color = termcolor.RED) + + +class Orientation1D: + """ + This class is used to compute the orientation of 1D elements and to build + the corresponding markers. + """ + + def __init__(self): + self.geom = None + self._vectorYCoords = None + self._angle = 0.0 + + def __repr__(self): + reprdict = self.__dict__.copy() + del reprdict["geom"] + return '%s(%s)' % (self.__class__.__name__, reprdict) + + def addParams(self, params): + """ + Add orientation parameters. `params` is a dictionary containing one or + several orientation parameters. The valid parameters are: + + * "VECT_Y": Triplet defining the local Y axis (the X axis is the + main direction of the 1D element). + * "ANGL_VRIL": Angle of rotation along the X axis to define the local + coordinate system. + + The parameters can be specified several times. In this case, only the + first "VECT_Y" is taken into account, and the values of "ANGL_VRIL" + are added to obtain the total rotation angle. + """ + mydict = params.copy() + if mydict.has_key("VECT_Y"): + newVecCoords = mydict.pop("VECT_Y") + if self._vectorYCoords is None: + logger.debug("Setting orientation vector Y to %s" % + str(newVecCoords)) + self._vectorYCoords = newVecCoords + else: + logger.warning('Orientation parameter "VECT_Y" is specified ' + 'several times for the same mesh group, vector' + ' %s will be used' % str(self._vectorYCoords)) + if mydict.has_key("ANGL_VRIL"): + newAngle = mydict.pop("ANGL_VRIL") + self._angle += newAngle + logger.debug("Adding angle %f to orientation, new angle is %f." % + (newAngle, self._angle)) + if len(mydict) > 0: + logger.warning("Invalid orientation parameter(s) (ignored): %s" % + str(mydict)) + + def _buildDefaultMarker(self, center, vecX): + """ + Create the default marker, that use the main direction of the 1D + object as the local X axis and the global Z axis to determine the + local Z axis. + """ + xPoint = self.geom.MakeTranslationVector(center, vecX) + givenVecZ = self.geom.MakeVectorDXDYDZ(0.0, 0.0, 1.0) + angle = self.geom.GetAngleRadians(vecX, givenVecZ) + if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7: + logger.warning("Beam X axis is colinear to absolute Z axis. " + "Absolute X axis will be used to determine " + "local Z axis.") + givenVecZ = self.geom.MakeVectorDXDYDZ(1.0, 0.0, 0.0) + zPoint = self.geom.MakeTranslationVector(center, givenVecZ) + locPlaneZX = self.geom.MakePlaneThreePnt(center, zPoint, xPoint, 1.0) + locY = self.geom.GetNormal(locPlaneZX) + marker = self.geom.MakeMarkerPntTwoVec(center,vecX,locY) + return marker + + def buildMarker(self, geom, center, vecX): + """ + Create a marker with origin `center` and X axis `vecX`. `geom` is the + pseudo-geompy object used to build the geometric shapes. + """ + self.geom = geom + marker = None + if self._vectorYCoords is None: + marker = self._buildDefaultMarker(center, vecX) + else: + xPoint = self.geom.MakeTranslationVector(center, vecX) + givenLocY = self.geom.MakeVectorDXDYDZ(self._vectorYCoords[0], + self._vectorYCoords[1], + self._vectorYCoords[2]) + angle = self.geom.GetAngleRadians(vecX, givenLocY) + if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7: + logger.warning("Vector Y is colinear to the beam X axis, " + "using default LCS.") + marker = self._buildDefaultMarker(center, vecX) + else: + yPoint = self.geom.MakeTranslationVector(center, givenLocY) + locPlaneXY = self.geom.MakePlaneThreePnt(center, xPoint, + yPoint, 1.0) + locZ = self.geom.GetNormal(locPlaneXY) + zPoint = self.geom.MakeTranslationVector(center, locZ) + locPlaneZX = self.geom.MakePlaneThreePnt(center, zPoint, + xPoint, 1.0) + locY = self.geom.GetNormal(locPlaneZX) + marker = self.geom.MakeMarkerPntTwoVec(center,vecX,locY) + + if self._angle != 0.0: + angleRad = math.radians(self._angle) + marker = self.geom.Rotate(marker, vecX, angleRad) + + return marker + + +class Orientation2D: + """ + This class is used to compute the orientation of 2D elements and to build + the corresponding markers. Angles `alpha` and `beta` are used to determine + the local coordinate system for the 2D element. If `vect` is not + :const:`None`, it is used instead of `alpha` and `beta`. + """ + + def __init__(self, alpha, beta, vect): + self.geom = None + self._alpha = alpha + self._beta = beta + self._vect = vect + + def __repr__(self): + reprdict = self.__dict__.copy() + del reprdict["geom"] + return '%s(%s)' % (self.__class__.__name__, reprdict) + + def _buildDefaultMarker(self, center, normal, warnings = True): + """ + Create the default marker, that use the normal vector of the 2D object + as the local Z axis and the global X axis to determine the local X + axis. `warnings` can be used to enable or disable the logging of + warning messages. + """ + marker = None + globalVecX = self.geom.MakeVectorDXDYDZ(1.0, 0.0, 0.0) + angle = self.geom.GetAngleRadians(normal, globalVecX) + if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7: + if warnings: + logger.warning("Face normal is colinear to absolute X axis. " + "Absolute Y axis will be used to determine " + "local X axis.") + globalVecY = self.geom.MakeVectorDXDYDZ(0.0, 1.0, 0.0) + marker = self._buildMarkerRefVecX(center, normal, globalVecY) + else: + marker = self._buildMarkerRefVecX(center, normal, globalVecX) + return marker + + def _buildMarkerRefVecX(self, center, normal, refVecX): + """ + Create a marker using `normal` as Z axis and `refVecX` to determine + the X axis. + """ + xPoint = self.geom.MakeTranslationVector(center, refVecX) + zPoint = self.geom.MakeTranslationVector(center, normal) + locPlaneZX = self.geom.MakePlaneThreePnt(center, zPoint, xPoint, 1.0) + locY = self.geom.GetNormal(locPlaneZX) + yPoint = self.geom.MakeTranslationVector(center, locY) + locPlaneYZ = self.geom.MakePlaneThreePnt(center, yPoint, zPoint, 1.0) + locX = self.geom.GetNormal(locPlaneYZ) + marker = self.geom.MakeMarkerPntTwoVec(center, locX, locY) + return marker + + def buildMarker(self, geom, center, normal, warnings = True): + """ + Create a marker with origin `center` and `normal` as Z axis. The other + axes are computed using the parameters alpha and beta of the + Orientation2D instance. `geom` is the pseudo-geompy object used to + build the geometric shapes. `warnings` can be used to enable or + disable the logging of warning messages. + """ + self.geom = geom + marker = None + refVecX = None + if self._vect is not None: + # Using vector parameter + if abs(self._vect[0]) <= 1e-7 and abs(self._vect[1]) <= 1e-7 and \ + abs(self._vect[2]) <= 1e-7: + if warnings: + logger.warning("Vector too small: %s, using default LCS" % + self._vect) + else: + refVecX = self.geom.MakeVectorDXDYDZ(self._vect[0], + self._vect[1], + self._vect[2]) + elif self._alpha is not None and self._beta is not None: + # Using alpha and beta angles + alphaRad = math.radians(self._alpha) + betaRad = math.radians(self._beta) + if abs(alphaRad) <= 1e-7 and abs(betaRad) <= 1e-7: + if warnings: + logger.warning("Angles too small: (%g, %g), using " + "default LCS" % (self._alpha, self._beta)) + else: + # rotate global CS with angles alpha and beta + refVecX = self.geom.MakeVectorDXDYDZ(1.0, 0.0, 0.0) + refVecY = self.geom.MakeVectorDXDYDZ(0.0, 1.0, 0.0) + globalVecZ = self.geom.MakeVectorDXDYDZ(0.0, 0.0, 1.0) + if abs(alphaRad) > 1e-7: + refVecX = self.geom.Rotate(refVecX, globalVecZ, alphaRad) + refVecY = self.geom.Rotate(refVecY, globalVecZ, alphaRad) + if abs(betaRad) > 1e-7: + refVecX = self.geom.Rotate(refVecX, refVecY, betaRad) + + if refVecX is not None: + # build local coordinate system + angle = self.geom.GetAngleRadians(normal, refVecX) + if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7: + if warnings: + logger.warning("Face normal is colinear to the reference " + "X axis, using default LCS.") + else: + marker = self._buildMarkerRefVecX(center, normal, refVecX) + + if marker is None: + marker = self._buildDefaultMarker(center, normal, warnings) + + return marker diff --git a/src/GEOM_PY/structelem/parts.py b/src/GEOM_PY/structelem/parts.py new file mode 100644 index 000000000..0781f6d83 --- /dev/null +++ b/src/GEOM_PY/structelem/parts.py @@ -0,0 +1,969 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2009 EDF R&D +# +# This file is part of PAL_SRC. +# +# PAL_SRC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PAL_SRC 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PAL_SRC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +""" +This module defines the different structural element parts. It is used to +build the geometric shapes of the structural elements. It should not be used +directly in the general case. Structural elements should be created by the +class :class:`~salome.geom.structelem.StructuralElementManager`. +""" + +import salome + +from salome.kernel.logger import Logger +from salome.kernel import termcolor +logger = Logger("salome.geom.structelem.parts", color = termcolor.RED) +from salome.geom.geomtools import getGeompy + +import orientation + +# Filling for the beams +FULL = "FULL" +HOLLOW = "HOLLOW" + +# Minimum dimension for the shapes to extrude +MIN_DIM_FOR_EXTRUDED_SHAPE = 2e-4 +MIN_LENGTH_FOR_EXTRUSION = 1e-4 +MIN_THICKNESS = 1e-5 + + +class InvalidParameterError(Exception): + """ + This exception is raised when an invalid parameter is used to build a + structural element part. + """ + + def __init__(self, groupName, expression, minValue, value): + self.groupName = groupName + self.expression = expression + self.minValue = minValue + self.value = value + + def __str__(self): + return "%s < %g (%s = %g in %s)" % (self.expression, self.minValue, + self.expression, self.value, + self.groupName) + + +class SubShapeID: + """ + This class enables the use of subshapes in sets or as dictionary keys. + It implements __eq__ and __hash__ methods so that subshapes with the same + CORBA object `mainShape` and the same `id` are considered equal. + """ + + def __init__(self, mainShape, id): + self._mainShape = mainShape + self._id = id + + def getObj(self, geom): + """ + Return the subshape (GEOM object). `geom` is a pseudo-geompy object + used to find the geometrical object. + """ + return geom.GetSubShape(self._mainShape, [self._id]) + + def __eq__(self, other): + return self._mainShape._is_equivalent(other._mainShape) and \ + self._id == other._id + + def __hash__(self): + return self._mainShape._hash(2147483647) ^ self._id + + +class StructuralElementPart: + """ + This class is the base class for all structural element parts. It should + not be instantiated directly (consider it as an "abstract" class). + + :type studyId: integer + :param studyId: the ID of the study in which the part is created. + + :type groupName: string + :param groupName: the name of the underlying geometrical primitive in the + study. + + :type groupGeomObj: GEOM object + :param groupGeomObj: the underlying geometrical primitive. + + :type parameters: dictionary + :param parameters: parameters defining the structural element (see + subclasses for details). + + :type name: string + :param name: name to use for the created object in the study. + + """ + + DEFAULT_NAME = "StructElemPart" + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = DEFAULT_NAME): + self._parameters = parameters + self.groupName = groupName + self._groupGeomObj = groupGeomObj + self._orientation = None + self._paramUserName = {} + self.name = name + self.geom = getGeompy(studyId) + self.baseShapesSet = set() + mainShape = self.geom.GetMainShape(groupGeomObj) + listIDs = self.geom.GetObjectIDs(groupGeomObj) + if mainShape is not None and listIDs is not None: + for id in listIDs: + self.baseShapesSet.add(SubShapeID(mainShape, id)) + + def _getParameter(self, nameList, default = None): + """ + This method finds the value of a parameter in the parameters + dictionary. The argument is a list because some parameters can have + several different names. + """ + if len(nameList) > 0: + paramName = nameList[0] + for name in nameList: + if self._parameters.has_key(name): + self._paramUserName[paramName] = name + return self._parameters[name] + return default + + def _getParamUserName(self, paramName): + """ + This method finds the user name for a parameter. + """ + if self._paramUserName.has_key(paramName): + return self._paramUserName[paramName] + else: + return paramName + + def __repr__(self): + reprdict = self.__dict__.copy() + del reprdict["_parameters"] + del reprdict["groupName"] + del reprdict["_groupGeomObj"] + del reprdict["_paramUserName"] + del reprdict["name"] + del reprdict["geom"] + del reprdict["baseShapesSet"] + return '%s("%s", %s)' % (self.__class__.__name__, self.groupName, + reprdict) + + def addOrientation(self, orientParams): + """ + Add orientation information to the structural element part. See class + :class:`~salome.geom.structelem.orientation.Orientation1D` for the description + of the parameters. + """ + self._orientation.addParams(orientParams) + + def _checkSize(self, value, mindim, expression): + """ + This method checks that some parameters or some expressions involving + those parameters are greater than a minimum value. + """ + if value < mindim: + raise InvalidParameterError(self.groupName, expression, + mindim, value) + + def build(self): + """ + Build the geometric shapes and the markers corresponding to the + structural element part in the study `studyId`. + """ + shape = self._buildPart() + markers = self._buildMarkers() + shape.SetColor(self._groupGeomObj.GetColor()) + for marker in markers: + marker.SetColor(self._groupGeomObj.GetColor()) + return (shape, markers) + + def _buildPart(self): + """ + This abstract method must be implemented in subclasses and should + create the geometrical shape(s) of the structural element part. + """ + raise NotImplementedError("Method _buildPart not implemented in class" + " %s (it must be implemented in " + "StructuralElementPart subclasses)." % + self.__class__.__name__) + + def _buildMarkers(self): + """ + This abstract method must be implemented in subclasses and should + create the markers defining the orientation of the structural element + part. + """ + raise NotImplementedError("Method _buildMarker not implemented in " + "class %s (it must be implemented in " + "StructuralElementPart subclasses)." % + self.__class__.__name__) + + def _getSubShapes(self, minDim = MIN_LENGTH_FOR_EXTRUSION): + """ + Find and return the base subshapes in the structural element part. + """ + subShapes = [] + for subShapeID in self.baseShapesSet: + subShape = subShapeID.getObj(self.geom) + length = self.geom.BasicProperties(subShape)[0] + if length < minDim: + logger.warning("Length too short (%s - ID %s, length = %g), " + "subshape will not be used in structural " + "element" % (self.groupName, subShapeID._id, + length)) + else: + subShapes.append(subShape) + return subShapes + + +class Beam(StructuralElementPart): + """ + This class is an "abstract" class for all 1D structural element parts. It + should not be instantiated directly. See class + :class:`StructuralElementPart` for the description of the parameters. + """ + + DEFAULT_NAME = "Beam" + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = DEFAULT_NAME): + StructuralElementPart.__init__(self, studyId, groupName, groupGeomObj, + parameters, name) + self._orientation = orientation.Orientation1D() + + def _isReversed(self, path): + """ + This method checks if a 1D object is "reversed", i.e. if its + orientation is different than the orientation of the underlying OCC + object. + """ + fParam = 0. + lParam = 1. + fPoint = self.geom.MakeVertexOnCurve(path, fParam) + lPoint = self.geom.MakeVertexOnCurve(path, lParam) + + fNormal = self.geom.MakeTangentOnCurve(path, fParam) + lNormal = self.geom.MakeTangentOnCurve(path, lParam) + + fCircle = self.geom.MakeCircle(fPoint, fNormal, 10) + lCircle = self.geom.MakeCircle(lPoint, lNormal, 10) + + try: + pipe = self.geom.MakePipeWithDifferentSections([fCircle, lCircle], + [fPoint, lPoint], + path, False, False) + except RuntimeError, e: + # This dirty trick is needed if the wire is not oriented in the + # direction corresponding to parameters 0.0 -> 1.0. In this case, + # we catch the error and invert the ends of the wire. This trick + # will be removed when the function giving the orientation of an + # edge will be added in geompy (see issue 1144 in PAL bugtracker). + if (str(e) == "MakePipeWithDifferentSections : First location " + "shapes is not coincided with first vertex of " + "aWirePath"): + return True + else: + raise + return False + + def _getVertexAndTangentOnOrientedWire(self, path, param): + """ + Get a vertex and the corresponding tangent on a wire by parameter. + This method takes into account the "real" orientation of the wire + (i.e. the orientation of the underlying OCC object). + """ + if self._isReversed(path): + vertex = self.geom.MakeVertexOnCurve(path, 1.0 - param) + invtangent = self.geom.MakeTangentOnCurve(path, 1.0 - param) + tanpoint = self.geom.MakeTranslationVectorDistance(vertex, + invtangent, + -1.0) + tangent = self.geom.MakeVector(vertex, tanpoint) + else: + vertex = self.geom.MakeVertexOnCurve(path, param) + tangent = self.geom.MakeTangentOnCurve(path, param) + return (vertex, tangent) + + def _makeSolidPipeFromWires(self, wire1, wire2, point1, point2, path): + """ + Create a solid by the extrusion of section `wire1` to section `wire2` + along `path`. + """ + face1 = self.geom.MakeFace(wire1, True) + face2 = self.geom.MakeFace(wire2, True) + shell = self.geom.MakePipeWithDifferentSections([wire1, wire2], + [point1, point2], + path, False, False) + closedShell = self.geom.MakeShell([face1, face2, shell]) + solid = self.geom.MakeSolid([closedShell]) + return solid + + def _buildPart(self): + """ + Build the structural element part. + """ + # Get all the subshapes in the group (normally only edges and wires) + paths = self._getSubShapes() + listPipes = [] + withContact = False + withCorrection = False + + for path in paths: + # Build the sections (rectangular or circular) at each end of the + # beam + (fPoint, fNormal) = self._getVertexAndTangentOnOrientedWire(path, + 0.0) + (lPoint, lNormal) = self._getVertexAndTangentOnOrientedWire(path, + 1.0) + (outerWire1, innerWire1, outerWire2, innerWire2) = \ + self._makeSectionWires(fPoint, fNormal, lPoint, lNormal) + + # Create the resulting solid + outerSolid = self._makeSolidPipeFromWires(outerWire1, outerWire2, + fPoint, lPoint, path) + if self.filling == HOLLOW: + innerSolid = self._makeSolidPipeFromWires(innerWire1, + innerWire2, fPoint, + lPoint, path) + resultSolid = self.geom.MakeCut(outerSolid, innerSolid) + listPipes.append(resultSolid) + else: + listPipes.append(outerSolid) + + if len(listPipes) == 0: + return None + elif len(listPipes) == 1: + return listPipes[0] + else: + return self.geom.MakeCompound(listPipes) + + def _buildMarkers(self): + """ + Build the markers defining the orientation of the structural element + part. + """ + param = 0.5 + paths = self._getSubShapes() + listMarkers = [] + for path in paths: + (center, vecX) = self._getVertexAndTangentOnOrientedWire(path, + param) + marker = self._orientation.buildMarker(self.geom, center, vecX) + listMarkers.append(marker) + return listMarkers + + +class GeneralBeam(Beam): + """ + This class defines a beam with a generic section. It is represented only + as the underlying wire. See class :class:`StructuralElementPart` for the + description of the parameters. + """ + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = Beam.DEFAULT_NAME): + Beam.__init__(self, studyId, groupName, groupGeomObj, parameters, + name) + logger.debug(repr(self)) + + def _buildPart(self): + """ + Create a copy of the underlying wire. + """ + edges = self._getSubShapes(1e-7) + wire = None + if len(edges) > 0: + wire = self.geom.MakeWire(edges) + return wire + + +class CircularBeam(Beam): + """ + This class defines a beam with a circular section. It can be full or + hollow, and its radius and thickness can vary from one end of the beam to + the other. The valid parameters for circular beams are: + + * "R1" or "R": radius at the first end of the beam. + * "R2" or "R": radius at the other end of the beam. + * "EP1" or "EP" (optional): thickness at the first end of the beam. + If not specified or equal to 0, the beam is considered full. + * "EP2" or "EP" (optional): thickness at the other end of the beam. + If not specified or equal to 0, the beam is considered full. + + See class :class:`StructuralElementPart` for the description of the + other parameters. + + """ + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = Beam.DEFAULT_NAME): + Beam.__init__(self, studyId, groupName, groupGeomObj, parameters, + name) + + self.R1 = self._getParameter(["R1", "R"]) + self.R2 = self._getParameter(["R2", "R"]) + self.EP1 = self._getParameter(["EP1", "EP"]) + self.EP2 = self._getParameter(["EP2", "EP"]) + + if self.EP1 is None or self.EP2 is None or \ + self.EP1 == 0 or self.EP2 == 0: + self.filling = FULL + else: + self.filling = HOLLOW + + logger.debug(repr(self)) + + # Check parameters + self._checkSize(self.R1, MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0, + self._getParamUserName("R1")) + self._checkSize(self.R2, MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0, + self._getParamUserName("R2")) + if self.filling == HOLLOW: + self._checkSize(self.EP1, MIN_THICKNESS, + self._getParamUserName("EP1")) + self._checkSize(self.EP2, MIN_THICKNESS, + self._getParamUserName("EP2")) + self._checkSize(self.R1 - self.EP1, + MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0, + "%s - %s" % (self._getParamUserName("R1"), + self._getParamUserName("EP1"))) + self._checkSize(self.R2 - self.EP2, + MIN_DIM_FOR_EXTRUDED_SHAPE / 2.0, + "%s - %s" % (self._getParamUserName("R2"), + self._getParamUserName("EP2"))) + + def _makeSectionWires(self, fPoint, fNormal, lPoint, lNormal): + """ + Create the circular sections used to build the pipe. + """ + outerCircle1 = self.geom.MakeCircle(fPoint, fNormal, self.R1) + outerCircle2 = self.geom.MakeCircle(lPoint, lNormal, self.R2) + if self.filling == HOLLOW: + innerCircle1 = self.geom.MakeCircle(fPoint, fNormal, + self.R1 - self.EP1) + innerCircle2 = self.geom.MakeCircle(lPoint, lNormal, + self.R2 - self.EP2) + else: + innerCircle1 = None + innerCircle2 = None + + return (outerCircle1, innerCircle1, outerCircle2, innerCircle2) + + +class RectangularBeam(Beam): + """ + This class defines a beam with a rectangular section. It can be full or + hollow, and its dimensions can vary from one end of the beam to the other. + The valid parameters for rectangular beams are: + + * "HY1", "HY", "H1" or "H": width at the first end of the beam. + * "HZ1", "HZ", "H1" or "H": height at the first end of the beam. + * "HY2", "HY", "H2" or "H": width at the other end of the beam. + * "HZ2", "HZ", "H2" or "H": height at the other end of the beam. + * "EPY1", "EPY", "EP1" or "EP" (optional): thickness in the width + direction at the first end of the beam. If not specified or equal to 0, + the beam is considered full. + * "EPZ1", "EPZ", "EP1" or "EP" (optional): thickness in the height + direction at the first end of the beam. If not specified or equal to 0, + the beam is considered full. + * "EPY2", "EPY", "EP2" or "EP" (optional): thickness in the width + direction at the other end of the beam. If not specified or equal to 0, + the beam is considered full. + * "EPZ2", "EPZ", "EP2" or "EP" (optional): thickness in the height + direction at the other end of the beam. If not specified or equal to 0, + the beam is considered full. + + See class :class:`StructuralElementPart` for the description of the + other parameters. + + """ + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = Beam.DEFAULT_NAME): + Beam.__init__(self, studyId, groupName, groupGeomObj, parameters, + name) + + self.HY1 = self._getParameter(["HY1", "HY", "H1", "H"]) + self.HZ1 = self._getParameter(["HZ1", "HZ", "H1", "H"]) + self.HY2 = self._getParameter(["HY2", "HY", "H2", "H"]) + self.HZ2 = self._getParameter(["HZ2", "HZ", "H2", "H"]) + self.EPY1 = self._getParameter(["EPY1", "EPY", "EP1", "EP"]) + self.EPZ1 = self._getParameter(["EPZ1", "EPZ", "EP1", "EP"]) + self.EPY2 = self._getParameter(["EPY2", "EPY", "EP2", "EP"]) + self.EPZ2 = self._getParameter(["EPZ2", "EPZ", "EP2", "EP"]) + + if self.EPY1 is None or self.EPZ1 is None or \ + self.EPY2 is None or self.EPZ2 is None or \ + self.EPY1 == 0 or self.EPZ1 == 0 or \ + self.EPY2 == 0 or self.EPZ2 == 0: + self.filling = FULL + else: + self.filling = HOLLOW + + logger.debug(repr(self)) + + # Check parameters + self._checkSize(self.HY1, MIN_DIM_FOR_EXTRUDED_SHAPE, + self._getParamUserName("HY1")) + self._checkSize(self.HZ1, MIN_DIM_FOR_EXTRUDED_SHAPE, + self._getParamUserName("HZ1")) + self._checkSize(self.HY2, MIN_DIM_FOR_EXTRUDED_SHAPE, + self._getParamUserName("HY2")) + self._checkSize(self.HZ2, MIN_DIM_FOR_EXTRUDED_SHAPE, + self._getParamUserName("HZ2")) + if self.filling == HOLLOW: + self._checkSize(self.EPY1, MIN_THICKNESS, + self._getParamUserName("EPY1")) + self._checkSize(self.EPZ1, MIN_THICKNESS, + self._getParamUserName("EPZ1")) + self._checkSize(self.EPY2, MIN_THICKNESS, + self._getParamUserName("EPY2")) + self._checkSize(self.EPZ2, MIN_THICKNESS, + self._getParamUserName("EPZ2")) + self._checkSize(self.HY1 - 2 * self.EPY1, + MIN_DIM_FOR_EXTRUDED_SHAPE, + "%s - 2 * %s" % (self._getParamUserName("HY1"), + self._getParamUserName("EPY1"))) + self._checkSize(self.HZ1 - 2 * self.EPZ1, + MIN_DIM_FOR_EXTRUDED_SHAPE, + "%s - 2 * %s" % (self._getParamUserName("HZ1"), + self._getParamUserName("EPZ1"))) + self._checkSize(self.HY2 - 2 * self.EPY2, + MIN_DIM_FOR_EXTRUDED_SHAPE, + "%s - 2 * %s" % (self._getParamUserName("HY2"), + self._getParamUserName("EPY2"))) + self._checkSize(self.HZ2 - 2 * self.EPZ2, + MIN_DIM_FOR_EXTRUDED_SHAPE, + "%s - 2 * %s" % (self._getParamUserName("HZ2"), + self._getParamUserName("EPZ2"))) + + def _makeRectangle(self, HY, HZ, planeSect): + """ + Create a rectangle in the specified plane. + """ + halfHY = HY / 2.0 + halfHZ = HZ / 2.0 + sketchStr = "Sketcher:F %g" % (-halfHZ) + " %g" % (-halfHY) + ":" + sketchStr += "TT %g" % (halfHZ) + " %g" % (-halfHY) + ":" + sketchStr += "TT %g" % (halfHZ) + " %g" % (halfHY) + ":" + sketchStr += "TT %g" % (-halfHZ) + " %g" % (halfHY) + ":WW" + logger.debug('Drawing rectangle: "%s"' % sketchStr) + sketch = self.geom.MakeSketcherOnPlane(sketchStr, planeSect) + return sketch + + def _makeSectionWires(self, fPoint, fNormal, lPoint, lNormal): + """ + Create the rectangular sections used to build the pipe. + """ + planeSect1 = self.geom.MakePlane(fPoint, fNormal, 1.0) + outerRect1 = self._makeRectangle(self.HY1, self.HZ1, planeSect1) + planeSect2 = self.geom.MakePlane(lPoint, lNormal, 1.0) + outerRect2 = self._makeRectangle(self.HY2, self.HZ2, planeSect2) + if self.filling == HOLLOW: + innerRect1 = self._makeRectangle(self.HY1 - 2 * self.EPY1, + self.HZ1 - 2 * self.EPZ1, + planeSect1) + innerRect2 = self._makeRectangle(self.HY2 - 2 * self.EPY2, + self.HZ2 - 2 * self.EPZ2, + planeSect2) + else: + innerRect1 = None + innerRect2 = None + + return (outerRect1, innerRect1, outerRect2, innerRect2) + + +class StructuralElementPart2D(StructuralElementPart): + """ + This class is an "abstract" class for all 2D structural element parts. It + should not be instantiated directly. See class + :class:`StructuralElementPart` for the description of the parameters. + """ + + DEFAULT_NAME = "StructuralElementPart2D" + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = DEFAULT_NAME): + StructuralElementPart.__init__(self, studyId, groupName, groupGeomObj, + parameters, name) + self._orientation = orientation.Orientation2D( + self._getParameter(["angleAlpha"]), + self._getParameter(["angleBeta"]), + self._getParameter(["Vecteur"])) + self.offset = self._getParameter(["Excentre"], 0.0) + + def _makeFaceOffset(self, face, offset, epsilon = 1e-6): + """ + Create a copy of a face at a given offset. + """ + if abs(offset) < epsilon: + return self.geom.MakeCopy(face) + else: + offsetObj = self.geom.MakeOffset(face, offset) + # We have to explode the resulting object into faces because it is + # created as a polyhedron and not as a single face + faces = self.geom.SubShapeAll(offsetObj, + self.geom.ShapeType["FACE"]) + return faces[0] + + def _buildMarkersWithOffset(self, offset): + """ + Build the markers for the structural element part with a given offset + from the base face. + """ + uParam = 0.5 + vParam = 0.5 + listMarkers = [] + subShapes = self._getSubShapes() + + for subShape in subShapes: + faces = self.geom.SubShapeAll(subShape, + self.geom.ShapeType["FACE"]) + for face in faces: + offsetFace = self._makeFaceOffset(face, offset) + # get tangent plane on surface by parameters + center = self.geom.MakeVertexOnSurface(offsetFace, + uParam, vParam) + tangPlane = self.geom.MakeTangentPlaneOnFace(offsetFace, + uParam, vParam, + 1.0) + normal = self.geom.GetNormal(tangPlane) + marker = self._orientation.buildMarker(self.geom, + center, normal) + listMarkers.append(marker) + + return listMarkers + + +class ThickShell(StructuralElementPart2D): + """ + This class defines a shell with a given thickness. It can be shifted from + the base face. The valid parameters for thick shells are: + + * "Epais": thickness of the shell. + * "Excentre": offset of the shell from the base face. + * "angleAlpha": angle used to build the markers (see class + :class:`~salome.geom.structelem.orientation.Orientation2D`) + * "angleBeta": angle used to build the markers (see class + :class:`~salome.geom.structelem.orientation.Orientation2D`) + * "Vecteur": vector used instead of the angles to build the markers (see + class :class:`~salome.geom.structelem.orientation.Orientation2D`) + + See class :class:`StructuralElementPart` for the description of the + other parameters. + """ + + DEFAULT_NAME = "ThickShell" + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = DEFAULT_NAME): + StructuralElementPart2D.__init__(self, studyId, groupName, + groupGeomObj, parameters, name) + self.thickness = self._getParameter(["Epais"]) + logger.debug(repr(self)) + + def _buildPart(self): + """ + Create the geometrical shapes corresponding to the thick shell. + """ + subShapes = self._getSubShapes() + listSolids = [] + + for subShape in subShapes: + faces = self.geom.SubShapeAll(subShape, + self.geom.ShapeType["FACE"]) + for face in faces: + shape = self._buildThickShellForFace(face) + listSolids.append(shape) + + if len(listSolids) == 0: + return None + elif len(listSolids) == 1: + return listSolids[0] + else: + return self.geom.MakeCompound(listSolids) + + def _buildThickShellForFace(self, face): + """ + Create the geometrical shapes corresponding to the thick shell for a + given face. + """ + epsilon = 1e-6 + if self.thickness < 2 * epsilon: + return self._makeFaceOffset(face, self.offset, epsilon) + + upperOffset = self.offset + self.thickness / 2.0 + lowerOffset = self.offset - self.thickness / 2.0 + ruledMode = True + modeSolid = False + + upperFace = self._makeFaceOffset(face, upperOffset, epsilon) + lowerFace = self._makeFaceOffset(face, lowerOffset, epsilon) + listShapes = [upperFace, lowerFace] + upperWires = self.geom.SubShapeAll(upperFace, + self.geom.ShapeType["WIRE"]) + lowerWires = self.geom.SubShapeAll(lowerFace, + self.geom.ShapeType["WIRE"]) + if self.geom.KindOfShape(face)[0] == self.geom.kind.CYLINDER2D: + # if the face is a cylinder, we remove the extra side edge + upperWires = self._removeCylinderExtraEdge(upperWires) + lowerWires = self._removeCylinderExtraEdge(lowerWires) + for i in range(len(upperWires)): + resShape = self.geom.MakeThruSections([upperWires[i], + lowerWires[i]], + modeSolid, epsilon, + ruledMode) + listShapes.append(resShape) + resultShell = self.geom.MakeShell(listShapes) + resultSolid = self.geom.MakeSolid([resultShell]) + return resultSolid + + def _removeCylinderExtraEdge(self, wires): + """ + Remove the side edge in a cylinder. + """ + result = [] + for wire in wires: + edges = self.geom.SubShapeAll(wire, self.geom.ShapeType["EDGE"]) + for edge in edges: + if self.geom.KindOfShape(edge)[0] == self.geom.kind.CIRCLE: + result.append(edge) + return result + + def _buildMarkers(self): + """ + Build the markers defining the orientation of the thick shell. + """ + return self._buildMarkersWithOffset(self.offset + + self.thickness / 2.0) + + +class Grid(StructuralElementPart2D): + """ + This class defines a grid. A grid is represented by a 2D face patterned + with small lines in the main direction of the grid frame. The valid + parameters for grids are: + + * "Excentre": offset of the grid from the base face. + * "angleAlpha": angle used to build the markers (see class + :class:`~salome.geom.structelem.orientation.Orientation2D`) + * "angleBeta": angle used to build the markers (see class + :class:`~salome.geom.structelem.orientation.Orientation2D`) + * "Vecteur": vector used instead of the angles to build the markers (see + class :class:`~salome.geom.structelem.orientation.Orientation2D`) + * "origAxeX": X coordinate of the origin of the axis used to determine the + orientation of the frame in the case of a cylindrical grid. + * "origAxeY": Y coordinate of the origin of the axis used to determine the + orientation of the frame in the case of a cylindrical grid. + * "origAxeZ": Z coordinate of the origin of the axis used to determine the + orientation of the frame in the case of a cylindrical grid. + * "axeX": X coordinate of the axis used to determine the orientation of + the frame in the case of a cylindrical grid. + * "axeY": Y coordinate of the axis used to determine the orientation of + the frame in the case of a cylindrical grid. + * "axeZ": Z coordinate of the axis used to determine the orientation of + the frame in the case of a cylindrical grid. + + See class :class:`StructuralElementPart` for the description of the + other parameters. + """ + + DEFAULT_NAME = "Grid" + + def __init__(self, studyId, groupName, groupGeomObj, parameters, + name = DEFAULT_NAME): + StructuralElementPart2D.__init__(self, studyId, groupName, + groupGeomObj, parameters, name) + self.xr = self._getParameter(["origAxeX"]) + self.yr = self._getParameter(["origAxeY"]) + self.zr = self._getParameter(["origAxeZ"]) + self.vx = self._getParameter(["axeX"]) + self.vy = self._getParameter(["axeY"]) + self.vz = self._getParameter(["axeZ"]) + logger.debug(repr(self)) + + def _buildPart(self): + """ + Create the geometrical shapes representing the grid. + """ + subShapes = self._getSubShapes() + listGridShapes = [] + + for subShape in subShapes: + faces = self.geom.SubShapeAll(subShape, + self.geom.ShapeType["FACE"]) + for face in faces: + if self.geom.KindOfShape(face)[0] == \ + self.geom.kind.CYLINDER2D and \ + self.xr is not None and self.yr is not None and \ + self.zr is not None and self.vx is not None and \ + self.vy is not None and self.vz is not None: + shape = self._buildGridForCylinderFace(face) + else: + shape = self._buildGridForNormalFace(face) + listGridShapes.append(shape) + + if len(listGridShapes) == 0: + return None + elif len(listGridShapes) == 1: + return listGridShapes[0] + else: + return self.geom.MakeCompound(listGridShapes) + + def _buildGridForNormalFace(self, face): + """ + Create the geometrical shapes representing the grid for a given + non-cylindrical face. + """ + baseFace = self._makeFaceOffset(face, self.offset) + gridList = [baseFace] + + # Compute display length for grid elements + p1 = self.geom.MakeVertexOnSurface(baseFace, 0.0, 0.0) + p2 = self.geom.MakeVertexOnSurface(baseFace, 0.1, 0.1) + length = self.geom.MinDistance(p1, p2) / 2.0 + + for u in range(1, 10): + uParam = u * 0.1 + for v in range(1, 10): + vParam = v * 0.1 + # get tangent plane on surface by parameters + center = self.geom.MakeVertexOnSurface(baseFace, + uParam, vParam) + tangPlane = self.geom.MakeTangentPlaneOnFace(baseFace, uParam, + vParam, 1.0) + + # use the marker to get the orientation of the frame + normal = self.geom.GetNormal(tangPlane) + marker = self._orientation.buildMarker(self.geom, center, + normal, False) + [Ox,Oy,Oz, Zx,Zy,Zz, Xx,Xy,Xz] = self.geom.GetPosition(marker) + xPoint = self.geom.MakeTranslation(center, Xx * length, + Xy * length, Xz * length) + gridLine = self.geom.MakeLineTwoPnt(center, xPoint) + gridList.append(gridLine) + grid = self.geom.MakeCompound(gridList) + return grid + + def _buildGridForCylinderFace(self, face): + """ + Create the geometrical shapes representing the grid for a given + cylindrical face. + """ + baseFace = self._makeFaceOffset(face, self.offset) + gridList = [baseFace] + + # Compute display length for grid elements + p1 = self.geom.MakeVertexOnSurface(baseFace, 0.0, 0.0) + p2 = self.geom.MakeVertexOnSurface(baseFace, 0.1, 0.1) + length = self.geom.MinDistance(p1, p2) / 2.0 + + # Create reference vector V + origPoint = self.geom.MakeVertex(self.xr, self.yr, self.zr) + vPoint = self.geom.MakeTranslation(origPoint, + self.vx, self.vy, self.vz) + refVec = self.geom.MakeVector(origPoint, vPoint) + + for u in range(10): + uParam = u * 0.1 + for v in range(1, 10): + vParam = v * 0.1 + + # Compute the local orientation of the frame + center = self.geom.MakeVertexOnSurface(baseFace, + uParam, vParam) + locPlaneYZ = self.geom.MakePlaneThreePnt(origPoint, center, + vPoint, 1.0) + locOrient = self.geom.GetNormal(locPlaneYZ) + xPoint = self.geom.MakeTranslationVectorDistance(center, + locOrient, + length) + gridLine = self.geom.MakeLineTwoPnt(center, xPoint) + gridList.append(gridLine) + + grid = self.geom.MakeCompound(gridList) + return grid + + def _buildMarkers(self): + """ + Create the markers defining the orientation of the grid. + """ + return self._buildMarkersWithOffset(self.offset) + + +def VisuPoutreGenerale(studyId, groupName, groupGeomObj, parameters, + name = "POUTRE"): + """ + Alias for class :class:`GeneralBeam`. + """ + return GeneralBeam(studyId, groupName, groupGeomObj, parameters, name) + +def VisuPoutreCercle(studyId, groupName, groupGeomObj, parameters, + name = "POUTRE"): + """ + Alias for class :class:`CircularBeam`. + """ + return CircularBeam(studyId, groupName, groupGeomObj, parameters, name) + +def VisuPoutreRectangle(studyId, groupName, groupGeomObj, parameters, + name = "POUTRE"): + """ + Alias for class :class:`RectangularBeam`. + """ + return RectangularBeam(studyId, groupName, groupGeomObj, parameters, name) + +def VisuBarreGenerale(studyId, groupName, groupGeomObj, parameters, + name = "BARRE"): + """ + Alias for class :class:`GeneralBeam`. + """ + return GeneralBeam(studyId, groupName, groupGeomObj, parameters, name) + +def VisuBarreRectangle(studyId, groupName, groupGeomObj, parameters, + name = "BARRE"): + """ + Alias for class :class:`RectangularBeam`. + """ + return RectangularBeam(studyId, groupName, groupGeomObj, parameters, name) + +def VisuBarreCercle(studyId, groupName, groupGeomObj, parameters, + name = "BARRE"): + """ + Alias for class :class:`CircularBeam`. + """ + return CircularBeam(studyId, groupName, groupGeomObj, parameters, name) + +def VisuCable(studyId, groupName, groupGeomObj, parameters, name = "CABLE"): + """ + Alias for class :class:`CircularBeam`. + """ + return CircularBeam(studyId, groupName, groupGeomObj, parameters, name) + +def VisuCoque(studyId, groupName, groupGeomObj, parameters, name = "COQUE"): + """ + Alias for class :class:`ThickShell`. + """ + return ThickShell(studyId, groupName, groupGeomObj, parameters, name) + +def VisuGrille(studyId, groupName, groupGeomObj, parameters, name = "GRILLE"): + """ + Alias for class :class:`Grid`. + """ + return Grid(studyId, groupName, groupGeomObj, parameters, name) diff --git a/src/Makefile.am b/src/Makefile.am index 446af680c..968fbdd33 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,7 +24,7 @@ SUBDIRS = ARCHIMEDE NMTDS NMTTools GEOMAlgo SKETCHER OCC2VTK GEOM \ BREPExport BREPImport IGESExport IGESImport STEPExport \ STEPImport STLExport VTKExport ShHealOper GEOMImpl GEOM_I \ - GEOMClient GEOM_I_Superv GEOM_SWIG + GEOMClient GEOM_I_Superv GEOM_SWIG GEOM_PY if GEOM_ENABLE_GUI SUBDIRS += OBJECT DlgRef GEOMFiltersSelection GEOMGUI GEOMBase GEOMToolsGUI \ @@ -40,4 +40,4 @@ DIST_SUBDIRS = ARCHIMEDE NMTDS NMTTools GEOMAlgo SKETCHER OCC2VTK GEOM BREPExpor GEOMToolsGUI DisplayGUI BasicGUI PrimitiveGUI GenerationGUI \ EntityGUI BuildGUI BooleanGUI TransformationGUI OperationGUI \ RepairGUI MeasureGUI GroupGUI BlocksGUI AdvancedGUI \ - GEOM_SWIG_WITHIHM + GEOM_SWIG_WITHIHM GEOM_PY