# -*- coding: utf-8 -*-
import json
import importlib.resources as pkg_resources
import importlib
from lxml import etree
from . import schema_dicts
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.gbxml_xsd_functions as gbxml_xsd_functions
import math
from . import render_functions
from .geometry_functions import vector_normalize_3d, vector_multiplication_3d, vector_addition_3d
[docs]def get_parser(version='6.01'):
"""Returns a lxml.etree.XMLParser containing custom elements for gbXML files.
:param version: The gbxml version string. Default is '6.01'.
:type version: str
:rtype: lxml.etree.XMLParser
"""
#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()
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.
.. note::
The returned object is a subclass of lxml.Element, not an lxml.ElementTree.
:rtype: gbXML
"""
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):
"""The default element class for the gbxml parser.
"""
def __repr__(self):
"""The repr for the class
: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,**kwargs):
"""Adds a new child element to the element.
:param child_nntag: The 'no namespace' tag of the child element.
:type child_nntag: str
:param kwargs: Attributes to be set for the child element.
:returns: The newly created child element.
:rtype: (subclass of) gbElement
"""
return gbxml_functions.add_child_to_gbxml_element(
self,
child_nntag,
self.xsd_schema,
**kwargs)
@property
def get_attributes(self):
"""The attributes of the 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(self,
self.xsd_schema)
@property
def id(self):
"""The id of the element.
:raises KeyError: If, on retrieval, the 'id' attribute is not present in the element.
:rtype: str
"""
return self.get_attribute('id')
@id.setter
def id(self,value):
""
self.set_attribute('id',value)
[docs] def get_attribute(self,attribute_name):
"""Returns the attribute value as a python type.
:param attribute_name: The name of the attribute.
:param attribute_name: str
:raises KeyError: If the attribute is not present in the element.
:rtype: bool, str, float
"""
return gbxml_functions.get_attribute_of_gbxml_element(self,
attribute_name,
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 ??: 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 child elements with a specified tag.
:param child_nntag: The 'no e coercednamespace' tag of the child elements.
:type child_nntag: str
:rtype: list ??
"""
return gbCollection(
*gbxml_functions.get_children_of_gbxml_element(
self,
child_nntag
)
)
[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.
Value is coerced to the correct python type if needed.
: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 does not exist in the schema.
:raises ValueError: If attribute has enumerations, and 'value' does not
match one of the enumeration options.
:rtype: The (coerced) value assigned to the attribute.
"""
return gbxml_functions.set_attribute_on_gbxml_element(self,
attribute_name,
value,
self.xsd_schema)
@property
def nntag(self):
"""Returns the tag without the namespace ('no namespace tag')
Example:
>>> print(gbXML.tag)
{http://www.gbxml.org/schema}gbXML
>>> print(gbXML.nntag)
gbXML
:rtype: str
"""
return xml_functions.nntag(self)
@property
def ns(self):
"""The namespace dictionary for xpath calls.
:rtype: dict
"""
return gbxml_functions.ns
[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):
"""The value of the element text as a python type.
:rtype: str, float
"""
xsd_type=gbxml_xsd_functions.get_xsd_type_of_text_of_xsd_element(
self.nntag,
self.xsd_schema
)
python_type=xml_functions.xsd_type_to_python_type(xsd_type)
return python_type(self.text)
@value.setter
def value(self,value):
""
self.text=str(value)
[docs]class gbCollection(collections.abc.Sequence):
"""
"""
def __getattr__(self,key):
""
#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):
""
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 Campus():
""
[docs] def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
surface_kwargs=None):
"""
"""
#print(self)
for su in self.Surfaces:
try:
ax=su.render(ax=ax)
except Exception 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.
:type coordinates: int, float
:returns: The newly creeated Coordinate elements.
:rtype: list(Coordinate)
"""
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 Opening():
""
[docs] def get_shell(self):
"""Returns a Polygon 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)
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
surface_kwargs=None):
""
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 surface_kwargs is None:
surface_kwargs=x
else:
for k,v in x.items:
if not k in surface_kwargs:
surface_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,
surface_kwargs=surface_kwargs
)
return ax
[docs]class PlanarGeometry():
""
[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 a Polygon of the polyloop child element.
:rtype: tuple
"""
return gbxml_functions.get_shell_of_PlanarGeometry(
self,
self.xsd_schema
)
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
surface_kwargs=None):
""
ax=self.PolyLoop.render(
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
surface_kwargs=surface_kwargs
)
return ax
[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_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):
"""
"""
return gbxml_functions.get_shell_of_PolyLoop(
self,
self.xsd_schema
)
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
surface_kwargs=None):
""
ax=render_functions.render_polygon_3d(
polygon=(self.get_shell(),[]),
polygon_triangles=None,
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
surface_kwargs=surface_kwargs
)
return ax
[docs]class RectangularGeometry():
"""
"""
[docs] def get_shell(self):
"""Returns the 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
)
[docs] def get_shell_from_height_and_width(self):
"""
"""
return gbxml_functions.get_shell_from_height_and_width_of_RectangularGeometry(
self,
self.xsd_schema
)
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
surface_kwargs=None):
""
ax=render_functions.render_polygon_3d(
polygon=(self.get_shell(),[]),
polygon_triangles=None,
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
surface_kwargs=surface_kwargs
)
return ax
[docs]class Surface():
""
[docs] def copy_opening(self,
opening,
tolerance=0.01):
"""
"""
return gbxml_functions.copy_Opening_to_Surface(
opening,
self,
self.xsd_schema,
tolerance=tolerance
)
[docs] def get_holes(self):
"""
"""
return gbxml_functions.get_holes_of_Surface(self,
self.xsd_schema)
[docs] def get_shell(self):
"""Returns a Polygon 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.
"""
return gbxml_functions.get_Spaces_of_Surface(self)
[docs] def get_polygon(self):
"""Returns a Polygon of the outer polyloop of the surface.
The following sources are tried in order:
- PlanarGeometry
- RectangularGeometry/PolyLoop
- RectangularGeoemetry... from height and width
:rtype: tuple(tuple(float))
"""
return gbxml_functions.get_polygon_of_Surface(self,
self.xsd_schema)
def render(self,
ax=None,
set_lims=True,
outline_kwargs=None,
surface_kwargs=None):
""
ax=render_functions.render_polygon_3d(
polygon=self.get_polygon(),
polygon_triangles=None,
ax=ax,
set_lims=set_lims,
outline_kwargs=outline_kwargs,
surface_kwargs=surface_kwargs
)
for op in self.Openings:
ax=op.render(ax=ax)
return ax