# -*- coding: utf-8 -*-
import importlib.resources as pkg_resources
import importlib
from lxml import etree
from . import schemas
import collections.abc
import inspect
from copy import copy
import xgbxml.gbxml_functions as gbxml_functions
import xgbxml.xml_functions as xml_functions
import xgbxml.xsd_functions as xsd_functions
import xgbxml.gbxml_xsd_functions as gbxml_xsd_functions
from . import render_functions
from . import geometry_functions
[docs]
def get_parser(version='6.01'):
"""Returns a lxml.etree.XMLParser containing custom elements for gbXML files.
:param version: The `gbxml version string <https://www.gbxml.org/schema_doc/6.01/GreenBuildingXML_Ver6.01.html#Link27C>`_. Default is '6.01'.
:type version: str
:rtype: lxml.etree.XMLParser
.. note::
Using this parser means that when lxml is used to read in a gbXML file,
the different elements (`gbXML`, `Campus`, `Building` etc.) are instantiated with
custom classes.
This means that the lxml/xgbxml elements have additional features, which are
specifically designed for working with gbXML files.
For example, the `gbXML` element has additional properties such as
:code:`version` and :code:`temperatureUnit` for direct access to the
XML attributes. It also has additional methods, such as :code:`add_Campus`,
for creating new child elements.
The *xgbxml* parser provides three types of additional properties and methods:
1. Properties and methods inherited from the :py:class:`~xgbxml.xgbxml.gbElement` class.
2. Properties and methods that are automatically generated from the
gbXML schema file. For example, this method generates the :code:`version` property and
the :code:`add_Campus` method for the gbXML element.
3. Properties and methods which are custom written for the element.
This is bespoke code written for a particular element to provide
additional functionality. An example is the
:py:func:`~xgbxml.xgbxml.Surface.get_shell` method of the
:py:class:`~xgbxml.xgbxml.Surface` class.
.. rubric:: Code Example
.. code-block:: python
from lxml import etree
import xgbxml
parser=xgbxml.get_parser() # default is gbXML version 6.01
tree=etree.parse('my_gbxml_file.xml', parser)
gbxml=tree.getroot()
"""
#create lookup
lookup = etree.ElementNamespaceClassLookup()
#set namespace
namespace = lookup.get_namespace('http://www.gbxml.org/schema')
#set default element
namespace[None]=gbElement
# load xsd_schema
xsd_schema_text=pkg_resources.read_text(schemas,
'GreenBuildingXML_Ver%s.xsd' % version)
xsd_schema=etree.fromstring(xsd_schema_text.encode())#.getroot()
# load schema_dict
# schema_text = pkg_resources.read_text(schema_dicts,
# 'schema_dict_%s.json' % version.replace('.','_'))
# schema_dict=json.loads(schema_text)
# load autogenerated gbElements_X_XX module
auto_gbElement_module = importlib.import_module('.auto.gbElements_%s' % version.replace('.','_'),
'xgbxml')
#print(auto_gbElement_module)
# loop through classes in module
for k,v in auto_gbElement_module.__dict__.items():
if not k.startswith('__'):
element_name=k[:-5].replace('-','_')
#print(element_name)
base_classes=[gbElement,v]
# add custom written element class in gbxml directory if it exists
try:
kls=globals()[element_name]
base_classes.append(kls)
except KeyError:
pass
# add class for element into namespace
namespace[element_name.replace('_','-')]=\
type(element_name,
tuple(base_classes),
dict(
#_class_schema_dict=schema_dict,
_xsd_schema=xsd_schema
)
)
parser = etree.XMLParser(remove_blank_text=True)
parser.set_element_class_lookup(lookup)
return parser
[docs]
def create_gbXML(id=None,
engine=None,
temperatureUnit='C',
lengthUnit='Meters',
areaUnit='SquareMeters',
volumeUnit='CubicMeters',
useSIUnitsForResults=True,
version='6.01',
SurfaceReferenceLocation=None
):
"""Returns a root `gbXML` element for a new, blank gbXML file.
The keyword arguments for this function set the XML attributes of the
newly created `gbXML` element. `See the gbXML schema for details
of these attributes.
<https://www.gbxml.org/schema_doc/6.01/GreenBuildingXML_Ver6.01.html#Link105>`_.
:rtype: xgbxml.xgbxml.gbXML (subclass of lxml.etree._Element)
.. note::
The returned object is a subclass of lxml.Element, not an lxml.ElementTree.
To access the ElementTree of the returned gbXML element, the lxml
method :code:`getroottree()` can be used. This is needed to save the
gbXML file using the ElementTree :code:`write()` method.
.. rubric:: Code Example
.. code-block:: python
import xgbxml
gbxml=xgbxml.create_gbXML()
tree=gbxml.getroottree()
tree.write('new_gbxml_file.xml')
"""
xml='<gbXML version="%s" xmlns="http://www.gbxml.org/schema"></gbXML>' % version
parser=get_parser(version)
root=etree.fromstring(xml,parser)
if not id is None: root.id=id
if not engine is None: root.engine=engine
root.temperatureUnit=temperatureUnit
root.lengthUnit=lengthUnit
root.areaUnit=areaUnit
root.volumeUnit=volumeUnit
root.useSIUnitsForResults=useSIUnitsForResults
if not SurfaceReferenceLocation is None: root.SurfaceReferenceLocation=SurfaceReferenceLocation
return root
[docs]
class gbElement(etree.ElementBase):
"""A base class which is inherited by all xgbxml element instances.
When using either :py:func:`~xgbxml.xgbxml.get_parser` or
:py:func:`~xgbxml.xgbxml.create_gbXML`, all elements created will have the
properties and methods available in this class.
"""
def _introspect(self):
""
print('self.__class__',self.__class__)
print('self.__class__.__bases__',self.__class__.__bases__)
print(self.__class__.__bases__[1])
print([x for x in dir(self.__class__.__bases__[1]) if not x.startswith('_')])
import inspect
for name,obj in inspect.getmembers(self.__class__.__bases__[1]):
print(name, type(obj))
if inspect.isfunction(obj):
print(inspect.signature(obj))
[docs]
def __repr__(self):
"""The repr for the class.
Determines how the instance is displayed when printed.
:returns: A different value is returned depending on if this is the
gbElement class or a subclass, and/or if the element has an 'id' attribute.
:rtype: str
"""
try:
id_=self.get_attribute('id')
id_st=' (id="%s")' % id_
except KeyError:
id_st=''
if self.__class__.__name__=='gbElement':
return '<%s %s%s>' % (self.__class__.__name__,
self.nntag,
id_st)
else:
return '<%s%s>' % (self.__class__.__name__,
id_st)
[docs]
def add_child(self,
child_nntag,
value=None,
**kwargs):
"""Adds a new child element to the element.
:param child_nntag: The 'no namespace' tag of the child element (i.e. "Campus")
:type child_nntag: str
:param value: The value for the element. Optional.
:type value: str, float, bool etc.
:param kwargs: Attributes to be set for the child element. Optional.
:raises KeyError: If the child name does not exist in the schema.
:raises: Other error may be raised if the optional value or attributes are
not specified correctly.
:returns: The newly created child element.
:rtype: (subclass of) gbElement
"""
return gbxml_functions.add_child_to_gbxml_element(
gbxml_element=self,
child_nntag=child_nntag,
xsd_schema=self.xsd_schema,
value=value,
**kwargs)
[docs]
def get_attribute(self,attribute_name):
"""Returns the attribute value of the element.
:param attribute_name: The name of the attribute.
:param attribute_name: str
:raises KeyError: If the attribute is not present in the element.
:returns: The text value of the attribute converted to the python type
of the attribute.
:rtype: bool, str or float etc.
"""
return gbxml_functions.get_attribute_of_gbxml_element(
gbxml_element=self,
attribute_name=attribute_name,
xsd_schema=self.xsd_schema
)
@property
def get_attributes(self):
"""Returns the attributes of the element.
:param gbxml_element: A gbXML element.
:type gbxml_element: lxml.etree._Element
:param xsd_schema: The root node of a gbXML schema.
:type xsd_schema: lxml.etree._Element
:returns: A dictionary of attributes where the attribute values
have been converted to the correct python types according to the
schema.
:rtype: dict
"""
return gbxml_functions.get_attributes_of_gbxml_element(
gbxml_element=self,
xsd_schema=self.xsd_schema
)
[docs]
def get_child(self,
child_nntag,
child_id=None):
"""Returns a child element with specified tag.
If child_id is not supplied, then the first child element found is returned.
:param child_nntag: The 'no namespace' tag of the child element.
:type child_nntag: str
:param child_id: Optional, the 'id' attribute of the child element.
:type child_id: str
:raises KeyError: If the child element is not present.
:rtype: (subclass of) gbElement
"""
return gbxml_functions.get_child_of_gbxml_element(
self,
child_nntag,
child_id=child_id)
[docs]
def get_children(self,child_nntag):
"""Returns all child element with specified tag.
:param child_nntag: The 'no namespace' tag of the child element (i.e. "Campus")
:type child_nntag: str
:rtype: gbCollection
"""
return gbCollection(
*gbxml_functions.get_children_of_gbxml_element(
self,
child_nntag
)
)
@property
def id(self):
"""Returns / sets the 'id' attribute of the element.
:param value: When setting, the value of the id attribute
:type value: str
:raises KeyError: When returning, if the 'id' attribute is not present in the element.
:raises KeyError: When setting, if attribute name does not exist in the schema.
:raises TypeError: When setting, if the attribute value is of a type that does not match
the schema.
:rtype: str (when returning)
"""
return self.get_attribute('id')
@id.setter
def id(self,value):
"""Sets the 'id' attribute of the element.
"""
self.set_attribute('id',value)
@property
def nntag(self):
"""Returns the tag without the namespace ('no namespace tag')
:rtype: str
.. rubric:: Code Example
.. code-block:: python
>>> print(gbxml.tag)
{http://www.gbxml.org/schema}gbXML
>>> print(gbxml.nntag)
gbXML
"""
return xml_functions.nntag(self)
@property
def ns(self):
"""The namespace dictionary for xpath calls.
:rtype: dict
"""
return gbxml_functions.ns
[docs]
def set_attribute(self,attribute_name,value):
"""Sets an attribute value of the element.
Attribute will be created if it does not already exist.
Attribute value is modified if attribute does already exist.
:param attribute_name: The name of the attribute.
:param attribute_name: str
:param value: The new value for the attribute.
:type value: bool, str, float
:raises KeyError: If attribute name does not exist in the schema.
:raises ValueError: If attribute has enumerations, and 'value' does not
match one of the enumeration options.
:raises TypeError: If the attribute value is of a type that does not match
the schema.
"""
return gbxml_functions.set_attribute_on_gbxml_element(self,
attribute_name,
value,
self.xsd_schema)
[docs]
def tostring(self):
"""Returns a string of the xml of the element.
:rtype: str
"""
return etree.tostring(copy(self), pretty_print=True).decode()
@property
def xsd_schema(self):
""
return self._xsd_schema
@property
def value(self):
"""Returns / set the value of the gbXML element.
This is stored in the text value of the XML element.
:param value: When setting, the value for the element.
:type value: str, float, bool etc.
:raises TypeError: When setting, if value is of a type that does not match
the schema.
:returns: When returning, a value which is converted from the element text node.
:rtype: str, int, float or book etc.
"""
xsd_type=gbxml_xsd_functions.get_xsd_type_of_text_of_xsd_element(
self.nntag,
self.xsd_schema
)
python_type=xsd_functions.xsd_type_to_python_type(xsd_type)
return python_type(self.text)
@value.setter
def value(self,value):
"""Sets the value of the element.
"""
gbxml_functions.set_value_of_gbxml_element(
gbxml_element=self,
value=value,
xsd_schema=self.xsd_schema
)
[docs]
class gbCollection(collections.abc.Sequence):
"""This class is a collection of xgbxml elements.
It acts like a list, but is immutable so cannot be changed or updated.
However the items in the list can be changed.
Instances of this class occur when a list of xgbxml elements is returned
using the :py:func:`get_children` method or a method that is based on
:py:func:`get_children`.
For example, the code below returns a :code:`gbCollection` instance of
Building elements:
.. code-block:: python
from lxml import etree
import xgbxml
parser=xgbxml.get_parser() # default is gbXML version 6.01
tree=etree.parse('gbXMLStandard.xml', parser)
gbxml=tree.getroot()
buildings=gbxml.Campus.Buildings
print(buildings)
# prints "gbCollection(<Building (id="aim0013")>)"
print(type(buildings))
# prints "<class 'xgbxml.xgbxml.gbCollection'>"
"""
[docs]
def __getattr__(self,key):
"""This method catches any unknown attribute calls on a gbCollection instance.
This allows gbCollection instances to propogate methods and property calls
onto the elements stored in the collection.
:param key: The key passes to __getattr__
:param key: str
This can be used to access all child elements of the elements in a gbCollection.
For example, the code below shows a quick access approach to query
all the openings in a gbXML file.
.. code-block:: python
from lxml import etree
import xgbxml
parser=xgbxml.get_parser() # default is gbXML version 6.01
tree=etree.parse('gbXMLStandard.xml', parser)
gbxml=tree.getroot()
openings=gbxml.Campus.Surfaces.Openings
print(len(openings))
# prints "138"
print(type(openings))
# prints "<class 'xgbxml.xgbxml.gbCollection'>"
This can also be used to access the properties of the elements of a gbCollection.
For example, the code below shows a quick-access approach to listing
all the openingTypes attributes of the openings in a gbXL file.
.. code-block:: python
from lxml import etree
import xgbxml
parser=xgbxml.get_parser() # default is gbXML version 6.01
tree=etree.parse('gbXMLStandard.xml', parser)
gbxml=tree.getroot()
opening_types=gbxml.Campus.Surfaces.Openings.openingType
print(len(opening_types))
# prints "138"
print(type(opening_types))
# prints "<class 'tuple'>"
print(opening_types)
# prints "('NonSlidingDoor', 'NonSlidingDoor', 'NonSlidingDoor', ...)"
The approach also works with method calls. For example, the code below
creates a list of the areas of all openings in a gbXML file.
.. code-block:: python
from lxml import etree
import xgbxml
parser=xgbxml.get_parser() # default is gbXML version 6.01
tree=etree.parse('gbXMLStandard.xml', parser)
gbxml=tree.getroot()
surface_areas=gbxml.Campus.Surfaces.Openings.get_area()
print(len(surface_areas))
# prints "138"
print(type(surface_areas))
# prints "<class 'tuple'>"
print(surface_areas)
# prints "(21.0, 21.000000056466668, 21.0, ...)"
"""
#print('__getattr__', key)
result=[]
for x in self:
y=getattr(x,key)
if isinstance(y,gbCollection):
result.extend(y)
else:
result.append(y)
if len(result)==0:
return []
elif isinstance(result[0],gbElement): # if result is a collection of elements
return gbCollection(*result)
elif inspect.ismethod(result[0]): # if result is a collection of methods
def boundmethods(*args,**kwargs):
y=[x(*args,**kwargs) for x in result]
if isinstance(y[0],gbElement):
return gbCollection(*y)
else:
return tuple(y)
return boundmethods
else:
return tuple(result)
def __getitem__(self,index):
"""
:param index: The index value passed to __getitem__
:type index: int
"""
if isinstance(index, slice):
indices = range(*index.indices(len(self._items)))
return gbCollection(*[self._items[i] for i in indices])
else:
return self._items[index]
def __init__(self,*items):
""
self._items=tuple(items)
def __len__(self):
""
return len(self._items)
def __repr__(self):
""
return '%s(%s)' % (self.__class__.__name__,
', '.join([str(c) for c in self]))
[docs]
class Building():
""
[docs]
def get_gaps_in_surfaces(
self
):
"""Identifies any gaps in the surfaces of the Building.
To be used for spotting errors in the geometry - such as small surfaces
not being exported from REVIT.
See https://forums.autodesk.com/t5/revit-api-forum/gbxml-from-adjacent-conceptual-mass-adjacent-space-missing-small/m-p/12232100.
This functions looks at each Space in the Building and determines if
the edges of the Surfaces adjacent to the Space match with each other.
Where they do not match, this may indicate a gap in the Surfaces.
:returns: A list of dictionaries. Each dictionary contains the shell
(exterior) of missing surface polygon and a list of the adjacent Spaces.
For example:
.. code-block:: python
[
{
'space_ids': ['aim2197'],
'shell': [
(72.2287629, -0.3141381, 0.0),
(72.2287629, -0.4999998, 0.0),
(72.0986211, -0.4999998, 0.0),
(72.2287629, -0.3141381, 0.0)
]
},
{
'space_ids': ['aim2553', 'aim7413'],
'shell': [
(80.2291667, 14.5625, 10.0),
(80.0208333, 14.5625, 10.0),
(80.0208333, 16.020833, 10.0),
(80.2291667, 16.020833, 10.0),
(80.2291667, 14.5625, 10.0)
]
}
]
:rtype: list
"""
result = gbxml_functions.get_gaps_in_Surfaces_of_Building(
self,
self.xsd_schema
)
return result
[docs]
class Campus():
""
[docs]
def render(self,
ax=None,
set_lims=True,
surface_outline_kwargs=None,
surface_fill_kwargs=None,
opening_outline_kwargs=None,
opening_fill_kwargs=None):
"""Renders the Campus in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param surface_outline_kwargs: matplotlib keywork arguments for formatting
the outlines of surfaces (passed to ax.plot method).
:param surface_fill_kwargs: matplotlib keywork arguments for formatting the
fill of surfaces (passed to Poly3DCollection method).
:param opening_outline_kwargs: matplotlib keywork arguments for formatting
the outlines of openings (passed to ax.plot method).
:param opening_fill_kwargs: matplotlib keywork arguments for formatting the
fill of openings (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
#print(self)
for su in self.Surfaces:
#try:
ax=su.render(ax=ax,
set_lims=set_lims,
surface_outline_kwargs=surface_outline_kwargs,
surface_fill_kwargs=surface_fill_kwargs,
opening_outline_kwargs=opening_outline_kwargs,
opening_fill_kwargs=opening_fill_kwargs
)
#except TypeError as err:
# print(su)
# print(type(err))
return ax
[docs]
class CartesianPoint():
""
[docs]
def create_Coordinates(self,*coordinates):
"""Creates Coordinate child elements and sets their value.
:param coordinates: The values of the x,y,(z) coordinates as an argument list.
For example: ((0,0,0),(1,0,0),(0,1,0))
:type coordinates: tuple otr list etc.
:returns: The newly creeated Coordinate elements.
:rtype: gbCollection
"""
return gbCollection(
*gbxml_functions.add_Coordinates_to_CartesianPoint(
gbxml_element=self,
xsd_schema=self.xsd_schema,
*coordinates
)
)
[docs]
def get_coordinates(self):
"""Returns the values of the Coordinate child elements.
:rtype: tuple(float)
"""
return gbxml_functions.get_Coordinate_values_from_CartesianPoint(
self,
self.xsd_schema
)
[docs]
class ClosedShell():
""
# def get_gaps(
# self
# ):
# """
# """
# return gbxml_functions.get_gaps_in_ClosedShell(
# self,
# self.xsd_schema
# )
[docs]
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
fill_kwargs=None):
"""Renders the PolyLoops of the ClosedShell in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param outline_kwargs: matplotlib keywork arguments for formatting
the outlines of surfaces (passed to ax.plot method).
:param fill_kwargs: matplotlib keywork arguments for formatting the
fill of surfaces (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
#print(self)
for pl in self.PolyLoops:
#try:
ax=pl.render(ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
fill_kwargs=fill_kwargs
)
#except TypeError as err:
# print(su)
# print(type(err))
return ax
[docs]
class Opening():
""
[docs]
def get_area(self):
"""Calculates the area of the opening.
:returns: The area of the Opening.
:rtype: float
"""
shell=self.get_shell()
holes=[]
return geometry_functions.polygon_area_3d(shell,holes)
[docs]
def get_shell(self):
"""Returns the shell of the outer polyloop of the opening.
The following sources are tried in order:
- PlanarGeometry
- RectangularGeometry/PolyLoop
- RectangularGeoemetry... from height and width
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_shell_of_Opening(self,
self.xsd_schema)
[docs]
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
fill_kwargs=None):
"""
Renders the Opening in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param outline_kwargs: matplotlib keywork arguments for formatting
the outlines of the openings (passed to ax.plot method).
:param surface_kwargs: matplotlib keywork arguments for formatting the
surface rendering (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
x=dict(color='green')
if outline_kwargs is None:
outline_kwargs=x
else:
for k,v in x.items:
if not k in outline_kwargs:
outline_kwargs[k]=v
x=dict(color='green')
if fill_kwargs is None:
fill_kwargs=x
else:
for k,v in x.items:
if not k in fill_kwargs:
fill_kwargs[k]=v
ax=render_functions.render_polygon_3d(
polygon=(self.get_shell(),[]),
polygon_triangles=None,
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
fill_kwargs=fill_kwargs
)
return ax
[docs]
class PlanarGeometry():
""
[docs]
def get_area(self):
"""Returns the area of the polygon described by the PlanarGeometry.
:rtype: float
"""
shell=self.get_shell()
holes=[]
return geometry_functions.polygon_area_3d(shell,holes)
[docs]
def get_coordinates(self):
"""Returns the coordinates of the polyloop child element.
:returns: Point_coordinates where each point_coordinate is a tuple of
the (x,y,(z)) coordinates of a CartesianPoint.
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_coordinate_values_from_PlanarGeometry(
self,
self.xsd_schema
)
[docs]
def get_shell(self):
"""Returns the shell of the polyloop child element.
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_shell_of_PlanarGeometry(
self,
self.xsd_schema
)
[docs]
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
fill_kwargs=None):
"""Renders the PlanarGeometry in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param outline_kwargs: matplotlib keywork arguments for formatting
the outlines (passed to ax.plot method).
:param fill_kwargs: matplotlib keywork arguments for formatting the
fill (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
ax=self.PolyLoop.render(
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
fill_kwargs=fill_kwargs
)
return ax
[docs]
def set_shell(
self,
shell
):
"""Creates new coordinate points for the PlanarGeometry.
:param shell: The exterior points of a polygon
(first and lost point of shell are the same).
:type shell: tuple(tuple(float))
"""
gbxml_functions.set_shell_of_PlanarGeometry(
gbxml_planar_geometry=self,
shell=shell,
xsd_schema=self.xsd_schema)
[docs]
class PolyLoop():
""
[docs]
def create_CartesianPoints(self,*points_coordinates):
"""Creates CartesianPoint child elements with Coordinate subelements.
:param points_coordinates: An argument list of tuple where each tuple is
the (x,y,(z)) coordinates of a CartesianPoint.
:type points_coordinates: tuple
:returns: The newly creeated CartesianPoint elements.
:rtype: list(CartesianPoints)
"""
for point_coordinates in points_coordinates:
self.add_CartesianPoint().create_Coordinates(*point_coordinates)
return self.CartesianPoints
[docs]
def get_area(self):
"""
:returns: The area of the PolyLoop
:rtype: float
"""
shell=self.get_shell()
holes=[]
return geometry_functions.polygon_area_3d(shell,holes)
[docs]
def get_coordinates(self):
"""Returns the coordinates of the CartesianPoint child elements.
:returns: Point_coordinates where each point_coordinate is a tuple of
the (x,y,(z)) coordinates of a CartesianPoint.
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_coordinate_values_from_PolyLoop(
self,
self.xsd_schema
)
[docs]
def get_shell(self):
"""Returns the shell of the Polyloop.
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_shell_of_PolyLoop(
self,
self.xsd_schema
)
[docs]
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
fill_kwargs=None):
"""Renders the PolyLoop in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param outline_kwargs: matplotlib keywork arguments for formatting
the outlines (passed to ax.plot method).
:param fill_kwargs: matplotlib keywork arguments for formatting the
fill (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
ax=render_functions.render_polygon_3d(
polygon=(self.get_shell(),[]),
polygon_triangles=None,
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
fill_kwargs=fill_kwargs
)
return ax
[docs]
class RectangularGeometry():
"""
"""
[docs]
def get_shell(self):
"""Returns the shell coordinates of the rectangular geometry.
The following sources are tried in order:
- RectangularGeometry/PolyLoop
- RectangularGeoemetry... from height and width
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_shell_of_RectangularGeometry(
self,
self.xsd_schema
)
# def get_shell_from_height_and_width(self):
# """Returns the shell coordinates of the rectangular geometry.
# :rtype: tuple(tuple(float))
# """
# return gbxml_functions.get_shell_from_height_and_width_of_RectangularGeometry(
# self,
# self.xsd_schema
# )
[docs]
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
fill_kwargs=None):
"""Renders the RectangularGeometry in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param outline_kwargs: matplotlib keywork arguments for formatting
the outlines (passed to ax.plot method).
:param fill_kwargs: matplotlib keywork arguments for formatting the
fill (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
ax=render_functions.render_polygon_3d(
polygon=(self.get_shell(),[]),
polygon_triangles=None,
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
fill_kwargs=fill_kwargs
)
return ax
[docs]
class Space():
# def get_gaps_in_surfaces(
# self
# ):
# """
# """
# result = gbxml_functions.get_gaps_in_Surfaces_of_Space(
# self,
# self.xsd_schema
# )
# return result
[docs]
def get_surfaces(
self
):
"""Returns all Surfaces adjacent to the Space.
:rtype: gbCollection(Surfaces)
"""
result = gbxml_functions.get_Surfaces_of_Space(
self,
self.xsd_schema
)
return gbCollection(*result)
[docs]
def render(self,
ax=None,
set_lims=True,
surface_outline_kwargs=None,
surface_fill_kwargs=None,
opening_outline_kwargs=None,
opening_fill_kwargs=None):
"""Renders the Surfaces of the Space in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param surface_outline_kwargs: matplotlib keywork arguments for formatting
the outlines of surfaces (passed to ax.plot method).
:param surface_fill_kwargs: matplotlib keywork arguments for formatting the
fill of surfaces (passed to Poly3DCollection method).
:param opening_outline_kwargs: matplotlib keywork arguments for formatting
the outlines of openings (passed to ax.plot method).
:param opening_fill_kwargs: matplotlib keywork arguments for formatting the
fill of openings (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
#print(self)
for su in self.get_surfaces():
#try:
ax=su.render(ax=ax,
set_lims=set_lims,
surface_outline_kwargs=surface_outline_kwargs,
surface_fill_kwargs=surface_fill_kwargs,
opening_outline_kwargs=opening_outline_kwargs,
opening_fill_kwargs=opening_fill_kwargs
)
#except TypeError as err:
# print(su)
# print(type(err))
return ax
[docs]
class Surface():
""
[docs]
def copy_opening(self,
opening,
tolerance=0.01):
"""Makes a copy of an Opening and places it on the Surface.
:param opening: The opening to be copied.
:type opening: xgbxml.xgbxml.Opening
:param tolerance: The distance which an opening can be 'snapped' to a surface in meters.
:type tolerance: fload
:returns: The newly created Opening.
:rtype: xgbxml.xgbxml.Opening
"""
return gbxml_functions.copy_Opening_to_Surface(
opening,
self,
self.xsd_schema,
tolerance=tolerance
)
[docs]
def get_area(self):
"""Calculates the area of the surface (does not include the area of any openings)
:returns: The area of the Surface (the shell area minus the Opening areas)
:rtype: float
"""
shell=self.get_shell()
holes=self.get_holes()
return geometry_functions.polygon_area_3d(shell,holes)
[docs]
def get_holes(self):
"""Returns the coordinates of the holes of the surface.
:rtype: list(tuple(tuple(float)))
"""
return gbxml_functions.get_holes_of_Surface(self,
self.xsd_schema)
[docs]
def get_shell(self):
"""Returns the shell coordinates of the outer polyloop of the opening.
The following sources are tried in order:
- PlanarGeometry
- RectangularGeometry/PolyLoop
- RectangularGeoemetry... from height and width
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_shell_of_Surface(self,
self.xsd_schema)
[docs]
def get_Spaces(self):
"""Returns the space elements adjacent to the surface.
:returns: The Spaces as given by the AdjacentSpaceId elements.
:rtype: gbCollection
"""
return gbCollection(
*gbxml_functions.get_Spaces_of_Surface(self)
)
[docs]
def get_polygon(self):
"""Returns the polygon coordinates of the outer polyloop of the surface.
The following sources are tried in order:
- PlanarGeometry
- RectangularGeometry/PolyLoop
- RectangularGeoemetry... from height and width
:returns: (shell_coordinates, list of hole coordinates)
:rtype: ( tuple(tuple(float)), list(tuple(tuple(float))) )
"""
return gbxml_functions.get_polygon_of_Surface(self,
self.xsd_schema)
[docs]
def render(self,
ax=None,
set_lims=True,
surface_outline_kwargs=None,
surface_fill_kwargs=None,
opening_outline_kwargs=None,
opening_fill_kwargs=None):
"""Renders the Surface in 3D using matplotlib.
:param ax: A matplotlib 3D Axes instance. Optional, if not supplied
then an axis is created and returned.
:type ax: matplotlib.axes._subplots.Axes3DSubplot
:param set_lims: If True, then the x, y and z axis limits are set
automatically based on the geometry being rendered.
:type set_lims: bool
:param surface_outline_kwargs: matplotlib keywork arguments for formatting
the outlines of surfaces (passed to ax.plot method).
:param surface_fill_kwargs: matplotlib keywork arguments for formatting the
fill of surfaces (passed to Poly3DCollection method).
:param opening_outline_kwargs: matplotlib keywork arguments for formatting
the outlines of openings (passed to ax.plot method).
:param opening_fill_kwargs: matplotlib keywork arguments for formatting the
fill of openings (passed to Poly3DCollection method).
:returns: The axis instance.
:rtype: matplotlib.axes._subplots.Axes3DSubplot
"""
ax=render_functions.render_polygon_3d(
polygon=self.get_polygon(),
polygon_triangles=None,
ax=ax,
set_lims=set_lims,
outline_kwargs=surface_outline_kwargs,
fill_kwargs=surface_fill_kwargs
)
for op in self.Openings:
ax=op.render(ax=ax,
set_lims=set_lims,
outline_kwargs=opening_outline_kwargs,
fill_kwargs=opening_fill_kwargs)
return ax