diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/README.md b/README.md new file mode 100644 index 00000000..ebfb3665 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# documentation diff --git a/docs/.buildinfo b/docs/.buildinfo new file mode 100644 index 00000000..345502f9 --- /dev/null +++ b/docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: dec7e3e48b0ee4a16b147426c444a772 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/.doctrees/cli/compare.doctree b/docs/.doctrees/cli/compare.doctree new file mode 100644 index 00000000..0876d282 Binary files /dev/null and b/docs/.doctrees/cli/compare.doctree differ diff --git a/docs/.doctrees/cli/create.doctree b/docs/.doctrees/cli/create.doctree new file mode 100644 index 00000000..73c72ec9 Binary files /dev/null and b/docs/.doctrees/cli/create.doctree differ diff --git a/docs/.doctrees/cli/edit.doctree b/docs/.doctrees/cli/edit.doctree new file mode 100644 index 00000000..ac312be3 Binary files /dev/null and b/docs/.doctrees/cli/edit.doctree differ diff --git a/docs/.doctrees/cli/index.doctree b/docs/.doctrees/cli/index.doctree new file mode 100644 index 00000000..f809f0dd Binary files /dev/null and b/docs/.doctrees/cli/index.doctree differ diff --git a/docs/.doctrees/cli/lib.doctree b/docs/.doctrees/cli/lib.doctree new file mode 100644 index 00000000..02fd1ba3 Binary files /dev/null and b/docs/.doctrees/cli/lib.doctree differ diff --git a/docs/.doctrees/cli/main.doctree b/docs/.doctrees/cli/main.doctree new file mode 100644 index 00000000..fc347cfa Binary files /dev/null and b/docs/.doctrees/cli/main.doctree differ diff --git a/docs/.doctrees/cli/setconfig.doctree b/docs/.doctrees/cli/setconfig.doctree new file mode 100644 index 00000000..278f9fb4 Binary files /dev/null and b/docs/.doctrees/cli/setconfig.doctree differ diff --git a/docs/.doctrees/cli/validate.doctree b/docs/.doctrees/cli/validate.doctree new file mode 100644 index 00000000..a125b498 Binary files /dev/null and b/docs/.doctrees/cli/validate.doctree differ diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle new file mode 100644 index 00000000..0faf7531 Binary files /dev/null and b/docs/.doctrees/environment.pickle differ diff --git a/docs/.doctrees/honeybee.altnumber.doctree b/docs/.doctrees/honeybee.altnumber.doctree new file mode 100644 index 00000000..16d68a9d Binary files /dev/null and b/docs/.doctrees/honeybee.altnumber.doctree differ diff --git a/docs/.doctrees/honeybee.aperture.doctree b/docs/.doctrees/honeybee.aperture.doctree new file mode 100644 index 00000000..a89f57f6 Binary files /dev/null and b/docs/.doctrees/honeybee.aperture.doctree differ diff --git a/docs/.doctrees/honeybee.boundarycondition.doctree b/docs/.doctrees/honeybee.boundarycondition.doctree new file mode 100644 index 00000000..b641eb28 Binary files /dev/null and b/docs/.doctrees/honeybee.boundarycondition.doctree differ diff --git a/docs/.doctrees/honeybee.checkdup.doctree b/docs/.doctrees/honeybee.checkdup.doctree new file mode 100644 index 00000000..b00e1f81 Binary files /dev/null and b/docs/.doctrees/honeybee.checkdup.doctree differ diff --git a/docs/.doctrees/honeybee.cli.compare.doctree b/docs/.doctrees/honeybee.cli.compare.doctree new file mode 100644 index 00000000..52a639ce Binary files /dev/null and b/docs/.doctrees/honeybee.cli.compare.doctree differ diff --git a/docs/.doctrees/honeybee.cli.create.doctree b/docs/.doctrees/honeybee.cli.create.doctree new file mode 100644 index 00000000..0557879b Binary files /dev/null and b/docs/.doctrees/honeybee.cli.create.doctree differ diff --git a/docs/.doctrees/honeybee.cli.doctree b/docs/.doctrees/honeybee.cli.doctree new file mode 100644 index 00000000..625a952a Binary files /dev/null and b/docs/.doctrees/honeybee.cli.doctree differ diff --git a/docs/.doctrees/honeybee.cli.edit.doctree b/docs/.doctrees/honeybee.cli.edit.doctree new file mode 100644 index 00000000..25e46e75 Binary files /dev/null and b/docs/.doctrees/honeybee.cli.edit.doctree differ diff --git a/docs/.doctrees/honeybee.cli.lib.doctree b/docs/.doctrees/honeybee.cli.lib.doctree new file mode 100644 index 00000000..cc8196b4 Binary files /dev/null and b/docs/.doctrees/honeybee.cli.lib.doctree differ diff --git a/docs/.doctrees/honeybee.cli.setconfig.doctree b/docs/.doctrees/honeybee.cli.setconfig.doctree new file mode 100644 index 00000000..2e180a70 Binary files /dev/null and b/docs/.doctrees/honeybee.cli.setconfig.doctree differ diff --git a/docs/.doctrees/honeybee.cli.validate.doctree b/docs/.doctrees/honeybee.cli.validate.doctree new file mode 100644 index 00000000..f55c9934 Binary files /dev/null and b/docs/.doctrees/honeybee.cli.validate.doctree differ diff --git a/docs/.doctrees/honeybee.colorobj.doctree b/docs/.doctrees/honeybee.colorobj.doctree new file mode 100644 index 00000000..65f9e8e4 Binary files /dev/null and b/docs/.doctrees/honeybee.colorobj.doctree differ diff --git a/docs/.doctrees/honeybee.config.doctree b/docs/.doctrees/honeybee.config.doctree new file mode 100644 index 00000000..3c5cc8f7 Binary files /dev/null and b/docs/.doctrees/honeybee.config.doctree differ diff --git a/docs/.doctrees/honeybee.dictutil.doctree b/docs/.doctrees/honeybee.dictutil.doctree new file mode 100644 index 00000000..65e8e440 Binary files /dev/null and b/docs/.doctrees/honeybee.dictutil.doctree differ diff --git a/docs/.doctrees/honeybee.doctree b/docs/.doctrees/honeybee.doctree new file mode 100644 index 00000000..be92d228 Binary files /dev/null and b/docs/.doctrees/honeybee.doctree differ diff --git a/docs/.doctrees/honeybee.door.doctree b/docs/.doctrees/honeybee.door.doctree new file mode 100644 index 00000000..feeb423a Binary files /dev/null and b/docs/.doctrees/honeybee.door.doctree differ diff --git a/docs/.doctrees/honeybee.extensionutil.doctree b/docs/.doctrees/honeybee.extensionutil.doctree new file mode 100644 index 00000000..536c5d67 Binary files /dev/null and b/docs/.doctrees/honeybee.extensionutil.doctree differ diff --git a/docs/.doctrees/honeybee.face.doctree b/docs/.doctrees/honeybee.face.doctree new file mode 100644 index 00000000..9931c7a9 Binary files /dev/null and b/docs/.doctrees/honeybee.face.doctree differ diff --git a/docs/.doctrees/honeybee.facetype.doctree b/docs/.doctrees/honeybee.facetype.doctree new file mode 100644 index 00000000..07d5024e Binary files /dev/null and b/docs/.doctrees/honeybee.facetype.doctree differ diff --git a/docs/.doctrees/honeybee.logutil.doctree b/docs/.doctrees/honeybee.logutil.doctree new file mode 100644 index 00000000..b4414044 Binary files /dev/null and b/docs/.doctrees/honeybee.logutil.doctree differ diff --git a/docs/.doctrees/honeybee.model.doctree b/docs/.doctrees/honeybee.model.doctree new file mode 100644 index 00000000..c6fef077 Binary files /dev/null and b/docs/.doctrees/honeybee.model.doctree differ diff --git a/docs/.doctrees/honeybee.orientation.doctree b/docs/.doctrees/honeybee.orientation.doctree new file mode 100644 index 00000000..fd1a0548 Binary files /dev/null and b/docs/.doctrees/honeybee.orientation.doctree differ diff --git a/docs/.doctrees/honeybee.properties.doctree b/docs/.doctrees/honeybee.properties.doctree new file mode 100644 index 00000000..6de8b3ed Binary files /dev/null and b/docs/.doctrees/honeybee.properties.doctree differ diff --git a/docs/.doctrees/honeybee.room.doctree b/docs/.doctrees/honeybee.room.doctree new file mode 100644 index 00000000..0004a77d Binary files /dev/null and b/docs/.doctrees/honeybee.room.doctree differ diff --git a/docs/.doctrees/honeybee.search.doctree b/docs/.doctrees/honeybee.search.doctree new file mode 100644 index 00000000..e17103fc Binary files /dev/null and b/docs/.doctrees/honeybee.search.doctree differ diff --git a/docs/.doctrees/honeybee.shade.doctree b/docs/.doctrees/honeybee.shade.doctree new file mode 100644 index 00000000..4fa689c6 Binary files /dev/null and b/docs/.doctrees/honeybee.shade.doctree differ diff --git a/docs/.doctrees/honeybee.shademesh.doctree b/docs/.doctrees/honeybee.shademesh.doctree new file mode 100644 index 00000000..dc030419 Binary files /dev/null and b/docs/.doctrees/honeybee.shademesh.doctree differ diff --git a/docs/.doctrees/honeybee.typing.doctree b/docs/.doctrees/honeybee.typing.doctree new file mode 100644 index 00000000..8fc6d5c3 Binary files /dev/null and b/docs/.doctrees/honeybee.typing.doctree differ diff --git a/docs/.doctrees/honeybee.units.doctree b/docs/.doctrees/honeybee.units.doctree new file mode 100644 index 00000000..37856b18 Binary files /dev/null and b/docs/.doctrees/honeybee.units.doctree differ diff --git a/docs/.doctrees/honeybee.writer.aperture.doctree b/docs/.doctrees/honeybee.writer.aperture.doctree new file mode 100644 index 00000000..63f42e2b Binary files /dev/null and b/docs/.doctrees/honeybee.writer.aperture.doctree differ diff --git a/docs/.doctrees/honeybee.writer.doctree b/docs/.doctrees/honeybee.writer.doctree new file mode 100644 index 00000000..2e446b0a Binary files /dev/null and b/docs/.doctrees/honeybee.writer.doctree differ diff --git a/docs/.doctrees/honeybee.writer.door.doctree b/docs/.doctrees/honeybee.writer.door.doctree new file mode 100644 index 00000000..89c97549 Binary files /dev/null and b/docs/.doctrees/honeybee.writer.door.doctree differ diff --git a/docs/.doctrees/honeybee.writer.face.doctree b/docs/.doctrees/honeybee.writer.face.doctree new file mode 100644 index 00000000..a44f6d55 Binary files /dev/null and b/docs/.doctrees/honeybee.writer.face.doctree differ diff --git a/docs/.doctrees/honeybee.writer.model.doctree b/docs/.doctrees/honeybee.writer.model.doctree new file mode 100644 index 00000000..dc103305 Binary files /dev/null and b/docs/.doctrees/honeybee.writer.model.doctree differ diff --git a/docs/.doctrees/honeybee.writer.room.doctree b/docs/.doctrees/honeybee.writer.room.doctree new file mode 100644 index 00000000..f5253dc4 Binary files /dev/null and b/docs/.doctrees/honeybee.writer.room.doctree differ diff --git a/docs/.doctrees/honeybee.writer.shade.doctree b/docs/.doctrees/honeybee.writer.shade.doctree new file mode 100644 index 00000000..3f60f13e Binary files /dev/null and b/docs/.doctrees/honeybee.writer.shade.doctree differ diff --git a/docs/.doctrees/honeybee.writer.shademesh.doctree b/docs/.doctrees/honeybee.writer.shademesh.doctree new file mode 100644 index 00000000..baae9eb0 Binary files /dev/null and b/docs/.doctrees/honeybee.writer.shademesh.doctree differ diff --git a/docs/.doctrees/index.doctree b/docs/.doctrees/index.doctree new file mode 100644 index 00000000..0148948b Binary files /dev/null and b/docs/.doctrees/index.doctree differ diff --git a/docs/.doctrees/modules.doctree b/docs/.doctrees/modules.doctree new file mode 100644 index 00000000..8bd5a375 Binary files /dev/null and b/docs/.doctrees/modules.doctree differ diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..ebfb3665 --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +# documentation diff --git a/docs/_modules/honeybee/altnumber.html b/docs/_modules/honeybee/altnumber.html new file mode 100644 index 00000000..0a53bc9d --- /dev/null +++ b/docs/_modules/honeybee/altnumber.html @@ -0,0 +1,1169 @@ + + + + + + + honeybee.altnumber — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.altnumber

+"""Objects used as alternatives to various numerical properties."""
+
+
+class _AltNumber(object):
+    __slots__ = ()
+
+    def __init__(self):
+        pass
+
+    @property
+    def name(self):
+        return self.__class__.__name__
+
+    def to_dict(self):
+        """Get the object as a dictionary."""
+        return {'type': self.name}
+
+    def ToString(self):
+        return self.__repr__()
+
+    def __eq__(self, other):
+        return self.__class__ == other.__class__
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return self.name
+
+
+
[docs]class NoLimit(_AltNumber): + """Object representing no limit to a certain numerical value.""" + __slots__ = () + pass
+ + +
[docs]class Autocalculate(_AltNumber): + """Object representing when a certain numerical value is automatically calculated. + + Typically, this means that the value is determined from other variables. + """ + __slots__ = () + pass
+ + +no_limit = NoLimit() +autocalculate = Autocalculate() +
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/aperture.html b/docs/_modules/honeybee/aperture.html new file mode 100644 index 00000000..aa40cf13 --- /dev/null +++ b/docs/_modules/honeybee/aperture.html @@ -0,0 +1,1951 @@ + + + + + + + honeybee.aperture — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.aperture

+# coding: utf-8
+"""Honeybee Aperture."""
+from __future__ import division
+import math
+
+from ladybug_geometry.geometry2d.pointvector import Vector2D
+from ladybug_geometry.geometry3d.pointvector import Point3D
+from ladybug_geometry.geometry3d.face import Face3D
+from ladybug.color import Color
+
+from ._basewithshade import _BaseWithShade
+from .typing import clean_string
+from .properties import ApertureProperties
+from .boundarycondition import boundary_conditions, Outdoors, Surface
+from .shade import Shade
+import honeybee.writer.aperture as writer
+
+
+
[docs]class Aperture(_BaseWithShade): + """A single planar Aperture in a Face. + + Args: + identifier: Text string for a unique Aperture ID. Must be < 100 characters and + not contain any spaces or special characters. + geometry: A ladybug-geometry Face3D. + boundary_condition: Boundary condition object (Outdoors, Surface). + Default: Outdoors. + is_operable: Boolean to note whether the Aperture can be opened for + ventilation. (Default: False). + + Properties: + * identifier + * display_name + * boundary_condition + * is_operable + * indoor_shades + * outdoor_shades + * parent + * top_level_parent + * has_parent + * geometry + * vertices + * upper_left_vertices + * triangulated_mesh3d + * normal + * center + * area + * perimeter + * min + * max + * tilt + * altitude + * azimuth + * type_color + * bc_color + * user_data + """ + __slots__ = ('_geometry', '_parent', '_boundary_condition', '_is_operable') + TYPE_COLOR = Color(64, 180, 255, 100) + BC_COLORS = { + 'Outdoors': Color(128, 204, 255, 100), + 'Surface': Color(0, 190, 0, 100) + } + + def __init__(self, identifier, geometry, boundary_condition=None, is_operable=False): + """A single planar aperture in a face.""" + _BaseWithShade.__init__(self, identifier) # process the identifier + + # process the geometry + assert isinstance(geometry, Face3D), \ + 'Expected ladybug_geometry Face3D. Got {}'.format(type(geometry)) + self._geometry = geometry + self._parent = None # _parent will be set when the Aperture is added to a Face + + # process the boundary condition and type + self.boundary_condition = boundary_condition or boundary_conditions.outdoors + self.is_operable = is_operable + + # initialize properties for extensions + self._properties = ApertureProperties(self) + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize an Aperture from a dictionary. + + Args: + data: A dictionary representation of an Aperture object. + """ + try: + # check the type of dictionary + assert data['type'] == 'Aperture', 'Expected Aperture dictionary. ' \ + 'Got {}.'.format(data['type']) + + # serialize the aperture + is_operable = data['is_operable'] if 'is_operable' in data else False + if data['boundary_condition']['type'] == 'Outdoors': + boundary_condition = Outdoors.from_dict(data['boundary_condition']) + elif data['boundary_condition']['type'] == 'Surface': + boundary_condition = Surface.from_dict(data['boundary_condition'], True) + else: + raise ValueError( + 'Boundary condition "{}" is not supported for Apertures.'.format( + data['boundary_condition']['type'])) + aperture = cls(data['identifier'], Face3D.from_dict(data['geometry']), + boundary_condition, is_operable) + if 'display_name' in data and data['display_name'] is not None: + aperture.display_name = data['display_name'] + if 'user_data' in data and data['user_data'] is not None: + aperture.user_data = data['user_data'] + aperture._recover_shades_from_dict(data) + + # assign extension properties + if data['properties']['type'] == 'ApertureProperties': + aperture.properties._load_extension_attr_from_dict(data['properties']) + return aperture + except Exception as e: + cls._from_dict_error_message(data, e)
+ +
[docs] @classmethod + def from_vertices(cls, identifier, vertices, boundary_condition=None, + is_operable=False): + """Create an Aperture from vertices with each vertex as an iterable of 3 floats. + + Args: + identifier: Text string for a unique Aperture ID. Must be < 100 characters + and not contain any spaces or special characters. + vertices: A flattened list of 3 or more vertices as (x, y, z). + boundary_condition: Boundary condition object (eg. Outdoors, Surface). + Default: Outdoors. + is_operable: Boolean to note whether the Aperture can be opened for + natural ventilation. Default: False + """ + geometry = Face3D(tuple(Point3D(*v) for v in vertices)) + return cls(identifier, geometry, boundary_condition, is_operable)
+ + @property + def boundary_condition(self): + """Get or set the boundary condition of this aperture.""" + return self._boundary_condition + + @boundary_condition.setter + def boundary_condition(self, value): + if not isinstance(value, Outdoors): + if isinstance(value, Surface): + assert len(value.boundary_condition_objects) == 3, 'Surface boundary ' \ + 'condition for Aperture must have 3 boundary_condition_objects.' + else: + raise ValueError('Aperture only supports Outdoor or Surface boundary ' + 'condition. Got {}'.format(type(value))) + self._boundary_condition = value + + @property + def is_operable(self): + """Get or set a boolean for whether the Aperture can be opened for ventilation. + """ + return self._is_operable + + @is_operable.setter + def is_operable(self, value): + try: + self._is_operable = bool(value) + except TypeError: + raise TypeError( + 'Expected boolean for Aperture.is_operable. Got {}.'.format(value)) + + @property + def parent(self): + """Get the parent Face if assigned. None if not assigned.""" + return self._parent + + @property + def top_level_parent(self): + """Get the top-level parent object if assigned. + + This will be a Room if there is a parent Face that has a parent Room and + will be a Face if the parent Face is orphaned. Will be None if no parent + is assigned. + """ + if self.has_parent: + if self._parent.has_parent: + return self._parent._parent + return self._parent + return None + + @property + def has_parent(self): + """Get a boolean noting whether this Aperture has a parent Face.""" + return self._parent is not None + + @property + def geometry(self): + """Get a ladybug_geometry Face3D object representing the aperture.""" + return self._geometry + + @property + def vertices(self): + """Get a list of vertices for the aperture (in counter-clockwise order).""" + return self._geometry.vertices + + @property + def upper_left_vertices(self): + """Get a list of vertices starting from the upper-left corner. + + This property should be used when exporting to EnergyPlus / OpenStudio. + """ + return self._geometry.upper_left_counter_clockwise_vertices + + @property + def triangulated_mesh3d(self): + """Get a ladybug_geometry Mesh3D of the aperture geometry composed of triangles. + + In EnergyPlus / OpenStudio workflows, this property is used to subdivide + the aperture when it has more than 4 vertices. This is necessary since + EnergyPlus cannot accept sub-faces with more than 4 vertices. + """ + return self._geometry.triangulated_mesh3d + + @property + def normal(self): + """Get a ladybug_geometry Vector3D for the direction the aperture is pointing. + """ + return self._geometry.normal + + @property + def center(self): + """Get a ladybug_geometry Point3D for the center of the aperture. + + Note that this is the center of the bounding rectangle around this geometry + and not the area centroid. + """ + return self._geometry.center + + @property + def area(self): + """Get the area of the aperture.""" + return self._geometry.area + + @property + def perimeter(self): + """Get the perimeter of the aperture.""" + return self._geometry.perimeter + + @property + def min(self): + """Get a Point3D for the minimum of the bounding box around the object.""" + return self._min_with_shades(self._geometry) + + @property + def max(self): + """Get a Point3D for the maximum of the bounding box around the object.""" + return self._max_with_shades(self._geometry) + + @property + def tilt(self): + """Get the tilt of the geometry between 0 (up) and 180 (down).""" + return math.degrees(self._geometry.tilt) + + @property + def altitude(self): + """Get the altitude of the geometry between +90 (up) and -90 (down).""" + return math.degrees(self._geometry.altitude) + + @property + def azimuth(self): + """Get the azimuth of the geometry, between 0 and 360. + + Given Y-axis as North, 0 = North, 90 = East, 180 = South, 270 = West + This will be zero if the Face3D is perfectly horizontal. + """ + return math.degrees(self._geometry.azimuth) + + @property + def type_color(self): + """Get a Color to be used in visualizations by type.""" + return self.TYPE_COLOR + + @property + def bc_color(self): + """Get a Color to be used in visualizations by boundary condition.""" + return self.BC_COLORS[self.boundary_condition.name] + +
[docs] def horizontal_orientation(self, north_vector=Vector2D(0, 1)): + """Get a number between 0 and 360 for the orientation of the aperture in degrees. + + 0 = North, 90 = East, 180 = South, 270 = West + + Args: + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + """ + return math.degrees( + north_vector.angle_clockwise(Vector2D(self.normal.x, self.normal.y)))
+ +
[docs] def cardinal_direction(self, north_vector=Vector2D(0, 1)): + """Get text description for the cardinal direction that the aperture is pointing. + + Will be one of the following: ('North', 'NorthEast', 'East', 'SouthEast', + 'South', 'SouthWest', 'West', 'NorthWest'). + + Args: + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + """ + orient = self.horizontal_orientation(north_vector) + orient_text = ('North', 'NorthEast', 'East', 'SouthEast', 'South', + 'SouthWest', 'West', 'NorthWest') + angles = (22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5, 337.5) + for i, ang in enumerate(angles): + if orient < ang: + return orient_text[i] + return orient_text[0]
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier of this object and child objects by inserting a prefix. + + This is particularly useful in workflows where you duplicate and edit + a starting object and then want to combine it with the original object + into one Model (like making a model of repeated rooms) since all objects + within a Model must have unique identifiers. + + Args: + prefix: Text that will be inserted at the start of this object's + (and child objects') identifier and display_name. It is recommended + that this prefix be short to avoid maxing out the 100 allowable + characters for honeybee identifiers. + """ + self._identifier = clean_string('{}_{}'.format(prefix, self.identifier)) + self.display_name = '{}_{}'.format(prefix, self.display_name) + self.properties.add_prefix(prefix) + self._add_prefix_shades(prefix) + if isinstance(self._boundary_condition, Surface): + new_bc_objs = (clean_string('{}_{}'.format(prefix, adj_name)) for adj_name + in self._boundary_condition._boundary_condition_objects) + self._boundary_condition = Surface(new_bc_objs, True)
+ +
[docs] def set_adjacency(self, other_aperture): + """Set this aperture to be adjacent to another. + + Note that this method does not verify whether the other_aperture geometry is + co-planar or compatible with this one so it is recommended that a test + be performed before using this method in order to verify these criteria. + The Face3D.is_centered_adjacent() or the Face3D.is_geometrically_equivalent() + methods are both suitable for this purpose. + + Args: + other_aperture: Another Aperture object to be set adjacent to this one. + """ + assert isinstance(other_aperture, Aperture), \ + 'Expected Aperture. Got {}.'.format(type(other_aperture)) + assert other_aperture.is_operable is self.is_operable, \ + 'Adjacent apertures must have matching is_operable properties.' + self._boundary_condition = boundary_conditions.surface(other_aperture, True) + other_aperture._boundary_condition = boundary_conditions.surface(self, True)
+ +
[docs] def overhang(self, depth, angle=0, indoor=False, tolerance=0.01, base_name=None): + """Add a single overhang for this Aperture. + + Args: + depth: A number for the overhang depth. + angle: A number for the for an angle to rotate the overhang in degrees. + Positive numbers indicate a downward rotation while negative numbers + indicate an upward rotation. Default is 0 for no rotation. + indoor: Boolean for whether the overhang should be generated facing the + opposite direction of the aperture normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to return None if the overhang has a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + base_name: Optional base name for the shade objects. If None, the default + is InOverhang or OutOverhang depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + if base_name is None: + base_name = 'InOverhang' if indoor else 'OutOverhang' + return self.louvers_by_count(1, depth, angle=angle, indoor=indoor, + tolerance=tolerance, base_name=base_name)
+ +
[docs] def right_fin(self, depth, angle=0, indoor=False, tolerance=0.01, base_name=None): + """Add a single vertical fin on the right side of this Aperture. + + Args: + depth: A number for the fin depth. + angle: A number for the for an angle to rotate the fin in degrees. + Default is 0 for no rotation. + indoor: Boolean for whether the fin should be generated facing the + opposite direction of the aperture normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to return None if the fin has a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + base_name: Optional base name for the shade objects. If None, the default + is InRightFin or OutRightFin depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + if base_name is None: + base_name = 'InRightFin' if indoor else 'OutRightFin' + return self.louvers_by_count( + 1, depth, angle=angle, contour_vector=Vector2D(1, 0), + indoor=indoor, tolerance=tolerance, base_name=base_name)
+ +
[docs] def left_fin(self, depth, angle=0, indoor=False, tolerance=0.01, base_name=None): + """Add a single vertical fin on the left side of this Aperture. + + Args: + depth: A number for the fin depth. + angle: A number for the for an angle to rotate the fin in degrees. + Default is 0 for no rotation. + indoor: Boolean for whether the fin should be generated facing the + opposite direction of the aperture normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to return None if the fin has a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + base_name: Optional base name for the shade objects. If None, the default + is InLeftFin or OutLeftFin depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + if base_name is None: + base_name = 'InLeftFin' if indoor else 'OutLeftFin' + return self.louvers_by_count( + 1, depth, angle=angle, contour_vector=Vector2D(1, 0), + flip_start_side=True, indoor=indoor, tolerance=tolerance, + base_name=base_name)
+ +
[docs] def extruded_border(self, depth, indoor=False, base_name=None): + """Add a series of Shade objects to this Aperture that form an extruded border. + + Args: + depth: A number for the extrusion depth. + indoor: Boolean for whether the extrusion should be generated facing the + opposite direction of the aperture normal and added to the Aperture's + indoor_shades instead of outdoor_shades. Default: False. + base_name: Optional base name for the shade objects. If None, the default + is InBorder or OutBorder depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + extru_vec = self.normal if indoor is False else self.normal.reverse() + extru_vec = extru_vec * depth + extrusion = [] + shd_count = 0 + if base_name is None: + shd_name_base = '{}_InBorder{}' if indoor else '{}_OutBorder{}' + else: + shd_name_base = '{}_' + str(base_name) + '{}' + for seg in self.geometry.boundary_segments: + shade_geo = Face3D.from_extrusion(seg, extru_vec) + extrusion.append( + Shade(shd_name_base.format(self.identifier, shd_count), shade_geo)) + shd_count += 1 + if self.geometry.has_holes: + for hole in self.geometry.hole_segments: + for seg in hole: + shade_geo = Face3D.from_extrusion(seg, extru_vec) + extrusion.append( + Shade(shd_name_base.format(self.identifier, shd_count), + shade_geo)) + shd_count += 1 + if indoor: + self.add_indoor_shades(extrusion) + else: + self.add_outdoor_shades(extrusion) + return extrusion
+ +
[docs] def louvers_by_count(self, louver_count, depth, offset=0, angle=0, + contour_vector=Vector2D(0, 1), flip_start_side=False, + indoor=False, tolerance=0.01, base_name=None): + """Add a series of louvered Shade objects covering this Aperture. + + Args: + louver_count: A positive integer for the number of louvers to generate. + depth: A number for the depth to extrude the louvers. + offset: A number for the distance to louvers from this aperture. + Default is 0 for no offset. + angle: A number for the for an angle to rotate the louvers in degrees. + Positive numbers indicate a downward rotation while negative numbers + indicate an upward rotation. Default is 0 for no rotation. + contour_vector: A Vector2D for the direction along which contours + are generated. This 2D vector will be interpreted into a 3D vector + within the plane of this Aperture. (0, 1) will usually generate + horizontal contours in 3D space, (1, 0) will generate vertical + contours, and (1, 1) will generate diagonal contours. Default: (0, 1). + flip_start_side: Boolean to note whether the side the louvers start from + should be flipped. Default is False to have louvers on top or right. + Setting to True will start contours on the bottom or left. + indoor: Boolean for whether louvers should be generated facing the + opposite direction of the aperture normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to remove any louvers with a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + base_name: Optional base name for the shade objects. If None, the default + is InShd or OutShd depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + assert louver_count > 0, 'louver_count must be greater than 0.' + angle = math.radians(angle) + louvers = [] + ap_geo = self.geometry if indoor is False else self.geometry.flip() + shade_faces = ap_geo.contour_fins_by_number( + louver_count, depth, offset, angle, + contour_vector, flip_start_side, tolerance) + if base_name is None: + shd_name_base = '{}_InShd{}' if indoor else '{}_OutShd{}' + else: + shd_name_base = '{}_' + str(base_name) + '{}' + for i, shade_geo in enumerate(shade_faces): + louvers.append(Shade(shd_name_base.format(self.identifier, i), shade_geo)) + if indoor: + self.add_indoor_shades(louvers) + else: + self.add_outdoor_shades(louvers) + return louvers
+ +
[docs] def louvers_by_distance_between( + self, distance, depth, offset=0, angle=0, contour_vector=Vector2D(0, 1), + flip_start_side=False, indoor=False, tolerance=0.01, max_count=None, + base_name=None): + """Add a series of louvered Shade objects covering this Aperture. + + Args: + distance: A number for the approximate distance between each louver. + depth: A number for the depth to extrude the louvers. + offset: A number for the distance to louvers from this aperture. + Default is 0 for no offset. + angle: A number for the for an angle to rotate the louvers in degrees. + Positive numbers indicate a downward rotation while negative numbers + indicate an upward rotation. Default is 0 for no rotation. + contour_vector: A Vector2D for the direction along which contours + are generated. This 2D vector will be interpreted into a 3D vector + within the plane of this Aperture. (0, 1) will usually generate + horizontal contours in 3D space, (1, 0) will generate vertical + contours, and (1, 1) will generate diagonal contours. Default: (0, 1). + flip_start_side: Boolean to note whether the side the louvers start from + should be flipped. Default is False to have contours on top or right. + Setting to True will start contours on the bottom or left. + indoor: Boolean for whether louvers should be generated facing the + opposite direction of the aperture normal (typically meaning + indoor geometry). Default: 0.01, suitable for objects in meters. + tolerance: An optional value to remove any louvers with a length less + than the tolerance. Default is 0, which will include all louvers + no matter how small. + max_count: Optional integer to set the maximum number of louvers that + will be generated. If None, louvers will cover the entire aperture. + base_name: Optional base name for the shade objects. If None, the default + is InShd or OutShd depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + # set defaults + angle = math.radians(angle) + ap_geo = self.geometry if indoor is False else self.geometry.flip() + if base_name is None: + shd_name_base = '{}_InShd{}' if indoor else '{}_OutShd{}' + else: + shd_name_base = '{}_' + str(base_name) + '{}' + + # generate shade geometries + shade_faces = ap_geo.contour_fins_by_distance_between( + distance, depth, offset, angle, + contour_vector, flip_start_side, tolerance) + if max_count: + try: + shade_faces = shade_faces[:max_count] + except IndexError: # fewer shades were generated than the max count + pass + + # create the shade objects + louvers = [] + for i, shade_geo in enumerate(shade_faces): + louvers.append(Shade(shd_name_base.format(self.identifier, i), shade_geo)) + if indoor: + self.add_indoor_shades(louvers) + else: + self.add_outdoor_shades(louvers) + return louvers
+ +
[docs] def move(self, moving_vec): + """Move this Aperture along a vector. + + Args: + moving_vec: A ladybug_geometry Vector3D with the direction and distance + to move the face. + """ + self._geometry = self.geometry.move(moving_vec) + self.move_shades(moving_vec) + self.properties.move(moving_vec) + self._reset_parent_geometry()
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Aperture by a certain angle around an axis and origin. + + Args: + axis: A ladybug_geometry Vector3D axis representing the axis of rotation. + angle: An angle for rotation in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate(axis, math.radians(angle), origin) + self.rotate_shades(axis, angle, origin) + self.properties.rotate(axis, angle, origin) + self._reset_parent_geometry()
+ +
[docs] def rotate_xy(self, angle, origin): + """Rotate this Aperture counterclockwise in the world XY plane by an angle. + + Args: + angle: An angle in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate_xy(math.radians(angle), origin) + self.rotate_xy_shades(angle, origin) + self.properties.rotate_xy(angle, origin) + self._reset_parent_geometry()
+ +
[docs] def reflect(self, plane): + """Reflect this Aperture across a plane. + + Args: + plane: A ladybug_geometry Plane across which the object will + be reflected. + """ + self._geometry = self.geometry.reflect(plane.n, plane.o) + self.reflect_shades(plane) + self.properties.reflect(plane) + self._reset_parent_geometry()
+ +
[docs] def scale(self, factor, origin=None): + """Scale this Aperture by a factor from an origin point. + + Args: + factor: A number representing how much the object should be scaled. + origin: A ladybug_geometry Point3D representing the origin from which + to scale. If None, it will be scaled from the World origin (0, 0, 0). + """ + self._geometry = self.geometry.scale(factor, origin) + self.scale_shades(factor, origin) + self.properties.scale(factor, origin) + self._reset_parent_geometry()
+ +
[docs] def remove_colinear_vertices(self, tolerance=0.01): + """Remove all colinear and duplicate vertices from this object's geometry. + + Note that this does not affect any assigned Shades. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + """ + try: + self._geometry = self.geometry.remove_colinear_vertices(tolerance) + except AssertionError as e: # usually a sliver face of some kind + raise ValueError( + 'Aperture "{}" is invalid with dimensions less than the ' + 'tolerance.\n{}'.format(self.full_id, e))
+ +
[docs] def is_geo_equivalent(self, aperture, tolerance=0.01): + """Get a boolean for whether this object is geometrically equivalent to another. + + The total number of vertices and the ordering of these vertices can be + different but the geometries must share the same center point and be + next to one another to within the tolerance. + + Args: + aperture: Another Aperture for which geometric equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered geometrically equivalent. + + Returns: + True if geometrically equivalent. False if not geometrically equivalent. + """ + meta_1 = (self.display_name, self.is_operable, self.boundary_condition) + meta_2 = (aperture.display_name, aperture.is_operable, + aperture.boundary_condition) + if meta_1 != meta_2: + return False + if abs(self.area - aperture.area) > tolerance * self.area: + return False + if not self.geometry.is_centered_adjacent(aperture.geometry, tolerance): + return False + if not self._are_shades_equivalent(aperture, tolerance): + return False + return True
+ +
[docs] def check_planar(self, tolerance=0.01, raise_exception=True, detailed=False): + """Check whether all of the Aperture's vertices lie within the same plane. + + Args: + tolerance: The minimum distance between a given vertex and a the + object's plane at which the vertex is said to lie in the plane. + Default: 0.01, suitable for objects in meters. + raise_exception: Boolean to note whether an ValueError should be + raised if a vertex does not lie within the object's plane. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + try: + self.geometry.check_planar(tolerance, raise_exception=True) + except ValueError as e: + msg = 'Aperture "{}" is not planar.\n{}'.format(self.full_id, e) + full_msg = self._validation_message( + msg, raise_exception, detailed, '000101', + error_type='Non-Planar Geometry') + if detailed: # add the out-of-plane points to helper_geometry + help_pts = [ + p.to_dict() for p in self.geometry.non_planar_vertices(tolerance) + ] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def check_self_intersecting(self, tolerance=0.01, raise_exception=True, + detailed=False): + """Check whether the edges of the Aperture intersect one another (like a bowtie). + + Note that objects that have duplicate vertices will not be considered + self-intersecting and are valid in honeybee. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + raise_exception: If True, a ValueError will be raised if the object + intersects with itself. Default: True. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + if self.geometry.is_self_intersecting: + msg = 'Aperture "{}" has self-intersecting edges.'.format(self.full_id) + try: # see if it is self-intersecting because of a duplicate vertex + new_geo = self.geometry.remove_duplicate_vertices(tolerance) + if not new_geo.is_self_intersecting: + return [] if detailed else '' # valid with removed dup vertex + except AssertionError: + return [] if detailed else '' # degenerate geometry + full_msg = self._validation_message( + msg, raise_exception, detailed, '000102', + error_type='Self-Intersecting Geometry') + if detailed: # add the self-intersection points to helper_geometry + help_pts = [p.to_dict() for p in self.geometry.self_intersection_points] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def display_dict(self): + """Get a list of DisplayFace3D dictionaries for visualizing the object.""" + base = [self._display_face(self.geometry, self.type_color)] + for shd in self.shades: + base.extend(shd.display_dict()) + return base
+ + @property + def to(self): + """Aperture writer object. + + Use this method to access Writer class to write the aperture in other formats. + + Usage: + + .. code-block:: python + + aperture.to.idf(aperture) -> idf string. + aperture.to.radiance(aperture) -> Radiance string. + """ + return writer + +
[docs] def to_dict(self, abridged=False, included_prop=None, include_plane=True): + """Return Aperture as a dictionary. + + Args: + abridged: Boolean to note whether the extension properties of the + object (ie. materials, constructions) should be included in detail + (False) or just referenced by identifier (True). (Default: False). + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + include_plane: Boolean to note wether the plane of the Face3D should be + included in the output. This can preserve the orientation of the + X/Y axes of the plane but is not required and can be removed to + keep the dictionary smaller. (Default: True). + """ + base = {'type': 'Aperture'} + base['identifier'] = self.identifier + base['display_name'] = self.display_name + base['properties'] = self.properties.to_dict(abridged, included_prop) + enforce_upper_left = True if 'energy' in base['properties'] else False + base['geometry'] = self._geometry.to_dict(include_plane, enforce_upper_left) + base['is_operable'] = self.is_operable + if isinstance(self.boundary_condition, Outdoors) and \ + 'energy' in base['properties']: + base['boundary_condition'] = self.boundary_condition.to_dict(full=True) + else: + base['boundary_condition'] = self.boundary_condition.to_dict() + self._add_shades_to_dict(base, abridged, included_prop, include_plane) + if self.user_data is not None: + base['user_data'] = self.user_data + return base
+ + def _reset_parent_geometry(self): + """Reset parent punched_geometry in the case that the object is transformed.""" + if self.has_parent: + self._parent._punched_geometry = None + + def __copy__(self): + new_ap = Aperture(self.identifier, self.geometry, self.boundary_condition, + self.is_operable) + new_ap._display_name = self._display_name + new_ap._user_data = None if self.user_data is None else self.user_data.copy() + self._duplicate_child_shades(new_ap) + new_ap._properties._duplicate_extension_attr(self._properties) + return new_ap + + def __repr__(self): + return 'Aperture: %s' % self.display_name
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/boundarycondition.html b/docs/_modules/honeybee/boundarycondition.html new file mode 100644 index 00000000..e28752ef --- /dev/null +++ b/docs/_modules/honeybee/boundarycondition.html @@ -0,0 +1,1480 @@ + + + + + + + honeybee.boundarycondition — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.boundarycondition

+"""Boundary Condition for Face, Aperture, Door."""
+import re
+
+from .typing import float_in_range, tuple_with_length
+from .altnumber import autocalculate
+
+
+class _BoundaryCondition(object):
+    """Base boundary condition class."""
+
+    __slots__ = ()
+
+    def __init__(self):
+        """Initialize Boundary condition."""
+
+    @property
+    def name(self):
+        """Get the name of the boundary condition (ie. 'Outdoors', 'Ground')."""
+        return self.__class__.__name__
+
+    @property
+    def view_factor(self):
+        """Get the view factor to the ground."""
+        return 'autocalculate'
+
+    @property
+    def sun_exposure_idf(self):
+        """Get a text string for sun exposure, which is write-able into an IDF."""
+        return 'NoSun'
+
+    @property
+    def wind_exposure_idf(self):
+        """ Get a text string for wind exposure, which is write-able into an IDF."""
+        return 'NoWind'
+
+    def to_dict(self):
+        """Get the boundary condition as a dictionary."""
+        return {'type': self.name}
+
+    def ToString(self):
+        """Overwrite .NET ToString."""
+        return self.__repr__()
+
+    def __repr__(self):
+        return self.name
+
+
+
[docs]class Outdoors(_BoundaryCondition): + """Outdoor boundary condition. + + Args: + sun_exposure: A boolean noting whether the boundary is exposed to sun. + Default: True. + wind_exposure: A boolean noting whether the boundary is exposed to wind. + Default: True. + view_factor: A number between 0 and 1 for the view factor to the ground. + This input can also be an Autocalculate object to signify that the view + factor automatically calculated. Default: autocalculate. + """ + + __slots__ = ('_sun_exposure', '_wind_exposure', '_view_factor') + + def __init__(self, sun_exposure=True, wind_exposure=True, + view_factor=autocalculate): + """Initialize Outdoors boundary condition.""" + assert isinstance(sun_exposure, bool), \ + 'Input sun_exposure must be a Boolean. Got {}.'.format(type(sun_exposure)) + self._sun_exposure = sun_exposure + assert isinstance(wind_exposure, bool), \ + 'Input wind_exposure must be a Boolean. Got {}.'.format(type(wind_exposure)) + self._wind_exposure = wind_exposure + if view_factor == autocalculate: + self._view_factor = autocalculate + else: + self._view_factor = float_in_range( + view_factor, 0.0, 1.0, 'view factor to ground') + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize Outdoors BoundaryCondition from a dictionary. + + Args: + data: A dictionary representation of the boundary condition. + """ + assert data['type'] == 'Outdoors', 'Expected dictionary for Outdoors boundary ' \ + 'condition. Got {}.'.format(data['type']) + sun_exposure = True if 'sun_exposure' not in data else data['sun_exposure'] + wind_exposure = True if 'wind_exposure' not in data else data['wind_exposure'] + view_factor = autocalculate if 'view_factor' not in data or \ + data['view_factor'] == autocalculate.to_dict() else data['view_factor'] + return cls(sun_exposure, wind_exposure, view_factor)
+ + @property + def sun_exposure(self): + """Get a boolean noting whether the boundary is exposed to sun.""" + return self._sun_exposure + + @property + def wind_exposure(self): + """Get a boolean noting whether the boundary is exposed to wind.""" + return self._wind_exposure + + @property + def view_factor(self): + """Get the view factor to the ground as a number or 'autocalculate'.""" + return self._view_factor + + @property + def sun_exposure_idf(self): + """Get a text string for sun exposure, which is write-able into an IDF.""" + return 'NoSun' if not self.sun_exposure else 'SunExposed' + + @property + def wind_exposure_idf(self): + """Get a text string for wind exposure, which is write-able into an IDF.""" + return 'NoWind' if not self.wind_exposure else 'WindExposed' + +
[docs] def to_dict(self, full=False): + """Get the boundary condition as a dictionary. + + Args: + full: Set to True to get the full dictionary which includes energy + simulation specific keys such as sun_exposure, wind_exposure and + view_factor. (Default: False). + """ + bc_dict = {'type': self.name} + if full: + bc_dict['sun_exposure'] = self.sun_exposure + bc_dict['wind_exposure'] = self.wind_exposure + bc_dict['view_factor'] = autocalculate.to_dict() if \ + self.view_factor == autocalculate else self.view_factor + return bc_dict
+ + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self.sun_exposure, self.wind_exposure, self.view_factor) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Outdoors) and self.__key() == other.__key()
+ + +
[docs]class Surface(_BoundaryCondition): + """Boundary condition when an object is adjacent to another object.""" + + __slots__ = ('_boundary_condition_objects',) + + def __init__(self, boundary_condition_objects, sub_face=False): + """Initialize Surface boundary condition. + + Args: + boundary_condition_objects: A list of up to 3 object identifiers that are + adjacent to this one. The first object is always immediately + adjacent and is of the same object type (Face, Aperture, Door). When + this boundary condition is applied to a Face, the second object in the + tuple will be the parent Room of the adjacent object. When the boundary + condition is applied to a sub-face (Door or Aperture), the second object + will be the parent Face of the adjacent sub-face and the third object + will be the parent Room of the adjacent sub-face. + sub_face: Boolean to note whether this boundary condition is applied to a + sub-face (an Aperture or a Door) instead of a Face. (Default: False). + """ + if sub_face: + self._boundary_condition_objects = tuple_with_length( + boundary_condition_objects, 3, str, + 'boundary_condition_objects for Apertures or Doors') + else: + self._boundary_condition_objects = tuple_with_length( + boundary_condition_objects, 2, str, + 'boundary_condition_objects for Faces') + +
[docs] @classmethod + def from_dict(cls, data, sub_face=False): + """Initialize Surface BoundaryCondition from a dictionary. + + Args: + data: A dictionary representation of the boundary condition. + sub_face: Boolean to note whether this boundary condition is applied to a + sub-face (an Aperture or a Door) instead of a Face. Default: False. + """ + assert data['type'] == 'Surface', 'Expected dictionary for Surface boundary ' \ + 'condition. Got {}.'.format(data['type']) + return cls(data['boundary_condition_objects'], sub_face)
+ +
[docs] @classmethod + def from_other_object(cls, other_object, sub_face=False): + """Initialize Surface boundary condition from an adjacent other object. + + Args: + other_object: Another object (Face, Aperture, Door) of the same type + that this boundary condition is assigned. This other_object will be + set as the adjacent object in this boundary condition. + sub_face: Boolean to note whether this boundary condition is applied to a + sub-face (an Aperture or a Door) instead of a Face. Default: False. + """ + error_msg = 'Surface boundary conditions can only be assigned to objects' \ + ' with parent Rooms.' + bc_objects = [other_object.identifier] + if other_object.has_parent: + bc_objects.append(other_object.parent.identifier) + if sub_face: + if other_object.parent.has_parent: + bc_objects.append(other_object.parent.parent.identifier) + else: + raise AttributeError(error_msg) + else: + raise AttributeError(error_msg) + return cls(bc_objects, sub_face)
+ + @property + def boundary_condition_objects(self): + """Get a tuple of up to 3 object identifiers that are adjacent to this one. + + The first object is always the one that is immediately adjacent and is of + the same object type (Face, Aperture, Door). + When this boundary condition is applied to a Face, the second object in the + tuple will be the parent Room of the adjacent object. + When the boundary condition is applied to a sub-face (Door or Aperture), + the second object will be the parent Face of the sub-face and the third + object will be the parent Room of the adjacent sub-face. + """ + return self._boundary_condition_objects + + @property + def boundary_condition_object(self): + """Get the identifier of the object adjacent to this one.""" + return self._boundary_condition_objects[0] + +
[docs] def to_dict(self): + """Get the boundary condition as a dictionary. + + Args: + full: Set to True to get the full dictionary which includes energy + simulation specific keys such as sun_exposure, wind_exposure and + view_factor. Default: False. + """ + return {'type': self.name, + 'boundary_condition_objects': self.boundary_condition_objects}
+ + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return self.boundary_condition_objects + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Surface) and self.__key() == other.__key()
+ + +
[docs]class Ground(_BoundaryCondition): + """Ground boundary condition. + + Args: + data: A dictionary representation of the boundary condition. + """ + __slots__ = () + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize Ground BoundaryCondition from a dictionary.""" + assert data['type'] == 'Ground', 'Expected dictionary for Ground boundary ' \ + 'condition. Got {}.'.format(data['type']) + return cls()
+ + def __eq__(self, other): + return isinstance(other, Ground)
+ + +class _BoundaryConditions(object): + """Boundary conditions.""" + + def __init__(self): + self._outdoors = Outdoors() + self._ground = Ground() + self._bc_name_dict = None + + @property + def outdoors(self): + """Default outdoor boundary condition.""" + return self._outdoors + + @property + def ground(self): + """Default ground boundary condition.""" + return self._ground + + def surface(self, other_object, sub_face=False): + """Get a Surface boundary condition. + + Args: + other_object: The other object that is adjacent to the one that will + bear this Surface boundary condition. + sub_face: Boolean to note whether the boundary condition is for a + sub-face (Aperture or Door) instead of a Face. (Default: False). + """ + return Surface.from_other_object(other_object, sub_face) + + def by_name(self, bc_name): + """Get a boundary condition object instance by its name. + + This method will correct for capitalization as well as the presence of + spaces and underscores. Note that this method only works for boundary + conditions with all of their inputs defaulted. + + Args: + bc_name: A boundary condition name. + """ + if self._bc_name_dict is None: + self._build_bc_name_dict() + try: + return self._bc_name_dict[re.sub(r'[\s_]', '', bc_name.lower())] + except KeyError: + raise ValueError( + '"{}" is not a valid boundary condition name.\nChoose from the ' + 'following: {}'.format(bc_name, list(self._bc_name_dict.keys()))) + + def _build_bc_name_dict(self): + """Build a dictionary that can be used to lookup boundary conditions by name.""" + attr = [atr for atr in dir(self) if not atr.startswith('_')] + clean_attr = [re.sub(r'[\s_]', '', atr.lower()) for atr in attr] + self._bc_name_dict = {} + for atr_name, atr in zip(clean_attr, attr): + try: + full_attr = getattr(self, '_' + atr) + self._bc_name_dict[atr_name] = full_attr + except AttributeError: + pass # callable method that has no static default object + + def __contains__(self, value): + return isinstance(value, _BoundaryCondition) + + +boundary_conditions = _BoundaryConditions() + + +
[docs]def get_bc_from_position(positions, ground_depth=0): + """Return a boundary condition based on the relationship to a ground plane. + + Positions that are entirely at or below the ground_depth will get a Ground + boundary condition. If there are any positions above the ground_depth, an + Outdoors boundary condition will be returned. + + args: + positions: A list of ladybug_geometry Point3D objects representing the + vertices of an object. + ground_depth: The Z value above which positions are considered Outdoors + instead of Ground. + + Returns: + Face type instance. + """ + for position in positions: + if position.z > ground_depth: + return boundary_conditions.outdoors + return boundary_conditions.ground
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/checkdup.html b/docs/_modules/honeybee/checkdup.html new file mode 100644 index 00000000..9628cb37 --- /dev/null +++ b/docs/_modules/honeybee/checkdup.html @@ -0,0 +1,1295 @@ + + + + + + + honeybee.checkdup — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.checkdup

+# coding=utf-8
+"""Utilities to check whether there are any duplicate values in a list of ids."""
+
+import collections
+
+
+
[docs]def check_duplicate_identifiers( + objects_to_check, raise_exception=True, obj_name='', detailed=False, + code='000000', extension='Core', error_type='Duplicate Object Identifier'): + """Check whether there are duplicated identifiers across a list of objects. + + Args: + objects_to_check: A list of honeybee objects across which duplicate + identifiers will be checked. + raise_exception: Boolean to note whether an exception should be raised if + duplicated identifiers are found. (Default: True). + obj_name: An optional name for the object to be included in the error + message. Fro example, 'Room', 'Face', 'Aperture'. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + code: Text for the error code. (Default: 0000). + extension: Text for the name of the Honeybee extension for which duplicate + identifiers are being evaluated. (Default: Core). + error_type: Text for the type of error. This should be directly linked + to the error code and should simply be a human-readable version of + the error code. (Default: Unknown Error). + + Returns: + A message string indicating the duplicated identifiers (if detailed is False) + or a list of dictionaries with information about the duplicated identifiers + (if detailed is True). This string (or list) will be empty if no duplicates + were found. + """ + detailed = False if raise_exception else detailed + obj_id_iter = (obj.identifier for obj in objects_to_check) + dup = [t for t, c in collections.Counter(obj_id_iter).items() if c > 1] + if len(dup) != 0: + if detailed: + # find the object display names + dis_names = [] + for obj_id in dup: + dis_name = None + for obj in objects_to_check: + if obj.identifier == obj_id: + dis_name = obj.display_name + dis_names.append(dis_name) + err_list = [] + for dup_id, dis_name in zip(dup, dis_names): + msg = 'There is a duplicated {} identifier: {}'.format(obj_name, dup_id) + dup_dict = { + 'type': 'ValidationError', + 'code': code, + 'error_type': error_type, + 'extension_type': extension, + 'element_type': obj_name, + 'element_id': [dup_id], + 'message': msg + } + if dis_name is not None: + dup_dict['element_name'] = [dis_name] + err_list.append(dup_dict) + return err_list + msg = 'The following duplicated {} identifiers were found:\n{}'.format( + obj_name, '\n'.join(dup)) + if raise_exception: + raise ValueError(msg) + return msg + return [] if detailed else ''
+ + +
[docs]def check_duplicate_identifiers_parent( + objects_to_check, raise_exception=True, obj_name='', detailed=False, + code='000000', extension='Core', error_type='Duplicate Object Identifier'): + """Check whether there are duplicated identifiers across a list of objects. + + The error message will include the identifiers of top-level parents in order + to make it easier to find the duplicated objects in the model. + + Args: + objects_to_check: A list of honeybee objects across which duplicate + identifiers will be checked. These objects must have the ability to + have parents for this method to run correctly. + raise_exception: Boolean to note whether an exception should be raised if + duplicated identifiers are found. (Default: True). + obj_name: An optional name for the object to be included in the error + message. For example, 'Room', 'Face', 'Aperture'. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + code: Text for the error code. (Default: 0000). + extension: Text for the name of the Honeybee extension for which duplicate + identifiers are being evaluated. (Default: Core). + error_type: Text for the type of error. This should be directly linked + to the error code and should simply be a human-readable version of + the error code. (Default: Unknown Error). + + Returns: + A message string indicating the duplicated identifiers (if detailed is False) + or a list of dictionaries with information about the duplicated identifiers + (if detailed is True). This string (or list) will be empty if no duplicates + were found. + """ + detailed = False if raise_exception else detailed + obj_id_iter = (obj.identifier for obj in objects_to_check) + dup = [t for t, c in collections.Counter(obj_id_iter).items() if c > 1] + if len(dup) != 0: + # find the relevant top-level parents + top_par, dis_names = [], [] + for obj_id in dup: + rel_parents, dis_name = [], None + for obj in objects_to_check: + if obj.identifier == obj_id: + dis_name = obj.display_name + if obj.has_parent: + try: + par_obj = obj.top_level_parent + except AttributeError: + par_obj = obj.parent + rel_parents.append(par_obj) + top_par.append(rel_parents) + dis_names.append(dis_name) + # if a detailed dictionary is requested, then create it + if detailed: + err_list = [] + for dup_id, dis_name, rel_par in zip(dup, dis_names, top_par): + dup_dict = { + 'type': 'ValidationError', + 'code': code, + 'error_type': error_type, + 'extension_type': extension, + 'element_type': obj_name, + 'element_id': [dup_id] + } + if dis_name is not None: + dup_dict['element_name'] = [dis_name] + msg = 'There is a duplicated {} identifier: {}'.format(obj_name, dup_id) + if len(rel_par) != 0: + dup_dict['top_parents'] = [] + msg += '\n Relevant Top-Level Parents:\n' + for par_o in rel_par: + par_dict = { + 'parent_type': par_o.__class__.__name__, + 'id': par_o.identifier, + 'name': par_o.display_name + } + dup_dict['top_parents'].append(par_dict) + msg += ' {} "{}"\n'.format( + par_o.__class__.__name__, par_o.full_id) + dup_dict['message'] = msg + err_list.append(dup_dict) + return err_list + # if just an error message is requested, then build it from the information + msg = 'The following duplicated {} identifiers were found:\n'.format(obj_name) + for obj_id, rel_par in zip(dup, top_par): + obj_msg = obj_id + '\n' + if len(rel_par) != 0: + obj_msg += ' Relevant Top-Level Parents:\n' + for par_o in rel_par: + obj_msg += ' {} "{}"\n'.format( + par_o.__class__.__name__, par_o.full_id) + msg += obj_msg + msg = msg.strip() + if raise_exception: + raise ValueError(msg) + return msg + return [] if detailed else ''
+ + +
[docs]def is_equivalent(object_1, object_2): + """Check if two objects are equal with an initial check for the same instance. + """ + if object_1 is object_2: # first see if they're the same instance + return True + return object_1 == object_2 # two objects that should have == operators
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/colorobj.html b/docs/_modules/honeybee/colorobj.html new file mode 100644 index 00000000..86394426 --- /dev/null +++ b/docs/_modules/honeybee/colorobj.html @@ -0,0 +1,1485 @@ + + + + + + + honeybee.colorobj — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.colorobj

+# coding=utf-8
+"""Module for coloring geometry with attributes."""
+from __future__ import division
+
+from .shademesh import ShadeMesh
+from .shade import Shade
+from .door import Door
+from .aperture import Aperture
+from .face import Face
+from .room import Room
+from .facetype import Floor
+from .search import get_attr_nested
+
+from ladybug.graphic import GraphicContainer
+from ladybug.legend import LegendParameters, LegendParametersCategorized
+from ladybug_geometry.geometry3d.pointvector import Point3D
+
+
+class _ColorObject(object):
+    """Base class for visualization objects.
+
+    Properties:
+        * legend_parameters
+        * attr_name
+        * attr_name_end
+        * attributes
+        * attributes_unique
+        * attributes_original
+        * min_point
+        * max_point
+        * graphic_container
+    """
+    __slots__ = ('_attr_name', '_legend_parameters', '_attr_name_end',
+                 '_attributes', '_attributes_unique', '_attributes_original',
+                 '_min_point', '_max_point')
+
+    def __init__(self, legend_parameters=None):
+        """Initialize ColorObject."""
+        # assign the legend parameters of this object
+        self.legend_parameters = legend_parameters
+
+        self._attr_name = None
+        self._attr_name_end = None
+        self._attributes = None
+        self._attributes_unique = None
+        self._attributes_original = None
+        self._min_point = None
+        self._max_point = None
+
+    @property
+    def legend_parameters(self):
+        """Get or set the legend parameters."""
+        return self._legend_parameters
+
+    @legend_parameters.setter
+    def legend_parameters(self, value):
+        if value is not None:
+            assert isinstance(value, LegendParameters) and not \
+                isinstance(value, LegendParametersCategorized), \
+                'Expected LegendParameters. Got {}.'.format(type(value))
+            self._legend_parameters = value
+        else:
+            self._legend_parameters = LegendParameters()
+
+    @property
+    def attr_name(self):
+        """Get a text string of an attribute that the input objects should have."""
+        return self._attr_name
+
+    @property
+    def attr_name_end(self):
+        """Get text for the last attribute in the attr_name.
+
+        Useful when attr_name is nested.
+        """
+        return self._attr_name_end
+
+    @property
+    def attributes(self):
+        """Get a tuple of text for the attributes assigned to the objects.
+
+        If the input attr_name is a valid attribute for the object but None is
+        assigned, the output will be 'None'. If the input attr_name is not valid
+        for the input object, 'N/A' will be returned.
+        """
+        return self._attributes
+
+    @property
+    def attributes_unique(self):
+        """Get a tuple of text for the unique attributes assigned to the objects."""
+        return self._attributes_unique
+
+    @property
+    def attributes_original(self):
+        """Get a tuple of objects for the attributes assigned to the objects.
+
+        These will follow the original object typing of the attribute and won't
+        be strings like the attributes.
+        """
+        return self._attributes_original
+
+    @property
+    def min_point(self):
+        """Get a Point3D for the minimum of the box around the objects."""
+        return self._min_point
+
+    @property
+    def max_point(self):
+        """Get a Point3D for the maximum of the box around the objects."""
+        return self._max_point
+
+    @property
+    def graphic_container(self):
+        """Get a ladybug GraphicContainer that relates to this object.
+
+        The GraphicContainer possesses almost all things needed to visualize the
+        ColorRooms object including the legend, value_colors, etc.
+        """
+        # produce a range of values from the collected attributes
+        attr_dict = {i: val for i, val in enumerate(self._attributes_unique)}
+        attr_dict_rev = {val: i for i, val in attr_dict.items()}
+        try:
+            values = tuple(attr_dict_rev[r_attr] for r_attr in self._attributes)
+        except KeyError:  # possibly caused by float cast to -0.0
+            values = []
+            for r_attr in self._attributes:
+                if r_attr == '-0.0':
+                    values.append(attr_dict_rev['0.0'])
+                else:
+                    values.append(attr_dict_rev[r_attr])
+
+        # produce legend parameters with an ordinal dict for the attributes
+        l_par = self.legend_parameters.duplicate()
+        if l_par.is_segment_count_default:
+            l_par.segment_count = len(self._attributes_unique)
+        l_par.ordinal_dictionary = attr_dict
+        if l_par.is_title_default:
+            l_par.title = self.attr_name_end.replace('_', ' ').title()
+
+        return GraphicContainer(values, self.min_point, self.max_point, l_par)
+
+    def _process_attribute_name(self, attr_name):
+        """Process the attribute name and assign it to this object."""
+        self._attr_name = str(attr_name)
+        at_split = self._attr_name.split('.')
+        if len(at_split) == 1:
+            self._attr_name_end = at_split[-1]
+        elif at_split[-1] == 'display_name':
+            self._attr_name_end = at_split[-2]
+        elif at_split[-1] == '__name__' and at_split[-2] == '__class__':
+            self._attr_name_end = at_split[-3]
+        else:
+            self._attr_name_end = at_split[-1]
+
+    def _process_attributes(self, hb_objs):
+        """Process the attributes of honeybee objects."""
+        nd = self.legend_parameters.decimal_count
+        attributes = [get_attr_nested(obj, self._attr_name, nd) for obj in hb_objs]
+        attributes_unique = set(attributes)
+        float_attr = [atr for atr in attributes_unique if isinstance(atr, float)]
+        str_attr = [atr for atr in attributes_unique if isinstance(atr, str)]
+        float_attr.sort()
+        str_attr.sort()
+        self._attributes = tuple(str(val) for val in attributes)
+        self._attributes_unique = tuple(str_attr) + tuple(str(val) for val in float_attr)
+        self._attributes_original = \
+            tuple(get_attr_nested(obj, self._attr_name, cast_to_str=False)
+                  for obj in hb_objs)
+
+    def _calculate_min_max(self, hb_objs):
+        """Calculate maximum and minimum Point3D for a set of rooms."""
+        st_rm_min, st_rm_max = hb_objs[0].geometry.min, hb_objs[0].geometry.max
+        min_pt = [st_rm_min.x, st_rm_min.y, st_rm_min.z]
+        max_pt = [st_rm_max.x, st_rm_max.y, st_rm_max.z]
+
+        for room in hb_objs[1:]:
+            rm_min, rm_max = room.geometry.min, room.geometry.max
+            if rm_min.x < min_pt[0]:
+                min_pt[0] = rm_min.x
+            if rm_min.y < min_pt[1]:
+                min_pt[1] = rm_min.y
+            if rm_min.z < min_pt[2]:
+                min_pt[2] = rm_min.z
+            if rm_max.x > max_pt[0]:
+                max_pt[0] = rm_max.x
+            if rm_max.y > max_pt[1]:
+                max_pt[1] = rm_max.y
+            if rm_max.z > max_pt[2]:
+                max_pt[2] = rm_max.z
+
+        self._min_point = Point3D(min_pt[0], min_pt[1], min_pt[2])
+        self._max_point = Point3D(max_pt[0], max_pt[1], max_pt[2])
+
+    def ToString(self):
+        """Overwrite .NET ToString."""
+        return self.__repr__()
+
+
+
[docs]class ColorRoom(_ColorObject): + """Object for visualizing room-level attributes. + + Args: + rooms: An array of honeybee Rooms, which will be colored with the attribute. + attr_name: A text string of an attribute that the input rooms should have. + This can have '.' that separate the nested attributes from one another. + For example, 'properties.energy.program_type'. + legend_parameters: An optional LegendParameter object to change the display + of the ColorRoom (Default: None). + + Properties: + * rooms + * attr_name + * legend_parameters + * attr_name_end + * attributes + * attributes_unique + * attributes_original + * floor_faces + * graphic_container + * min_point + * max_point + """ + __slots__ = ('_rooms',) + + def __init__(self, rooms, attr_name, legend_parameters=None): + """Initialize ColorRoom.""" + try: # check the input rooms + rooms = tuple(rooms) + except TypeError: + raise TypeError('Input rooms must be an array. Got {}.'.format(type(rooms))) + assert len(rooms) > 0, 'ColorRooms must have at least one room.' + for room in rooms: + assert isinstance(room, Room), 'Expected honeybee Room for ' \ + 'ColorRoom rooms. Got {}.'.format(type(room)) + self._rooms = rooms + self._calculate_min_max(rooms) + + # assign the legend parameters of this object + self.legend_parameters = legend_parameters + + # get the attributes of the input rooms + self._process_attribute_name(attr_name) + self._process_attributes(rooms) + + @property + def rooms(self): + """Get a tuple of honeybee Rooms assigned to this object.""" + return self._rooms + + @property + def floor_faces(self): + """Get a nested array with each sub-array having all floor Face3Ds of each room. + + This is useful for producing visualizations since coloring floors or rooms + instead of the entire room solid allows more of the model to be viewed at once. + """ + flr_faces = [] + for room in self.rooms: + flr_faces.append( + [face.geometry for face in room.faces if isinstance(face.type, Floor)]) + return flr_faces + + def __repr__(self): + """Color Room representation.""" + return 'Color Room:\n{} Rooms\n{}'.format(len(self.rooms), self.attr_name_end)
+ + +
[docs]class ColorFace(_ColorObject): + """Object for visualizing face and sub-face level attributes. + + Args: + faces: An array of honeybee Faces, Apertures, Doors, Shades and/or ShadeMeshes + which will be colored with their attributes. + attr_name: A text string of an attribute that the input faces should have. + This can have '.' that separate the nested attributes from one another. + For example, 'properties.energy.construction'. + legend_parameters: An optional LegendParameter object to change the display + of the ColorFace (Default: None). + + Properties: + * faces + * attr_name + * legend_parameters + * flat_faces + * flat_geometry + * attr_name_end + * attributes + * attributes_unique + * attributes_original + * floor_faces + * graphic_container + * min_point + * max_point + """ + __slots__ = ('_faces', '_flat_faces', '_flat_geometry') + + def __init__(self, faces, attr_name, legend_parameters=None): + """Initialize ColorFace.""" + try: # check the input faces + faces = tuple(faces) + except TypeError: + raise TypeError('Input faces must be an array. Got {}.'.format(type(faces))) + assert len(faces) > 0, 'ColorFaces must have at least one face.' + flat_f = [] + for face in faces: + if isinstance(face, Face): + flat_f.append(face) + flat_f.extend(face.shades) + for ap in face.apertures: + flat_f.append(ap) + flat_f.extend(ap.shades) + for dr in face.doors: + flat_f.append(dr) + flat_f.extend(dr.shades) + elif isinstance(face, (Aperture, Door)): + flat_f.append(face) + flat_f.extend(face.shades) + elif isinstance(face, Shade): + flat_f.append(face) + elif isinstance(face, ShadeMesh): + flat_f.append(face) + else: + raise ValueError( + 'Expected honeybee Face, Aperture, Door, Shade or ShadeMesh ' + 'for ColorFaces. Got {}.'.format(type(face))) + self._faces = faces + self._flat_faces = tuple(flat_f) + self._flat_geometry = tuple(face.geometry if not isinstance(face, Face) + else face.punched_geometry for face in flat_f) + self._calculate_min_max(faces) + + # assign the legend parameters of this object + self.legend_parameters = legend_parameters + + # get the attributes of the input faces + self._process_attribute_name(attr_name) + self._process_attributes(flat_f) + + @property + def faces(self): + """Get the honeybee Faces, Apertures, Doors and Shades assigned to this object. + """ + return self._faces + + @property + def flat_faces(self): + """Get non-nested honeybee Faces, Apertures, Doors and Shades on this object. + + The objects here align with the attributes and graphic_container colors. + """ + return self._flat_faces + + @property + def flat_geometry(self): + """Get non-nested array of faces on this object. + + The geometries here align with the attributes and graphic_container colors. + """ + return self._flat_geometry + + def __repr__(self): + """Color Room representation.""" + return 'Color Faces:\n{} Faces\n{}'.format(len(self.faces), self.attr_name_end)
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/config.html b/docs/_modules/honeybee/config.html new file mode 100644 index 00000000..f959e09f --- /dev/null +++ b/docs/_modules/honeybee/config.html @@ -0,0 +1,1469 @@ + + + + + + + honeybee.config — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.config

+"""Honeybee configurations.
+
+Import this into every module where access configurations are needed.
+
+Usage:
+
+.. code-block:: python
+
+    from honeybee.config import folders
+    print(folders.python_exe_path)
+    print(folders.default_simulation_folder)
+    folders.default_simulation_folder = "C:/my_sim_folder"
+"""
+import ladybug.config as lb_config
+
+import os
+import platform
+import sys
+import subprocess
+import json
+import tempfile
+
+
+
[docs]class Folders(object): + """Honeybee folders. + + Args: + config_file: The path to the config.json file from which folders are loaded. + If None, the config.json module included in this package will be used. + Default: None. + mute: If False, the paths to the various folders will be printed as they + are found. If True, no printing will occur upon initialization of this + class. Default: True. + + Properties: + * default_simulation_folder + * honeybee_core_version + * honeybee_core_version_str + * honeybee_schema_version + * honeybee_schema_version_str + * python_package_path + * python_scripts_path + * python_exe_path + * python_version + * python_version_str + * default_standards_folder + * config_file + * mute + """ + + def __init__(self, config_file=None, mute=True): + # set the mute value + self.mute = bool(mute) + + # load paths from the config JSON file + self.config_file = config_file + + # set python version to only be retrieved if requested + self._python_version = None + self._python_version_str = None + + # search for the version of honeybee-core and honeybee-schema + self._honeybee_core_version = self._find_honeybee_core_version() + self._honeybee_schema_version = self._find_honeybee_schema_version() + + @property + def default_simulation_folder(self): + """Get or set the path to the default simulation folder.""" + return self._default_simulation_folder + + @default_simulation_folder.setter + def default_simulation_folder(self, path): + if not path: # check the default location for simulations + path = self._find_default_simulation_folder() + + self._default_simulation_folder = path + + if not self.mute and self._default_simulation_folder: + print('Path to the default simulation folder is set to: ' + '{}'.format(self._default_simulation_folder)) + + @property + def honeybee_core_version(self): + """Get a tuple for the installed version of honeybee-core (eg. (1, 47, 26)). + + This will be None if the version could not be sensed (it was not installed + via pip). + """ + return self._honeybee_core_version + + @property + def honeybee_core_version_str(self): + """Get a string for the installed version of honeybee-core (eg. "1.47.26"). + + This will be None if the version could not be sensed. + """ + if self._honeybee_core_version is not None: + return '.'.join([str(item) for item in self._honeybee_core_version]) + return None + + @property + def honeybee_schema_version(self): + """Get a tuple for the installed version of honeybee-schema (eg. (1, 35, 0)). + + This will be None if the version could not be sensed (it was not installed + via pip) or if no honeybee-schema installation was found next to the + honeybee-core installation. + """ + return self._honeybee_schema_version + + @property + def honeybee_schema_version_str(self): + """Get a string for the installed version of honeybee-schema (eg. "1.35.0"). + + This will be None if the version could not be sensed. + """ + if self._honeybee_schema_version is not None: + return '.'.join([str(item) for item in self._honeybee_schema_version]) + return None + + @property + def python_package_path(self): + """Get the path to where this Python package is installed.""" + # check the ladybug_tools folder for a Python installation + py_pack = None + lb_install = lb_config.folders.ladybug_tools_folder + if os.path.isdir(lb_install): + if os.name == 'nt': + py_pack = os.path.join(lb_install, 'python', 'Lib', 'site-packages') + elif platform.system() == 'Darwin': # on mac, python version is in path + py_pack = os.path.join( + lb_install, 'python', 'lib', 'python3.7', 'site-packages') + if py_pack is not None and os.path.isdir(py_pack): + return py_pack + return os.path.split(os.path.dirname(__file__))[0] # we're on some other cPython + + @property + def python_scripts_path(self): + """Get the path to where Python CLI executable files are installed. + + This can be used to call command line interface (CLI) executable files + directly (instead of using their usual entry points). + """ + # check the ladybug_tools folder for a Python installation + lb_install = lb_config.folders.ladybug_tools_folder + if os.path.isdir(lb_install): + py_scripts = os.path.join(lb_install, 'python', 'Scripts') \ + if os.name == 'nt' else \ + os.path.join(lb_install, 'python', 'bin') + if os.path.isdir(py_scripts): + return py_scripts + sys_dir = os.path.dirname(sys.executable) # assume we are on some other cPython + return os.path.join(sys_dir, 'Scripts') if os.name == 'nt' else sys_dir + + @property + def python_exe_path(self): + """Get the path to the Python executable to be used for Ladybug Tools CLI calls. + + If a version of Python is found within the ladybug_tools installation folder, + this will be the path to that version of Python. Otherwise, it will be + assumed that this is package is installed in cPython outside of the ladybug_tools + folder and the sys.executable will be returned. + """ + # check the ladybug_tools folder for a Python installation + lb_install = lb_config.folders.ladybug_tools_folder + if os.path.isdir(lb_install): + py_exe_file = os.path.join(lb_install, 'python', 'python.exe') \ + if os.name == 'nt' else \ + os.path.join(lb_install, 'python', 'bin', 'python3') + if os.path.isfile(py_exe_file): + return py_exe_file + return sys.executable # assume we are on some other cPython + + @property + def python_version(self): + """Get a tuple for the version of python (eg. (3, 8, 2)). + + This will be None if the version could not be sensed or if no Python + installation was found. + """ + if self._python_version_str is None and self.python_exe_path: + self._python_version_from_cli() + return self._python_version + + @property + def python_version_str(self): + """Get text for the full version of python (eg."3.8.2"). + + This will be None if the version could not be sensed or if no Python + installation was found. + """ + if self._python_version_str is None and self.python_exe_path: + self._python_version_from_cli() + return self._python_version_str + + @property + def default_standards_folder(self): + """Get or set the path to the default standards library used by extensions. + """ + return self._default_standards_folder + + @default_standards_folder.setter + def default_standards_folder(self, path): + if not path: # check the default locations of the template library + path = self._find_default_standards_folder() + + # set the default_standards_folder + self._default_standards_folder = path + if path and not self.mute: + print('Path to the default_standards_folder is set to: ' + '{}'.format(self._default_standards_folder)) + + @property + def config_file(self): + """Get or set the path to the config.json file from which folders are loaded. + + Setting this to None will result in using the config.json module included + in this package. + """ + return self._config_file + + @config_file.setter + def config_file(self, cfg): + if cfg is None: + cfg = os.path.join(os.path.dirname(__file__), 'config.json') + self._load_from_file(cfg) + self._config_file = cfg + + def _load_from_file(self, file_path): + """Set all of the the properties of this object from a config JSON file. + + Args: + file_path: Path to a JSON file containing the file paths. A sample of this + JSON is the config.json file within this package. + """ + # check the default file path + assert os.path.isfile(file_path), \ + ValueError('No file found at {}'.format(file_path)) + + # set the default paths to be all blank + default_path = { + "default_simulation_folder": r'', + "default_standards_folder": r'' + } + + with open(file_path, 'r') as cfg: + try: + paths = json.load(cfg) + except Exception as e: + print('Failed to load paths from {}.\nThey will be set to defaults ' + 'instead\n{}'.format(file_path, e)) + else: + for key, p in paths.items(): + if not key.startswith('__') and p.strip(): + default_path[key] = p.strip() + + # set paths for the default_simulation_folder + self.default_simulation_folder = default_path["default_simulation_folder"] + self.default_standards_folder = default_path["default_standards_folder"] + + def _python_version_from_cli(self): + """Set this object's Python version by making a call to a Python command.""" + cmds = [self.python_exe_path, '--version'] + use_shell = True if os.name == 'nt' else False + process = subprocess.Popen(cmds, stdout=subprocess.PIPE, shell=use_shell) + stdout = process.communicate() + base_str = str(stdout[0]).replace("b'", '').replace(r"\r\n'", '') + self._python_version_str = base_str.split(' ')[-1] + try: + self._python_version = \ + tuple(int(i) for i in self._python_version_str.split('.')) + except Exception: + pass # failed to parse the version into values + + @staticmethod + def _find_default_simulation_folder(): + """Find the the default simulation folder in its usual location. + + An attempt will be made to create the directory if it does not already exist. + """ + home_folder = os.getenv('HOME') or os.path.expanduser('~') + if not os.access(home_folder, os.W_OK): + home_folder = tempfile.gettempdir() + sim_folder = os.path.join(home_folder, 'simulation') + if not os.path.isdir(sim_folder): + try: + os.makedirs(sim_folder) + except OSError as e: + if e.errno != 17: # avoid race conditions between multiple tasks + raise OSError('Failed to create default simulation ' + 'folder: %s\n%s' % (sim_folder, e)) + return sim_folder + + @staticmethod + def _find_default_standards_folder(): + """Find the user standards library in its default location. + + The %AppData%/ladybug_tools/standards folder will be checked first, which + can contain libraries that are not overwritten with the update of the + honeybee_energy package. If this is not found, the ladybug_tools/resources/ + standards/honeybee_standards folder will be checked next. If no such folder + is found, this None will be returned. + """ + # first check if there's a user-defined folder in AppData + app_folder = os.getenv('APPDATA') + if app_folder is not None: + lib_folder = os.path.join(app_folder, 'ladybug_tools', 'standards') + if os.path.isdir(lib_folder): + return lib_folder + + # then check the ladybug_tools installation folder were permanent lib is + lb_install = lb_config.folders.ladybug_tools_folder + if os.path.isdir(lb_install): + lib_folder = os.path.join( + lb_install, 'resources', 'standards', 'honeybee_standards') + if os.path.isdir(lib_folder): + return lib_folder + + # default to None if nothing was found + return None + + def _find_honeybee_core_version(self): + """Get a tuple of 3 integers for the version of honeybee_core if installed.""" + return self._find_package_version('honeybee_core') + + def _find_honeybee_schema_version(self): + """Get a tuple of 3 integers for the version of honeybee_schema if installed.""" + return self._find_package_version('honeybee_schema') + + def _find_package_version(self, package_name): + """Get a tuple of 3 integers for the version of a package.""" + hb_info_folder = None + for item in os.listdir(self.python_package_path): + if item.startswith(package_name + '-') and item.endswith('.dist-info'): + if os.path.isdir(os.path.join(self.python_package_path, item)): + hb_info_folder = item + break + if hb_info_folder is not None: + hb_info_folder = hb_info_folder.replace('.dist-info', '') + ver = ''.join(s for s in hb_info_folder if (s.isdigit() or s == '.')) + if ver: # version was found in the file path name + return tuple(int(d) for d in ver.split('.')) + return None
+ + +"""Object possesing all key folders within the configuration.""" +folders = Folders() +
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/dictutil.html b/docs/_modules/honeybee/dictutil.html new file mode 100644 index 00000000..9573de4d --- /dev/null +++ b/docs/_modules/honeybee/dictutil.html @@ -0,0 +1,1176 @@ + + + + + + + honeybee.dictutil — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.dictutil

+# coding=utf-8
+"""Utilities to convert any dictionary to Python objects.
+
+Note that importing this module will import almost all modules within the
+library in order to be able to re-serialize almost any dictionary produced
+from the library.
+"""
+from honeybee.model import Model
+from honeybee.room import Room
+from honeybee.face import Face
+from honeybee.aperture import Aperture
+from honeybee.door import Door
+from honeybee.shade import Shade
+import honeybee.boundarycondition as hbc
+
+
+
[docs]def dict_to_object(honeybee_dict, raise_exception=True): + """Re-serialize a dictionary of almost any object within honeybee. + + This includes any Model, Room, Face, Aperture, Door, Shade, or boundary + condition object. + + Args: + honeybee_dict: A dictionary of any Honeybee object. Note + that this should be a non-abridged dictionary to be valid. + raise_exception: Boolean to note whether an exception should be raised + if the object is not identified as a part of honeybee. + Default: True. + + Returns: + A Python object derived from the input honeybee_dict. + """ + try: # get the type key from the dictionary + obj_type = honeybee_dict['type'] + except KeyError: + raise ValueError('Honeybee dictionary lacks required "type" key.') + + if obj_type == 'Model': + return Model.from_dict(honeybee_dict) + elif obj_type == 'Room': + return Room.from_dict(honeybee_dict) + elif obj_type == 'Face': + return Face.from_dict(honeybee_dict) + elif obj_type == 'Aperture': + return Aperture.from_dict(honeybee_dict) + elif obj_type == 'Door': + return Door.from_dict(honeybee_dict) + elif obj_type == 'Shade': + return Shade.from_dict(honeybee_dict) + elif hasattr(hbc, obj_type): + bc_class = getattr(hbc, obj_type) + return bc_class.from_dict(honeybee_dict) + elif raise_exception: + raise ValueError('{} is not a recognized honeybee object'.format(obj_type))
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/door.html b/docs/_modules/honeybee/door.html new file mode 100644 index 00000000..824e71d4 --- /dev/null +++ b/docs/_modules/honeybee/door.html @@ -0,0 +1,1763 @@ + + + + + + + honeybee.door — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.door

+# coding: utf-8
+"""Honeybee Door."""
+from __future__ import division
+import math
+
+from ladybug_geometry.geometry2d.pointvector import Vector2D
+from ladybug_geometry.geometry3d.pointvector import Point3D
+from ladybug_geometry.geometry3d.face import Face3D
+from ladybug.color import Color
+
+from ._basewithshade import _BaseWithShade
+from .typing import clean_string
+from .properties import DoorProperties
+from .boundarycondition import boundary_conditions, Outdoors, Surface
+from .shade import Shade
+import honeybee.writer.door as writer
+
+
+
[docs]class Door(_BaseWithShade): + """A single planar Door in a Face. + + Args: + identifier: Text string for a unique Door ID. Must be < 100 characters and + not contain any spaces or special characters. + geometry: A ladybug-geometry Face3D. + boundary_condition: Boundary condition object (Outdoors, Surface, etc.). + Default: Outdoors. + is_glass: Boolean to note whether this object is a glass door as opposed + to an opaque door. Default: False. + + Properties: + * identifier + * display_name + * boundary_condition + * is_glass + * indoor_shades + * outdoor_shades + * parent + * top_level_parent + * has_parent + * geometry + * vertices + * upper_left_vertices + * triangulated_mesh3d + * normal + * center + * area + * perimeter + * min + * max + * tilt + * altitude + * azimuth + * type_color + * bc_color + * user_data + """ + __slots__ = ('_geometry', '_parent', '_boundary_condition', '_is_glass') + TYPE_COLORS = { + False: Color(160, 150, 100), + True: Color(128, 204, 255, 100) + } + BC_COLORS = { + 'Outdoors': Color(128, 204, 255), + 'Surface': Color(0, 190, 0) + } + + def __init__(self, identifier, geometry, boundary_condition=None, is_glass=False): + """A single planar Door in a Face.""" + _BaseWithShade.__init__(self, identifier) # process the identifier + + # process the geometry + assert isinstance(geometry, Face3D), \ + 'Expected ladybug_geometry Face3D. Got {}'.format(type(geometry)) + self._geometry = geometry + self._parent = None # _parent will be set when the Face is added to a Face + + # process the boundary condition and type + self.boundary_condition = boundary_condition or boundary_conditions.outdoors + self.is_glass = is_glass + + # initialize properties for extensions + self._properties = DoorProperties(self) + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize an Door from a dictionary. + + Args: + data: A dictionary representation of an Door object. + """ + try: + # check the type of dictionary + assert data['type'] == 'Door', 'Expected Door dictionary. ' \ + 'Got {}.'.format(data['type']) + + # serialize the door + is_glass = data['is_glass'] if 'is_glass' in data else False + if data['boundary_condition']['type'] == 'Outdoors': + boundary_condition = Outdoors.from_dict(data['boundary_condition']) + elif data['boundary_condition']['type'] == 'Surface': + boundary_condition = Surface.from_dict(data['boundary_condition'], True) + else: + raise ValueError( + 'Boundary condition "{}" is not supported for Door.'.format( + data['boundary_condition']['type'])) + door = cls(data['identifier'], Face3D.from_dict(data['geometry']), + boundary_condition, is_glass) + if 'display_name' in data and data['display_name'] is not None: + door.display_name = data['display_name'] + if 'user_data' in data and data['user_data'] is not None: + door.user_data = data['user_data'] + door._recover_shades_from_dict(data) + + # assign extension properties + if data['properties']['type'] == 'DoorProperties': + door.properties._load_extension_attr_from_dict(data['properties']) + return door + except Exception as e: + cls._from_dict_error_message(data, e)
+ +
[docs] @classmethod + def from_vertices(cls, identifier, vertices, boundary_condition=None, + is_glass=False): + """Create a Door from vertices with each vertex as an iterable of 3 floats. + + Args: + identifier: Text string for a unique Door ID. Must be < 100 characters and + not contain any spaces or special characters. + vertices: A flattened list of 3 or more vertices as (x, y, z). + boundary_condition: Boundary condition object (eg. Outdoors, Surface). + Default: Outdoors. + is_glass: Boolean to note whether this object is a glass door as opposed + to an opaque door. Default: False. + """ + geometry = Face3D(tuple(Point3D(*v) for v in vertices)) + return cls(identifier, geometry, boundary_condition, is_glass)
+ + @property + def boundary_condition(self): + """Get or set the boundary condition of this door.""" + return self._boundary_condition + + @boundary_condition.setter + def boundary_condition(self, value): + if not isinstance(value, Outdoors): + if isinstance(value, Surface): + assert len(value.boundary_condition_objects) == 3, 'Surface boundary ' \ + 'condition for Door must have 3 boundary_condition_objects.' + else: + raise ValueError('Door only supports Outdoor or Surface boundary ' + 'condition. Got {}'.format(type(value))) + self._boundary_condition = value + + @property + def is_glass(self): + """Get or set a boolean to note whether this object is a glass door.""" + return self._is_glass + + @is_glass.setter + def is_glass(self, value): + try: + self._is_glass = bool(value) + except TypeError: + raise TypeError( + 'Expected boolean for Door.is_glass. Got {}.'.format(value)) + + @property + def parent(self): + """Get the parent Face if assigned. None if not assigned.""" + return self._parent + + @property + def top_level_parent(self): + """Get the top-level parent object if assigned. + + This will be a Room if there is a parent Face that has a parent Room and + will be a Face if the parent Face is orphaned. Will be None if no parent + is assigned. + """ + if self.has_parent: + if self._parent.has_parent: + return self._parent._parent + return self._parent + return None + + @property + def has_parent(self): + """Get a boolean noting whether this Door has a parent Face.""" + return self._parent is not None + + @property + def geometry(self): + """Get a ladybug_geometry Face3D object representing the door.""" + return self._geometry + + @property + def vertices(self): + """Get a list of vertices for the door (in counter-clockwise order).""" + return self._geometry.vertices + + @property + def upper_left_vertices(self): + """Get a list of vertices starting from the upper-left corner. + + This property should be used when exporting to EnergyPlus / OpenStudio. + """ + return self._geometry.upper_left_counter_clockwise_vertices + + @property + def triangulated_mesh3d(self): + """Get a ladybug_geometry Mesh3D of the door geometry composed of triangles. + + In EnergyPlus / OpenStudio workflows, this property is used to subdivide + the door when it has more than 4 vertices. This is necessary since + EnergyPlus cannot accept sub-faces with more than 4 vertices. + """ + return self._geometry.triangulated_mesh3d + + @property + def normal(self): + """Get a ladybug_geometry Vector3D for the direction the door is pointing. + """ + return self._geometry.normal + + @property + def center(self): + """Get a ladybug_geometry Point3D for the center of the door. + + Note that this is the center of the bounding rectangle around this geometry + and not the area centroid. + """ + return self._geometry.center + + @property + def area(self): + """Get the area of the door.""" + return self._geometry.area + + @property + def perimeter(self): + """Get the perimeter of the door.""" + return self._geometry.perimeter + + @property + def min(self): + """Get a Point3D for the minimum of the bounding box around the object.""" + return self._min_with_shades(self._geometry) + + @property + def max(self): + """Get a Point3D for the maximum of the bounding box around the object.""" + return self._max_with_shades(self._geometry) + + @property + def tilt(self): + """Get the tilt of the geometry between 0 (up) and 180 (down).""" + return math.degrees(self._geometry.tilt) + + @property + def altitude(self): + """Get the altitude of the geometry between +90 (up) and -90 (down).""" + return math.degrees(self._geometry.altitude) + + @property + def azimuth(self): + """Get the azimuth of the geometry, between 0 and 360. + + Given Y-axis as North, 0 = North, 90 = East, 180 = South, 270 = West + This will be zero if the Face3D is perfectly horizontal. + """ + return math.degrees(self._geometry.azimuth) + + @property + def type_color(self): + """Get a Color to be used in visualizations by type.""" + return self.TYPE_COLORS[self.is_glass] + + @property + def bc_color(self): + """Get a Color to be used in visualizations by boundary condition.""" + return self.BC_COLORS[self.boundary_condition.name] + +
[docs] def horizontal_orientation(self, north_vector=Vector2D(0, 1)): + """Get a number between 0 and 360 for the orientation of the door in degrees. + + 0 = North, 90 = East, 180 = South, 270 = West + + Args: + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + """ + return math.degrees( + north_vector.angle_clockwise(Vector2D(self.normal.x, self.normal.y)))
+ +
[docs] def cardinal_direction(self, north_vector=Vector2D(0, 1)): + """Get text description for the cardinal direction that the door is pointing. + + Will be one of the following: ('North', 'NorthEast', 'East', 'SouthEast', + 'South', 'SouthWest', 'West', 'NorthWest'). + + Args: + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + """ + orient = self.horizontal_orientation(north_vector) + orient_text = ('North', 'NorthEast', 'East', 'SouthEast', 'South', + 'SouthWest', 'West', 'NorthWest') + angles = (22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5, 337.5) + for i, ang in enumerate(angles): + if orient < ang: + return orient_text[i] + return orient_text[0]
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier of this object and child objects by inserting a prefix. + + This is particularly useful in workflows where you duplicate and edit + a starting object and then want to combine it with the original object + into one Model (like making a model of repeated rooms) since all objects + within a Model must have unique identifiers. + + Args: + prefix: Text that will be inserted at the start of this object's + (and child objects') identifier and display_name. It is recommended + that this prefix be short to avoid maxing out the 100 allowable + characters for honeybee identifiers. + """ + self._identifier = clean_string('{}_{}'.format(prefix, self.identifier)) + self.display_name = '{}_{}'.format(prefix, self.display_name) + self.properties.add_prefix(prefix) + self._add_prefix_shades(prefix) + if isinstance(self._boundary_condition, Surface): + new_bc_objs = (clean_string('{}_{}'.format(prefix, adj_name)) for adj_name + in self._boundary_condition._boundary_condition_objects) + self._boundary_condition = Surface(new_bc_objs, True)
+ +
[docs] def set_adjacency(self, other_door): + """Set this door to be adjacent to another (and vice versa). + + Note that this method does not verify whether the other_door geometry is + co-planar or compatible with this one so it is recommended that a test + be performed before using this method in order to verify these criteria. + The Face3D.is_centered_adjacent() or the Face3D.is_geometrically_equivalent() + methods are both suitable for this purpose. + + Args: + other_door: Another Door object to be set adjacent to this one. + """ + assert isinstance(other_door, Door), \ + 'Expected Door. Got {}.'.format(type(other_door)) + assert other_door.is_glass is self.is_glass, \ + 'Adjacent doors must have matching is_glass properties.' + self._boundary_condition = boundary_conditions.surface(other_door, True) + other_door._boundary_condition = boundary_conditions.surface(self, True)
+ +
[docs] def overhang(self, depth, angle=0, indoor=False, tolerance=0.01, base_name=None): + """Add a single overhang for this Door. Can represent entryway awnings. + + Args: + depth: A number for the overhang depth. + angle: A number for the for an angle to rotate the overhang in degrees. + Positive numbers indicate a downward rotation while negative numbers + indicate an upward rotation. Default is 0 for no rotation. + indoor: Boolean for whether the overhang should be generated facing the + opposite direction of the aperture normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to return None if the overhang has a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + base_name: Optional base name for the shade objects. If None, the default + is InOverhang or OutOverhang depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + # get a name for the shade + if base_name is None: + base_name = 'InOverhang' if indoor else 'OutOverhang' + shd_name_base = '{}_' + str(base_name) + '{}' + + # create the shade geometry + angle = math.radians(angle) + dr_geo = self.geometry if indoor is False else self.geometry.flip() + shade_faces = dr_geo.contour_fins_by_number( + 1, depth, 0, angle, Vector2D(0, 1), False, tolerance) + + # create the Shade objects + overhang = [] + for i, shade_geo in enumerate(shade_faces): + overhang.append(Shade(shd_name_base.format(self.identifier, i), shade_geo)) + if indoor: + self.add_indoor_shades(overhang) + else: + self.add_outdoor_shades(overhang) + return overhang
+ +
[docs] def move(self, moving_vec): + """Move this Door along a vector. + + Args: + moving_vec: A ladybug_geometry Vector3D with the direction and distance + to move the face. + """ + self._geometry = self.geometry.move(moving_vec) + self.move_shades(moving_vec) + self.properties.move(moving_vec) + self._reset_parent_geometry()
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Door by a certain angle around an axis and origin. + + Args: + axis: A ladybug_geometry Vector3D axis representing the axis of rotation. + angle: An angle for rotation in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate(axis, math.radians(angle), origin) + self.rotate_shades(axis, angle, origin) + self.properties.rotate(axis, angle, origin) + self._reset_parent_geometry()
+ +
[docs] def rotate_xy(self, angle, origin): + """Rotate this Door counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate_xy(math.radians(angle), origin) + self.rotate_xy_shades(angle, origin) + self.properties.rotate_xy(angle, origin) + self._reset_parent_geometry()
+ +
[docs] def reflect(self, plane): + """Reflect this Door across a plane. + + Args: + plane: A ladybug_geometry Plane across which the object will + be reflected. + """ + self._geometry = self.geometry.reflect(plane.n, plane.o) + self.reflect_shades(plane) + self.properties.reflect(plane) + self._reset_parent_geometry()
+ +
[docs] def scale(self, factor, origin=None): + """Scale this Door by a factor from an origin point. + + Args: + factor: A number representing how much the object should be scaled. + origin: A ladybug_geometry Point3D representing the origin from which + to scale. If None, it will be scaled from the World origin (0, 0, 0). + """ + self._geometry = self.geometry.scale(factor, origin) + self.scale_shades(factor, origin) + self.properties.scale(factor, origin) + self._reset_parent_geometry()
+ +
[docs] def remove_colinear_vertices(self, tolerance=0.01): + """Remove all colinear and duplicate vertices from this object's geometry. + + Note that this does not affect any assigned Shades. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + """ + try: + self._geometry = self.geometry.remove_colinear_vertices(tolerance) + except AssertionError as e: # usually a sliver face of some kind + raise ValueError( + 'Door "{}" is invalid with dimensions less than the ' + 'tolerance.\n{}'.format(self.full_id, e))
+ +
[docs] def is_geo_equivalent(self, door, tolerance=0.01): + """Get a boolean for whether this object is geometrically equivalent to another. + + The total number of vertices and the ordering of these vertices can be + different but the geometries must share the same center point and be + next to one another to within the tolerance. + + Args: + door: Another Door for which geometric equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered geometrically equivalent. + + Returns: + True if geometrically equivalent. False if not geometrically equivalent. + """ + meta_1 = (self.display_name, self.is_glass, self.boundary_condition) + meta_2 = (door.display_name, door.is_glass, door.boundary_condition) + if meta_1 != meta_2: + return False + if abs(self.area - door.area) > tolerance * self.area: + return False + if not self.geometry.is_centered_adjacent(door.geometry, tolerance): + return False + if not self._are_shades_equivalent(door, tolerance): + return False + return True
+ +
[docs] def check_planar(self, tolerance=0.01, raise_exception=True, detailed=False): + """Check whether all of the Door's vertices lie within the same plane. + + Args: + tolerance: The minimum distance between a given vertex and a the + object's plane at which the vertex is said to lie in the plane. + Default: 0.01, suitable for objects in meters. + raise_exception: Boolean to note whether an ValueError should be + raised if a vertex does not lie within the object's plane. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + try: + self.geometry.check_planar(tolerance, raise_exception=True) + except ValueError as e: + msg = 'Door "{}" is not planar.\n{}'.format(self.full_id, e) + full_msg = self._validation_message( + msg, raise_exception, detailed, '000101', + error_type='Non-Planar Geometry') + if detailed: # add the out-of-plane points to helper_geometry + help_pts = [ + p.to_dict() for p in self.geometry.non_planar_vertices(tolerance) + ] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def check_self_intersecting(self, tolerance=0.01, raise_exception=True, + detailed=False): + """Check whether the edges of the Door intersect one another (like a bowtie). + + Note that objects that have duplicate vertices will not be considered + self-intersecting and are valid in honeybee. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + raise_exception: If True, a ValueError will be raised if the object + intersects with itself. Default: True. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + if self.geometry.is_self_intersecting: + msg = 'Door "{}" has self-intersecting edges.'.format(self.full_id) + try: # see if it is self-intersecting because of a duplicate vertex + new_geo = self.geometry.remove_duplicate_vertices(tolerance) + if not new_geo.is_self_intersecting: + return [] if detailed else '' # valid with removed dup vertex + except AssertionError: + return [] if detailed else '' # degenerate geometry + full_msg = self._validation_message( + msg, raise_exception, detailed, '000102', + error_type='Self-Intersecting Geometry') + if detailed: # add the self-intersection points to helper_geometry + help_pts = [p.to_dict() for p in self.geometry.self_intersection_points] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def display_dict(self): + """Get a list of DisplayFace3D dictionaries for visualizing the object.""" + base = [self._display_face(self.geometry, self.type_color)] + for shd in self.shades: + base.extend(shd.display_dict()) + return base
+ + @property + def to(self): + """Door writer object. + + Use this method to access Writer class to write the door in different formats. + + Usage: + + .. code-block:: python + + door.to.idf(door) -> idf string. + door.to.radiance(door) -> Radiance string. + """ + return writer + +
[docs] def to_dict(self, abridged=False, included_prop=None, include_plane=True): + """Return Door as a dictionary. + + Args: + abridged: Boolean to note whether the extension properties of the + object (ie. materials, constructions) should be included in detail + (False) or just referenced by identifier (True). (Default: False). + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + include_plane: Boolean to note wether the plane of the Face3D should be + included in the output. This can preserve the orientation of the + X/Y axes of the plane but is not required and can be removed to + keep the dictionary smaller. (Default: True). + """ + base = {'type': 'Door'} + base['identifier'] = self.identifier + base['display_name'] = self.display_name + base['properties'] = self.properties.to_dict(abridged, included_prop) + enforce_upper_left = True if 'energy' in base['properties'] else False + base['geometry'] = self._geometry.to_dict(include_plane, enforce_upper_left) + base['is_glass'] = self.is_glass + if isinstance(self.boundary_condition, Outdoors) and \ + 'energy' in base['properties']: + base['boundary_condition'] = self.boundary_condition.to_dict(full=True) + else: + base['boundary_condition'] = self.boundary_condition.to_dict() + self._add_shades_to_dict(base, abridged, included_prop, include_plane) + if self.user_data is not None: + base['user_data'] = self.user_data + return base
+ + def _reset_parent_geometry(self): + """Reset parent punched_geometry in the case that the object is transformed.""" + if self.has_parent: + self._parent._punched_geometry = None + + def __copy__(self): + new_door = Door(self.identifier, self.geometry, self.boundary_condition, + self.is_glass) + new_door._display_name = self._display_name + new_door._user_data = None if self.user_data is None else self.user_data.copy() + self._duplicate_child_shades(new_door) + new_door._properties._duplicate_extension_attr(self._properties) + return new_door + + def __repr__(self): + return 'Door: %s' % self.display_name
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/extensionutil.html b/docs/_modules/honeybee/extensionutil.html new file mode 100644 index 00000000..a25963c1 --- /dev/null +++ b/docs/_modules/honeybee/extensionutil.html @@ -0,0 +1,1330 @@ + + + + + + + honeybee.extensionutil — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.extensionutil

+# coding: utf-8
+"""A series of utility functions that are useful across several honeybee extensions."""
+
+
+
[docs]def model_extension_dicts(data, extension_key, room_ext_dicts, face_ext_dicts, + shade_ext_dicts, aperture_ext_dicts, door_ext_dicts): + """Get all Model property dictionaries of an extension organized by geometry type. + + Note that the order in which dictionaries appear in the output lists is the + same order as the geometry objects appear when requested from the model. + For example, the shade_ext_dicts align with the model.shades. + + Args: + data: A dictionary representation of an entire honeybee-core Model. + extension_key: Text for the key of the extension (eg. "energy", "radiance"). + + Returns: + A tuple with five elements + + - room_ext_dicts: A list of Room extension property dictionaries that + align with the serialized model.rooms. + + - face_ext_dicts: A list of Face extension property dictionaries that + align with the serialized model.faces. + + - shade_ext_dicts: A list of Shade extension property dictionaries that + align with the serialized model.shades plus the model.shade_meshes. + + - aperture_ext_dicts: A list of Aperture extension property dictionaries that + align with the serialized model.apertures. + + - door_ext_dicts: A list of Door extension property dictionaries that + align with the serialized model.doors. + """ + assert data['type'] == 'Model', \ + 'Expected Model dictionary. Got {}.'.format(data['type']) + + # loop through the model dictionary using the same logic that the + # model does when you request rooms, faces, shades, apertures and doors. + if 'rooms' in data and data['rooms'] is not None: + room_extension_dicts(data['rooms'], extension_key, room_ext_dicts, + face_ext_dicts, shade_ext_dicts, aperture_ext_dicts, + door_ext_dicts) + if 'orphaned_faces' in data and data['orphaned_faces'] is not None: + face_extension_dicts(data['orphaned_faces'], extension_key, face_ext_dicts, + shade_ext_dicts, aperture_ext_dicts, door_ext_dicts) + if 'orphaned_apertures' in data and data['orphaned_apertures'] is not None: + aperture_extension_dicts(data['orphaned_apertures'], extension_key, + aperture_ext_dicts, shade_ext_dicts) + if 'orphaned_doors' in data and data['orphaned_doors'] is not None: + door_extension_dicts(data['orphaned_doors'], extension_key, door_ext_dicts, + shade_ext_dicts) + if 'orphaned_shades' in data and data['orphaned_shades'] is not None: + shade_extension_dicts(data['orphaned_shades'], extension_key, shade_ext_dicts) + if 'shade_meshes' in data and data['shade_meshes'] is not None: + shade_extension_dicts(data['shade_meshes'], extension_key, shade_ext_dicts) + + return room_ext_dicts, face_ext_dicts, shade_ext_dicts, \ + aperture_ext_dicts, door_ext_dicts
+ + +
[docs]def room_extension_dicts(room_list, extension_key, room_ext_dicts, face_ext_dicts, + shade_ext_dicts, aperture_ext_dicts, door_ext_dicts): + """Get all Room property dictionaries of an extension organized by geometry type. + + Args: + room_list: A list of Room dictionaries. + extension_key: Text for the key of the extension (eg. "energy", "radiance"). + + Returns: + A tuple with five elements + + - room_ext_dicts: A list with the Room extension property dictionaries. + + - face_ext_dicts: A list with Face extension property dictionaries. + + - shade_ext_dicts: A list with Shade extension property dictionaries. + + - aperture_ext_dicts: A list with Aperture extension property dictionaries. + + - door_ext_dicts: A list with Door extension property dictionaries. + """ + for room_dict in room_list: + try: + room_ext_dicts.append(room_dict['properties'][extension_key]) + except KeyError: + room_ext_dicts.append(None) + if 'outdoor_shades' in room_dict and room_dict['outdoor_shades'] is not None: + shade_extension_dicts(room_dict['outdoor_shades'], extension_key, + shade_ext_dicts) + if 'indoor_shades' in room_dict and room_dict['indoor_shades'] is not None: + shade_extension_dicts(room_dict['indoor_shades'], extension_key, + shade_ext_dicts) + face_extension_dicts(room_dict['faces'], extension_key, face_ext_dicts, + shade_ext_dicts, aperture_ext_dicts, door_ext_dicts) + return room_ext_dicts, face_ext_dicts, shade_ext_dicts, \ + aperture_ext_dicts, door_ext_dicts
+ + +
[docs]def face_extension_dicts(face_list, extension_key, face_ext_dicts, + shade_ext_dicts, aperture_ext_dicts, door_ext_dicts): + """Get all Face property dictionaries of an extension organized by geometry type. + + Args: + face_list: A list of Room dictionaries. + extension_key: Text for the key of the extension (eg. "energy", "radiance"). + + Returns: + A tuple with four elements + + - face_ext_dicts: A list with Face extension property dictionaries. + + - shade_ext_dicts: A list with Shade extension property dictionaries. + + - aperture_ext_dicts: A list with Aperture extension property dictionaries. + + - door_ext_dicts: A list with Door extension property dictionaries. + """ + for face_dict in face_list: + try: + face_ext_dicts.append(face_dict['properties'][extension_key]) + except KeyError: + face_ext_dicts.append(None) + if 'outdoor_shades' in face_dict and face_dict['outdoor_shades'] is not None: + shade_extension_dicts(face_dict['outdoor_shades'], extension_key, + shade_ext_dicts) + if 'indoor_shades' in face_dict and face_dict['indoor_shades'] is not None: + shade_extension_dicts(face_dict['indoor_shades'], extension_key, + shade_ext_dicts) + if 'apertures' in face_dict and face_dict['apertures'] is not None: + aperture_extension_dicts(face_dict['apertures'], extension_key, + aperture_ext_dicts, shade_ext_dicts) + if 'doors' in face_dict and face_dict['doors'] is not None: + door_extension_dicts(face_dict['doors'], extension_key, + door_ext_dicts, shade_ext_dicts) + return face_ext_dicts, shade_ext_dicts, aperture_ext_dicts, door_ext_dicts
+ + +
[docs]def shade_extension_dicts(shade_list, extension_key, shade_ext_dicts): + """Get all Shade property dictionaries of an extension organized by geometry type. + + Args: + shade_list: A list of Shade dictionaries. + extension_key: Text for the key of the extension (eg. "energy", "radiance"). + + Returns: + shade_ext_dicts -- A list with Shade extension property dictionaries. + """ + for shd_dict in shade_list: + try: + shade_ext_dicts.append(shd_dict['properties'][extension_key]) + except KeyError: + shade_ext_dicts.append(None) + return shade_ext_dicts
+ + +
[docs]def aperture_extension_dicts(aperture_list, extension_key, aperture_ext_dicts, + shade_ext_dicts): + """Get all Aperture property dictionaries of an extension organized by geometry type. + + Args: + aperture_list: A list of Aperture dictionaries. + extension_key: Text for the key of the extension (eg. "energy", "radiance"). + + Returns: + A tuple with two elements + + - aperture_ext_dicts: A list with Aperture extension property dictionaries. + + - shade_ext_dicts: A list with Shade extension property dictionaries. + """ + for ap_dict in aperture_list: + try: + aperture_ext_dicts.append(ap_dict['properties'][extension_key]) + except KeyError: + aperture_ext_dicts.append(None) + if 'outdoor_shades' in ap_dict and ap_dict['outdoor_shades'] is not None: + shade_extension_dicts(ap_dict['outdoor_shades'], extension_key, shade_ext_dicts) + if 'indoor_shades' in ap_dict and ap_dict['indoor_shades'] is not None: + shade_extension_dicts(ap_dict['indoor_shades'], extension_key, shade_ext_dicts) + return aperture_ext_dicts, shade_ext_dicts
+ + +
[docs]def door_extension_dicts(door_list, extension_key, door_ext_dicts, + shade_ext_dicts): + """Get all Door property dictionaries of an extension organized by geometry type. + + Args: + door_list: A list of Door dictionaries. + extension_key: Text for the key of the extension (eg. "energy", "radiance"). + + Returns: + A tuple with two elements + + - door_ext_dicts: A list with Door extension property dictionaries. + + - shade_ext_dicts: A list with Shade extension property dictionaries. + """ + for dr_dict in door_list: + try: + door_ext_dicts.append(dr_dict['properties'][extension_key]) + except KeyError: + door_ext_dicts.append(None) + if 'outdoor_shades' in dr_dict and dr_dict['outdoor_shades'] is not None: + shade_extension_dicts(dr_dict['outdoor_shades'], extension_key, shade_ext_dicts) + if 'indoor_shades' in dr_dict and dr_dict['indoor_shades'] is not None: + shade_extension_dicts(dr_dict['indoor_shades'], extension_key, shade_ext_dicts) + return door_ext_dicts, shade_ext_dicts
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/face.html b/docs/_modules/honeybee/face.html new file mode 100644 index 00000000..601ffc46 --- /dev/null +++ b/docs/_modules/honeybee/face.html @@ -0,0 +1,3216 @@ + + + + + + + honeybee.face — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.face

+# coding: utf-8
+"""Honeybee Face."""
+from __future__ import division
+import math
+
+from ladybug_geometry.geometry2d import Vector2D, Point2D, Polygon2D, Mesh2D
+from ladybug_geometry.geometry3d import Vector3D, Point3D, Plane, Face3D
+from ladybug.color import Color
+
+from ._basewithshade import _BaseWithShade
+from .typing import clean_string, invalid_dict_error
+from .properties import FaceProperties
+from .facetype import face_types, get_type_from_normal, AirBoundary, Floor, RoofCeiling
+from .boundarycondition import boundary_conditions, get_bc_from_position, \
+    _BoundaryCondition, Outdoors, Surface, Ground
+from .shade import Shade
+from .aperture import Aperture
+from .door import Door
+import honeybee.boundarycondition as hbc
+import honeybee.writer.face as writer
+
+
+
[docs]class Face(_BaseWithShade): + """A single planar face. + + Args: + identifier: Text string for a unique Face ID. Must be < 100 characters and + not contain any spaces or special characters. + geometry: A ladybug-geometry Face3D. + type: Face type. Default varies depending on the direction that + the Face geometry is points. + RoofCeiling = pointing upward within 30 degrees + Wall = oriented vertically within +/- 60 degrees + Floor = pointing downward within 30 degrees + boundary_condition: Face boundary condition (Outdoors, Ground, etc.) + Default is Outdoors unless all vertices of the geometry lie + below the below the XY plane, in which case it will be set to Ground. + + Properties: + * identifier + * display_name + * type + * boundary_condition + * apertures + * doors + * sub_faces + * indoor_shades + * outdoor_shades + * parent + * has_parent + * has_sub_faces + * can_be_ground + * geometry + * punched_geometry + * vertices + * punched_vertices + * upper_left_vertices + * normal + * center + * area + * perimeter + * min + * max + * aperture_area + * aperture_ratio + * tilt + * altitude + * azimuth + * type_color + * bc_color + * user_data + """ + TYPES = face_types + __slots__ = ('_geometry', '_parent', '_punched_geometry', + '_apertures', '_doors', '_type', '_boundary_condition') + TYPE_COLORS = { + 'Wall': Color(230, 180, 60), + 'RoofCeiling': Color(128, 20, 20), + 'Floor': Color(128, 128, 128), + 'AirBoundary': Color(255, 255, 200, 100), + 'InteriorWall': Color(230, 215, 150), + 'InteriorRoofCeiling': Color(255, 128, 128), + 'InteriorFloor': Color(255, 128, 128), + 'InteriorAirBoundary': Color(255, 255, 200, 100) + } + BC_COLORS = { + 'Outdoors': Color(64, 180, 255), + 'Surface': Color(0, 128, 0), + 'Ground': Color(165, 82, 0), + 'Adiabatic': Color(255, 128, 128), + 'Other': Color(255, 255, 200) + } + + def __init__(self, identifier, geometry, type=None, boundary_condition=None): + """A single planar face.""" + _BaseWithShade.__init__(self, identifier) # process the identifier + + # process the geometry + assert isinstance(geometry, Face3D), \ + 'Expected ladybug_geometry Face3D. Got {}'.format(geometry) + self._geometry = geometry + self._parent = None # _parent will be set when the Face is added to a Room + # initialize with no apertures/doors (they can be assigned later) + self._punched_geometry = None + self._apertures = [] + self._doors = [] + + # initialize properties for extensions + self._properties = FaceProperties(self) + + # set face type based on normal if not provided + if type is not None: + assert type in self.TYPES, '{} is not a valid face type.'.format(type) + self._type = type or get_type_from_normal(geometry.normal) + + # set boundary condition by the relation to a zero ground plane if not provided + self.boundary_condition = boundary_condition or \ + get_bc_from_position(geometry.boundary) + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize an Face from a dictionary. + + Args: + data: A dictionary representation of an Face object. + """ + try: + # check the type of dictionary + assert data['type'] == 'Face', 'Expected Face dictionary. ' \ + 'Got {}.'.format(data['type']) + + # first serialize it with an outdoor boundary condition + face_type = face_types.by_name(data['face_type']) + face = cls(data['identifier'], Face3D.from_dict(data['geometry']), + face_type, boundary_conditions.outdoors) + if 'display_name' in data and data['display_name'] is not None: + face.display_name = data['display_name'] + if 'user_data' in data and data['user_data'] is not None: + face.user_data = data['user_data'] + + # add sub-faces and shades + if 'apertures' in data and data['apertures'] is not None: + aps = [] + for ap in data['apertures']: + try: + aps.append(Aperture.from_dict(ap)) + except Exception as e: + invalid_dict_error(ap, e) + face.add_apertures(aps) + if 'doors' in data and data['doors'] is not None: + drs = [] + for dr in data['doors']: + try: + drs.append(Door.from_dict(dr)) + except Exception as e: + invalid_dict_error(dr, e) + face.add_doors(drs) + face._recover_shades_from_dict(data) + + # get the boundary condition and assign it + try: + bc_class = getattr(hbc, data['boundary_condition']['type']) + face.boundary_condition = bc_class.from_dict(data['boundary_condition']) + except AttributeError: # extension boundary condition; default to Outdoors + pass + + # assign extension properties + if data['properties']['type'] == 'FaceProperties': + face.properties._load_extension_attr_from_dict(data['properties']) + return face + except Exception as e: + cls._from_dict_error_message(data, e)
+ +
[docs] @classmethod + def from_vertices(cls, identifier, vertices, type=None, boundary_condition=None): + """Create a Face from vertices with each vertex as an iterable of 3 floats. + + Note that this method is not recommended for a face with one or more holes + since the distinction between hole vertices and boundary vertices cannot + be derived from a single list of vertices. + + Args: + identifier: Text string for a unique Face ID. Must be < 100 characters and + not contain any spaces or special characters. + vertices: A flattened list of 3 or more vertices as (x, y, z). + type: Face type object (eg. Wall, Floor). + boundary_condition: Boundary condition object (eg. Outdoors, Ground) + """ + geometry = Face3D(tuple(Point3D(*v) for v in vertices)) + return cls(identifier, geometry, type, boundary_condition)
+ + @property + def type(self): + """Get or set an object for Type of Face (ie. Wall, Floor, Roof). + + Note that setting this property will reset extension attributes on this + Face to their default values. + """ + return self._type + + @type.setter + def type(self, value): + assert value in self.TYPES, '{} is not a valid face type.'.format(value) + if isinstance(value, AirBoundary): + assert self._apertures == [] or self._doors == [], \ + '{} cannot be assigned to a Face with Apertures or Doors.'.format(value) + self.properties.reset_to_default() # reset constructions/modifiers + self._type = value + + @property + def boundary_condition(self): + """Get or set the boundary condition of the Face. (ie. Outdoors, Ground, etc.). + """ + return self._boundary_condition + + @boundary_condition.setter + def boundary_condition(self, value): + assert isinstance(value, _BoundaryCondition), \ + 'Expected BoundaryCondition. Got {}'.format(type(value)) + if self._apertures != [] or self._doors != []: + assert isinstance(value, (Outdoors, Surface)), \ + '{} cannot be assigned to a Face with apertures or doors.'.format(value) + self._boundary_condition = value + + @property + def apertures(self): + """Get a tuple of apertures in this Face.""" + return tuple(self._apertures) + + @property + def doors(self): + """Get a tuple of doors in this Face.""" + return tuple(self._doors) + + @property + def sub_faces(self): + """Get a tuple of apertures and doors in this Face.""" + return tuple(self._apertures + self._doors) + + @property + def parent(self): + """Get the parent Room if assigned. None if not assigned.""" + return self._parent + + @property + def has_parent(self): + """Get a boolean noting whether this Face has a parent Room.""" + return self._parent is not None + + @property + def has_sub_faces(self): + """Get a boolean noting whether this Face has Apertures or Doors.""" + return not (self._apertures == [] and self._doors == []) + + @property + def can_be_ground(self): + """Get a boolean for whether this Face can support a Ground boundary condition. + """ + return self._apertures == [] and self._doors == [] \ + and not isinstance(self._type, AirBoundary) + + @property + def geometry(self): + """Get a ladybug_geometry Face3D object representing the Face. + + Note that this Face3D only represents the parent face and does not have any + holes cut in it for apertures or doors. + """ + return self._geometry + + @property + def punched_geometry(self): + """Get a Face3D object with holes cut in it for apertures and doors. + """ + if self._punched_geometry is None: + _sub_faces = tuple(sub_f.geometry for sub_f in self._apertures + self._doors) + if len(_sub_faces) != 0: + self._punched_geometry = Face3D.from_punched_geometry( + self._geometry, _sub_faces) + else: + self._punched_geometry = self._geometry + return self._punched_geometry + + @property + def vertices(self): + """Get a list of vertices for the face (in counter-clockwise order). + + Note that these vertices only represent the outer boundary of the face + and do not account for holes cut in the face by apertures or doors. + """ + return self._geometry.vertices + + @property + def punched_vertices(self): + """Get a list of vertices with holes cut in it for apertures and doors. + + Note that some vertices will be repeated since the vertices effectively + trace out a single boundary around the whole shape, winding inward to cut + out the holes. This property should be used when exporting to Radiance. + """ + return self.punched_geometry.vertices + + @property + def upper_left_vertices(self): + """Get a list of vertices starting from the upper-left corner. + + This property obeys the same rules as the vertices property but always starts + from the upper-left-most vertex. This property should be used when exporting to + EnergyPlus / OpenStudio. + """ + return self._geometry.upper_left_counter_clockwise_vertices + + @property + def normal(self): + """Get a Vector3D for the direction in which the face is pointing. + """ + return self._geometry.normal + + @property + def center(self): + """Get a ladybug_geometry Point3D for the center of the face. + + Note that this is the center of the bounding rectangle around this geometry + and not the area centroid. + """ + return self._geometry.center + + @property + def area(self): + """Get the area of the face.""" + return self._geometry.area + + @property + def perimeter(self): + """Get the perimeter of the face. This includes the length of holes in the face. + """ + return self._geometry.perimeter + + @property + def min(self): + """Get a Point3D for the minimum of the bounding box around the object.""" + all_geo = self._outdoor_shades + self._indoor_shades + all_geo.extend(self._apertures) + all_geo.extend(self._doors) + all_geo.append(self.geometry) + return self._calculate_min(all_geo) + + @property + def max(self): + """Get a Point3D for the maximum of the bounding box around the object.""" + all_geo = self._outdoor_shades + self._indoor_shades + all_geo.extend(self._apertures) + all_geo.extend(self._doors) + all_geo.append(self.geometry) + return self._calculate_max(all_geo) + + @property + def aperture_area(self): + """Get the combined area of the face's apertures.""" + return sum([ap.area for ap in self._apertures]) + + @property + def aperture_ratio(self): + """Get a number between 0 and 1 for the area ratio of the apertures to the face. + """ + return self.aperture_area / self.area + + @property + def tilt(self): + """Get the tilt of the geometry between 0 (up) and 180 (down).""" + return math.degrees(self._geometry.tilt) + + @property + def altitude(self): + """Get the altitude of the geometry between +90 (up) and -90 (down).""" + return math.degrees(self._geometry.altitude) + + @property + def azimuth(self): + """Get the azimuth of the geometry, between 0 and 360. + + Given Y-axis as North, 0 = North, 90 = East, 180 = South, 270 = West + This will be zero if the Face3D is perfectly horizontal. + """ + return math.degrees(self._geometry.azimuth) + + @property + def type_color(self): + """Get a Color to be used in visualizations by type.""" + ts = self.type.name if isinstance(self.boundary_condition, (Outdoors, Ground)) \ + else 'Interior{}'.format(self.type.name) + return self.TYPE_COLORS[ts] + + @property + def bc_color(self): + """Get a Color to be used in visualizations by boundary condition.""" + try: + return self.BC_COLORS[self.boundary_condition.name] + except KeyError: # extension boundary condition + return self.BC_COLORS['Other'] + +
[docs] def horizontal_orientation(self, north_vector=Vector2D(0, 1)): + """Get a number between 0 and 360 for the orientation of the face in degrees. + + 0 = North, 90 = East, 180 = South, 270 = West + + Args: + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + """ + return math.degrees( + north_vector.angle_clockwise(Vector2D(self.normal.x, self.normal.y)))
+ +
[docs] def cardinal_direction(self, north_vector=Vector2D(0, 1)): + """Get text description for the cardinal direction that the face is pointing. + + Will be one of the following: ('North', 'NorthEast', 'East', 'SouthEast', + 'South', 'SouthWest', 'West', 'NorthWest'). + + Args: + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + """ + orient = self.horizontal_orientation(north_vector) + orient_text = ('North', 'NorthEast', 'East', 'SouthEast', 'South', + 'SouthWest', 'West', 'NorthWest') + angles = (22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5, 337.5) + for i, ang in enumerate(angles): + if orient < ang: + return orient_text[i] + return orient_text[0]
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier of this object and child objects by inserting a prefix. + + This is particularly useful in workflows where you duplicate and edit + a starting object and then want to combine it with the original object + into one Model (like making a model of repeated rooms) since all objects + within a Model must have unique identifiers. + + Args: + prefix: Text that will be inserted at the start of this object's + (and child objects') identifier and display_name. It is recommended + that this prefix be short to avoid maxing out the 100 allowable + characters for honeybee identifiers. + """ + self._identifier = clean_string('{}_{}'.format(prefix, self.identifier)) + self.display_name = '{}_{}'.format(prefix, self.display_name) + self.properties.add_prefix(prefix) + for ap in self._apertures: + ap.add_prefix(prefix) + for dr in self._doors: + dr.add_prefix(prefix) + self._add_prefix_shades(prefix) + if isinstance(self._boundary_condition, Surface): + new_bc_objs = (clean_string('{}_{}'.format(prefix, adj_name)) for adj_name + in self._boundary_condition._boundary_condition_objects) + self._boundary_condition = Surface(new_bc_objs, False)
+ +
[docs] def remove_sub_faces(self): + """Remove all apertures and doors from the face.""" + self.remove_apertures() + self.remove_doors()
+ +
[docs] def remove_apertures(self): + """Remove all apertures from the face.""" + for aperture in self._apertures: + aperture._parent = None + self._apertures = [] + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def remove_doors(self): + """Remove all doors from the face.""" + for door in self._apertures: + door._parent = None + self._doors = [] + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def add_aperture(self, aperture): + """Add an Aperture to this face. + + This method does not check the co-planarity between this Face and the + Aperture or whether the Aperture has all vertices within the boundary of + this Face. To check this, the Face3D.is_sub_face() method can be used + with the Aperture and Face geometry before using this method or the + are_sub_faces_valid() method can be used afterwards. + + Args: + aperture: An Aperture to add to this face. + """ + assert isinstance(aperture, Aperture), \ + 'Expected Aperture. Got {}.'.format(type(aperture)) + self._acceptable_sub_face_check(Aperture) + aperture._parent = self + if self.normal.angle(aperture.normal) > math.pi / 2: # reversed normal + aperture._geometry = aperture._geometry.flip() + self._apertures.append(aperture) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def add_door(self, door): + """Add a Door to this face. + + This method does not check the co-planarity between this Face and the + Door or whether the Door has all vertices within the boundary of + this Face. To check this, the Face3D.is_sub_face() method can be used + with the Door and Face geometry before using this method or the + are_sub_faces_valid() method can be used afterwards. + + Args: + door: A Door to add to this face. + """ + assert isinstance(door, Door), \ + 'Expected Door. Got {}.'.format(type(door)) + self._acceptable_sub_face_check(Door) + door._parent = self + if self.normal.angle(door.normal) > math.pi / 2: # reversed normal + door._geometry = door._geometry.flip() + self._doors.append(door) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def add_apertures(self, apertures): + """Add a list of Apertures to this face.""" + for aperture in apertures: + self.add_aperture(aperture)
+ +
[docs] def add_doors(self, doors): + """Add a list of Doors to this face.""" + for door in doors: + self.add_door(door)
+ +
[docs] def replace_apertures(self, apertures): + """Replace all sub-faces assigned to this Face with a new list of Apertures.""" + self.remove_sub_faces() + self.add_apertures(apertures)
+ +
[docs] def set_adjacency(self, other_face, tolerance=0.01): + """Set this face adjacent to another and set the other face adjacent to this one. + + Note that this method does not verify whether the other_face geometry is + co-planar or compatible with this one so it is recommended that either the + Face3D.is_centered_adjacent() or the Face3D.is_geometrically_equivalent() + method be used with this face geometry and the other_face geometry + before using this method in order to verify these criteria. + + However, this method will use the proximity of apertures and doors within + the input tolerance to determine which of the sub faces in the other_face + are adjacent to the ones in this face. An exception will be thrown if not + all sub-faces can be matched. + + Args: + other_face: Another Face object to be set adjacent to this one. + tolerance: The minimum distance between the center of two aperture + geometries at which they are considered adjacent. Default: 0.01, + suitable for objects in meters. + + Returns: + A dictionary of adjacency information with the following keys + + - adjacent_apertures - A list of tuples with each tuple containing 2 + objects for Apertures paired in the process of solving adjacency. + + - adjacent_doors - A list of tuples with each tuple containing 2 + objects for Doors paired in the process of solving adjacency. + """ + # check the inputs and the ability of the faces to be adjacent + assert isinstance(other_face, Face), \ + 'Expected honeybee Face. Got {}.'.format(type(other_face)) + + # set the boundary conditions of the faces + self._boundary_condition = boundary_conditions.surface(other_face) + other_face._boundary_condition = boundary_conditions.surface(self) + + adj_info = {'adjacent_apertures': [], 'adjacent_doors': []} + + # set the apertures to be adjacent to one another + assert len(self._apertures) == len(other_face._apertures), \ + 'Number of apertures does not match between {} and {}.'.format( + self.display_name, other_face.display_name) + if len(self._apertures) > 0: + found_adjacencies = 0 + for aper_1 in self._apertures: + for aper_2 in other_face._apertures: + if aper_1.center.distance_to_point(aper_2.center) <= tolerance: + aper_1.set_adjacency(aper_2) + adj_info['adjacent_apertures'].append((aper_1, aper_2)) + found_adjacencies += 1 + break + assert len(self._apertures) == found_adjacencies, \ + 'Not all apertures of {} were found to be adjacent to apertures in {}.' \ + '\nTry increasing the tolerance.'.format( + self.display_name, other_face.display_name) + + # set the doors to be adjacent to one another + assert len(self._doors) == len(other_face._doors), \ + 'Number of doors does not match between {} and {}.'.format( + self.display_name, other_face.display_name) + if len(self._doors) > 0: + found_adjacencies = 0 + for door_1 in self._doors: + for door_2 in other_face._doors: + if door_1.center.distance_to_point(door_2.center) <= tolerance: + door_1.set_adjacency(door_2) + adj_info['adjacent_doors'].append((door_1, door_2)) + found_adjacencies += 1 + break + assert len(self._doors) == found_adjacencies, \ + 'Not all doors of {} were found to be adjacent to doors in {}.' \ + '\nTry increasing the tolerance.'.format( + self.display_name, other_face.display_name) + + return adj_info
+ +
[docs] def rectangularize_apertures( + self, subdivision_distance=None, max_separation=None, + merge_all=False, tolerance=0.01, angle_tolerance=1.0): + """Convert all Apertures on this Face to be rectangular. + + This is useful when exporting to simulation engines that only accept + rectangular window geometry. This method will always result ing Rooms where + all Apertures are rectangular. However, if the subdivision_distance is not + set, some Apertures may extend past the parent Face or may collide with + one another. + + Args: + subdivision_distance: A number for the resolution at which the + non-rectangular Apertures will be subdivided into smaller + rectangular units. Specifying a number here ensures that the + resulting rectangular Apertures do not extend past the parent + Face or collide with one another. If None, all non-rectangular + Apertures will be rectangularized by taking the bounding rectangle + around the Aperture. (Default: None). + max_separation: A number for the maximum distance between non-rectangular + Apertures at which point the Apertures will be merged into a single + rectangular geometry. This is often helpful when there are several + triangular Apertures that together make a rectangle when they are + merged across their frames. In such cases, this max_separation + should be set to a value that is slightly larger than the window frame. + If None, no merging of Apertures will happen before they are + converted to rectangles. (Default: None). + merge_all: Boolean to note whether all apertures should be merged before + they are rectangularized. If False, only non-rectangular apertures + will be merged before rectangularization. Note that this argument + has no effect when the max_separation is None. (Default: False). + tolerance: The maximum difference between point values for them to be + considered equivalent. (Default: 0.01, suitable for objects in meters). + angle_tolerance: The max angle in degrees that the corners of the + rectangle can differ from a right angle before it is not + considered a rectangle. (Default: 1). + + Returns: + True if the Apertures were changed. False if they were unchanged. + """ + # sort the rectangular and non-rectangular apertures + apertures = self._apertures + if len(apertures) == 0: + return False + tol, ang_tol = tolerance, math.radians(angle_tolerance) + rect_aps, non_rect_aps, non_rect_geos = [], [], [] + for aperture in apertures: + try: + clean_geo = aperture.geometry.remove_colinear_vertices(tol) + except AssertionError: # degenerate Aperture to be ignored + continue + if max_separation is None or not merge_all: + if clean_geo.polygon2d.is_rectangle(ang_tol): + rect_aps.append(aperture) + else: + non_rect_aps.append(aperture) + non_rect_geos.append(clean_geo) + else: + non_rect_aps.append(aperture) + non_rect_geos.append(clean_geo) + if not non_rect_geos: # nothing to be rectangularized + return False + + # reset boundary conditions to outdoors so new apertures can be added + if not isinstance(self.boundary_condition, Outdoors): + self.boundary_condition = boundary_conditions.outdoors + for ap in rect_aps: + ap.boundary_condition = boundary_conditions.outdoors + edits_occurred = False + + # try to merge the non-rectangular apertures if a max_separation is specified + ref_plane = self._reference_plane(ang_tol) + if max_separation is not None: + if merge_all or (not merge_all and len(non_rect_geos) > 1): + edits_occurred = True + if max_separation <= tol: # just join the Apertures at the tolerance + non_rect_geos = Face3D.join_coplanar_faces(non_rect_geos, tol) + else: # join the Apertures using the max_separation + # get polygons for the faces that all lie within the same plane + face_polys = [] + for fg in non_rect_geos: + verts2d = tuple(ref_plane.xyz_to_xy(_v) for _v in fg.boundary) + face_polys.append(Polygon2D(verts2d)) + if fg.has_holes: + for hole in fg.holes: + verts2d = tuple(ref_plane.xyz_to_xy(_v) for _v in hole) + face_polys.append(Polygon2D(verts2d)) + # get the joined boundaries around the Polygon2D + joined_bounds = Polygon2D.gap_crossing_boundary( + face_polys, max_separation, tolerance) + # convert the boundary polygons back to Face3D + if len(joined_bounds) == 1: # can be represented with a single Face3D + verts3d = tuple(ref_plane.xy_to_xyz(_v) for _v in joined_bounds[0]) + non_rect_geos = [Face3D(verts3d, plane=ref_plane)] + else: # need to separate holes from distinct Face3Ds + bound_faces = [] + for poly in joined_bounds: + verts3d = tuple(ref_plane.xy_to_xyz(_v) for _v in poly) + bound_faces.append(Face3D(verts3d, plane=ref_plane)) + non_rect_geos = Face3D.merge_faces_to_holes(bound_faces, tolerance) + clean_aps = [] + for ap_geo in non_rect_geos: + try: + clean_aps.append(ap_geo.remove_colinear_vertices(tol)) + except AssertionError: # degenerate Aperture to be ignored + continue + non_rect_geos = clean_aps + + # convert the remaining Aperture geometries to rectangles + if subdivision_distance is None: # just take the bounding rectangle + edits_occurred = True + # get the bounding rectangle around all of the geometries + ap_geos = [] + for ap_geo in non_rect_geos: + if ap_geo.polygon2d.is_rectangle(ang_tol): + ap_geos.append(ap_geo) # catch rectangles found in merging + continue + geo_2d = Polygon2D([ref_plane.xyz_to_xy(v) for v in ap_geo.vertices]) + g_min, g_max = geo_2d.min, geo_2d.max + base, hgt = g_max.x - g_min.x, g_max.y - g_min.y + bound_poly = Polygon2D.from_rectangle(g_min, Vector2D(0, 1), base, hgt) + geo_3d = Face3D([ref_plane.xy_to_xyz(v) for v in bound_poly.vertices]) + ap_geos.append(geo_3d) + non_rect_geos = ap_geos + + # create Aperture objects from all of the merged geometries + if not edits_occurred: + new_aps = non_rect_aps + else: + new_aps = [] + for i, ap_face in enumerate(non_rect_geos): + exist_ap = None + for old_ap in non_rect_aps: + if old_ap.center.is_equivalent(ap_face.center, tolerance): + exist_ap = old_ap + break + if exist_ap is None: # could not be matched; just make a new aperture + new_ap = Aperture('{}_RG{}'.format(self.identifier, i), ap_face) + else: + new_ap = Aperture(exist_ap.identifier, ap_face, + is_operable=exist_ap.is_operable) + new_ap.display_name = '{}_{}'.format(exist_ap.display_name, i) + new_aps.append(new_ap) + + # we can just add the apertures if there's no subdivision going on + if subdivision_distance is None: + # remove any Apertures that are overlapping + all_aps = rect_aps + new_aps + all_aps = self._remove_overlapping_sub_faces(all_aps, tolerance) + self.remove_apertures() + self.add_apertures(all_aps) + return True + + # if distance is provided, subdivide the apertures into strips + new_ap_objs = [] + for ap_obj in new_aps: + ap_geo = ap_obj.geometry + if ap_geo.polygon2d.is_rectangle(ang_tol): + new_ap_objs.append(ap_obj) # catch rectangles found in merging + continue + # create a mesh grid over the Aperture in the reference plane + geo_2d = Polygon2D([ref_plane.xyz_to_xy(v) for v in ap_geo.vertices]) + try: + grid = Mesh2D.from_polygon_grid( + geo_2d, subdivision_distance, subdivision_distance, False) + except AssertionError: # Aperture smaller than resolution; ignore + continue + + # group face by y value. All the rows will be merged together + vertices = grid.vertices + groups = {} + start_y = None + last_y = vertices[grid.faces[0][0]].y + for i, face in enumerate(grid.faces): + min_2d = vertices[face[0]] + for xy in groups: + if abs(min_2d.x - xy[0]) < tolerance and \ + abs(min_2d.y - last_y) < tolerance: + groups[(xy[0], start_y)].append(face) + break + else: + start_y = min_2d.y + groups[(min_2d.x, start_y)] = [face] + last_y = vertices[face[3]].y + + # get the max and min of each group + sorted_groups = [] + for group in groups.values(): + # find min_2d and max_2d for each group + min_2d = vertices[group[0][0]] + max_2d = vertices[group[-1][2]] + sorted_groups.append({'min': min_2d, 'max': max_2d}) + + def _get_last_row(groups, start=0): + """An internal function to return the index for the last row that can be + merged with the start row that is passed to this function. + + This function compares the min and max x and y values for each row to see + if they can be merged into a rectangle. + """ + for count, group in enumerate(groups[start:]): + next_group = groups[count + start + 1] + if abs(group['min'].y - next_group['min'].y) <= tolerance \ + and abs(group['max'].y - next_group['max'].y) <= tolerance \ + and abs(next_group['min'].x - group['max'].x) <= tolerance: + continue + else: + return start + count + + return start + count + 1 + + # merge the rows if they have the same number of grid cells + sorted_groups.sort(key=lambda x: x['min'].x) + merged_groups = [] + start_row = 0 + last_row = -1 + while last_row < len(sorted_groups): + try: + last_row = _get_last_row(sorted_groups, start=start_row) + except IndexError: + merged_groups.append( + { + 'min': sorted_groups[start_row]['min'], + 'max': sorted_groups[len(sorted_groups) - 1]['max'] + } + ) + break + else: + merged_groups.append( + { + 'min': sorted_groups[start_row]['min'], + 'max': sorted_groups[last_row]['max'] + } + ) + if last_row == start_row: + # the row was not grouped with anything else + start_row += 1 + else: + start_row = last_row + 1 + + # convert the groups into rectangular strips + for i, group in enumerate(merged_groups): + min_2d = group['min'] + max_2d = group['max'] + base, hgt = max_2d.x - min_2d.x, max_2d.y - min_2d.y + bound_poly = Polygon2D.from_rectangle(min_2d, Vector2D(0, 1), base, hgt) + geo_3d = Face3D([ref_plane.xy_to_xyz(v) for v in bound_poly.vertices]) + new_ap = Aperture( + '{}_Glz{}'.format(ap_obj.identifier, i), + geo_3d, is_operable=ap_obj.is_operable) + new_ap.display_name = '{}_{}'.format(ap_obj.display_name, i) + new_ap_objs.append(new_ap) + + # replace the apertures with the new ones + self.remove_apertures() + self.add_apertures(rect_aps + new_ap_objs) + return True
+ + def _reference_plane(self, angle_tolerance): + """Get a Plane for this Face geometry derived from the Face3D plane. + + This will be oriented with the plane Y-Axis either aligned with the + World Z or World Y, which is helpful in rectangularization. + + Args: + angle_tolerance: The max angle in radians that Face normal can differ + from the World Z before the Face is treated as being in the + World XY plane. + """ + parent_llc = self.geometry.lower_left_corner + rel_plane = self.geometry.plane + vertical = Vector3D(0, 0, 1) + vert_ang = rel_plane.n.angle(vertical) + if vert_ang <= angle_tolerance or vert_ang >= math.pi - angle_tolerance: + proj_x = Vector3D(1, 0, 0) + else: + proj_y = vertical.project(rel_plane.n) + proj_x = proj_y.rotate(rel_plane.n, math.pi / -2) + + ref_plane = Plane(rel_plane.n, parent_llc, proj_x) + return ref_plane + +
[docs] def offset_aperture_edges(self, offset_distance, tolerance=0.01): + """Offset the edges of all apertures by a certain distance. + + This is useful for translating between interfaces that expect the window + frame to be included within or excluded from the geometry of the Aperture. + + Note that this operation can often create Apertures that collide with + one another or extend past the parent Face. So it may be desirable + to run the fix_invalid_sub_faces after using this method. + + Args: + offset_distance: Distance with which the edges of each Aperture will + be offset from the original geometry. Positive values will + offset the geometry outwards and negative values will offset the + geometries inwards. + tolerance: The minimum difference between point values for them to be + considered the distinct. (Default: 0.01, suitable for objects + in meters). + """ + # convert the apertures to polygons and offset them + new_apertures = [] + prim_pl = self.geometry.plane + for ap in self.apertures: + try: + verts_2d = tuple(prim_pl.xyz_to_xy(pt) for pt in ap.geometry.boundary) + poly = Polygon2D(verts_2d).remove_colinear_vertices(tolerance) + off_poly = poly.offset(-offset_distance, True) + if off_poly is not None: + verts_3d = tuple(prim_pl.xy_to_xyz(pt) for pt in off_poly) + new_ap = ap.duplicate() + new_ap._geometry = Face3D(verts_3d, prim_pl) + new_apertures.append(new_ap) + else: + new_apertures.append(ap) + except AssertionError: # degenerate geometry to ignore + new_apertures.append(ap) + # assign the new apertures + self.remove_apertures() + self.add_apertures(new_apertures)
+ +
[docs] def merge_neighboring_sub_faces(self, merge_distance=0.05, tolerance=0.01): + """Merge neighboring Apertures and/or Doors on this Face together. + + This method is particularly useful for simplifying Apertures in concave + Faces since trying to simplify such Apertures down to a ratio will + produce a triangulated result that is not particularly clean. + + Args: + merge_distance: Distance between Apertures and/or Doors at which point they + will be merged into a single Aperture. When this value is less than + or equal to the tolerance, apertures will only be merged if they + touch one another. (Default: 0.05, suitable for objects in meters). + tolerance: The minimum difference between point values for them to be + considered the distinct. (Default: 0.01, suitable for objects + in meters). + """ + # first, check that there are Apertures to e merged + sub_faces = self.sub_faces + if len(sub_faces) <= 1: # no apertures to be merged + return + + # collect the sub-face geometries as polygons in the face plane + clean_polys, original_objs, original_area = [], [], 0 + prim_pl = self.geometry.plane + for sub_f in sub_faces: + try: + verts_2d = tuple(prim_pl.xyz_to_xy(pt) for pt in sub_f.geometry.boundary) + poly = Polygon2D(verts_2d).remove_colinear_vertices(tolerance) + clean_polys.append(poly) + original_area += poly.area + original_objs.append(sub_f) + except AssertionError: # degenerate geometry to ignore + pass + original_polys = clean_polys[:] + + # join the polygons together + if merge_distance <= tolerance: # only join the polygons that touch one another + clean_polys = Polygon2D.joined_intersected_boundary(clean_polys, tolerance) + else: + clean_polys = Polygon2D.gap_crossing_boundary( + clean_polys, merge_distance, tolerance) + + # assuming that the operations have edited the polygons, create new sub-faces + new_area = sum(p.area for p in clean_polys) + area_diff = abs(original_area - new_area) + if len(clean_polys) != len(original_polys) or area_diff > tolerance: + clean_polys = [poly.remove_colinear_vertices(tolerance) + for poly in clean_polys] + self.remove_sub_faces() + for i, n_poly in enumerate(clean_polys): + new_geo = Face3D([prim_pl.xy_to_xyz(pt) for pt in n_poly], prim_pl) + for o_poly, o_obj in zip(original_polys, original_objs): + if n_poly.is_point_inside_bound_rect(o_poly.center): + orig_obj = o_obj + break + else: # could not be matched with any original object + orig_obj = None + if orig_obj is None: + new_ap = Aperture('{}_{}'.format(self.identifier, i), new_geo) + self.add_aperture(new_ap) + elif isinstance(orig_obj, Aperture): + new_ap = orig_obj.duplicate() + new_ap._geometry = new_geo + self.add_aperture(new_ap) + elif isinstance(orig_obj, Door): + new_door = orig_obj.duplicate() + new_door._geometry = new_geo + self.add_door(new_door)
+ +
[docs] def fix_invalid_sub_faces( + self, trim_with_parent=True, union_overlaps=True, + offset_distance=0.05, tolerance=0.01): + """Fix invalid Apertures and Doors on this face by performing two operations. + + First, sub-faces that extend past their parent Face are trimmed with the + parent and will have their edges offset towards the inside of the Face. + Second, any sub-faces that overlap or touch one another will be unioned + into a single Aperture or Door. + + Args: + trim_with_parent: Boolean to note whether the fixing operation should + check all sub-faces that extend past their parent and trim + them, offsetting them towards the inside of the Face. (Default: True). + union_overlaps: Boolean to note whether the fixing operation should + check all sub-faces that overlap with one another and union any + sub-faces together that overlap. (Default: True). + offset_distance: Distance from the edge of the parent Face that the + sub-faces will be offset to in order to make them valid. This + should be larger than the tolerance. (Default: 0.05, suitable for + objects in meters). + tolerance: The minimum difference between point values for them to be + considered the distinct. (Default: 0.01, suitable for objects + in meters). + """ + # collect the sub-face geometries as polygons in the face plane + clean_polys, original_objs, original_area = [], [], 0 + prim_pl = self.geometry.plane + for sub_f in self.sub_faces: + try: + verts_2d = tuple(prim_pl.xyz_to_xy(pt) for pt in sub_f.geometry.boundary) + poly = Polygon2D(verts_2d).remove_colinear_vertices(tolerance) + clean_polys.append(poly) + original_area += poly.area + original_objs.append(sub_f) + except AssertionError: # degenerate geometry to ignore + pass + original_polys = clean_polys[:] + + # trim objects with the parent polygon if they extend past it + if trim_with_parent: + face_3d = self.geometry + verts2d = tuple(prim_pl.xyz_to_xy(pt) for pt in face_3d.boundary) + parent_poly, parent_holes = Polygon2D(verts2d), None + if face_3d.has_holes: + parent_holes = tuple( + Polygon2D(prim_pl.xyz_to_xy(pt) for pt in hole) + for hole in face_3d.holes + ) + # loop through the polygons and offset them if they are not correctly bounded + new_polygons = [] + for polygon in clean_polys: + if not self._is_sub_polygon(polygon, parent_poly, parent_holes): + # find the boolean intersection of the polygon with the room + sub_face = Face3D([prim_pl.xy_to_xyz(pt) for pt in polygon]) + bool_int = Face3D.coplanar_intersection( + face_3d, sub_face, tolerance, math.radians(1)) + if bool_int is None: # sub-face completely outside parent + continue + # offset the result of the boolean intersection from the edge + parent_edges = face_3d.boundary_segments if face_3d.holes is None \ + else face_3d.boundary_segments + \ + tuple(seg for hole in face_3d.hole_segments for seg in hole) + for new_f in bool_int: + new_pts_2d = [] + for pt in new_f.boundary: + for edge in parent_edges: + close_pt = edge.closest_point(pt) + if pt.distance_to_point(close_pt) < offset_distance: + move_vec = edge.v.rotate(prim_pl.n, math.pi / 2) + move_vec = move_vec.normalize() * offset_distance + pt = pt.move(move_vec) + new_pts_2d.append(prim_pl.xyz_to_xy(pt)) + new_polygons.append(Polygon2D(new_pts_2d)) + else: + new_polygons.append(polygon) + clean_polys = new_polygons + + # union overlaps and merge sub-faces that are touching + if union_overlaps: + grouped_polys = Polygon2D.group_by_overlap(clean_polys, tolerance) + # union any of the polygons that overlap + if not all(len(g) == 1 for g in grouped_polys): + clean_polys = [] + for p_group in grouped_polys: + if len(p_group) == 1: + clean_polys.append(p_group[0]) + else: + union_poly = Polygon2D.boolean_union_all(p_group, tolerance) + for new_poly in union_poly: + clean_polys.append( + new_poly.remove_colinear_vertices(tolerance)) + # join the polygons that touch one another + clean_polys = Polygon2D.joined_intersected_boundary(clean_polys, tolerance) + + # assuming that the operations have edited the polygons, create new sub-faces + new_area = sum(p.area for p in clean_polys) + area_diff = abs(original_area - new_area) + if len(clean_polys) != len(original_polys) or area_diff > tolerance: + self.remove_sub_faces() + for i, n_poly in enumerate(clean_polys): + new_geo = Face3D([prim_pl.xy_to_xyz(pt) for pt in n_poly], prim_pl) + for o_poly, o_obj in zip(original_polys, original_objs): + if n_poly.is_point_inside_bound_rect(o_poly.center): + orig_obj = o_obj + break + else: # could not be matched with any original object + orig_obj = None + if orig_obj is None: + new_ap = Aperture('{}_{}'.format(self.identifier, i), new_geo) + self.add_aperture(new_ap) + elif isinstance(orig_obj, Aperture): + new_ap = orig_obj.duplicate() + new_ap._geometry = new_geo + self.add_aperture(new_ap) + elif isinstance(orig_obj, Door): + new_door = orig_obj.duplicate() + new_door._geometry = new_geo + self.add_door(new_door)
+ +
[docs] def apertures_by_ratio(self, ratio, tolerance=0.01): + """Add apertures to this Face given a ratio of aperture area to face area. + + Note that this method removes any existing apertures and doors on the Face. + This method attempts to generate as few apertures as necessary to meet the ratio. + + Args: + ratio: A number between 0 and 1 (but not perfectly equal to 1) + for the desired ratio between aperture area and face area. + tolerance: The maximum difference between point values for them to be + considered the same. This is used in the event that this face is + concave and an attempt to subdivide the face into a rectangle is + made. It does not affect the ability to produce apertures for + convex Faces. Default: 0.01, suitable for objects in meters. + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + room.faces[1].apertures_by_ratio(0.4) + """ + assert 0 <= ratio < 1, 'Ratio must be between 0 and 1. Got {}'.format(ratio) + self._acceptable_sub_face_check(Aperture) + self.remove_sub_faces() + if ratio == 0: + return + try: + geo = self._geometry.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate face that should not have apertures + return + ap_faces = geo.sub_faces_by_ratio_rectangle(ratio, tolerance) + for i, ap_face in enumerate(ap_faces): + aperture = Aperture('{}_Glz{}'.format(self.identifier, i), ap_face) + self.add_aperture(aperture)
+ +
[docs] def apertures_by_ratio_rectangle(self, ratio, aperture_height, sill_height, + horizontal_separation, vertical_separation=0, + tolerance=0.01): + """Add apertures to this face given a ratio of aperture area to face area. + + Note that this method removes any existing apertures on the Face. + + This function is virtually equivalent to the apertures_by_ratio method but + any rectangular portions of this face will produce customizable rectangular + apertures using the other inputs (aperture_height, sill_height, + horizontal_separation, vertical_separation). + + Args: + ratio: A number between 0 and 0.95 for the ratio between the area of + the apertures and the area of this face. + aperture_height: A number for the target height of the output apertures. + Note that, if the ratio is too large for the height, the ratio will + take precedence and the actual aperture_height will be larger + than this value. + sill_height: A number for the target height above the bottom edge of + the rectangle to start the apertures. Note that, if the + ratio is too large for the height, the ratio will take precedence + and the sill_height will be smaller than this value. + horizontal_separation: A number for the target separation between + individual aperture center lines. If this number is larger than + the parent rectangle base, only one aperture will be produced. + vertical_separation: An optional number to create a single vertical + separation between top and bottom apertures. The default is + 0 for no separation. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. Default: 0.01, suitable for + objects in meters. + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + room.faces[1].apertures_by_ratio_rectangle(0.4, 2, 0.9, 3) + """ + assert 0 <= ratio <= 0.95, \ + 'Ratio must be between 0 and 0.95. Got {}'.format(ratio) + self._acceptable_sub_face_check(Aperture) + self.remove_sub_faces() + if ratio == 0: + return + try: + geo = self._geometry.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate face that should not have apertures + return + ap_faces = geo.sub_faces_by_ratio_sub_rectangle( + ratio, aperture_height, sill_height, horizontal_separation, + vertical_separation, tolerance) + for i, ap_face in enumerate(ap_faces): + aperture = Aperture('{}_Glz{}'.format(self.identifier, i), ap_face) + self.add_aperture(aperture)
+ +
[docs] def apertures_by_ratio_gridded(self, ratio, x_dim, y_dim=None, tolerance=0.01): + """Add apertures to this face given a ratio of aperture area to face area. + + Note that this method removes any existing apertures on the Face. + + Apertures will be arranged in a grid derived from this face's plane. + Because the x_dim and y_dim refer to dimensions within the X and Y + coordinate system of this faces's plane, rotating this plane will + result in rotated grid cells. This is particularly useful for generating + skylights based on a glazing ratio. + + If the x_dim and/or y_dim are too large for this face, this method will + return essentially the same result as the apertures_by_ratio method. + + Args: + ratio: A number between 0 and 1 for the ratio between the area of + the apertures and the area of this face. + x_dim: The x dimension of the grid cells as a number. + y_dim: The y dimension of the grid cells as a number. Default is None, + which will assume the same cell dimension for y as is set for x. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. Default: 0.01, suitable for + objects in meters. + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + room.faces[-1].apertures_by_ratio_gridded(0.05, 3) + """ + assert 0 <= ratio < 1, 'Ratio must be between 0 and 1. Got {}'.format(ratio) + self._acceptable_sub_face_check(Aperture) + self.remove_sub_faces() + if ratio == 0: + return + try: + geo = self._geometry.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate face that should not have apertures + return + ap_faces = geo.sub_faces_by_ratio_gridded(ratio, x_dim, y_dim) + for i, ap_face in enumerate(ap_faces): + aperture = Aperture('{}_Glz{}'.format(self.identifier, i), ap_face) + self.add_aperture(aperture)
+ +
[docs] def apertures_by_width_height_rectangle(self, aperture_height, aperture_width, + sill_height, horizontal_separation, + tolerance=0.01): + """Add repeating apertures to this face given the aperture width and height. + + Note that this method removes any existing apertures on the Face. + + Note that this method will effectively fill any rectangular portions of + this Face with apertures at the specified width, height and separation. + If no rectangular portion of this Face can be identified, no apertures + will be added. + + Args: + aperture_height: A number for the target height of the apertures. + aperture_width: A number for the target width of the apertures. + sill_height: A number for the target height above the bottom edge of + the rectangle to start the apertures. If the aperture_height + is too large for the sill_height to fit within the rectangle, + the aperture_height will take precedence. + horizontal_separation: A number for the target separation between + individual apertures center lines. If this number is larger than + the parent rectangle base, only one aperture will be produced. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. Default: 0.01, suitable for + objects in meters. + + Usage: + + .. code-block:: python + + room = Room.from_box(5.0, 10.0, 3.2, 180) + room.faces[1].apertures_by_width_height_rectangle(1.5, 2, 0.8, 2.5) + """ + assert horizontal_separation > 0, \ + 'horizontal_separation must be above 0. Got {}'.format(horizontal_separation) + if aperture_height <= 0 or aperture_width <= 0: + return + self._acceptable_sub_face_check(Aperture) + self.remove_sub_faces() + try: + geo = self._geometry.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate face that should not have apertures + return + ap_faces = geo.sub_faces_by_dimension_rectangle( + aperture_height, aperture_width, sill_height, horizontal_separation, + tolerance) + for i, ap_face in enumerate(ap_faces): + aperture = Aperture('{}_Glz{}'.format(self.identifier, i), ap_face) + self.add_aperture(aperture)
+ +
[docs] def aperture_by_width_height(self, width, height, sill_height=1, + aperture_identifier=None): + """Add a single rectangular aperture to the center of this Face. + + A rectangular window with the input width and height will always be added + by this method regardless of whether this parent Face contains a recognizable + rectangular portion or not. Furthermore, this method preserves any existing + apertures on the Face. + + While the resulting aperture will always be in the plane of this Face, + this method will not check to ensure that the aperture has all of its + vertices completely within the boundary of this Face or that it does not + intersect with other apertures in the Face. The are_sub_faces_valid() + method can be used afterwards to check this. + + Args: + width: A number for the Aperture width. + height: A number for the Aperture height. + sill_height: A number for the sill height. (Default: 1). + aperture_identifier: Optional string for the aperture identifier. + If None, the default will follow the convention + "[face_identifier]_Glz[count]" where [count] is one more than + the current number of apertures in the face. + + Returns: + The new Aperture object that has been generated. + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + room[1].aperture_by_width_height(2, 2, .7) # aperture in front + room[2].aperture_by_width_height(4, 1.5, .5) # aperture on right + room[2].aperture_by_width_height(4, 0.5, 2.2) # aperture on right + """ + # Perform checks + if width <= 0 or height <= 0: + return + self._acceptable_sub_face_check(Aperture) + # Generate the aperture geometry + origin = self._geometry.lower_left_counter_clockwise_vertices[0] + face_plane = Plane(self._geometry.plane.n, origin) + if face_plane.y.z < 0: + face_plane = face_plane.rotate(face_plane.n, math.pi, face_plane.o) + center2d = face_plane.xyz_to_xy(self._geometry.center) + x_dist = width / 2 + lower_left = Point2D(center2d.x - x_dist, sill_height) + lower_right = Point2D(center2d.x + x_dist, sill_height) + upper_right = Point2D(center2d.x + x_dist, sill_height + height) + upper_left = Point2D(center2d.x - x_dist, sill_height + height) + ap_verts2d = (lower_left, lower_right, upper_right, upper_left) + ap_verts3d = tuple(face_plane.xy_to_xyz(pt) for pt in ap_verts2d) + ap_face = Face3D(ap_verts3d, self._geometry.plane) + if self.normal.angle(ap_face.normal) > math.pi / 2: # reversed normal + ap_face = ap_face.flip() + + # Create the aperture and add it to this Face + identifier = aperture_identifier or \ + '{}_Glz{}'.format(self.identifier, len(self.apertures)) + aperture = Aperture(identifier, ap_face) + self.add_aperture(aperture) + return aperture
+ +
[docs] def overhang(self, depth, angle=0, indoor=False, tolerance=0.01, base_name=None): + """Add an overhang to this Face. + + Args: + depth: A number for the overhang depth. + angle: A number for the for an angle to rotate the overhang in degrees. + Positive numbers indicate a downward rotation while negative numbers + indicate an upward rotation. Default is 0 for no rotation. + indoor: Boolean for whether the overhang should be generated facing the + opposite direction of the aperture normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to not add the overhang if it has a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + base_name: Optional base identifier for the shade objects. If None, + the default is InOverhang or OutOverhang depending on whether + indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + if base_name is None: + base_name = 'InOverhang' if indoor else 'OutOverhang' + return self.louvers_by_count(1, depth, angle=angle, indoor=indoor, + tolerance=tolerance, base_name=base_name)
+ +
[docs] def louvers_by_count(self, louver_count, depth, offset=0, angle=0, + contour_vector=Vector2D(0, 1), flip_start_side=False, + indoor=False, tolerance=0.01, base_name=None): + """Add a series of louvered Shade objects over this Face. + + Args: + louver_count: A positive integer for the number of louvers to generate. + depth: A number for the depth to extrude the louvers. + offset: A number for the distance to louvers from this Face. + Default is 0 for no offset. + angle: A number for the for an angle to rotate the louvers in degrees. + Positive numbers indicate a downward rotation while negative numbers + indicate an upward rotation. Default is 0 for no rotation. + contour_vector: A Vector2D for the direction along which contours + are generated. This 2D vector will be interpreted into a 3D vector + within the plane of this Face. (0, 1) will usually generate + horizontal contours in 3D space, (1, 0) will generate vertical + contours, and (1, 1) will generate diagonal contours. Default: (0, 1). + flip_start_side: Boolean to note whether the side the louvers start from + should be flipped. Default is False to have louvers on top or right. + Setting to True will start contours on the bottom or left. + indoor: Boolean for whether louvers should be generated facing the + opposite direction of the Face normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to remove any louvers with a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + base_name: Optional base identifier for the shade objects. If None, + the default is InShd or OutShd depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + assert louver_count > 0, 'louver_count must be greater than 0.' + angle = math.radians(angle) + louvers = [] + face_geo = self.geometry if indoor is False else self.geometry.flip() + if base_name is None: + shd_name_base = '{}_InShd{}' if indoor else '{}_OutShd{}' + else: + shd_name_base = '{}_' + str(base_name) + '{}' + shade_faces = face_geo.contour_fins_by_number( + louver_count, depth, offset, angle, + contour_vector, flip_start_side, tolerance) + for i, shade_geo in enumerate(shade_faces): + louvers.append(Shade(shd_name_base.format(self.identifier, i), shade_geo)) + if indoor: + self.add_indoor_shades(louvers) + else: + self.add_outdoor_shades(louvers) + return louvers
+ +
[docs] def louvers_by_distance_between( + self, distance, depth, offset=0, angle=0, contour_vector=Vector2D(0, 1), + flip_start_side=False, indoor=False, tolerance=0.01, max_count=None, + base_name=None): + """Add a series of louvered Shade objects over this Face. + + Args: + distance: A number for the approximate distance between each louver. + depth: A number for the depth to extrude the louvers. + offset: A number for the distance to louvers from this Face. + Default is 0 for no offset. + angle: A number for the for an angle to rotate the louvers in degrees. + Positive numbers indicate a downward rotation while negative numbers + indicate an upward rotation. Default is 0 for no rotation. + contour_vector: A Vector2D for the direction along which contours + are generated. This 2D vector will be interpreted into a 3D vector + within the plane of this Face. (0, 1) will usually generate + horizontal contours in 3D space, (1, 0) will generate vertical + contours, and (1, 1) will generate diagonal contours. Default: (0, 1). + flip_start_side: Boolean to note whether the side the louvers start from + should be flipped. Default is False to have contours on top or right. + Setting to True will start contours on the bottom or left. + indoor: Boolean for whether louvers should be generated facing the + opposite direction of the Face normal (typically meaning + indoor geometry). Default: False. + tolerance: An optional value to remove any louvers with a length less + than the tolerance. Default: 0.01, suitable for objects in meters. + max_count: Optional integer to set the maximum number of louvers that + will be generated. If None, louvers will cover the entire face. + base_name: Optional base identifier for the shade objects. If None, the + default is InShd or OutShd depending on whether indoor is True. + + Returns: + A list of the new Shade objects that have been generated. + """ + # set defaults + angle = math.radians(angle) + face_geo = self.geometry if indoor is False else self.geometry.flip() + if base_name is None: + shd_name_base = '{}_InShd{}' if indoor else '{}_OutShd{}' + else: + shd_name_base = '{}_' + str(base_name) + '{}' + + # generate shade geometries + shade_faces = face_geo.contour_fins_by_distance_between( + distance, depth, offset, angle, contour_vector, flip_start_side, tolerance) + if max_count: + try: + shade_faces = shade_faces[:max_count] + except IndexError: # fewer shades were generated than the max count + pass + + # create the shade objects + louvers = [] + for i, shade_geo in enumerate(shade_faces): + louvers.append(Shade(shd_name_base.format(self.identifier, i), shade_geo)) + if indoor: + self.add_indoor_shades(louvers) + else: + self.add_outdoor_shades(louvers) + return louvers
+ +
[docs] def move(self, moving_vec): + """Move this Face along a vector. + + Args: + moving_vec: A ladybug_geometry Vector3D with the direction and distance + to move the face. + """ + self._geometry = self.geometry.move(moving_vec) + for ap in self._apertures: + ap.move(moving_vec) + for dr in self._doors: + dr.move(moving_vec) + self.move_shades(moving_vec) + self.properties.move(moving_vec) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Face by a certain angle around an axis and origin. + + Args: + axis: A ladybug_geometry Vector3D axis representing the axis of rotation. + angle: An angle for rotation in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate(axis, math.radians(angle), origin) + for ap in self._apertures: + ap.rotate(axis, angle, origin) + for dr in self._doors: + dr.rotate(axis, angle, origin) + self.rotate_shades(axis, angle, origin) + self.properties.rotate(axis, angle, origin) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def rotate_xy(self, angle, origin): + """Rotate this Face counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate_xy(math.radians(angle), origin) + for ap in self._apertures: + ap.rotate_xy(angle, origin) + for dr in self._doors: + dr.rotate_xy(angle, origin) + self.rotate_xy_shades(angle, origin) + self.properties.rotate_xy(angle, origin) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def reflect(self, plane): + """Reflect this Face across a plane. + + Args: + plane: A ladybug_geometry Plane across which the object will + be reflected. + """ + self._geometry = self.geometry.reflect(plane.n, plane.o) + for ap in self._apertures: + ap.reflect(plane) + for dr in self._doors: + dr.reflect(plane) + self.reflect_shades(plane) + self.properties.reflect(plane) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def scale(self, factor, origin=None): + """Scale this Face by a factor from an origin point. + + Args: + factor: A number representing how much the object should be scaled. + origin: A ladybug_geometry Point3D representing the origin from which + to scale. If None, it will be scaled from the World origin (0, 0, 0). + """ + self._geometry = self.geometry.scale(factor, origin) + for ap in self._apertures: + ap.scale(factor, origin) + for dr in self._doors: + dr.scale(factor, origin) + self.scale_shades(factor, origin) + self.properties.scale(factor, origin) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def remove_colinear_vertices(self, tolerance=0.01): + """Remove all colinear and duplicate vertices from this object's geometry. + + Note that this does not affect any assigned Apertures, Doors or Shades. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + """ + try: + self._geometry = self.geometry.remove_colinear_vertices(tolerance) + except AssertionError as e: # usually a sliver face of some kind + raise ValueError( + 'Face "{}" is invalid with dimensions less than the ' + 'tolerance.\n{}'.format(self.full_id, e)) + self._punched_geometry = None # reset so that it can be re-computed
+ +
[docs] def remove_degenerate_sub_faces(self, tolerance=0.01): + """Remove colinear vertices from sub-faces and eliminate degenerate ones. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + """ + for i, ap in enumerate(self._apertures): + try: + ap.remove_colinear_vertices(tolerance) + except ValueError: + self._apertures.pop(i) + for i, dr in enumerate(self._doors): + try: + dr.remove_colinear_vertices(tolerance) + except ValueError: + self._apertures.pop(i)
+ +
[docs] def is_geo_equivalent(self, face, tolerance=0.01): + """Get a boolean for whether this object is geometrically equivalent to another. + + This will also check all child Apertures and Doors for equivalency but not + assigned shades. + + Args: + face: Another Face for which geometric equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered geometrically equivalent. + + Returns: + True if geometrically equivalent. False if not geometrically equivalent. + """ + meta_1 = (self.display_name, self.type, self.boundary_condition) + meta_2 = (face.display_name, face.type, face.boundary_condition) + if meta_1 != meta_2: + return False + if abs(self.area - face.area) > tolerance * self.area: + return False + if not self.geometry.is_centered_adjacent(face.geometry, tolerance): + return False + if len(self._apertures) != len(face._apertures): + return False + if len(self._doors) != len(face._doors): + return False + for ap1, ap2 in zip(self._apertures, face._apertures): + if not ap1.is_geo_equivalent(ap2, tolerance): + return False + for dr1, dr2 in zip(self._doors, face._doors): + if not dr1.is_geo_equivalent(dr2, tolerance): + return False + if not self._are_shades_equivalent(face, tolerance): + return False + return True
+ +
[docs] def check_sub_faces_valid(self, tolerance=0.01, angle_tolerance=1, + raise_exception=True, detailed=False): + """Check that sub-faces are co-planar with this Face within the Face boundary. + + Note this does not check the planarity of the sub-faces themselves, whether + they self-intersect, or whether they have a non-zero area. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered coplanar. + Default: 1 degree. + raise_exception: Boolean to note whether a ValueError should be raised + if an sub-face is not valid. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with dictionaries if detailed is True. + """ + detailed = False if raise_exception else detailed + ap = self.check_apertures_valid(tolerance, angle_tolerance, False, detailed) + dr = self.check_doors_valid(tolerance, angle_tolerance, False, detailed) + full_msgs = ap + dr if detailed else [m for m in (ap, dr) if m != ''] + if raise_exception and len(full_msgs) != 0: + raise ValueError('\n'.join(full_msgs)) + return full_msgs if detailed else '\n'.join(full_msgs)
+ +
[docs] def check_apertures_valid(self, tolerance=0.01, angle_tolerance=1, + raise_exception=True, detailed=False): + """Check that apertures are co-planar with this Face within the Face boundary. + + Note this does not check the planarity of the apertures themselves, whether + they self-intersect, or whether they have a non-zero area. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered coplanar. + Default: 1 degree. + raise_exception: Boolean to note whether a ValueError should be raised + if an aperture is not valid. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with dictionaries if detailed is True. + """ + detailed = False if raise_exception else detailed + angle_tolerance = math.radians(angle_tolerance) + msgs = [] + for ap in self._apertures: + if not self.geometry.is_sub_face(ap.geometry, tolerance, angle_tolerance): + msg = 'Aperture "{}" is not coplanar or fully bounded by its parent ' \ + 'Face "{}".'.format(ap.full_id, self.full_id) + msg = self._validation_message_child( + msg, ap, detailed, '000104', error_type='Invalid Sub-Face Geometry') + msgs.append(msg) + full_msg = msgs if detailed else '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_doors_valid(self, tolerance=0.01, angle_tolerance=1, + raise_exception=True, detailed=False): + """Check that doors are co-planar with this Face within the Face boundary. + + Note this does not check the planarity of the doors themselves, whether + they self-intersect, or whether they have a non-zero area. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered coplanar. + Default: 1 degree. + raise_exception: Boolean to note whether a ValueError should be raised + if an door is not valid. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with dictionaries if detailed is True. + """ + detailed = False if raise_exception else detailed + angle_tolerance = math.radians(angle_tolerance) + msgs = [] + for dr in self._doors: + if not self.geometry.is_sub_face(dr.geometry, tolerance, angle_tolerance): + msg = 'Door "{}" is not coplanar or fully bounded by its parent ' \ + 'Face "{}".'.format(dr.full_id, self.full_id) + msg = self._validation_message_child( + msg, dr, detailed, '000104', error_type='Invalid Sub-Face Geometry') + msgs.append(msg) + full_msg = msgs if detailed else '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_sub_faces_overlapping( + self, tolerance=0.01, raise_exception=True, detailed=False): + """Check that this Face's sub-faces do not overlap with one another. + + Args: + tolerance: The minimum distance that two sub-faces must overlap in order + for them to be considered overlapping and invalid. (Default: 0.01, + suitable for objects in meters). + raise_exception: Boolean to note whether a ValueError should be raised + if a sub-faces overlap with one another. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with dictionaries if detailed is True. + """ + sub_faces = self.sub_faces + if len(sub_faces) == 0: + return [] if detailed else '' + sf_groups = self._group_sub_faces_by_overlap(sub_faces, tolerance) + if not all(len(g) == 1 for g in sf_groups): + base_msg = 'Face "{}" contains Apertures and/or ' \ + 'Doors that overlap with each other.'.format(self.full_id) + if raise_exception: + raise ValueError(base_msg) + if not detailed: # just give a message about the Face if not detailed + return base_msg + all_overlaps = [] + for sf_group in sf_groups: + if len(sf_group) != 1: + det_msg = 'The following sub-faces overlap with one another:' \ + '\n{}'.format('\n'.join([sf.full_id for sf in sf_group])) + msg = '{}\n{}'.format(base_msg, det_msg) + err_obj = self._validation_message_child( + msg, sf_group[0], detailed, '000105', + error_type='Overlapping Sub-Face Geometry') + err_obj['element_type'] = 'SubFace' + for ov_obj in sf_group[1:]: + err_obj['element_id'].append(ov_obj.identifier) + err_obj['element_name'].append(ov_obj.display_name) + err_obj['parents'].append(err_obj['parents'][0]) + all_overlaps.append(err_obj) + return all_overlaps + return [] if detailed else ''
+ +
[docs] def check_upside_down(self, angle_tolerance=1, raise_exception=True, detailed=False): + """Check whether the face is pointing in the correct direction for the face type. + + This method will only report Floors that are pointing upwards or RoofCeilings + that are pointed downwards. These cases are likely modeling errors and are in + danger of having their vertices flipped by EnergyPlus, causing them to + not see the sun. + + Args: + angle_tolerance: The max angle in degrees that the Face normal can + differ from up or down before it is considered a case of a downward + pointing RoofCeiling or upward pointing Floor. Default: 1 degree. + raise_exception: Boolean to note whether an ValueError should be + raised if the Face is an an upward pointing Floor or a downward + pointing RoofCeiling. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + msg = None + if isinstance(self.type, Floor) and self.altitude > 90 - angle_tolerance: + msg = 'Face "{}" is an upward-pointing Floor, which should be ' \ + 'changed to a RoofCeiling.'.format(self.full_id) + elif isinstance(self.type, RoofCeiling) and self.altitude < angle_tolerance - 90: + msg = 'Face "{}" is an downward-pointing RoofCeiling, which should be ' \ + 'changed to a Floor.'.format(self.full_id) + if msg: + full_msg = self._validation_message( + msg, raise_exception, detailed, '000109', + error_type='Upside Down Face') + return full_msg + return [] if detailed else ''
+ +
[docs] def check_planar(self, tolerance=0.01, raise_exception=True, detailed=False): + """Check whether all of the Face's vertices lie within the same plane. + + Args: + tolerance: The minimum distance between a given vertex and a the + object's plane at which the vertex is said to lie in the plane. + Default: 0.01, suitable for objects in meters. + raise_exception: Boolean to note whether an ValueError should be + raised if a vertex does not lie within the object's plane. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + try: + self.geometry.check_planar(tolerance, raise_exception=True) + except ValueError as e: + msg = 'Face "{}" is not planar.\n{}'.format(self.full_id, e) + full_msg = self._validation_message( + msg, raise_exception, detailed, '000101', + error_type='Non-Planar Geometry') + if detailed: # add the out-of-plane points to helper_geometry + help_pts = [ + p.to_dict() for p in self.geometry.non_planar_vertices(tolerance) + ] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def check_self_intersecting(self, tolerance=0.01, raise_exception=True, + detailed=False): + """Check whether the edges of the Face intersect one another (like a bowtie). + + Note that objects that have duplicate vertices will not be considered + self-intersecting and are valid in honeybee. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + raise_exception: If True, a ValueError will be raised if the object + intersects with itself. Default: True. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + if self.geometry.is_self_intersecting: + msg = 'Face "{}" has self-intersecting edges.'.format(self.full_id) + try: # see if it is self-intersecting because of a duplicate vertex + new_geo = self.geometry.remove_duplicate_vertices(tolerance) + if not new_geo.is_self_intersecting: + return [] if detailed else '' # valid with removed dup vertex + except AssertionError: + pass # degenerate face; treat it as self-intersecting + full_msg = self._validation_message( + msg, raise_exception, detailed, '000102', + error_type='Self-Intersecting Geometry') + if detailed: # add the self-intersection points to helper_geometry + help_pts = [p.to_dict() for p in self.geometry.self_intersection_points] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def display_dict(self): + """Get a list of DisplayFace3D dictionaries for visualizing the object.""" + base = [self._display_face(self.punched_geometry, self.type_color)] + for ap in self._apertures: + base.extend(ap.display_dict()) + for dr in self._doors: + base.extend(dr.display_dict()) + for shd in self.shades: + base.extend(shd.display_dict()) + return base
+ + @property + def to(self): + """Face writer object. + + Use this method to access Writer class to write the face in other formats. + + Usage: + + .. code-block:: python + + face.to.idf(face) -> idf string. + face.to.radiance(face) -> Radiance string. + """ + return writer + +
[docs] def to_dict(self, abridged=False, included_prop=None, include_plane=True): + """Return Face as a dictionary. + + Args: + abridged: Boolean to note whether the extension properties of the + object (ie. materials, constructions) should be included in detail + (False) or just referenced by identifier (True). (Default: False). + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + include_plane: Boolean to note wether the plane of the Face3D should be + included in the output. This can preserve the orientation of the + X/Y axes of the plane but is not required and can be removed to + keep the dictionary smaller. (Default: True). + """ + base = {'type': 'Face'} + base['identifier'] = self.identifier + base['display_name'] = self.display_name + base['properties'] = self.properties.to_dict(abridged, included_prop) + enforce_upper_left = True if 'energy' in base['properties'] else False + base['geometry'] = self._geometry.to_dict(include_plane, enforce_upper_left) + + base['face_type'] = self.type.name + if isinstance(self.boundary_condition, Outdoors) and \ + 'energy' in base['properties']: + base['boundary_condition'] = self.boundary_condition.to_dict(full=True) + else: + base['boundary_condition'] = self.boundary_condition.to_dict() + + if self._apertures != []: + base['apertures'] = [ap.to_dict(abridged, included_prop, include_plane) + for ap in self._apertures] + if self._doors != []: + base['doors'] = [dr.to_dict(abridged, included_prop, include_plane) + for dr in self._doors] + self._add_shades_to_dict(base, abridged, included_prop, include_plane) + if self.user_data is not None: + base['user_data'] = self.user_data + return base
+ + def _acceptable_sub_face_check(self, sub_face_type=Aperture): + """Check whether the Face can accept sub-faces and raise an exception if not.""" + assert isinstance(self.boundary_condition, Outdoors), \ + '{} cannot be added to Face "{}" with a {} boundary condition.'.format( + sub_face_type.__name__, self.full_id, self.boundary_condition) + assert not isinstance(self.type, AirBoundary), \ + '{} cannot be added to AirBoundary Face "{}".'.format( + sub_face_type.__name__, self.full_id) + + @staticmethod + def _remove_overlapping_sub_faces(sub_faces, tolerance): + """Get a list of Apertures and/or Doors with no overlaps. + + Args: + sub_faces: A list of Apertures or Doors to be checked for overlapping. + tolerance: The minimum distance from the edge of a neighboring Face3D + at which a point is considered to overlap with that Face3D. + + Returns: + A list of the input sub_faces with smaller overlapping geometries removed. + """ + # group the sub-faces according to the overlaps with one another + grouped_sfs = Face._group_sub_faces_by_overlap(sub_faces, tolerance) + # build a list of sub-faces without any overlaps + clean_sub_faces = [] + for sf_group in grouped_sfs: + if len(sf_group) == 1: + clean_sub_faces.append(sf_group[0]) + else: # take the subface with the largest area + sf_group.sort(key=lambda x: x.area, reverse=True) + clean_sub_faces.append(sf_group[0]) + return clean_sub_faces + + @staticmethod + def _group_sub_faces_by_overlap(sub_faces, tolerance): + """Group a Apertures and/or Doors depending on whether they overlap one another. + + Args: + sub_faces: A list of Apertures or Doors to be checked for overlapping. + tolerance: The minimum distance from the edge of a neighboring Face3D + at which a point is considered to overlap with that Face3D. + + Returns: + A list of lists where each sub-list represents a group of Apertures and/or + Doors that overlap with one another. + """ + # sort the sub-faces by area + sub_faces = list(sorted(sub_faces, key=lambda x: x.area, reverse=True)) + # create polygons for all of the faces + r_plane = sub_faces[0].geometry.plane + polygons = [Polygon2D([r_plane.xyz_to_xy(pt) for pt in face.vertices]) + for face in sub_faces] + # loop through the polygons and check to see if it overlaps with the others + grouped_polys, grouped_sfs = [[polygons[0]]], [[sub_faces[0]]] + for poly, face in zip(polygons[1:], sub_faces[1:]): + group_found = False + for poly_group, face_group in zip(grouped_polys, grouped_sfs): + for oth_poly in poly_group: + if poly.polygon_relationship(oth_poly, tolerance) >= 0: + poly_group.append(poly) + face_group.append(face) + group_found = True + break + if group_found: + break + if not group_found: # the polygon does not overlap with any of the others + grouped_polys.append([poly]) # make a new group for the polygon + grouped_sfs.append([face]) # make a new group for the face + return grouped_sfs + + @staticmethod + def _is_sub_polygon(sub_poly, parent_poly, parent_holes=None): + """Check if a sub-polygon is valid for a given assumed parent polygon. + + Args: + sub_poly: A sub-Polygon2D for which sub-face equivalency will be tested. + parent_poly: A parent Polygon2D. + parent_holes: An optional list of Polygon2D for any holes that may + exist in the parent polygon. (Default: None). + """ + if parent_holes is None: + return parent_poly.is_polygon_inside(sub_poly) + else: + if not parent_poly.is_polygon_inside(sub_poly): + return False + for hole_poly in parent_holes: + if not hole_poly.is_polygon_outside(sub_poly): + return False + return True + + def __copy__(self): + new_f = Face(self.identifier, self.geometry, self.type, self.boundary_condition) + new_f._display_name = self._display_name + new_f._user_data = None if self.user_data is None else self.user_data.copy() + new_f._apertures = [ap.duplicate() for ap in self._apertures] + new_f._doors = [dr.duplicate() for dr in self._doors] + for ap in new_f._apertures: + ap._parent = new_f + for dr in new_f._doors: + dr._parent = new_f + self._duplicate_child_shades(new_f) + new_f._punched_geometry = self._punched_geometry + new_f._properties._duplicate_extension_attr(self._properties) + return new_f + + def __repr__(self): + return 'Face: %s' % self.display_name
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/facetype.html b/docs/_modules/honeybee/facetype.html new file mode 100644 index 00000000..10a8e78b --- /dev/null +++ b/docs/_modules/honeybee/facetype.html @@ -0,0 +1,1275 @@ + + + + + + + honeybee.facetype — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.facetype

+"""Face Types."""
+from ladybug_geometry.geometry3d.pointvector import Vector3D
+import re
+import math
+
+
+class _FaceType(object):
+    __slots__ = ()
+
+    def __init__(self):
+        pass
+
+    @property
+    def name(self):
+        return self.__class__.__name__
+
+    def ToString(self):
+        return self.__repr__()
+
+    def __eq__(self, other):
+        return self.__class__ == other.__class__
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __repr__(self):
+        return self.name
+
+
+
[docs]class Wall(_FaceType): + """Type for walls.""" + __slots__ = () + pass
+ + +
[docs]class RoofCeiling(_FaceType): + """Type for roofs and ceilings.""" + __slots__ = () + pass
+ + +
[docs]class Floor(_FaceType): + """Type for floors.""" + __slots__ = () + pass
+ + +
[docs]class AirBoundary(_FaceType): + """Type for air boundaries (aka. virtual partitions) between Rooms.""" + __slots__ = () + pass
+ + +class _FaceTypes(object): + """Face types.""" + + def __init__(self): + self._wall = Wall() + self._roof_ceiling = RoofCeiling() + self._floor = Floor() + self._air_boundary = AirBoundary() + self._type_name_dict = None + + @property + def wall(self): + return self._wall + + @property + def roof_ceiling(self): + return self._roof_ceiling + + @property + def floor(self): + return self._floor + + @property + def air_boundary(self): + return self._air_boundary + + def by_name(self, face_type_name): + """Get a Face Type instance from its name. + + This method will correct for capitalization as well as the presence of + spaces and underscores. + + Args: + face_type_name: A text string for the face type (eg. "Wall"). + """ + if self._type_name_dict is None: + self._build_type_name_dict() + try: + return self._type_name_dict[re.sub(r'[\s_]', '', face_type_name.lower())] + except KeyError: + raise ValueError( + '"{}" is not a valid face type name.\nChoose from the following' + ': {}'.format(face_type_name, list(self._type_name_dict.keys()))) + + def _build_type_name_dict(self): + """Build a dictionary that can be used to lookup face types by name.""" + attr = [atr for atr in dir(self) if not atr.startswith('_')] + clean_attr = [re.sub(r'[\s_]', '', atr.lower()) for atr in attr] + self._type_name_dict = {} + for atr_name, atr in zip(clean_attr, attr): + try: + full_attr = getattr(self, '_' + atr) + self._type_name_dict[atr_name] = full_attr + except AttributeError: + pass # callable method that has no static default object + + def __contains__(self, value): + return isinstance(value, _FaceType) + + def __repr__(self): + attr = [atr for atr in dir(self) if not atr.startswith('_') and atr != 'by_name'] + return 'Face Types:\n{}'.format('\n'.join(attr)) + + +face_types = _FaceTypes() + + +
[docs]def get_type_from_normal(normal_vector, roof_angle=60, floor_angle=130): + """Return face type based on the angle between Z axis and normal vector. + + Angles between 0 and roof_angle will be set to roof_ceiling. + Angles between roof_angle and floor_angle will be set to wall. + Angles larger than floor angle will be set to floor. + + Args: + normal_vector: Normal vector as a ladybug_geometry Vector3D. + roof_angle: A number between 0 and 90 to set the angle from the horizontal + plane below which faces will be considered roofs instead of + walls. 90 indicates that all vertical faces are roofs and 0 + indicates that all horizontal faces are walls. (Default: 60, + recommended by the ASHRAE 90.1 standard). + floor_angle: A number between 90 and 180 to set the angle from the horizontal + plane above which faces will be considered floors instead of + walls. 180 indicates that all vertical faces are floors and 0 + indicates that all horizontal faces are walls. (Default: 130, + recommended by the ASHRAE 90.1 standard). + + Returns: + Face type instance. + """ + z_axis = Vector3D(0, 0, 1) + angle = math.degrees(z_axis.angle(normal_vector)) + if angle < roof_angle: + return face_types.roof_ceiling + elif roof_angle <= angle < floor_angle: + return face_types.wall + else: + return face_types.floor + + return face_types.wall
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/logutil.html b/docs/_modules/honeybee/logutil.html new file mode 100644 index 00000000..2e95a1bc --- /dev/null +++ b/docs/_modules/honeybee/logutil.html @@ -0,0 +1,1201 @@ + + + + + + + honeybee.logutil — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.logutil

+import logging
+from logging.handlers import TimedRotatingFileHandler
+import os
+import tempfile
+
+
+# This is copied from logging module since python 2 doesn't have it under the same name.
+CRITICAL = 50
+FATAL = CRITICAL
+ERROR = 40
+WARNING = 30
+WARN = WARNING
+INFO = 20
+DEBUG = 10
+NOTSET = 0
+
+_name_to_level = {
+    'CRITICAL': CRITICAL,
+    'FATAL': FATAL,
+    'ERROR': ERROR,
+    'WARN': WARNING,
+    'WARNING': WARNING,
+    'INFO': INFO,
+    'DEBUG': DEBUG,
+    'NOTSET': NOTSET,
+}
+
+
+def _get_log_folder():
+    home_folder = os.getenv('HOME') or os.path.expanduser('~')
+    if not os.access(home_folder, os.W_OK):
+        home_folder = tempfile.gettempdir()
+    log_folder = os.path.join(home_folder, '.honeybee')
+    if not os.path.isdir(log_folder):
+        try:
+            os.mkdir(log_folder)
+        except OSError as e:
+            if e.errno != 17:  # avoid race conditions between multiple tasks
+                raise OSError('Failed to create log folder: %s\n%s' % (log_folder, e))
+    return log_folder
+
+
+def _get_log_level(level):
+    level = _name_to_level.get(level)
+    return level or logging.INFO
+
+
+
[docs]def get_logger(name, filename='honeybee.log', file_log_level='DEBUG', + console_log_level='WARNING'): + """Get a logger to be used for each module. + + Args: + name: Logger name. The good practice is to set it to __init__ from inside each + modules. + filename: Logger filename.Setting filename to None will remove the file handler + (Default: honeybee.log). + file_log_level: Log level for file handler as a string (Default: DEBUG). + console_log_level: Log level for stream handler as a string (Default: WARNING). + """ + logger = logging.getLogger(name) + + # create a file handler to log debug and higher level logs + if filename: + log_file = os.path.join(_get_log_folder(), filename) + file_handler = TimedRotatingFileHandler(log_file, when='midnight') + file_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(file_format) + file_handler.setLevel(_get_log_level(file_log_level)) + logger.addHandler(file_handler) + + # create a console handler that only prints out errors and warnings + stream_handler = logging.StreamHandler() + stream_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s') + stream_handler.setFormatter(stream_format) + stream_handler.setLevel(_get_log_level(console_log_level)) + + logger.addHandler(stream_handler) + + return logger
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/model.html b/docs/_modules/honeybee/model.html new file mode 100644 index 00000000..5f7d4d53 --- /dev/null +++ b/docs/_modules/honeybee/model.html @@ -0,0 +1,4499 @@ + + + + + + + honeybee.model — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.model

+# coding: utf-8
+"""Honeybee Model."""
+from __future__ import division
+import os
+import sys
+import io
+import re
+import json
+import math
+import uuid
+try:  # check if we are in IronPython
+    import cPickle as pickle
+except ImportError:  # wea are in cPython
+    import pickle
+
+from ladybug_geometry.geometry3d import Plane, Face3D, Mesh3D
+from ladybug_geometry.interop.stl import STL
+
+from ._base import _Base
+from .units import conversion_factor_to_meters, UNITS, UNITS_TOLERANCES
+from .checkdup import check_duplicate_identifiers, check_duplicate_identifiers_parent
+from .properties import ModelProperties
+from .room import Room
+from .face import Face
+from .shade import Shade
+from .aperture import Aperture
+from .door import Door
+from .shademesh import ShadeMesh
+from .typing import float_positive, invalid_dict_error, clean_string, \
+    clean_and_number_string
+from .config import folders
+from .boundarycondition import Outdoors, Surface
+from .facetype import AirBoundary, Wall, Floor, RoofCeiling, face_types
+import honeybee.writer.model as writer
+from honeybee.boundarycondition import boundary_conditions as bcs
+try:
+    ad_bc = bcs.adiabatic
+except AttributeError:  # honeybee_energy is not loaded and adiabatic does not exist
+    ad_bc = None
+
+
+
[docs]class Model(_Base): + """A collection of Rooms, Faces, Shades, Apertures, and Doors representing a model. + + Args: + identifier: Text string for a unique Model ID. Must be < 100 characters and + not contain any spaces or special characters. + rooms: A list of Room objects in the model. + orphaned_faces: A list of the Face objects in the model that lack + a parent Room. Note that orphaned Faces are translated to sun-blocking + shade objects in energy simulation. + orphaned_shades: A list of the Shade objects in the model that lack + a parent. + orphaned_apertures: A list of the Aperture objects in the model that lack + a parent Face. Note that orphaned Apertures are translated to sun-blocking + shade objects in energy simulation. + orphaned_doors: A list of the Door objects in the model that lack + a parent Face. Note that orphaned Doors are translated to sun-blocking + shade objects in energy simulation. + shade_meshes: A list of the ShadeMesh objects in the model. + units: Text for the units system in which the model geometry + exists. Default: 'Meters'. Choose from the following: + + * Meters + * Millimeters + * Feet + * Inches + * Centimeters + + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. Zero indicates that no tolerance + checks should be performed. None indicates that the tolerance will be + set based on the units above, with the tolerance consistently being + between 1 cm and 1 mm (roughly the tolerance implicit in the OpenStudio + SDK and EnergyPlus). (Default: None). + angle_tolerance: The max angle difference in degrees that vertices are allowed + to differ from one another in order to consider them colinear. Zero indicates + that no angle tolerance checks should be performed. (Default: 1.0). + + Properties: + * identifier + * display_name + * units + * tolerance + * angle_tolerance + * rooms + * faces + * apertures + * doors + * shades + * shade_meshes + * indoor_shades + * outdoor_shades + * orphaned_faces + * orphaned_shades + * orphaned_apertures + * orphaned_doors + * stories + * volume + * floor_area + * exposed_area + * exterior_wall_area + * exterior_roof_area + * exterior_aperture_area + * exterior_wall_aperture_area + * exterior_skylight_aperture_area + * min + * max + * top_level_dict + * user_data + """ + __slots__ = ( + '_rooms', '_orphaned_faces', '_orphaned_apertures', '_orphaned_doors', + '_orphaned_shades', '_shade_meshes', + '_units', '_tolerance', '_angle_tolerance' + ) + + UNITS = UNITS + UNITS_TOLERANCES = UNITS_TOLERANCES + + def __init__(self, identifier, rooms=None, orphaned_faces=None, orphaned_shades=None, + orphaned_apertures=None, orphaned_doors=None, shade_meshes=None, + units='Meters', tolerance=None, angle_tolerance=1.0): + """A collection of Rooms, Faces, Apertures, and Doors for an entire model.""" + _Base.__init__(self, identifier) # process the identifier + + self.units = units + self.tolerance = tolerance + self.angle_tolerance = angle_tolerance + + self._rooms = [] + self._orphaned_faces = [] + self._orphaned_apertures = [] + self._orphaned_doors = [] + self._orphaned_shades = [] + self._shade_meshes = [] + if rooms is not None: + for room in rooms: + self.add_room(room) + if orphaned_faces is not None: + for face in orphaned_faces: + self.add_face(face) + if orphaned_apertures is not None: + for aperture in orphaned_apertures: + self.add_aperture(aperture) + if orphaned_doors is not None: + for door in orphaned_doors: + self.add_door(door) + if orphaned_shades is not None: + for shade in orphaned_shades: + self.add_shade(shade) + if shade_meshes is not None: + for shade_mesh in shade_meshes: + self.add_shade_mesh(shade_mesh) + + self._properties = ModelProperties(self) + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize a Model from a dictionary. + + Args: + data: A dictionary representation of a Model object. + """ + # check the type of dictionary + assert data['type'] == 'Model', 'Expected Model dictionary. ' \ + 'Got {}.'.format(data['type']) + + # import the units and tolerance values + units = 'Meters' if 'units' not in data or data['units'] is None \ + else data['units'] + tol = cls.UNITS_TOLERANCES[units] if 'tolerance' not in data or \ + data['tolerance'] is None else data['tolerance'] + angle_tol = 1.0 if 'angle_tolerance' not in data or \ + data['angle_tolerance'] is None else data['angle_tolerance'] + + # import all of the geometry + rooms = None # import rooms + if 'rooms' in data and data['rooms'] is not None: + rooms = [] + for r in data['rooms']: + try: + rooms.append(Room.from_dict(r, tol, angle_tol)) + except Exception as e: + invalid_dict_error(r, e) + orphaned_faces = None # import orphaned faces + if 'orphaned_faces' in data and data['orphaned_faces'] is not None: + orphaned_faces = [] + for f in data['orphaned_faces']: + try: + orphaned_faces.append(Face.from_dict(f)) + except Exception as e: + invalid_dict_error(f, e) + orphaned_apertures = None # import orphaned apertures + if 'orphaned_apertures' in data and data['orphaned_apertures'] is not None: + orphaned_apertures = [] + for a in data['orphaned_apertures']: + try: + orphaned_apertures.append(Aperture.from_dict(a)) + except Exception as e: + invalid_dict_error(a, e) + orphaned_doors = None # import orphaned doors + if 'orphaned_doors' in data and data['orphaned_doors'] is not None: + orphaned_doors = [] + for d in data['orphaned_doors']: + try: + orphaned_doors.append(Door.from_dict(d)) + except Exception as e: + invalid_dict_error(d, e) + orphaned_shades = None # import orphaned shades + if 'orphaned_shades' in data and data['orphaned_shades'] is not None: + orphaned_shades = [] + for s in data['orphaned_shades']: + try: + orphaned_shades.append(Shade.from_dict(s)) + except Exception as e: + invalid_dict_error(s, e) + shade_meshes = None # import shade meshes + if 'shade_meshes' in data and data['shade_meshes'] is not None: + shade_meshes = [] + for sm in data['shade_meshes']: + try: + shade_meshes.append(ShadeMesh.from_dict(sm)) + except Exception as e: + invalid_dict_error(sm, e) + + # build the model object + model = Model( + data['identifier'], rooms, orphaned_faces, orphaned_shades, + orphaned_apertures, orphaned_doors, shade_meshes, + units, tol, angle_tol) + if 'display_name' in data and data['display_name'] is not None: + model.display_name = data['display_name'] + if 'user_data' in data and data['user_data'] is not None: + model.user_data = data['user_data'] + + # assign extension properties to the model + model.properties.apply_properties_from_dict(data) + return model
+ +
[docs] @classmethod + def from_file(cls, hb_file): + """Initialize a Model from a HBJSON or HBpkl file, auto-sensing the type. + + Args: + hb_file: Path to either a HBJSON or HBpkl file. + """ + # sense the file type from the first character to avoid maxing memory with JSON + # this is needed since queenbee overwrites all file extensions + with io.open(hb_file, encoding='utf-8') as inf: + first_char = inf.read(1) + second_char = inf.read(1) + is_json = True if first_char == '{' or second_char == '{' else False + # load the file using either HBJSON pathway or HBpkl + if is_json: + return cls.from_hbjson(hb_file) + return cls.from_hbpkl(hb_file)
+ +
[docs] @classmethod + def from_hbjson(cls, hbjson_file): + """Initialize a Model from a HBJSON file. + + Args: + hbjson_file: Path to HBJSON file. + """ + assert os.path.isfile(hbjson_file), 'Failed to find %s' % hbjson_file + with io.open(hbjson_file, encoding='utf-8') as inf: + inf.read(1) + second_char = inf.read(1) + with io.open(hbjson_file, encoding='utf-8') as inf: + if second_char == '{': + inf.read(1) + data = json.load(inf) + return cls.from_dict(data)
+ +
[docs] @classmethod + def from_hbpkl(cls, hbpkl_file): + """Initialize a Model from a HBpkl file. + + Args: + hbpkl_file: Path to HBpkl file. + """ + assert os.path.isfile(hbpkl_file), 'Failed to find %s' % hbpkl_file + with open(hbpkl_file, 'rb') as inf: + data = pickle.load(inf) + return cls.from_dict(data)
+ +
[docs] @classmethod + def from_stl(cls, file_path, geometry_to_faces=False, units='Meters', + tolerance=None, angle_tolerance=1.0): + """Create a Honeybee Model from an STL file. + + Args: + file_path: Path to an STL file as a text string. The STL file can be + in either ASCII or binary format. + geometry_to_faces: A boolean to note whether the geometry in the STL + file should be imported as Faces (with Walls/Floors/RoofCeiling + set according to the normal). If False, all geometry will be + imported as ShadeMeshes instead of Faces. (Default: False). + units: Text for the units system in which the model geometry + exists. Default: 'Meters'. Choose from the following: + + * Meters + * Millimeters + * Feet + * Inches + * Centimeters + + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. Zero indicates that no tolerance + checks should be performed. None indicates that the tolerance will be + set based on the units above, with the tolerance consistently being + between 1 cm and 1 mm (roughly the tolerance implicit in the OpenStudio + SDK and EnergyPlus). (Default: None). + angle_tolerance: The max angle difference in degrees that vertices + are allowed to differ from one another in order to consider them + colinear. Zero indicates that no angle tolerance checks should be + performed. (Default: 1.0). + """ + stl_obj = STL.from_file(file_path) + all_id = clean_string(stl_obj.name) + all_geo = [] + if geometry_to_faces: + for verts, normal in zip(stl_obj.face_vertices, stl_obj.face_normals): + all_geo.append(Face3D(verts, plane=Plane(normal, verts[0]))) + hb_objs = [Face(all_id + '_' + str(uuid.uuid4())[:8], go) for go in all_geo] + return Model(all_id, orphaned_faces=hb_objs, units=units, + tolerance=tolerance, angle_tolerance=angle_tolerance) + else: + mesh3d = Mesh3D.from_face_vertices(stl_obj.face_vertices) + hb_objs = [ShadeMesh(all_id, mesh3d)] + return Model(all_id, shade_meshes=hb_objs, units=units, + tolerance=tolerance, angle_tolerance=angle_tolerance)
+ +
[docs] @classmethod + def from_sync(cls, base_model, other_model, sync_instructions): + """Initialize a Model from two models and instructions for syncing them. + + The SyncInstructions dictionary schema is essentially a variant of the + ComparisonReport schema that can be obtained by calling + base_model.comparison_report(other_model). The main difference is + that the XXX_changed properties should be replaced with update_XXX properties + for whether the change from the other_model should be accepted into + the new model or rejected from it. + + Args: + base_model: An base Honeybee Model that forms the base of + the new model to be created. + other_model: An other Honeybee Model that contains changes to + the base model to be merged into the base_model. + sync_instructions: A dictionary of SyncInstructions that states which + changes from the other_model should be accepted or rejected + when building a new Model from the base_model. + """ + # make sure the unit systems of the two models align + if base_model.units != other_model.units: + other_model = other_model.duplicate() + other_model.convert_to_units(base_model.units) + # set up dictionaries of objects and lists of changes + exist_dict = base_model.top_level_dict + other_dict = other_model.top_level_dict + add_dict = { + 'Room': [], 'Face': [], 'Aperture': [], 'Door': [], + 'Shade': [], 'ShadeMesh': [] + } + del_dict = { + 'Room': [], 'Face': [], 'Aperture': [], 'Door': [], + 'Shade': [], 'ShadeMesh': [] + } + # loop through the changed objects and record changes + if 'changed_objects' in sync_instructions: + for change in sync_instructions['changed_objects']: + ex_obj = exist_dict[change['element_id']] + up_obj = other_dict[change['element_id']] + base_obj = up_obj if 'update_geometry' in change \ + and change['update_geometry'] else ex_obj + base_obj.properties._update_by_sync( + change, ex_obj.properties, up_obj.properties) + del_dict[change['element_type']].append(change['element_id']) + add_dict[change['element_type']].append(base_obj) + # loop through deleted objects and record changes + if 'deleted_objects' in sync_instructions: + for change in sync_instructions['deleted_objects']: + del_dict[change['element_type']].append(change['element_id']) + # loop through added objects and record changes + if 'added_objects' in sync_instructions: + for change in sync_instructions['added_objects']: + up_obj = other_dict[change['element_id']] + add_dict[change['element_type']].append(up_obj) + # duplicate the base model and make changes to it + new_model = base_model.duplicate() + new_model.remove_rooms(del_dict['Room']) + new_model.remove_faces(del_dict['Face']) + new_model.remove_apertures(del_dict['Aperture']) + new_model.remove_doors(del_dict['Door']) + new_model.remove_shades(del_dict['Shade']) + new_model.remove_shade_meshes(del_dict['ShadeMesh']) + new_model.add_rooms(add_dict['Room']) + new_model.add_faces(add_dict['Face']) + new_model.add_apertures(add_dict['Aperture']) + new_model.add_doors(add_dict['Door']) + new_model.add_shades(add_dict['Shade']) + new_model.add_shade_meshes(add_dict['ShadeMesh']) + return new_model
+ +
[docs] @classmethod + def from_sync_files( + cls, base_model_file, other_model_file, sync_instructions_file): + """Initialize a Model from two model files and instructions for syncing them. + + Args: + base_model_file: An base Honeybee Model (as HBJSON or HBPkl) + that forms the base of the new model to be created. + other_model_file: An other Honeybee Model (as HBJSON or HBPkl) + that contains changes to the base model to be merged into + the base_model. + sync_instructions: A JSON of SyncInstructions that states which + changes from the other_model should be accepted or rejected + when building a new Model from the base_model. The SyncInstructions + schema is essentially a variant of the ComparisonReport schema + that can be obtained by calling base_model.comparison_report( + other_model). The main difference is that the XXX_changed + properties should be replaced with update_XXX properties for + whether the change from the other_model should be accepted into + the new model or rejected from it. + """ + base_model = cls.from_file(base_model_file) + other_model = cls.from_file(other_model_file) + assert os.path.isfile(sync_instructions_file), \ + 'Failed to find %s' % sync_instructions_file + if sys.version_info < (3, 0): + with open(sync_instructions_file) as inf: + sync_instructions = json.load(inf) + else: + with open(sync_instructions_file, encoding='utf-8') as inf: + sync_instructions = json.load(inf) + return cls.from_sync(base_model, other_model, sync_instructions)
+ +
[docs] @classmethod + def from_objects(cls, identifier, objects, units='Meters', + tolerance=None, angle_tolerance=1.0): + """Initialize a Model from a list of any type of honeybee-core geometry objects. + + Args: + identifier: Text string for a unique Model ID. Must be < 100 characters and + not contain any spaces or special characters. + objects: A list of honeybee Rooms, Faces, Shades, ShadeMEshes, + Apertures and Doors. + units: Text for the units system in which the model geometry + exists. Default: 'Meters'. Choose from the following: + + * Meters + * Millimeters + * Feet + * Inches + * Centimeters + + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. Zero indicates that no tolerance + checks should be performed. None indicates that the tolerance will be + set based on the units above, with the tolerance consistently being + between 1 cm and 1 mm (roughly the tolerance implicit in the OpenStudio + SDK and EnergyPlus). (Default: None). + angle_tolerance: The max angle difference in degrees that vertices + are allowed to differ from one another in order to consider them + colinear. Zero indicates that no angle tolerance checks should be + performed. (Default: 1.0). + """ + rooms = [] + faces = [] + shades = [] + shade_meshes = [] + apertures = [] + doors = [] + for obj in objects: + if isinstance(obj, Room): + rooms.append(obj) + elif isinstance(obj, Face): + faces.append(obj) + elif isinstance(obj, Shade): + shades.append(obj) + elif isinstance(obj, ShadeMesh): + shade_meshes.append(obj) + elif isinstance(obj, Aperture): + apertures.append(obj) + elif isinstance(obj, Door): + doors.append(obj) + else: + raise TypeError('Expected Room, Face, Shade, Aperture or Door ' + 'for Model. Got {}'.format(type(obj))) + + return cls(identifier, rooms, faces, shades, apertures, doors, shade_meshes, + units, tolerance, angle_tolerance)
+ +
[docs] @classmethod + def from_shoe_box( + cls, width, depth, height, orientation_angle=0, window_ratio=0, + adiabatic=True, units='Meters', tolerance=None, angle_tolerance=1.0): + """Create a model with a single shoe box Room. + + Args: + width: Number for the width of the box (in the X direction). + depth: Number for the depth of the box (in the Y direction). + height: Number for the height of the box (in the Z direction). + orientation_angle: A number between 0 and 360 for the clockwise + orientation of the box in degrees. (0=North, 90=East, 180=South, + 270=West). (Default: 0). + window_ratio: A number between 0 and 1 (but not equal to 1) for the ratio + between aperture area and area of the face pointing towards the + orientation-angle. Using 0 will generate no windows. (Default: 0). + adiabatic: Boolean to note whether the faces that are not in the direction + of the orientation-angle are adiabatic or outdoors. (Default: True) + units: Text for the units system in which the model geometry + exists. (Default: 'Meters'). + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. Zero indicates that no tolerance + checks should be performed. None indicates that the tolerance will be + set based on the units above, with the tolerance consistently being + between 1 cm and 1 mm (roughly the tolerance implicit in the OpenStudio + SDK and EnergyPlus). (Default: None). + angle_tolerance: The max angle difference in degrees that vertices + are allowed to differ from one another in order to consider them + colinear. Zero indicates that no angle tolerance checks should be + performed. (Default: 1.0). + """ + # create the box room and assign all of the attributes + unique_id = str(uuid.uuid4())[:8] # unique identifier for the shoe box + tolerance = tolerance if tolerance is not None else UNITS_TOLERANCES[units] + room_id = 'Shoe_Box_Room_{}'.format(unique_id) + room = Room.from_box(room_id, width, depth, height, orientation_angle) + room.display_name = 'Shoe_Box_Room' + front_face = room[1] + front_face.apertures_by_ratio(window_ratio, tolerance) + if adiabatic and ad_bc: + room[0].boundary_condition = ad_bc # make the floor adiabatic + for face in room[2:]: # make all other face adiabatic + face.boundary_condition = ad_bc + # create the model object + model_id = 'Shoe_Box_Model_{}'.format(unique_id) + return cls(model_id, [room], units=units, tolerance=tolerance, + angle_tolerance=angle_tolerance)
+ +
[docs] @classmethod + def from_rectangle_plan( + cls, width, length, floor_to_floor_height, perimeter_offset=0, story_count=1, + orientation_angle=0, outdoor_roof=True, ground_floor=True, + units='Meters', tolerance=None, angle_tolerance=1.0): + """Create a model with a rectangular floor plan. + + Note that the resulting Rooms in the model won't have any windows or solved + adjacencies. These can be added by using the Model.solve_adjacency method + and the various Face.apertures_by_XXX methods. + + Args: + width: Number for the width of the plan (in the X direction). + length: Number for the length of the plan (in the Y direction). + floor_to_floor_height: Number for the height of each floor of the model + (in the Z direction). + perimeter_offset: An optional positive number that will be used to offset + the perimeter to create core/perimeter Rooms. If this value is 0, + no offset will occur and each floor will have one Room. (Default: 0). + story_count: An integer for the number of stories to generate. (Default: 1). + orientation_angle: A number between 0 and 360 for the counterclockwise + orientation that the width of the box faces. (0=North, 90=East, + 180=South, 270=West). (Default: 0). + outdoor_roof: Boolean to note whether the roof faces of the top floor + should be outdoor or adiabatic. (Default: True). + ground_floor: Boolean to note whether the floor faces of the bottom + floor should be ground or adiabatic. (Default: True). + units: Text for the units system in which the model geometry + exists. (Default: 'Meters'). + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. Zero indicates that no tolerance + checks should be performed. None indicates that the tolerance will be + set based on the units above, with the tolerance consistently being + between 1 cm and 1 mm (roughly the tolerance implicit in the OpenStudio + SDK and EnergyPlus). (Default: None). + angle_tolerance: The max angle difference in degrees that vertices + are allowed to differ from one another in order to consider them + colinear. Zero indicates that no angle tolerance checks should be + performed. (Default: 1.0). + """ + # create the honeybee rooms + tolerance = tolerance if tolerance is not None else UNITS_TOLERANCES[units] + unique_id = str(uuid.uuid4())[:8] # unique identifier for the model + rooms = Room.rooms_from_rectangle_plan( + width, length, floor_to_floor_height, perimeter_offset, story_count, + orientation_angle, outdoor_roof, ground_floor, unique_id, tolerance) + # create the model object + model_id = 'Rectangle_Plan_Model_{}'.format(unique_id) + return cls(model_id, rooms, units=units, tolerance=tolerance, + angle_tolerance=angle_tolerance)
+ +
[docs] @classmethod + def from_l_shaped_plan( + cls, width_1, length_1, width_2, length_2, floor_to_floor_height, + perimeter_offset=0, story_count=1, orientation_angle=0, + outdoor_roof=True, ground_floor=True, + units='Meters', tolerance=None, angle_tolerance=1.0): + """Create a model with an L-shaped floor plan. + + Note that the resulting Rooms in the model won't have any windows or solved + adjacencies. These can be added by using the Model.solve_adjacency method + and the various Face.apertures_by_XXX methods. + + Args: + width_1: Number for the width of the lower part of the L segment. + length_1: Number for the length of the lower part of the L segment, not + counting the overlap between the upper and lower segments. + width_2: Number for the width of the upper (left) part of the L segment. + length_2: Number for the length of the upper (left) part of the L segment, + not counting the overlap between the upper and lower segments. + floor_to_floor_height: Number for the height of each floor of the model + (in the Z direction). + perimeter_offset: An optional positive number that will be used to offset + the perimeter to create core/perimeter Rooms. If this value is 0, + no offset will occur and each floor will have one Room. (Default: 0). + story_count: An integer for the number of stories to generate. (Default: 1). + orientation_angle: A number between 0 and 360 for the counterclockwise + orientation that the width of the box faces. (0=North, 90=East, + 180=South, 270=West). (Default: 0). + outdoor_roof: Boolean to note whether the roof faces of the top floor + should be outdoor or adiabatic. (Default: True). + ground_floor: Boolean to note whether the floor faces of the bottom + floor should be ground or adiabatic. (Default: True). + units: Text for the units system in which the model geometry + exists. (Default: 'Meters'). + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. Zero indicates that no tolerance + checks should be performed. None indicates that the tolerance will be + set based on the units above, with the tolerance consistently being + between 1 cm and 1 mm (roughly the tolerance implicit in the OpenStudio + SDK and EnergyPlus). (Default: None). + angle_tolerance: The max angle difference in degrees that vertices + are allowed to differ from one another in order to consider them + colinear. Zero indicates that no angle tolerance checks should be + performed. (Default: 1.0). + """ + # create the honeybee rooms + tolerance = tolerance if tolerance is not None else UNITS_TOLERANCES[units] + unique_id = str(uuid.uuid4())[:8] # unique identifier for the model + rooms = Room.rooms_from_l_shaped_plan( + width_1, length_1, width_2, length_2, floor_to_floor_height, + perimeter_offset, story_count, + orientation_angle, outdoor_roof, ground_floor, unique_id, tolerance) + # create the model object + model_id = 'L_Shaped_Plan_Model_{}'.format(unique_id) + return cls(model_id, rooms, units=units, tolerance=tolerance, + angle_tolerance=angle_tolerance)
+ + @property + def units(self): + """Get or set Text for the units system in which the model geometry exists.""" + return self._units + + @units.setter + def units(self, value): + value = value.title() + assert value in UNITS, '{} is not supported as a units system. ' \ + 'Choose from the following: {}'.format(value, UNITS) + self._units = value + + @property + def tolerance(self): + """Get or set a number for the max meaningful difference between x, y, z values. + + This value should be in the Model's units. Zero indicates cases + where no tolerance checks should be performed. + """ + return self._tolerance + + @tolerance.setter + def tolerance(self, value): + self._tolerance = float_positive(value, 'model tolerance') if value is not None \ + else UNITS_TOLERANCES[self.units] + + @property + def angle_tolerance(self): + """Get or set a number for the max meaningful angle difference in degrees. + + Face3D normal vectors differing by this amount are not considered parallel + and Face3D segments that differ from 180 by this amount are not considered + colinear. Zero indicates cases where no angle_tolerance checks should be + performed. + """ + return self._angle_tolerance + + @angle_tolerance.setter + def angle_tolerance(self, value): + self._angle_tolerance = float_positive(value, 'model angle_tolerance') + + @property + def rooms(self): + """Get a tuple of all Room objects in the model.""" + return tuple(self._rooms) + + @property + def faces(self): + """Get a list of all Face objects in the model.""" + child_faces = [face for room in self._rooms for face in room._faces] + return child_faces + self._orphaned_faces + + @property + def apertures(self): + """Get a list of all Aperture objects in the model.""" + child_apertures = [] + for room in self._rooms: + for face in room._faces: + child_apertures.extend(face._apertures) + for face in self._orphaned_faces: + child_apertures.extend(face._apertures) + return child_apertures + self._orphaned_apertures + + @property + def doors(self): + """Get a list of all Door objects in the model.""" + child_doors = [] + for room in self._rooms: + for face in room._faces: + child_doors.extend(face._doors) + for face in self._orphaned_faces: + child_doors.extend(face._doors) + return child_doors + self._orphaned_doors + + @property + def shades(self): + """Get a list of all Shade objects in the model.""" + child_shades = [] + for room in self._rooms: + child_shades.extend(room.shades) + for face in room.faces: + child_shades.extend(face.shades) + for ap in face._apertures: + child_shades.extend(ap.shades) + for dr in face._doors: + child_shades.extend(dr.shades) + for face in self._orphaned_faces: + child_shades.extend(face.shades) + for ap in face._apertures: + child_shades.extend(ap.shades) + for dr in face._doors: + child_shades.extend(dr.shades) + for ap in self._orphaned_apertures: + child_shades.extend(ap.shades) + for dr in self._orphaned_doors: + child_shades.extend(dr.shades) + return child_shades + self._orphaned_shades + + @property + def indoor_shades(self): + """Get a list of all indoor Shade objects in the model.""" + child_shades = [] + for room in self._rooms: + child_shades.extend(room._indoor_shades) + for face in room.faces: + child_shades.extend(face._indoor_shades) + for ap in face._apertures: + child_shades.extend(ap._indoor_shades) + for dr in face._doors: + child_shades.extend(dr._indoor_shades) + for face in self._orphaned_faces: + child_shades.extend(face._indoor_shades) + for ap in face._apertures: + child_shades.extend(ap._indoor_shades) + for dr in face._doors: + child_shades.extend(dr._indoor_shades) + for ap in self._orphaned_apertures: + child_shades.extend(ap._indoor_shades) + for dr in self._orphaned_doors: + child_shades.extend(dr._indoor_shades) + return child_shades + + @property + def outdoor_shades(self): + """Get a list of all outdoor Shade objects in the model. + + This includes all of the orphaned_shades. + """ + child_shades = [] + for room in self._rooms: + child_shades.extend(room._outdoor_shades) + for face in room.faces: + child_shades.extend(face._outdoor_shades) + for ap in face._apertures: + child_shades.extend(ap._outdoor_shades) + for dr in face._doors: + child_shades.extend(dr._outdoor_shades) + for face in self._orphaned_faces: + child_shades.extend(face._outdoor_shades) + for ap in face._apertures: + child_shades.extend(ap._outdoor_shades) + for dr in face._doors: + child_shades.extend(dr._outdoor_shades) + for ap in self._orphaned_apertures: + child_shades.extend(ap._outdoor_shades) + for dr in self._orphaned_doors: + child_shades.extend(dr._outdoor_shades) + return child_shades + self._orphaned_shades + + @property + def shade_meshes(self): + """Get a tuple of all ShadeMesh objects in the model.""" + return tuple(self._shade_meshes) + + @property + def grouped_shades(self): + """Get a list of lists where each sub-list contains Shades and/or ShadeMeshes + with the same display_name. + + Assigning a common display_name to Shades and ShadeMeshes is the officially + recommended way to group these objects for export to platforms that + support shade groups. In this case, it is customary to use the common + display_name as the name of the shade group. + + Note that, if no display_names have been assigned to the Shades and + ShadeMeshes, the unique object identifier is used, meaning each sublist + returned here should have only one item in it. + """ + all_shades = self.shades + self._shade_meshes + group_dict = {} + for shade in all_shades: + try: + group_dict[shade.display_name].append(shade) + except KeyError: + group_dict[shade.display_name] = [shade] + return list(group_dict.values()) + + @property + def orphaned_faces(self): + """Get a tuple of all Face objects without parent Rooms in the model.""" + return tuple(self._orphaned_faces) + + @property + def orphaned_apertures(self): + """Get a tuple of all Aperture objects without parent Faces in the model.""" + return tuple(self._orphaned_apertures) + + @property + def orphaned_doors(self): + """Get a tuple of all Door objects without parent Faces in the model.""" + return tuple(self._orphaned_doors) + + @property + def orphaned_shades(self): + """Get a tuple of all Shade objects without parent Rooms in the model.""" + return tuple(self._orphaned_shades) + + @property + def stories(self): + """Get a list of text for each unique story identifier in the Model. + + Note that this will be an empty list if the model has to rooms. + """ + _stories = set() + for room in self._rooms: + if room.story is not None: + _stories.add(room.story) + return list(_stories) + + @property + def volume(self): + """Get the combined volume of all rooms in the Model. + + Note that this property accounts for the room multipliers. Also note that, + if this model's rooms are not closed solids, the value of this property + will not be accurate. + """ + return sum([room.volume * room.multiplier for room in self._rooms]) + + @property + def floor_area(self): + """Get the combined area of all room floor faces in the Model. + + Note that this property accounts for the room multipliers. + """ + return sum([room.floor_area * room.multiplier for room in self._rooms + if not room.exclude_floor_area]) + + @property + def exposed_area(self): + """Get the combined area of all room faces with outdoor boundary conditions. + + Useful for estimating infiltration, often expressed as a flow per unit exposed + envelope area. Note that this property accounts for the room multipliers. + """ + return sum([room.exposed_area * room.multiplier for room in self._rooms]) + + @property + def exterior_wall_area(self): + """Get the combined area of all exterior walls on the model's rooms. + + This is NOT the area of the wall's punched_geometry and it includes BOTH + the area of opaque and transparent parts of the walls. Note that this + property accounts for the room multipliers. + """ + return sum([room.exterior_wall_area * room.multiplier for room in self._rooms]) + + @property + def exterior_roof_area(self): + """Get the combined area of all exterior roofs on the model's rooms. + + This is NOT the area of the roof's punched_geometry and it includes BOTH + the area of opaque and transparent parts of the roofs. Note that this + property accounts for the room multipliers. + """ + return sum([room.exterior_roof_area * room.multiplier for room in self._rooms]) + + @property + def exterior_aperture_area(self): + """Get the combined area of all exterior apertures on the model's rooms. + + Note that this property accounts for the room multipliers. + """ + return sum([room.exterior_aperture_area * room.multiplier + for room in self._rooms]) + + @property + def exterior_wall_aperture_area(self): + """Get the combined area of all apertures on exterior walls of the model's rooms. + + Note that this property accounts for the room multipliers. + """ + return sum([room.exterior_wall_aperture_area * room.multiplier + for room in self._rooms]) + + @property + def exterior_skylight_aperture_area(self): + """Get the combined area of all apertures on exterior roofs of the model's rooms. + + Note that this property accounts for the room multipliers. + """ + return sum([room.exterior_skylight_aperture_area * room.multiplier + for room in self._rooms]) + + @property + def min(self): + """Get a Point3D for the min bounding box vertex in the XY plane.""" + return self._calculate_min(self._all_objects()) + + @property + def max(self): + """Get a Point3D for the max bounding box vertex in the XY plane.""" + return self._calculate_max(self._all_objects()) + + @property + def top_level_dict(self): + """Get dictionary of top-level model objects with identifiers as the keys. + + This is useful for matching these objects to others using identifiers. + """ + base = {r.identifier: r for r in self._rooms} + for f in self._orphaned_faces: + base[f.identifier] = f + for a in self._orphaned_apertures: + base[a.identifier] = a + for d in self._orphaned_doors: + base[d.identifier] = d + for s in self._orphaned_shades: + base[s.identifier] = s + for sm in self._shade_meshes: + base[sm.identifier] = sm + return base + +
[docs] def add_model(self, other_model): + """Add another Model object to this model.""" + assert isinstance(other_model, Model), \ + 'Expected Model. Got {}.'.format(type(other_model)) + if self.units != other_model.units: + other_model.convert_to_units(self.units) + for room in other_model._rooms: + self._rooms.append(room) + for face in other_model._orphaned_faces: + self._orphaned_faces.append(face) + for shade in other_model._orphaned_shades: + self._orphaned_shades.append(shade) + for shade_mesh in other_model._shade_meshes: + self._shade_meshes.append(shade_mesh) + for aperture in other_model._orphaned_apertures: + self._orphaned_apertures.append(aperture) + for door in other_model._orphaned_doors: + self._orphaned_doors.append(door)
+ +
[docs] def add_room(self, obj): + """Add a Room object to the model.""" + assert isinstance(obj, Room), 'Expected Room. Got {}.'.format(type(obj)) + self._rooms.append(obj)
+ +
[docs] def add_face(self, obj): + """Add an orphaned Face object without a parent to the model.""" + assert isinstance(obj, Face), 'Expected Face. Got {}.'.format(type(obj)) + assert not obj.has_parent, 'Face "{}"" has a parent Room. Add the Room to '\ + 'the model instead of the Face.'.format(obj.display_name) + self._orphaned_faces.append(obj)
+ +
[docs] def add_aperture(self, obj): + """Add an orphaned Aperture object to the model.""" + assert isinstance(obj, Aperture), 'Expected Aperture. Got {}.'.format(type(obj)) + assert not obj.has_parent, 'Aperture "{}"" has a parent Face. Add the Face to '\ + 'the model instead of the Aperture.'.format(obj.display_name) + self._orphaned_apertures.append(obj)
+ +
[docs] def add_door(self, obj): + """Add an orphaned Door object to the model.""" + assert isinstance(obj, Door), 'Expected Door. Got {}.'.format(type(obj)) + assert not obj.has_parent, 'Door "{}"" has a parent Face. Add the Face to '\ + 'the model instead of the Door.'.format(obj.display_name) + self._orphaned_doors.append(obj)
+ +
[docs] def add_shade(self, obj): + """Add an orphaned Shade object to the model, typically representing context.""" + assert isinstance(obj, Shade), 'Expected Shade. Got {}.'.format(type(obj)) + assert not obj.has_parent, 'Shade "{}"" has a parent object. Add the object to '\ + 'the model instead of the Shade.'.format(obj.display_name) + self._orphaned_shades.append(obj)
+ +
[docs] def add_shade_mesh(self, obj): + """Add a ShadeMesh object to the model.""" + assert isinstance(obj, ShadeMesh), 'Expected ShadeMesh. Got {}.'.format(type(obj)) + self._shade_meshes.append(obj)
+ +
[docs] def remove_rooms(self, room_ids=None): + """Remove Rooms from the model. + + Args: + room_ids: An optional list of Room identifiers to only remove certain rooms + from the model. If None, all Rooms will be removed. (Default: None). + """ + self._rooms = self._remove_by_ids(self.rooms, room_ids)
+ +
[docs] def remove_faces(self, face_ids=None): + """Remove orphaned Faces from the model. + + Args: + face_ids: An optional list of Face identifiers to only remove certain faces + from the model. If None, all Faces will be removed. (Default: None). + """ + self._orphaned_faces = self._remove_by_ids(self._orphaned_faces, face_ids)
+ +
[docs] def remove_apertures(self, aperture_ids=None): + """Remove orphaned Apertures from the model. + + Args: + aperture_ids: An optional list of Aperture identifiers to only remove + certain apertures from the model. If None, all Apertures will + be removed. (Default: None). + """ + self._orphaned_apertures = self._remove_by_ids( + self._orphaned_apertures, aperture_ids)
+ +
[docs] def remove_doors(self, door_ids=None): + """Remove orphaned Doors from the model. + + Args: + door_ids: An optional list of Door identifiers to only remove certain doors + from the model. If None, all Doors will be removed. (Default: None). + """ + self._orphaned_doors = self._remove_by_ids(self._orphaned_doors, door_ids)
+ +
[docs] def remove_shades(self, shade_ids=None): + """Remove orphaned Shades from the model. + + Args: + shade_ids: An optional list of Shade identifiers to only remove + certain shades from the model. If None, all Shades will be + removed. (Default: None). + """ + self._orphaned_shades = self._remove_by_ids(self._orphaned_shades, shade_ids)
+ +
[docs] def remove_shade_meshes(self, shade_mesh_ids=None): + """Remove ShadeMeshes from the model. + + Args: + shade_mesh_ids: An optional list of ShadeMesh identifiers to only remove + certain shades from the model. If None, all Shades will be + removed. (Default: None). + """ + self._shade_meshes = self._remove_by_ids(self._shade_meshes, shade_mesh_ids)
+ +
[docs] def remove_assigned_apertures(self): + """Remove all Apertures assigned to the model's Faces. + + This includes nested apertures like those assigned to Faces with parent Rooms. + """ + for room in self._rooms: + for face in room.faces: + face.remove_apertures() + for face in self._orphaned_faces: + face.remove_apertures()
+ +
[docs] def remove_assigned_doors(self): + """Remove all Doors assigned to the model's Faces. + + This includes nested doors like those assigned to Faces with parent Rooms. + """ + for room in self._rooms: + for face in room.faces: + face.remove_doors() + for face in self._orphaned_faces: + face.remove_doors()
+ +
[docs] def remove_assigned_shades(self): + """Remove all Shades assigned to the model's Rooms, Faces, Apertures and Doors. + + This includes nested shades like those assigned to Apertures with parent + Faces that have parent Rooms. + """ + for room in self._rooms: + room.remove_shades() + for face in room.faces: + face.remove_shades() + for ap in face.apertures: + ap.remove_shades() + for dr in face.doors: + dr.remove_shades() + for face in self._orphaned_faces: + face.remove_shades() + for ap in face.apertures: + ap.remove_shades() + for dr in face.doors: + dr.remove_shades() + for aperture in self._orphaned_apertures: + aperture.remove_shades() + for door in self._orphaned_doors: + door.remove_shades()
+ +
[docs] def remove_all_apertures(self): + """Remove all Apertures from the model. + + This includes assigned apertures as well as orphaned apertures. + """ + self.remove_apertures() + self.remove_assigned_apertures()
+ +
[docs] def remove_all_doors(self): + """Remove all Doors from the model. + + This includes assigned doors as well as orphaned doors. + """ + self.remove_doors() + self.remove_assigned_doors()
+ +
[docs] def remove_all_shades(self): + """Remove all Shades from the model. + + This includes assigned shades as well as orphaned shades. + """ + self.remove_shades() + self.remove_assigned_shades()
+ +
[docs] def add_rooms(self, objs): + """Add a list of Room objects to the model.""" + for obj in objs: + self.add_room(obj)
+ +
[docs] def add_faces(self, objs): + """Add a list of orphaned Face objects to the model.""" + for obj in objs: + self.add_face(obj)
+ +
[docs] def add_apertures(self, objs): + """Add a list of orphaned Aperture objects to the model.""" + for obj in objs: + self.add_aperture(obj)
+ +
[docs] def add_doors(self, objs): + """Add a list of orphaned Door objects to the model.""" + for obj in objs: + self.add_door(obj)
+ +
[docs] def add_shades(self, objs): + """Add a list of orphaned Shade objects to the model.""" + for obj in objs: + self.add_shade(obj)
+ +
[docs] def add_shade_meshes(self, objs): + """Add a list of ShadeMesh objects to the model.""" + for obj in objs: + self.add_shade_mesh(obj)
+ +
[docs] def rooms_by_identifier(self, identifiers): + """Get a list of Room objects in the model given the Room identifiers.""" + rooms, missing_ids = [], [] + model_rooms = self._rooms + for obj_id in identifiers: + for room in model_rooms: + if room.identifier == obj_id: + rooms.append(room) + break + else: + missing_ids.append(obj_id) + if len(missing_ids) != 0: + all_objs = ' '.join(['"' + rid + '"' for rid in missing_ids]) + raise ValueError( + 'The following Rooms were not found in the model: {}'.format(all_objs) + ) + return rooms
+ +
[docs] def faces_by_identifier(self, identifiers): + """Get a list of Face objects in the model given the Face identifiers.""" + faces, missing_ids = [], [] + model_faces = self.faces + for obj_id in identifiers: + for face in model_faces: + if face.identifier == obj_id: + faces.append(face) + break + else: + missing_ids.append(obj_id) + if len(missing_ids) != 0: + all_objs = ' '.join(['"' + rid + '"' for rid in missing_ids]) + raise ValueError( + 'The following Faces were not found in the model: {}'.format(all_objs) + ) + return faces
+ +
[docs] def apertures_by_identifier(self, identifiers): + """Get a list of Aperture objects in the model given the Aperture identifiers.""" + apertures, missing_ids = [], [] + model_apertures = self.apertures + for obj_id in identifiers: + for aperture in model_apertures: + if aperture.identifier == obj_id: + apertures.append(aperture) + break + else: + missing_ids.append(obj_id) + if len(missing_ids) != 0: + all_objs = ' '.join(['"' + rid + '"' for rid in missing_ids]) + raise ValueError( + 'The following Apertures were not found in the model:\n' + '{}'.format(all_objs) + ) + return apertures
+ +
[docs] def doors_by_identifier(self, identifiers): + """Get a list of Door objects in the model given the Door identifiers.""" + doors, missing_ids = [], [] + model_doors = self.doors + for obj_id in identifiers: + for door in model_doors: + if door.identifier == obj_id: + doors.append(door) + break + else: + missing_ids.append(obj_id) + if len(missing_ids) != 0: + all_objs = ' '.join(['"' + rid + '"' for rid in missing_ids]) + raise ValueError( + 'The following Doors were not found in the model: {}'.format(all_objs) + ) + return doors
+ +
[docs] def shades_by_identifier(self, identifiers): + """Get a list of Shade objects in the model given the Shade identifiers.""" + shades, missing_ids = [], [] + model_shades = self.shades + for obj_id in identifiers: + for face in model_shades: + if face.identifier == obj_id: + shades.append(face) + break + else: + missing_ids.append(obj_id) + if len(missing_ids) != 0: + all_objs = ' '.join(['"' + rid + '"' for rid in missing_ids]) + raise ValueError( + 'The following Shades were not found in the model: {}'.format(all_objs) + ) + return shades
+ +
[docs] def shade_meshes_by_identifier(self, identifiers): + """Get a list of ShadeMesh objects in the model given the ShadeMesh identifiers. + """ + shades, missing_ids = [], [] + model_shades = self._shade_meshes + for obj_id in identifiers: + for sm in model_shades: + if sm.identifier == obj_id: + shades.append(sm) + break + else: + missing_ids.append(obj_id) + if len(missing_ids) != 0: + a_os = ' '.join(['"' + rid + '"' for rid in missing_ids]) + raise ValueError( + 'The following ShadeMeshes were not found in the model: {}'.format(a_os) + ) + return shades
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier of this object and child objects by inserting a prefix. + + This is particularly useful in workflows where you duplicate and edit + a starting object and then want to combine it with the original object + since all objects within a Model must have unique identifiers. + + Args: + prefix: Text that will be inserted at the start of this object's + (and child objects') identifier and display_name. It is recommended + that this prefix be short to avoid maxing out the 100 allowable + characters for honeybee identifiers. + """ + for room in self._rooms: + room.add_prefix(prefix) + for face in self._orphaned_faces: + face.add_prefix(prefix) + for aperture in self._orphaned_apertures: + aperture.add_prefix(prefix) + for door in self._orphaned_doors: + door.add_prefix(prefix) + for shade in self._orphaned_shades: + shade.add_prefix(prefix) + for shade_mesh in self._shade_meshes: + shade_mesh.add_prefix(prefix)
+ +
[docs] def reset_ids(self, repair_surface_bcs=True): + """Reset the identifiers of all Model objects to be derived from display_names. + + In the event that duplicate identifiers are found, an integer will be + automatically appended to the new ID to make it unique. This is similar + to the routines that automatically assign unique names to OpenStudio SDK objects. + + Args: + repair_surface_bcs: A Boolean to note whether all Surface boundary + conditions across the model should be updated with the new + identifiers that were generated from the display names. (Default: True). + """ + # set up dictionaries to hold various pieces of information + room_map = self.reset_room_ids() + face_dict, ap_dict, dr_dict, shd_dict, sm_dict = {}, {}, {}, {}, {} + face_map, ap_map, dr_map = {}, {}, {} + # loop through the objects and change their identifiers + for face in self.faces: + new_id = clean_and_number_string( + face.display_name, face_dict, 'Face identifier') + face_map[face.identifier] = new_id + face.identifier = new_id + for ap in self.apertures: + new_id = clean_and_number_string( + ap.display_name, ap_dict, 'Aperture identifier') + ap_map[ap.identifier] = new_id + ap.identifier = new_id + for dr in self.doors: + new_id = clean_and_number_string( + dr.display_name, dr_dict, 'Door identifier') + dr_map[dr.identifier] = new_id + dr.identifier = new_id + for shade in self.shades: + shade.identifier = clean_and_number_string( + shade.display_name, shd_dict, 'Shade identifier') + for shade_mesh in self.shade_meshes: + shade_mesh.identifier = clean_and_number_string( + shade_mesh.display_name, sm_dict, 'ShadeMesh identifier') + # reset all of the Surface boundary conditions if requested + if repair_surface_bcs: + for room in self.rooms: + for face in room.faces: + if isinstance(face.boundary_condition, Surface): + old_objs = face.boundary_condition.boundary_condition_objects + try: + new_objs = (face_map[old_objs[0]], room_map[old_objs[1]]) + except KeyError: # missing adjacency + try: # see if maybe the room reference is still there + new_objs = (old_objs[0], room_map[old_objs[1]]) + except KeyError: # just let the invalid adjacency pass + continue + new_bc = Surface(new_objs) + face.boundary_condition = new_bc + for ap in face.apertures: + old_objs = ap.boundary_condition.boundary_condition_objects + try: + new_objs = (ap_map[old_objs[0]], face_map[old_objs[1]], + room_map[old_objs[2]]) + except KeyError: # missing adjacency + new_objs = (old_objs[0], old_objs[1], + room_map[old_objs[2]]) + new_bc = Surface(new_objs, True) + ap.boundary_condition = new_bc + for dr in face.doors: + old_objs = dr.boundary_condition.boundary_condition_objects + try: + new_objs = (dr_map[old_objs[0]], face_map[old_objs[1]], + room_map[old_objs[2]]) + except KeyError: # missing adjacency + new_objs = (old_objs[0], old_objs[1], + room_map[old_objs[2]]) + new_bc = Surface(new_objs, True) + dr.boundary_condition = new_bc
+ +
[docs] def reset_room_ids(self): + """Reset the identifiers of the Model Rooms to be derived from display_names. + + In the event that duplicate Room identifiers are found, an integer will + be automatically appended to the new Room ID to make it unique. + + Returns: + A dictionary that relates the old identifiers (keys) to the new + identifiers (values). This can be used to map between old and new + objects and update things like Surface boundary conditions. + """ + room_dict, room_map = {}, {} + for room in self.rooms: + new_id = clean_and_number_string( + room.display_name, room_dict, 'Room identifier') + room_map[room.identifier] = new_id + room.identifier = new_id + return room_map
+ +
[docs] def solve_adjacency( + self, merge_coplanar=False, intersect=False, overwrite=False, + air_boundary=False, adiabatic=False, + tolerance=None, angle_tolerance=None): + """Solve adjacency between Rooms of the Model. + + Args: + merge_coplanar: Boolean to note whether coplanar Faces of the Rooms + should be merged before proceeding with the rest of the adjacency + solving. This is particularly helpful when used with the intersect + option since it will ensure the Room geometry is relatively + clean before the intersection and adjacency solving + occurs. (Default: False). + intersect: Boolean to note whether the Faces of the Rooms should be + intersected with one another before the adjacencies are + solved. (Default: False). + overwrite: Boolean to note whether existing Surface boundary + conditions should be overwritten. (Default: False). + air_boundary: Boolean to note whether the wall adjacencies should be + of the air boundary face type. (Default: False). + adiabatic: Boolean to note whether the adjacencies should be + surface or adiabatic. Note that this requires honeybee-energy + to be installed in order to have any meaning. (Default: False). + tolerance: The maximum difference between point values for them to be + considered equivalent. If None, the Model tolerance will be + used. (Default: None). + angle_tolerance: The max angle difference in degrees where Face normals + are no longer considered coplanar. If None, the Model + angle_tolerance will be used. (Default: None). + """ + tol = tolerance if tolerance else self.tolerance + ang_tol = angle_tolerance if angle_tolerance else self.angle_tolerance + + # merge coplanar faces if requested + if merge_coplanar: + for room in self.rooms: + room.merge_coplanar_faces(tol, ang_tol) + + # intersect adjacencies if requested + if intersect: + Room.intersect_adjacency(self.rooms, tol, ang_tol) + + # solve adjacency + if not overwrite: # only assign new adjacencies + adj_info = Room.solve_adjacency(self.rooms, tol) + else: # overwrite existing Surface BC + adj_faces = Room.find_adjacency(self.rooms, tol) + for face_pair in adj_faces: + face_pair[0].set_adjacency(face_pair[1]) + adj_info = {'adjacent_faces': adj_faces} + + # try to assign the air boundary face type + if air_boundary: + for face_pair in adj_info['adjacent_faces']: + if isinstance(face_pair[0].type, Wall): + face_pair[0].type = face_types.air_boundary + face_pair[1].type = face_types.air_boundary + + # try to assign the adiabatic boundary condition + if adiabatic and ad_bc: + for face_pair in adj_info['adjacent_faces']: + face_pair[0].boundary_condition = ad_bc + face_pair[1].boundary_condition = ad_bc
+ +
[docs] def move(self, moving_vec): + """Move this Model along a vector. + + Args: + moving_vec: A ladybug_geometry Vector3D with the direction and distance + to move the Model. + """ + for room in self._rooms: + room.move(moving_vec) + for face in self._orphaned_faces: + face.move(moving_vec) + for aperture in self._orphaned_apertures: + aperture.move(moving_vec) + for door in self._orphaned_doors: + door.move(moving_vec) + for shade in self._orphaned_shades: + shade.move(moving_vec) + for shade_mesh in self._shade_meshes: + shade_mesh.move(moving_vec) + self.properties.move(moving_vec)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Model by a certain angle around an axis and origin. + + Args: + axis: A ladybug_geometry Vector3D axis representing the axis of rotation. + angle: An angle for rotation in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + for room in self._rooms: + room.rotate(axis, angle, origin) + for face in self._orphaned_faces: + face.rotate(axis, angle, origin) + for aperture in self._orphaned_apertures: + aperture.rotate(axis, angle, origin) + for door in self._orphaned_doors: + door.rotate(axis, angle, origin) + for shade in self._orphaned_shades: + shade.rotate(axis, angle, origin) + for shade_mesh in self._shade_meshes: + shade_mesh.rotate(axis, angle, origin) + self.properties.rotate(axis, angle, origin)
+ +
[docs] def rotate_xy(self, angle, origin): + """Rotate this Model counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + for room in self._rooms: + room.rotate_xy(angle, origin) + for face in self._orphaned_faces: + face.rotate_xy(angle, origin) + for aperture in self._orphaned_apertures: + aperture.rotate_xy(angle, origin) + for door in self._orphaned_doors: + door.rotate_xy(angle, origin) + for shade in self._orphaned_shades: + shade.rotate_xy(angle, origin) + for shade_mesh in self._shade_meshes: + shade_mesh.rotate_xy(angle, origin) + self.properties.rotate_xy(angle, origin)
+ +
[docs] def reflect(self, plane): + """Reflect this Model across a plane with the input normal vector and origin. + + Args: + plane: A ladybug_geometry Plane across which the object will + be reflected. + """ + for room in self._rooms: + room.reflect(plane) + for face in self._orphaned_faces: + face.reflect(plane) + for aperture in self._orphaned_apertures: + aperture.reflect(plane) + for door in self._orphaned_doors: + door.reflect(plane) + for shade in self._orphaned_shades: + shade.reflect(plane) + for shade_mesh in self._shade_meshes: + shade_mesh.reflect(plane) + self.properties.reflect(plane)
+ +
[docs] def scale(self, factor, origin=None): + """Scale this Model by a factor from an origin point. + + Note that using this method does NOT scale the model tolerance and, if + it is desired that this tolerance be scaled with the model geometry, + it must be scaled separately. + + Args: + factor: A number representing how much the object should be scaled. + origin: A ladybug_geometry Point3D representing the origin from which + to scale. If None, it will be scaled from the World origin (0, 0, 0). + """ + for room in self._rooms: + room.scale(factor, origin) + for face in self._orphaned_faces: + face.scale(factor, origin) + for aperture in self._orphaned_apertures: + aperture.scale(factor, origin) + for door in self._orphaned_doors: + door.scale(factor, origin) + for shade in self._orphaned_shades: + shade.scale(factor, origin) + for shade_mesh in self._shade_meshes: + shade_mesh.scale(factor, origin) + self.properties.scale(factor, origin)
+ +
[docs] def generate_exterior_face_grid( + self, dimension, offset=0.1, face_type='Wall', punched_geometry=False): + """Get a gridded Mesh3D offset from the exterior Faces of this Model. + + This will be None if the Model has no exterior Faces. + + Args: + dimension: The dimension of the grid cells as a number. + offset: A number for how far to offset the grid from the base face. + Positive numbers indicate an offset towards the exterior. (Default + is 0.1, which will offset the grid to be 0.1 unit from the faces). + face_type: Text to specify the type of face that will be used to + generate grids. Note that only Faces with Outdoors boundary + conditions will be used, meaning that most Floors will typically + be excluded unless they represent the underside of a cantilever. + Choose from the following. (Default: Wall). + + * Wall + * Roof + * Floor + * All + + punched_geometry: Boolean to note whether the punched_geometry of the faces + should be used (True) with the areas of sub-faces removed from the grid + or the full geometry should be used (False). (Default:False). + """ + # select the correct face type based on the input + face_t = face_type.title() + if face_t == 'Wall': + ft = Wall + elif face_t in ('Roof', 'Roofceiling'): + ft = RoofCeiling + elif face_t == 'All': + ft = (Wall, RoofCeiling, Floor) + elif face_t == 'Floor': + ft = Floor + else: + raise ValueError('Unrecognized face_type "{}".'.format(face_type)) + face_attr = 'punched_geometry' if punched_geometry else 'geometry' + # loop through the faces and generate grids + face_grids = [] + for face in self.faces: + if isinstance(face.type, ft) and \ + isinstance(face.boundary_condition, Outdoors): + try: + f_geo = getattr(face, face_attr) + face_grids.append( + f_geo.mesh_grid(dimension, None, offset, False)) + except AssertionError: # grid tolerance not fine enough + pass + # join the grids together if there are several ones + if len(face_grids) == 1: + return face_grids[0] + elif len(face_grids) > 1: + return Mesh3D.join_meshes(face_grids) + return None
+ +
[docs] def generate_exterior_aperture_grid( + self, dimension, offset=0.1, aperture_type='All'): + """Get a gridded Mesh3D offset from the exterior Apertures of this Model. + + Will be None if the Model has no exterior Apertures. + + Args: + dimension: The dimension of the grid cells as a number. + offset: A number for how far to offset the grid from the base aperture. + Positive numbers indicate an offset towards the exterior while + negative numbers indicate an offset towards the interior, essentially + modeling the value of sun on the building interior. (Default + is 0.1, which will offset the grid to be 0.1 unit from the aperture). + aperture_type: Text to specify the type of Aperture that will be used to + generate grids. Window indicates Apertures in Walls. Choose from + the following. (Default: All). + + * Window + * Skylight + * All + """ + # select the correct face type based on the input + ap_t = aperture_type.title() + if ap_t == 'Window': + ft = Wall + elif ap_t == 'Skylight': + ft = RoofCeiling + elif ap_t == 'All': + ft = (Wall, RoofCeiling, Floor) + else: + raise ValueError('Unrecognized aperture_type "{}".'.format(aperture_type)) + # loop through the faces and generate grids + ap_grids = [] + for face in self.faces: + if isinstance(face.type, ft) and \ + isinstance(face.boundary_condition, Outdoors): + for ap in face.apertures: + try: + ap_grids.append( + ap.geometry.mesh_grid(dimension, None, offset, False)) + except AssertionError: # grid tolerance not fine enough + pass + # join the grids together if there are several ones + if len(ap_grids) == 1: + return ap_grids[0] + elif len(ap_grids) > 1: + return Mesh3D.join_meshes(ap_grids) + return None
+ +
[docs] def simplify_apertures(self, resolve_adjacency=True, tolerance=None): + """Convert all Apertures in this Model to be a simple window ratio. + + This is useful for studies where faster simulation times are desired and + the window ratio is the critical factor driving the results (as opposed + to the detailed geometry of the window). Apertures assigned to concave + Faces will not be simplified given that the Face.apertures_by_ratio method + likely won't improve the cleanliness of the apertures for such cases. + + Args: + resolve_adjacency: Boolean to note whether Room adjacencies should be + re-solved after the Apertures have been simplified. Setting this + to True should ensure that and interior Apertures that are + simplified retain their Surface boundary conditions. If False, + all interior Apertures that have been simplified will have an + Outdoors boundary condition. (Default: True). + tolerance: The maximum difference between point values for them to be + considered equivalent. If None, the Model tolerance will be + used. (Default: None). + """ + tol = tolerance if tolerance else self.tolerance + for room in self._rooms: + room.simplify_apertures(tol) + if resolve_adjacency: + self.solve_adjacency()
+ +
[docs] def rectangularize_apertures( + self, subdivision_distance=None, max_separation=None, merge_all=False, + resolve_adjacency=True, tolerance=None, angle_tolerance=None): + """Convert all Apertures on this Room to be rectangular. + + This is useful when exporting to simulation engines that only accept + rectangular window geometry. This method will always result ing Rooms where + all Apertures are rectangular. However, if the subdivision_distance is not + set, some Apertures may extend past the parent Face or may collide with + one another. + + Args: + subdivision_distance: A number for the resolution at which the + non-rectangular Apertures will be subdivided into smaller + rectangular units. Specifying a number here ensures that the + resulting rectangular Apertures do not extend past the parent + Face or collide with one another. If None, all non-rectangular + Apertures will be rectangularized by taking the bounding rectangle + around the Aperture. (Default: None). + max_separation: A number for the maximum distance between non-rectangular + Apertures at which point the Apertures will be merged into a single + rectangular geometry. This is often helpful when there are several + triangular Apertures that together make a rectangle when they are + merged across their frames. In such cases, this max_separation + should be set to a value that is slightly larger than the window frame. + If None, no merging of Apertures will happen before they are + converted to rectangles. (Default: None). + merge_all: Boolean to note whether all apertures should be merged before + they are rectangularized. If False, only non-rectangular apertures + will be merged before rectangularization. Note that this argument + has no effect when the max_separation is None. (Default: False). + resolve_adjacency: Boolean to note whether Room adjacencies should be + re-solved after the Apertures have been rectangularized. Setting this + to True should ensure that and interior Apertures that are + rectangularized retain their Surface boundary conditions. If False, + all interior Apertures that have been rectangularized will have an + Outdoors boundary condition. (Default: True). + tolerance: The maximum difference between point values for them to be + considered equivalent. If None, the Model tolerance will be + used. (Default: None). + angle_tolerance: The max angle in degrees that the corners of the + rectangle can differ from a right angle before it is not + considered a rectangle. If None, the Model angle_tolerance will be + used. (Default: None). + """ + tol = tolerance if tolerance else self.tolerance + a_tol = angle_tolerance if angle_tolerance else self.angle_tolerance + for room in self._rooms: + room.rectangularize_apertures( + subdivision_distance, max_separation, merge_all, tol, a_tol) + if resolve_adjacency: + self.solve_adjacency()
+ +
[docs] def wall_apertures_by_ratio(self, ratio, tolerance=None): + """Add apertures to all exterior walls given a ratio of aperture to face area. + + Note this method only affects the Models rooms (no orphaned faces) and it + removes any existing apertures and doors on the room's exterior walls. + This method attempts to generate as few apertures as necessary to meet the ratio. + + Args: + ratio: A number between 0 and 1 (but not perfectly equal to 1) + for the desired ratio between aperture area and face area. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. This is used in the event that + this face is concave and an attempt to subdivide the face into a + rectangle is made. It does not affect the ability to produce apertures + for convex Faces. If None, the Model tolerance will be + used. (Default: None). + """ + tol = tolerance if tolerance else self.tolerance + for room in self._rooms: + room.wall_apertures_by_ratio(ratio, tol)
+ +
[docs] def skylight_apertures_by_ratio(self, ratio, tolerance=None): + """Add apertures to all exterior roofs given a ratio of aperture to face area. + + Note this method only affects the Models rooms (no orphaned faces) and + removes any existing apertures and overhead doors on the Room's roofs. + This method attempts to generate as few apertures as necessary to meet the ratio. + + Args: + ratio: A number between 0 and 1 (but not perfectly equal to 1) + for the desired ratio between aperture area and face area. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. This is used in the event that + this face is concave and an attempt to subdivide the face into a + rectangle is made. It does not affect the ability to produce apertures + for convex Faces. If None, the Model tolerance will be + used. (Default: None). + """ + tol = tolerance if tolerance else self.tolerance + for room in self._rooms: + room.skylight_apertures_by_ratio(ratio, tol)
+ +
[docs] def assign_stories_by_floor_height(self, min_difference=2.0, overwrite=False): + """Assign story properties to the rooms of this Model using their floor heights. + + Stories will be named with a standard convention ('Floor1', 'Floor2', etc.). + + Args: + min_difference: An float value to denote the minimum difference + in floor heights that is considered meaningful. This can be used + to ensure rooms like those representing stair landings are grouped + with floors. Default: 2.0, which means that any difference in + floor heights less than 2.0 will be considered a part of the + same story. This assumption is suitable for models in meters. + overwrite: If True, all story properties of this model's rooms will + be overwritten by this method. If False, this method will only + assign stories to Rooms that do not already have a story identifier + already assigned to them. (Default: False). + + Returns: + A list of the unique story names that were assigned to the input rooms. + """ + if overwrite: + for room in self._rooms: + room.story = None + return Room.stories_by_floor_height(self._rooms, min_difference)
+ +
[docs] def convert_to_units(self, units='Meters'): + """Convert all of the geometry in this model to certain units. + + This involves scaling the geometry, scaling the Model tolerance, and + changing the Model's units property. + + Args: + units: Text for the units to which the Model geometry should be + converted. Default: Meters. Choose from the following: + + * Meters + * Millimeters + * Feet + * Inches + * Centimeters + """ + if self.units != units: + scale_fac1 = conversion_factor_to_meters(self.units) + scale_fac2 = conversion_factor_to_meters(units) + scale_fac = scale_fac1 / scale_fac2 + self.scale(scale_fac) + self.tolerance = self.tolerance * scale_fac + self.units = units
+ +
[docs] def rooms_to_orphaned(self): + """Convert all Rooms in this Model to orphaned geometry objects. + + This is useful when the energy load balance of Rooms is not important + and they are only significant as context shading. Note that this method + will effectively discount any geometries with a Surface boundary condition + or with an AirBoundary face type. + """ + for room in self._rooms: + for face in room._faces: + face._parent = None + if not isinstance(face.boundary_condition, Surface) and not \ + isinstance(face.type, AirBoundary): + self._orphaned_faces.append(face) + self._rooms = []
+ +
[docs] def remove_degenerate_geometry(self, tolerance=None): + """Remove any degenerate geometry from the model. + + Degenerate geometry refers to any objects that evaluate to less than 3 vertices + when duplicate and colinear vertices are removed at the tolerance. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered distinct. If None, the + Model's tolerance will be used. (Default: None). + """ + tolerance = self.tolerance if tolerance is None else tolerance + adj_dict = {} # dictionary to track adjacent geometries + for room in self.rooms: + try: + r_adj = room.clean_envelope(adj_dict, tolerance=tolerance) + adj_dict.update(r_adj) + except AssertionError as e: # room removed; likely wrong units + error = 'Failed to remove degenerate geometry for Room {}.\n{}'.format( + room.full_id, e) + raise ValueError(error) + self._remove_degenerate_faces(self._orphaned_faces, tolerance) + self._remove_degenerate_faces(self._orphaned_apertures, tolerance) + self._remove_degenerate_faces(self._orphaned_doors, tolerance) + self._remove_degenerate_faces(self._orphaned_shades, tolerance) + for sm in self._shade_meshes: + sm.triangulate_and_remove_degenerate_faces(tolerance)
+ +
[docs] def triangulate_non_planar_quads(self, tolerance=None): + """Triangulate any non-planar orphaned geometry in the model. + + This method will only planarize the orphaned Faces, Apertures, Doors and + Shades that are quadrilaterals, which usually has a minimal impact on results. + It does not impact the Rooms at all. + + Args: + tolerance: The minimum distance from the geometry plane at which the + geometry is not considered planar. If None, the Model's tolerance + will be used. (Default: None). + """ + tolerance = self.tolerance if tolerance is None else tolerance + self._orphaned_apertures = \ + self._triangulate_quad_faces(self._orphaned_apertures, tolerance) + self._orphaned_doors = \ + self._triangulate_quad_faces(self._orphaned_doors, tolerance) + self._orphaned_shades = \ + self._triangulate_quad_faces(self._orphaned_shades, tolerance)
+ +
[docs] def comparison_report(self, other_model, ignore_deleted=False, ignore_added=False): + """Get a dictionary outlining the differences between this model and another. + + The resulting dictionary will only report top-level objects that are different + between this model and the other. If an object has not changed at all, + then it will not show up in the report. + + Changes to geometry are reported separately from changes in metadata + (aka. properties) for each of the top level objects. + + If the Model units or tolerance are different between the two models, + then the units and tolerance of this model will take precedence and + the other_model will be converted to these units and tolerance for + geometry comparison. + + Args: + other_model: A new Model to which this current model will be compared. + ignore_deleted: A boolean to note whether objects that appear in this + current model but not in the other model should be reported. It is + useful to set this to True when the other model represents only a + subset of the current model. (Default: False). + ignore_added: A boolean to note whether objects that appear in the other + model but not in the current model should be reported. (Default: False). + + Returns: + A dictionary of differences between this model and the other model in + the format below. + """ + # make sure the unit systems of the two models align + tol = self.tolerance + if self.units != other_model.units: + other_model = other_model.duplicate() + other_model.convert_to_units(self.units) + # set up lists and dictionaries of objects for comparison + compare_dict = {'type': 'ComparisonReport'} + self_dict = self.top_level_dict + other_dict = other_model.top_level_dict + # loop through the new objects and detect changes between them + changed, added_objs = [], [] + for obj_id, new_obj in other_dict.items(): + try: + exist_obj = self_dict[obj_id] + change_dict = exist_obj._changed_dict(new_obj, tol) + if change_dict is not None: + changed.append(change_dict) + except KeyError: + added_objs.append(new_obj) + compare_dict['changed_objects'] = changed + # include the added objects in the comparison dictionary + if not ignore_added: + added = [] + for new_obj in added_objs: + added.append(new_obj._base_report_dict('AddedObject')) + compare_dict['added_objects'] = added + # include the deleted objects in the comparison dictionary + if not ignore_deleted: + deleted = [] + for obj_id, exist_obj in self_dict.items(): + try: + new_obj = other_dict[obj_id] + except KeyError: + deleted.append(exist_obj._base_report_dict('DeletedObject')) + compare_dict['deleted_objects'] = deleted + return compare_dict
+ +
[docs] def check_all(self, raise_exception=True, detailed=False): + """Check all of the aspects of the Model for possible errors. + + This includes basic properties like adjacency checks and all geometry checks. + Furthermore, all extension attributes will be checked assuming the extension + Model properties have a check_all function. Note that an exception will + always be raised if the model has a tolerance of zero as this means that + no geometry checks can be performed. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if any Model errors are found. If False, this method will simply + return a text string with all errors that were found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A text string with all errors that were found or a list if detailed is True. + This string (or list) will be empty if no errors were found. + """ + # set up defaults to ensure the method runs correctly + detailed = False if raise_exception else detailed + msgs = [] + # check that a tolerance has been specified in the model + assert self.tolerance != 0, \ + 'Model must have a non-zero tolerance in order to perform geometry checks.' + assert self.angle_tolerance != 0, \ + 'Model must have a non-zero angle_tolerance to perform geometry checks.' + tol = self.tolerance + ang_tol = self.angle_tolerance + + # perform checks for duplicate identifiers, which might mess with other checks + msgs.append(self.check_duplicate_room_identifiers(False, detailed)) + msgs.append(self.check_duplicate_face_identifiers(False, detailed)) + msgs.append(self.check_duplicate_sub_face_identifiers(False, detailed)) + msgs.append(self.check_duplicate_shade_identifiers(False, detailed)) + msgs.append(self.check_duplicate_shade_mesh_identifiers(False, detailed)) + + # perform several checks for the Honeybee schema geometry rules + msgs.append(self.check_planar(tol, False, detailed)) + msgs.append(self.check_self_intersecting(tol, False, detailed)) + # perform checks for degenerate rooms with a test that removes colinear vertices + for room in self.rooms: + try: + new_room = room.duplicate() # duplicate to avoid editing the original + new_room.remove_colinear_vertices_envelope(tol) + except ValueError as e: + deg_msg = str(e) + if detailed: + deg_msg = [{ + 'type': 'ValidationError', + 'code': '000107', + 'error_type': 'Degenerate Room Volume', + 'extension_type': 'Core', + 'element_type': 'Room', + 'element_id': [room.identifier], + 'element_name': [room.display_name], + 'message': deg_msg + }] + msgs.append(deg_msg) + msgs.append(self.check_degenerate_rooms(tol, False, detailed)) + # perform geometry checks related to parent-child relationships + msgs.append(self.check_sub_faces_valid(tol, ang_tol, False, detailed)) + msgs.append(self.check_sub_faces_overlapping(tol, False, detailed)) + msgs.append(self.check_upside_down_faces(ang_tol, False, detailed)) + msgs.append(self.check_rooms_solid(tol, ang_tol, False, detailed)) + + # perform checks related to adjacency relationships + msgs.append(self.check_room_volume_collisions(tol, False, detailed)) + msgs.append(self.check_missing_adjacencies(False, detailed)) + msgs.append(self.check_matching_adjacent_areas(tol, False, detailed)) + msgs.append(self.check_all_air_boundaries_adjacent(False, detailed)) + + # check the extension attributes + ext_msgs = self._properties._check_extension_attr(detailed) + if detailed: + ext_msgs = [m for m in ext_msgs if isinstance(m, list)] + msgs.extend(ext_msgs) + + # output a final report of errors or raise an exception + full_msgs = [msg for msg in msgs if msg] + if detailed: + return [m for msg in full_msgs for m in msg] + full_msg = '\n'.join(full_msgs) + if raise_exception and len(full_msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_duplicate_room_identifiers(self, raise_exception=True, detailed=False): + """Check that there are no duplicate Room identifiers in the model. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if duplicate identifiers are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + return check_duplicate_identifiers( + self._rooms, raise_exception, 'Room', detailed, '000004', 'Core', + 'Duplicate Room Identifier')
+ +
[docs] def check_duplicate_face_identifiers(self, raise_exception=True, detailed=False): + """Check that there are no duplicate Face identifiers in the model. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if duplicate identifiers are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + return check_duplicate_identifiers_parent( + self.faces, raise_exception, 'Face', detailed, '000003', 'Core', + 'Duplicate Face Identifier')
+ +
[docs] def check_duplicate_sub_face_identifiers(self, raise_exception=True, detailed=False): + """Check that there are no duplicate sub-face identifiers in the model. + + Note that both Apertures and Doors are checked for duplicates since the two + are counted together by EnergyPlus. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if duplicate identifiers are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + sub_faces = self.apertures + self.doors + return check_duplicate_identifiers_parent( + sub_faces, raise_exception, 'SubFace', detailed, '000002', 'Core', + 'Duplicate Sub-Face Identifier')
+ +
[docs] def check_duplicate_shade_identifiers(self, raise_exception=True, detailed=False): + """Check that there are no duplicate Shade identifiers in the model. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if duplicate identifiers are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + return check_duplicate_identifiers_parent( + self.shades, raise_exception, 'Shade', detailed, '000001', 'Core', + 'Duplicate Shade Identifier')
+ +
[docs] def check_duplicate_shade_mesh_identifiers( + self, raise_exception=True, detailed=False): + """Check that there are no duplicate ShadeMesh identifiers in the model. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if duplicate identifiers are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + return check_duplicate_identifiers( + self._shade_meshes, raise_exception, 'ShadeMesh', detailed, '000001', 'Core', + 'Duplicate ShadeMesh Identifier')
+ +
[docs] def check_planar(self, tolerance=None, raise_exception=True, detailed=False): + """Check that all of the Model's geometry components are planar. + + This includes all of the Model's Faces, Apertures, Doors and Shades. + + Args: + tolerance: The minimum distance between a given vertex and a the + object's plane at which the vertex is said to lie in the plane. + If None, the Model tolerance will be used. (Default: None). + raise_exception: Boolean to note whether an ValueError should be + raised if a vertex does not lie within the object's plane. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + tolerance = self.tolerance if tolerance is None else tolerance + detailed = False if raise_exception else detailed + msgs = [] + for face in self.faces: + msgs.append(face.check_planar(tolerance, False, detailed)) + for shd in self.shades: + msgs.append(shd.check_planar(tolerance, False, detailed)) + for ap in self.apertures: + msgs.append(ap.check_planar(tolerance, False, detailed)) + for dr in self.doors: + msgs.append(dr.check_planar(tolerance, False, detailed)) + full_msgs = [msg for msg in msgs if msg] + if detailed: + return [m for msg in full_msgs for m in msg] + full_msg = '\n'.join(full_msgs) + if raise_exception and len(full_msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_self_intersecting(self, tolerance=None, raise_exception=True, + detailed=False): + """Check that no edges of the Model's geometry components self-intersect. + + This includes all of the Model's Faces, Apertures, Doors and Shades. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. If None, the + Model tolerance will be used. (Default: None). + raise_exception: If True, a ValueError will be raised if an object + intersects with itself (like a bowtie). (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + tolerance = self.tolerance if tolerance is None else tolerance + detailed = False if raise_exception else detailed + msgs = [] + for room in self.rooms: + msgs.append(room.check_self_intersecting(tolerance, False, detailed)) + for face in self.orphaned_faces: + msgs.append(face.check_self_intersecting(tolerance, False, detailed)) + for shd in self.orphaned_shades: + msgs.append(shd.check_self_intersecting(tolerance, False, detailed)) + for ap in self.orphaned_apertures: + msgs.append(ap.check_self_intersecting(tolerance, False, detailed)) + for dr in self.orphaned_doors: + msgs.append(dr.check_self_intersecting(tolerance, False, detailed)) + full_msgs = [msg for msg in msgs if msg] + if detailed: + return [m for msg in full_msgs for m in msg] + full_msg = '\n'.join(full_msgs) + if raise_exception and len(full_msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_degenerate_rooms( + self, tolerance=None, raise_exception=True, detailed=False): + """Check whether there are degenerate Rooms (with zero volume) within the Model. + + Args: + tolerance: The maximum difference between x, y, and z values + at which face vertices are considered equivalent. If None, the + Model tolerance will be used. (Default: None). + raise_exception: Boolean to note whether a ValueError should be raised + if degenerate Rooms are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + tolerance = self.tolerance if tolerance is None else tolerance + detailed = False if raise_exception else detailed + msgs = [] + for room in self._rooms: + msg = room.check_degenerate(tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if detailed: + return msgs + full_msg = '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_sub_faces_valid(self, tolerance=None, angle_tolerance=None, + raise_exception=True, detailed=False): + """Check that model's sub-faces are co-planar with faces and in their boundary. + + Note this does not check the planarity of the sub-faces themselves, whether + they self-intersect, or whether they have a non-zero area. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. If None, the + Model tolerance will be used. (Default: None). + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered coplanar. + If None, the Model angle_tolerance will be used. (Default: None). + raise_exception: Boolean to note whether a ValueError should be raised + if an sub-face is not valid. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + tolerance = self.tolerance if tolerance is None else tolerance + angle_tolerance = self.angle_tolerance \ + if angle_tolerance is None else angle_tolerance + detailed = False if raise_exception else detailed + msgs = [] + for rm in self._rooms: + msg = rm.check_sub_faces_valid(tolerance, angle_tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + for f in self._orphaned_faces: + msg = f.check_sub_faces_valid(tolerance, angle_tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if detailed: + return msgs + full_msg = '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_sub_faces_overlapping( + self, tolerance=None, raise_exception=True, detailed=False): + """Check that model's sub-faces do not overlap with one another. + + Args: + tolerance: The minimum distance that two sub-faces must overlap in order + for them to be considered overlapping and invalid. If None, the + Model tolerance will be used. (Default: None). + raise_exception: Boolean to note whether a ValueError should be raised + if a sub-faces overlap with one another. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + tolerance = self.tolerance if tolerance is None else tolerance + detailed = False if raise_exception else detailed + msgs = [] + for rm in self._rooms: + msg = rm.check_sub_faces_overlapping(tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + for f in self._orphaned_faces: + msg = f.check_sub_faces_overlapping(tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if detailed: + return msgs + full_msg = '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_upside_down_faces( + self, angle_tolerance=None, raise_exception=True, detailed=False): + """Check that the Model's Faces have the correct direction for the face type. + + This method will only report Floors that are pointing upwards or RoofCeilings + that are pointed downwards. These cases are likely modeling errors and are in + danger of having their vertices flipped by EnergyPlus, causing them to + not see the sun. + + Args: + angle_tolerance: The max angle in degrees that the Face normal can + differ from up or down before it is considered a case of a downward + pointing RoofCeiling or upward pointing Floor. If None, it + will be the model angle tolerance. (Default: None). + raise_exception: Boolean to note whether an ValueError should be + raised if the Face is an an upward pointing Floor or a downward + pointing RoofCeiling. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + a_tol = self.angle_tolerance if angle_tolerance is None else angle_tolerance + detailed = False if raise_exception else detailed + msgs = [] + for rm in self._rooms: + msg = rm.check_upside_down_faces(a_tol, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if detailed: + return msgs + full_msg = '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_rooms_solid(self, tolerance=None, angle_tolerance=None, + raise_exception=True, detailed=False): + """Check whether the Model's rooms are closed solid to within tolerances. + + Args: + tolerance: tolerance: The maximum difference between x, y, and z values + at which face vertices are considered equivalent. If None, the Model + tolerance will be used. (Default: None). + angle_tolerance: The max angle difference in degrees that vertices are + allowed to differ from one another in order to consider them colinear. + If None, the Model angle_tolerance will be used. (Default: None). + raise_exception: Boolean to note whether a ValueError should be raised + if the room geometry does not form a closed solid. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + tolerance = self.tolerance if tolerance is None else tolerance + angle_tolerance = self.angle_tolerance \ + if angle_tolerance is None else angle_tolerance + detailed = False if raise_exception else detailed + msgs = [] + for room in self._rooms: + msg = room.check_solid(tolerance, angle_tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if detailed: + return msgs + full_msg = '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_room_volume_collisions( + self, tolerance=None, raise_exception=True, detailed=False): + """Check whether the Model's rooms collide with one another beyond the tolerance. + + Args: + tolerance: tolerance: The maximum difference between x, y, and z values + at which face vertices are considered equivalent. If None, the Model + tolerance will be used. (Default: None). + raise_exception: Boolean to note whether a ValueError should be raised + if the room geometry does not form a closed solid. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + # set default values + tolerance = self.tolerance if tolerance is None else tolerance + detailed = False if raise_exception else detailed + # group the rooms by their floor heights to enable collision checking + if len(self.rooms) == 0: + return [] if detailed else '' + room_groups, _ = Room.group_by_floor_height(self.rooms, tolerance) + # loop trough the groups and detect collisions + msgs = [] + for rg in room_groups: + msg = Room.check_room_volume_collisions(rg, tolerance, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if detailed: + return msgs + full_msg = '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_missing_adjacencies(self, raise_exception=True, detailed=False): + """Check that all Faces Apertures, and Doors have adjacent objects in the model. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if invalid adjacencies are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + detailed = False if raise_exception else detailed + # loop through all objects and get their adjacent object + room_ids = [] + face_bc_ids, face_set = [], set() + ap_bc_ids, ap_set = [], set() + door_bc_ids, dr_set = [], set() + sr = [] + for room in self._rooms: + for face in room._faces: + if isinstance(face.boundary_condition, Surface): + sr.append(self._self_adj_check( + 'Face', face, face_bc_ids, room_ids, face_set, detailed)) + for ap in face.apertures: + assert isinstance(ap.boundary_condition, Surface), \ + 'Aperture "{}" must have Surface boundary condition ' \ + 'if the parent Face has a Surface BC.'.format(ap.full_id) + sr.append(self._self_adj_check( + 'Aperture', ap, ap_bc_ids, room_ids, ap_set, detailed)) + for dr in face.doors: + assert isinstance(dr.boundary_condition, Surface), \ + 'Door "{}" must have Surface boundary condition ' \ + 'if the parent Face has a Surface BC.'.format(dr.full_id) + sr.append(self._self_adj_check( + 'Door', dr, door_bc_ids, room_ids, dr_set, detailed)) + # check to see if the adjacent objects are in the model + mr = self._missing_adj_check(self.rooms_by_identifier, room_ids) + mf = self._missing_adj_check(self.faces_by_identifier, face_bc_ids) + ma = self._missing_adj_check(self.apertures_by_identifier, ap_bc_ids) + md = self._missing_adj_check(self.doors_by_identifier, door_bc_ids) + # if not, go back and find the original object with the missing BC object + msgs = [] + if len(mr) != 0 or len(mf) != 0 or len(ma) != 0 or len(md) != 0: + for room in self._rooms: + for face in room._faces: + if isinstance(face.boundary_condition, Surface): + bc_obj, bc_room = self._adj_objects(face) + if bc_obj in mf: + self._missing_adj_msg( + msgs, face, bc_obj, 'Face', 'Face', detailed) + if bc_room in mr: + self._missing_adj_msg( + msgs, face, bc_room, 'Face', 'Room', detailed) + for ap in face.apertures: + bc_obj, bc_room = self._adj_objects(ap) + if bc_obj in ma: + self._missing_adj_msg( + msgs, ap, bc_obj, 'Aperture', 'Aperture', detailed) + if bc_room in mr: + self._missing_adj_msg( + msgs, ap, bc_room, 'Aperture', 'Room', detailed) + for dr in face.doors: + bc_obj, bc_room = self._adj_objects(dr) + if bc_obj in md: + self._missing_adj_msg( + msgs, dr, bc_obj, 'Door', 'Door', detailed) + if bc_room in mr: + self._missing_adj_msg( + msgs, dr, bc_room, 'Door', 'Room', detailed) + # return the final error messages + all_msgs = [m for m in sr + msgs if m] + if detailed: + return [m for msg in all_msgs for m in msg] + msg = '\n'.join(all_msgs) + if msg != '' and raise_exception: + raise ValueError(msg) + return msg
+ +
[docs] def check_matching_adjacent_areas(self, tolerance=None, raise_exception=True, + detailed=False): + """Check that all adjacent Faces have areas that match within the tolerance. + + This is required for energy simulation in order to get matching heat flow + across adjacent Faces. Otherwise, conservation of energy is violated. + Note that, if there are missing adjacencies in the model, the message from + this method will simply note this fact without reporting on mis-matched areas. + + Args: + tolerance: tolerance: The maximum difference between x, y, and z values + at which face vertices are considered equivalent. If None, the Model + tolerance will be used. (Default: None). + raise_exception: Boolean to note whether a ValueError should be raised + if invalid adjacencies are found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + tolerance = self.tolerance if tolerance is None else tolerance + detailed = False if raise_exception else detailed + + # first gather all interior faces in the model and their adjacent object + base_faces, adj_ids = [], [] + for room in self._rooms: + for face in room._faces: + if isinstance(face.boundary_condition, Surface): + base_faces.append(face) + adj_ids.append(face.boundary_condition.boundary_condition_object) + + # get the adjacent faces + try: + adj_faces = self.faces_by_identifier(adj_ids) + except ValueError as e: # the model has missing adjacencies + if detailed: # the user will get a more detailed error in honeybee-core + return [] + else: + msg = 'Matching adjacent areas could not be verified because ' \ + 'of missing adjacencies in the model. \n{}'.format(e) + if raise_exception: + raise ValueError(msg) + return msg + + # loop through the adjacent face pairs and report if areas are not matched + full_msgs, reported_items = [], set() + for base_f, adj_f in zip(base_faces, adj_faces): + if (base_f.identifier, adj_f.identifier) in reported_items: + continue + tol_area = math.sqrt(base_f.area) * tolerance + if abs(base_f.area - adj_f.area) > tol_area: + f_msg = 'Face "{}" with area {} is adjacent to Face "{}" with area {}.' \ + ' This difference is greater than the tolerance of {}.'.format( + base_f.full_id, base_f.area, adj_f.full_id, adj_f.area, tolerance + ) + f_msg = self._validation_message_child( + f_msg, base_f, detailed, '000205', + error_type='Mismatched Area Adjacency') + if detailed: + f_msg['element_id'].append(adj_f.identifier) + f_msg['element_name'].append(adj_f.display_name) + parents = [] + rel_obj = adj_f + while getattr(rel_obj, '_parent', None) is not None: + rel_obj = getattr(rel_obj, '_parent') + par_dict = { + 'parent_type': rel_obj.__class__.__name__, + 'id': rel_obj.identifier, + 'name': rel_obj.display_name + } + parents.append(par_dict) + f_msg['parents'].append(parents) + full_msgs.append(f_msg) + reported_items.add((adj_f.identifier, base_f.identifier)) + else: # check to ensure the shapes are the same when vertices are removed + try: + base_f_geo = base_f.geometry.remove_colinear_vertices(tolerance) + adj_f_geo = adj_f.geometry.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate Faces to ignore + continue + if len(base_f_geo) != len(adj_f_geo): + f_msg = 'Face "{}" is a shape with {} distinct vertices and is ' \ + 'adjacent to Face "{}", which has {} distinct vertices' \ + ' within the model tolerance of {}.'.format( + base_f.full_id, len(base_f_geo), + adj_f.full_id, len(adj_f_geo), tolerance + ) + f_msg = self._validation_message_child( + f_msg, base_f, detailed, '000205', + error_type='Mismatched Area Adjacency') + if detailed: + f_msg['element_id'].append(adj_f.identifier) + f_msg['element_name'].append(adj_f.display_name) + parents = [] + rel_obj = adj_f + while getattr(rel_obj, '_parent', None) is not None: + rel_obj = getattr(rel_obj, '_parent') + par_dict = { + 'parent_type': rel_obj.__class__.__name__, + 'id': rel_obj.identifier, + 'name': rel_obj.display_name + } + parents.append(par_dict) + f_msg['parents'].append(parents) + full_msgs.append(f_msg) + reported_items.add((adj_f.identifier, base_f.identifier)) + + # ensure that adjacent sub-faces have matching areas + if base_f.has_sub_faces: + base_subs, adj_subs, sub_ids = [], [], [] + for sf in base_f.sub_faces: + if isinstance(sf.boundary_condition, Surface): + base_subs.append(sf) + sub_ids.append(sf.boundary_condition.boundary_condition_object) + missing_sfs = False + for obj_id in sub_ids: + for adj_sf in adj_f.sub_faces: + if adj_sf.identifier == obj_id: + adj_subs.append(adj_sf) + break + else: # missing sub-face adjacencies will get reported elsewhere + missing_sfs = True + if not missing_sfs: + for base_sf, adj_sf in zip(base_subs, adj_subs): + tol_area = math.sqrt(base_sf.area) * tolerance + if abs(base_sf.area - adj_sf.area) > tol_area: + f_msg = 'SubFace "{}" with area {} is adjacent to ' \ + 'SubFace "{}" with area {}. This difference is greater ' \ + 'than the tolerance of {}.'.format( + base_sf.full_id, base_sf.area, + adj_sf.full_id, adj_sf.area, tolerance + ) + f_msg = self._validation_message_child( + f_msg, base_sf, detailed, '000205', + error_type='Mismatched Area Adjacency') + if detailed: + f_msg['element_id'].append(adj_sf.identifier) + f_msg['element_name'].append(adj_sf.display_name) + parents = [] + rel_obj = adj_sf + while getattr(rel_obj, '_parent', None) is not None: + rel_obj = getattr(rel_obj, '_parent') + par_dict = { + 'parent_type': rel_obj.__class__.__name__, + 'id': rel_obj.identifier, + 'name': rel_obj.display_name + } + parents.append(par_dict) + f_msg['parents'].append(parents) + full_msgs.append(f_msg) + reported_items.add((adj_f.identifier, base_f.identifier)) + + # return all of the validation error messages that were gathered + full_msg = full_msgs if detailed else '\n'.join(full_msgs) + if raise_exception and len(full_msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_all_air_boundaries_adjacent(self, raise_exception=True, detailed=False): + """Check that all Faces with the AirBoundary type are adjacent to other Faces. + + This is a requirement for energy simulation. + + Args: + raise_exception: Boolean to note whether a ValueError should be raised + if an AirBoundary without an adjacency is found. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + detailed = False if raise_exception else detailed + msgs = [] + for face in self.faces: + if isinstance(face.type, AirBoundary) and not \ + isinstance(face.boundary_condition, Surface): + msg = 'Face "{}" is an AirBoundary but is not adjacent ' \ + 'to another Face.'.format(face.full_id) + msg = self._validation_message_child( + msg, face, detailed, '000206', error_type='Non-Adjacent AirBoundary') + msgs.append(msg) + if detailed: + return msgs + full_msg = '\n'.join(msgs) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def triangulated_apertures(self): + """Get triangulated versions of the model Apertures that have more than 4 sides. + + This is necessary for energy simulation since EnergyPlus cannot accept + sub-faces with more than 4 sides. Note that this method does not alter the + Apertures within the Model object but just returns a list of modified + Apertures that all have 3 or 4 sides. + + Returns: + A tuple with two elements + + - triangulated_apertures: A list of lists where each list is a set of + triangle Apertures meant to replace an Aperture with more than + 4 sides in the model. + + - parents_to_edit: An list of lists that parallels the triangulated + apertures in that each item represents an Aperture that has been + triangulated in the model. However, each of these lists holds between + 1 and 3 values for the identifiers of the original aperture and parents + of the aperture. This information is intended to help edit parent + faces that have had their child faces triangulated. The 3 values + are as follows: + + * 0 = The identifier of the original Aperture that was triangulated. + * 1 = The identifier of the parent Face of the original Aperture + (if it exists). + * 2 = The identifier of the parent Room of the parent Face of the + original Aperture (if it exists). + """ + triangulated_apertures = [] + parents_to_edit = [] + all_apertures = self.apertures + adj_check = [] # confirms when interior apertures are triangulated by adjacency + for ap in all_apertures: + if len(ap.geometry) <= 4: + pass + elif ap.identifier not in adj_check: + # generate the new triangulated apertures + ap_mesh3d = ap.triangulated_mesh3d + new_verts = [[ap_mesh3d[v] for v in face] for face in ap_mesh3d.faces] + new_ap_geo = [Face3D(verts, ap.geometry.plane) for verts in new_verts] + new_ap_geo = self._remove_sliver_geometries(new_ap_geo) + new_aps, parent_edit_info = self._replace_aperture(ap, new_ap_geo) + triangulated_apertures.append(new_aps) + if parent_edit_info is not None: + parents_to_edit.append(parent_edit_info) + # coordinate new apertures with any adjacent apertures + if isinstance(ap.boundary_condition, Surface): + bc_obj_identifier = ap.boundary_condition.boundary_condition_object + for other_ap in all_apertures: + if other_ap.identifier == bc_obj_identifier: + adj_ap = other_ap + break + new_adj_ap_geo = [face.flip() for face in new_ap_geo] + new_adj_aps, edit_in = self._replace_aperture(adj_ap, new_adj_ap_geo) + for new_ap, new_adj_ap in zip(new_aps, new_adj_aps): + new_ap.set_adjacency(new_adj_ap) + triangulated_apertures.append(new_adj_aps) + if edit_in is not None: + parents_to_edit.append(edit_in) + adj_check.append(adj_ap.identifier) + return triangulated_apertures, parents_to_edit
+ +
[docs] def triangulated_doors(self): + """Get triangulated versions of the model Doors that have more than 4 sides. + + This is necessary for energy simulation since EnergyPlus cannot accept + sub-faces with more than 4 sides. Note that this method does not alter the + Doors within the Model object but just returns a list of Doors that + all have 3 or 4 sides. + + Returns: + A tuple with two elements + + - triangulated_doors: A list of lists where each list is a set of triangle + Doors meant to replace a Door with more than 4 sides in the model. + + - parents_to_edit: An list of lists that parallels the triangulated_doors + in that each item represents a Door that has been triangulated + in the model. However, each of these lists holds between 1 and 3 values + for the identifiers of the original door and parents of the door. + This information is intended to help edit parent faces that have had + their child faces triangulated. The 3 values are as follows: + + * 0 = The identifier of the original Door that was triangulated. + * 1 = The identifier of the parent Face of the original Door + (if it exists). + * 2 = The identifier of the parent Room of the parent Face of the + original Door (if it exists). + """ + triangulated_doors = [] + parents_to_edit = [] + all_doors = self.doors + adj_check = [] # confirms when interior doors are triangulated by adjacency + for dr in all_doors: + if len(dr.geometry) <= 4: + pass + elif dr.identifier not in adj_check: + # generate the new triangulated doors + dr_mesh3d = dr.triangulated_mesh3d + new_verts = [[dr_mesh3d[v] for v in face] for face in dr_mesh3d.faces] + new_dr_geo = [Face3D(verts, dr.geometry.plane) for verts in new_verts] + new_dr_geo = self._remove_sliver_geometries(new_dr_geo) + new_drs, parent_edit_info = self._replace_door(dr, new_dr_geo) + triangulated_doors.append(new_drs) + if parent_edit_info is not None: + parents_to_edit.append(parent_edit_info) + # coordinate new doors with any adjacent doors + if isinstance(dr.boundary_condition, Surface): + bc_obj_identifier = dr.boundary_condition.boundary_condition_object + for other_dr in all_doors: + if other_dr.identifier == bc_obj_identifier: + adj_dr = other_dr + break + new_adj_dr_geo = [face.flip() for face in new_dr_geo] + new_adj_drs, edit_in = self._replace_door(adj_dr, new_adj_dr_geo) + for new_dr, new_adj_dr in zip(new_drs, new_adj_drs): + new_dr.set_adjacency(new_adj_dr) + triangulated_doors.append(new_adj_drs) + if edit_in is not None: + parents_to_edit.append(edit_in) + adj_check.append(adj_dr.identifier) + return triangulated_doors, parents_to_edit
+ + def _remove_sliver_geometries(self, face3ds): + """Remove sliver geometries from a list of Face3Ds.""" + clean_face3ds = [] + for face in face3ds: + try: + if face.area >= self.tolerance: + clean_face3ds.append(face.remove_colinear_vertices(self.tolerance)) + except ValueError: + pass # degenerate triangle; remove it + return clean_face3ds + + def _remove_degenerate_faces(self, hb_objs, tolerance): + """Remove degenerate Faces, Apertures, Doors, or Shades from a list.""" + i_to_remove = [] + for i, face in enumerate(hb_objs): + try: + face.remove_colinear_vertices(tolerance) + except ValueError: # degenerate face found! + i_to_remove.append(i) + for i in reversed(i_to_remove): + hb_objs.pop(i) + + def _triangulate_quad_faces(self, hb_objs, tolerance): + """Triangulate quad geometries.""" + clean_objects = [] + for i, geo_obj in enumerate(hb_objs): + geo = geo_obj.geometry + if len(geo.vertices) == 4 and not geo.check_planar(tolerance, False): + verts = geo.vertices + obj_1 = geo_obj.duplicate() + obj_1.identifier = '{}..0'.format(geo_obj.identifier) + obj_1._geometry = Face3D((verts[0], verts[1], verts[2])) + clean_objects.append(obj_1) + obj_2 = geo_obj.duplicate() + obj_2.identifier = '{}..1'.format(geo_obj.identifier) + obj_2._geometry = Face3D((verts[2], verts[3], verts[0])) + clean_objects.append(obj_2) + else: + clean_objects.append(geo_obj) + return clean_objects + + def _replace_aperture(self, original_ap, new_ap_geo): + """Get new Apertures generated from new_ap_geo and the properties of original_ap. + + Note that this method does not re-link the new apertures to new adjacent + apertures in the model. This must be done with the returned apertures. + + Args: + original_ap: The original Aperture object from which properties + are borrowed. + new_ap_geo: A list of ladybug_geometry Face3D objects that will be used + to generate the new Aperture objects. + + Returns: + A tuple with two elements + + - new_aps: A list of the new Aperture objects. + + - parent_edit_info: An array of up to 3 values meant to help edit + parents that have had their child faces triangulated. The 3 values + are as follows: + + * 0 = The identifier of the original Aperture that was triangulated. + * 1 = The identifier of the parent Face of the original Aperture + (if it exists). + * 2 = The identifier of the parent Room of the parent Face of the + original Aperture (if it exists). + """ + # make the new Apertures and add them to the model + new_aps = [] + for i, ap_face in enumerate(new_ap_geo): + new_ap = Aperture('{}..{}'.format(original_ap.identifier, i), + ap_face, None, original_ap.is_operable) + new_ap._properties = original_ap._properties # transfer extension properties + if original_ap.has_parent: + new_ap._parent = original_ap.parent + new_aps.append(new_ap) + + # transfer over any child shades to the first triangulated object + if len(original_ap._indoor_shades) != 0: + new_shds = [shd.duplicate() for shd in original_ap._indoor_shades] + new_aps[0].add_indoor_shades(new_shds) + if len(original_ap._outdoor_shades) != 0: + new_shds = [shd.duplicate() for shd in original_ap._outdoor_shades] + new_aps[0].add_outdoor_shades(new_shds) + + # create the parent edit info + parent_edit_info = [original_ap.identifier] + if original_ap.has_parent: + parent_edit_info.append(original_ap.parent.identifier) + if original_ap.parent.has_parent: + parent_edit_info.append(original_ap.parent.parent.identifier) + return new_aps, parent_edit_info + + def _replace_door(self, original_dr, new_dr_geo): + """Get new Doors generated from new_dr_geo and the properties of original_dr. + + Note that this method does not re-link the new doors to new adjacent + doors in the model. This must be done with the returned doors. + + Args: + original_dr: The original Door object from which properties + are borrowed. + new_dr_geo: A list of ladybug_geometry Face3D objects that will be used + to generate the new Door objects. + + Returns: + A tuple with four elements + + - new_drs: A list of the new Door objects. + + - parent_edit_info: An array of up to 3 values meant to help edit + parents that have had their child faces triangulated. The 3 values + are as follows: + + * 0 = The identifier of the original Door that was triangulated. + * 1 = The identifier of the parent Face of the original Door + (if it exists). + * 2 = The identifier of the parent Room of the parent Face of the + original Door (if it exists). + """ + # make the new doors and add them to the model + new_drs = [] + for i, dr_face in enumerate(new_dr_geo): + new_dr = Door('{}..{}'.format(original_dr.identifier, i), dr_face) + new_dr._properties = original_dr._properties # transfer extension properties + if original_dr.has_parent: + new_dr._parent = original_dr.parent + new_drs.append(new_dr) + + # transfer over any child shades to the first triangulated object + if len(original_dr._indoor_shades) != 0: + new_shds = [shd.duplicate() for shd in original_dr._indoor_shades] + new_drs[0].add_indoor_shades(new_shds) + if len(original_dr._outdoor_shades) != 0: + new_shds = [shd.duplicate() for shd in original_dr._outdoor_shades] + new_drs[0].add_outdoor_shades(new_shds) + + # create the parent edit info + parent_edit_info = [original_dr.identifier] + if original_dr.has_parent: + parent_edit_info.append(original_dr.parent.identifier) + if original_dr.parent.has_parent: + parent_edit_info.append(original_dr.parent.parent.identifier) + return new_drs, parent_edit_info + + @property + def to(self): + """Model writer object. + + Use this method to access Writer class to write the model in other formats. + + Usage: + + .. code-block:: python + + model.to.idf(model) -> idf string. + model.to.radiance(model) -> Radiance string. + """ + return writer + +
[docs] def to_dict(self, included_prop=None, triangulate_sub_faces=False, + include_plane=True): + """Return Model as a dictionary. + + Args: + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + triangulate_sub_faces: Boolean to note whether sub-faces (including + Apertures and Doors) should be triangulated if they have more than + 4 sides (True) or whether they should be left as they are (False). + This triangulation is necessary when exporting directly to EnergyPlus + since it cannot accept sub-faces with more than 4 vertices. Note that + setting this to True will only triangulate sub-faces with parent Faces + that also have parent Rooms since orphaned Apertures and Faces are + not relevant for energy simulation. (Default: False). + include_plane: Boolean to note wether the planes of the Face3Ds should be + included in the output. This can preserve the orientation of the + X/Y axes of the planes but is not required and can be removed to + keep the dictionary smaller. (Default: True). + """ + # write all of the geometry objects and their properties + base = {'type': 'Model'} + base['identifier'] = self.identifier + base['display_name'] = self.display_name + base['units'] = self.units + base['properties'] = self.properties.to_dict(included_prop) + if self._rooms != []: + base['rooms'] = [r.to_dict(True, included_prop, include_plane) + for r in self._rooms] + if self._orphaned_faces != []: + base['orphaned_faces'] = [f.to_dict(True, included_prop, include_plane) + for f in self._orphaned_faces] + if self._orphaned_apertures != []: + base['orphaned_apertures'] = [ap.to_dict(True, included_prop, include_plane) + for ap in self._orphaned_apertures] + if self._orphaned_doors != []: + base['orphaned_doors'] = [dr.to_dict(True, included_prop, include_plane) + for dr in self._orphaned_doors] + if self._orphaned_shades != []: + base['orphaned_shades'] = [shd.to_dict(True, included_prop, include_plane) + for shd in self._orphaned_shades] + if self._shade_meshes != []: + base['shade_meshes'] = [sm.to_dict(True, included_prop) + for sm in self._shade_meshes] + if self.tolerance != 0: + base['tolerance'] = self.tolerance + if self.angle_tolerance != 0: + base['angle_tolerance'] = self.angle_tolerance + + # triangulate sub-faces if this was requested + if triangulate_sub_faces: + apertures, parents_to_edit = self.triangulated_apertures() + for tri_aps, edit_infos in zip(apertures, parents_to_edit): + if len(edit_infos) == 3: + for room in base['rooms']: + if room['identifier'] == edit_infos[2]: + break + for face in room['faces']: + if face['identifier'] == edit_infos[1]: + break + for i, ap in enumerate(face['apertures']): + if ap['identifier'] == edit_infos[0]: + break + del face['apertures'][i] + face['apertures'].extend( + [a.to_dict(True, included_prop) for a in tri_aps]) + doors, parents_to_edit = self.triangulated_doors() + for tri_drs, edit_infos in zip(doors, parents_to_edit): + if len(edit_infos) == 3: + for room in base['rooms']: + if room['identifier'] == edit_infos[2]: + break + for face in room['faces']: + if face['identifier'] == edit_infos[1]: + break + for i, ap in enumerate(face['doors']): + if ap['identifier'] == edit_infos[0]: + break + del face['doors'][i] + face['doors'].extend( + [dr.to_dict(True, included_prop) for dr in tri_drs]) + + # write in the optional keys if they are not None + if self.user_data is not None: + base['user_data'] = self.user_data + if folders.honeybee_schema_version is not None: + base['version'] = folders.honeybee_schema_version_str + + return base
+ +
[docs] def to_hbjson(self, name=None, folder=None, indent=None, + included_prop=None, triangulate_sub_faces=False): + """Write Honeybee model to HBJSON. + + Args: + name: A text string for the name of the HBJSON file. If None, the model + identifier wil be used. (Default: None). + folder: A text string for the directory where the HBJSON will be written. + If unspecified, the default simulation folder will be used. This + is usually at "C:\\Users\\USERNAME\\simulation." + indent: A positive integer to set the indentation used in the resulting + HBJSON file. (Default: None). + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + triangulate_sub_faces: Boolean to note whether sub-faces (including + Apertures and Doors) should be triangulated if they have more than + 4 sides (True) or whether they should be left as they are (False). + This triangulation is necessary when exporting directly to EnergyPlus + since it cannot accept sub-faces with more than 4 vertices. Note that + setting this to True will only triangulate sub-faces with parent Faces + that also have parent Rooms since orphaned Apertures and Faces are + not relevant for energy simulation. (Default: False). + """ + # create dictionary from the Honeybee Model + hb_dict = self.to_dict(included_prop=included_prop, + triangulate_sub_faces=triangulate_sub_faces) + + # set up a name and folder for the HBJSON + if name is None: + name = self.identifier + file_name = name if name.lower().endswith('.hbjson') or \ + name.lower().endswith('.json') else '{}.hbjson'.format(name) + folder = folder if folder is not None else folders.default_simulation_folder + hb_file = os.path.join(folder, file_name) + # write HBJSON + with open(hb_file, 'w') as fp: + json.dump(hb_dict, fp, indent=indent) + return hb_file
+ +
[docs] def to_hbpkl(self, name=None, folder=None, included_prop=None, + triangulate_sub_faces=False): + """Write Honeybee model to compressed pickle file (HBpkl). + + Args: + name: A text string for the name of the pickle file. If None, the model + identifier wil be used. (Default: None). + folder: A text string for the directory where the pickle file will be + written. If unspecified, the default simulation folder will be used. + This is usually at "C:\\Users\\USERNAME\\simulation." + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + triangulate_sub_faces: Boolean to note whether sub-faces (including + Apertures and Doors) should be triangulated if they have more than + 4 sides (True) or whether they should be left as they are (False). + This triangulation is necessary when exporting directly to EnergyPlus + since it cannot accept sub-faces with more than 4 vertices. Note that + setting this to True will only triangulate sub-faces with parent Faces + that also have parent Rooms since orphaned Apertures and Faces are + not relevant for energy simulation. (Default: False). + """ + # create dictionary from the Honeybee Model + hb_dict = self.to_dict(included_prop=included_prop, + triangulate_sub_faces=triangulate_sub_faces) + + # set up a name and folder for the HBpkl + if name is None: + name = self.identifier + file_name = name if name.lower().endswith('.hbpkl') or \ + name.lower().endswith('.pkl') else '{}.hbpkl'.format(name) + folder = folder if folder is not None else folders.default_simulation_folder + hb_file = os.path.join(folder, file_name) + # write the Model dictionary into a file + with open(hb_file, 'wb') as fp: + pickle.dump(hb_dict, fp) + return hb_file
+ +
[docs] def to_stl(self, name=None, folder=None): + """Write Honeybee model to an ASCII STL file. + + Note that all geometry is triangulated when it is converted to STL. + + Args: + name: A text string for the name of the STL file. If None, the model + identifier wil be used. (Default: None). + folder: A text string for the directory where the STL will be written. + If unspecified, the default simulation folder will be used. This + is usually at "C:\\Users\\USERNAME\\simulation." + """ + # set up a name and folder for the STL + if name is None: + name = self.identifier + file_name = name if name.lower().endswith('.stl') else '{}.stl'.format(name) + folder = folder if folder is not None else folders.default_simulation_folder + + # collect all of the Face3Ds across the model as triangles and normals + all_geo = [] + for face in self.faces: + all_geo.append(face.punched_geometry) + for ap in self.apertures: + all_geo.append(ap.geometry) + for dr in self.doors: + all_geo.append(dr.geometry) + for shd in self.doors: + all_geo.append(shd.geometry) + + # convert the Face3Ds into a format for export to STL + _face_vertices, _face_normals = [], [] + for face_3d in all_geo: + # add the geometry of a Face3D to the lists for STL export + if len(face_3d) == 3: + _face_vertices.append(face_3d.vertices) + _face_normals.append(face_3d.normal) + else: + tri_mesh = face_3d.triangulated_mesh3d + for m_fac in tri_mesh.face_vertices: + _face_vertices.append(m_fac) + _face_normals.append(face_3d.normal) + + # convert any shade meshes into STL vertices + for sm in self._shade_meshes: + for fvs, fns in zip(sm.geometry.face_vertices, sm.geometry.face_normals): + _face_vertices.append(fvs) + _face_normals.append(fns) + + # write the geometry into an STL file + stl_obj = STL(_face_vertices, _face_normals, self.identifier) + return stl_obj.to_file(folder, file_name)
+ + def _all_objects(self): + """Get a single list of all the Honeybee objects in a Model.""" + return self._rooms + self._orphaned_faces + self._orphaned_shades + \ + self._orphaned_apertures + self._orphaned_doors + self._shade_meshes + +
[docs] @staticmethod + def conversion_factor_to_meters(units): + """Get the conversion factor to meters based on input units. + + Args: + units: Text for the units. Choose from the following: + + * Meters + * Millimeters + * Feet + * Inches + * Centimeters + + Returns: + A number for the conversion factor, which should be multiplied by + all distance units taken from Rhino geometry in order to convert + them to meters. + """ + return conversion_factor_to_meters(units)
+ + def _self_adj_check(self, obj_type, hb_obj, bc_ids, room_ids, bc_set, detailed): + """Check that an adjacent object is referencing itself or its own room. + + A check will also be performed to ensure the adjacent object doesn't already + have an adjacent pair in the model. + """ + bc_objs = hb_obj.boundary_condition.boundary_condition_objects + bc_obj, bc_room = bc_objs[0], bc_objs[-1] + bc_ids.append(bc_obj) + room_ids.append(bc_room) + msgs = [] + # first ensure that the object is not referencing itself + if hb_obj.identifier == bc_obj: + msg = '{} "{}" cannot reference itself in its Surface boundary ' \ + 'condition.'.format(obj_type, hb_obj.full_id) + msg = self._validation_message_child( + msg, hb_obj, detailed, '000201', + error_type='Self-Referential Adjacency') + msgs.append(msg) + # then ensure that the object is not referencing its own room + if hb_obj.has_parent and hb_obj.parent.has_parent: + if hb_obj.parent.parent.identifier == bc_room: + msg = '{} "{}" and its adjacent object "{}" cannot be a part of the ' \ + 'same Room "{}".'.format(obj_type, hb_obj.full_id, bc_obj, bc_room) + msg = self._validation_message_child( + msg, hb_obj, detailed, '000202', + error_type='Intra-Room Adjacency') + msgs.append(msg) + # lastly make sure the adjacent object doesn't already have an adjacency + if bc_obj in bc_set: + msg = '{} "{}" is adjacent to object "{}", which has another adjacent ' \ + 'object in the Model.'.format(obj_type, hb_obj.full_id, bc_obj) + msg = self._validation_message_child( + msg, hb_obj, detailed, '000203', + error_type='Object with Multiple Adjacencies') + msgs.append(msg) + else: + bc_set.add(bc_obj) + return msgs if detailed else ''.join(msgs) + + def _missing_adj_msg(self, messages, hb_obj, bc_obj, + obj_type='Face', bc_obj_type='Face', detailed=False): + msg = '{} "{}" has an adjacent {} that is missing from the model: ' \ + '{}'.format(obj_type, hb_obj.full_id, bc_obj_type, bc_obj) + msg = self._validation_message_child( + msg, hb_obj, detailed, '000204', error_type='Missing Adjacency') + if detailed: + messages.append([msg]) + else: + messages.append(msg) + + @staticmethod + def _missing_adj_check(id_checking_function, bc_ids): + """Check whether adjacencies are missing from a model.""" + try: + id_checking_function(bc_ids) + return [] + except ValueError as e: + id_pattern = re.compile('\"([^"]*)\"') + return [obj_id for obj_id in id_pattern.findall(str(e))] + + @staticmethod + def _adj_objects(hb_obj): + """Check that an adjacent object is referencing itself.""" + bc_objs = hb_obj.boundary_condition.boundary_condition_objects + return bc_objs[0], bc_objs[-1] + + @staticmethod + def _remove_by_ids(objs, obj_ids): + """Remove items from a list using a list of object IDs.""" + if obj_ids == []: + return objs + new_objs = [] + if obj_ids is not None: + obj_id_set = set(obj_ids) + for obj in objs: + if obj.identifier not in obj_id_set: + new_objs.append(obj) + return new_objs + + def __add__(self, other): + new_model = self.duplicate() + new_model.add_model(other) + return new_model + + def __iadd__(self, other): + self.add_model(other) + return self + + def __copy__(self): + new_model = Model( + self.identifier, + [room.duplicate() for room in self._rooms], + [face.duplicate() for face in self._orphaned_faces], + [shade.duplicate() for shade in self._orphaned_shades], + [aperture.duplicate() for aperture in self._orphaned_apertures], + [door.duplicate() for door in self._orphaned_doors], + [shade_mesh.duplicate() for shade_mesh in self._shade_meshes], + self.units, self.tolerance, self.angle_tolerance) + new_model._display_name = self._display_name + new_model._user_data = None if self.user_data is None else self.user_data.copy() + new_model._properties._duplicate_extension_attr(self._properties) + return new_model + + def __repr__(self): + return 'Model: %s' % self.display_name
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/orientation.html b/docs/_modules/honeybee/orientation.html new file mode 100644 index 00000000..0d4544a7 --- /dev/null +++ b/docs/_modules/honeybee/orientation.html @@ -0,0 +1,1254 @@ + + + + + + + honeybee.orientation — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.orientation

+"""Collection of utilities for assigning different properties based on orientation.
+
+The functions here are meant to be adaptable to splitting up the compass based on
+however many bins the user desires, though the most common arrangement is likely
+to be a list of 4 values for North, East South, and West.
+
+Usage:
+
+.. code-block:: python
+
+    # list of constructions + materials to apply to faces of different orientations
+    ep_constructions = [constr_north, constr_east, constr_south, constr_west]
+    rad_materials = [mat_north, mat_east, mat_south, mat_west]
+
+    # check that the inputs align with one another
+    all_inputs = [ep_constructions, rad_materials]
+    all_inputs, num_orient = check_matching_inputs(all_inputs)
+
+    # assign properties based on orientation
+    angles = angles_from_num_orient(num_orient)
+    for face in hb_faces:
+        orient_i = face_orient_index(face, angles)
+        if orient_i is not None:
+            constr, mat = inputs_by_index(orient_i, all_inputs)
+            face.properties.energy.construction = constr
+            face.properties.radiance.modifier = mat
+"""
+from ladybug_geometry.geometry2d.pointvector import Vector2D
+
+
+
[docs]def angles_from_num_orient(num_subdivisions=4): + """Get a list of angles based on the number of compass subdivisions. + + Args: + num_subdivisions: An integer for the number of times that the compass + should be subdivided. Default: 4, which will yield angles for North, + East South, and West. + + Returns: + A list of angles in degrees with a length of the num_subdivisions, which + denote the boundaries of each orientation category. + """ + step = 360.0 / num_subdivisions + start = step / 2.0 + angles = [] + while start < 360: + angles.append(start) + start += step + return angles
+ + +
[docs]def face_orient_index(face, angles, north_vector=Vector2D(0, 1)): + """Get the index to be used for a given face/aperture orientation from an angle list. + + Args: + face: A honeybee Face or Aperture object. + angles: A list of angles that denote the boundaries of each orientation + category. + north_vector: An optional ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + + Returns: + An integer for the index used to assign properties to the object of the + input orientation. Will be None if the input Face or Aperture is perfectly + horizontal. + """ + try: + return orient_index(face.horizontal_orientation(north_vector), angles) + except ZeroDivisionError: # input face is perfectly horizontal + return None
+ + +
[docs]def orient_index(orientation, angles): + """Get the index to be used for a given face/aperture orientation from an angle list. + + Args: + orientation: The horizontal cardinal orientation of the Face or Aperture + in degrees. + angles: A list of angles that denote the boundaries of each orientation + category. + + Returns: + An integer for the index used to assign properties to the object of the + input orientation. + """ + for i, ang in enumerate(angles): + if orientation < ang: + return i + return 0
+ + +
[docs]def inputs_by_index(orientation_index, all_inputs): + """Get all of the inputs of a certain index from a list of all_inputs. + + This is useful for getting the set of all inputs that should be assigned to + a given Face or Aperture using its orientation_index. + """ + return [inp[orientation_index] for inp in all_inputs]
+ + +
[docs]def check_matching_inputs(all_inputs, num_orient=None): + """Check that all orientation-specific inputs are coordinated. + + This means that each input is either a array of values to be applied to each + orientation, which is has the same length as other orientation-specific arrays, + or it is a single value to be used for all orientations. + + Args: + all_inputs: An array of arrays where each sub-array corresponds to an + orientation-specific input. + num_orient: An optional integer for the number of orientation categories + to be used. If None, the length of the longest input list in the + all_inputs will be used. + + Returns: + A tuple with two elements + + - all_inputs -- The input all_inputs with all sub-arrays having the same length. + + - num_orient -- An integer for the number of orientations used in the check. + """ + num_orient = max([len(inp) for inp in all_inputs]) if num_orient is None \ + else num_orient + for i, param_list in enumerate(all_inputs): + if len(param_list) == 1: + all_inputs[i] = param_list * num_orient + else: + assert len(param_list) == num_orient, \ + 'The number of items in one of the inputs lists does not match the ' \ + 'others.\nPlease ensure that either the lists match or you put in '\ + 'a single value for all orientations.' + return all_inputs, num_orient
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/properties.html b/docs/_modules/honeybee/properties.html new file mode 100644 index 00000000..cc7d7366 --- /dev/null +++ b/docs/_modules/honeybee/properties.html @@ -0,0 +1,1886 @@ + + + + + + + honeybee.properties — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.properties

+# coding: utf-8
+"""Extension properties for Model, Room, Face, Shade, Aperture, Door.
+
+These objects hold all attributes assigned by extensions like honeybee-radiance
+and honeybee-energy.  Note that these Property objects are not intended to exist
+on their own but should have a host object.
+"""
+
+
+class _Properties(object):
+    """Base class for all Properties classes.
+
+    Args:
+        host: A honeybee-core geometry object that hosts these properties
+            (ie. Model, Room, Face, Shade, Aperture, Door).
+    """
+    _exclude = set(
+        ('host', 'move', 'rotate', 'rotate_xy', 'reflect', 'scale', 'is_equivalent',
+         'add_prefix', 'reset_to_default', 'to_dict', 'apply_properties_from_dict',
+         'ToString'))
+
+    def __init__(self, host):
+        """Initialize properties."""
+        self._host = host
+
+    @property
+    def host(self):
+        """Get the object hosting these properties."""
+        return self._host
+
+    @property
+    def _extension_attributes(self):
+        return (atr for atr in dir(self) if not atr.startswith('_')
+                and atr not in self._exclude)
+
+    def move(self, moving_vec):
+        """Apply a move transform to extension attributes.
+
+        This is useful in cases where extension attributes possess geometric data
+        that should be moved alongside the host object. For example, dynamic
+        geometry within the honeybee-radiance state of an aperture should be
+        moved if the host aperture is moved.
+
+        Args:
+            moving_vec: A ladybug_geometry Vector3D with the direction and distance
+                to move the face.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'move'):
+                continue
+            try:
+                var.move(moving_vec)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to move {}: {}'.format(var, e))
+
+    def rotate(self, axis, angle, origin):
+        """Apply a rotation transform to extension attributes.
+
+        This is useful in cases where extension attributes possess geometric data
+        that should be rotated alongside the host object. For example, dynamic
+        geometry within the honeybee-radiance state of an aperture should be
+        rotated if the host aperture is rotated.
+
+        Args:
+            axis: A ladybug_geometry Vector3D axis representing the axis of rotation.
+            angle: An angle for rotation in degrees.
+            origin: A ladybug_geometry Point3D for the origin around which the
+                object will be rotated.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'rotate'):
+                continue
+            try:
+                var.rotate(axis, angle, origin)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to rotate {}: {}'.format(var, e))
+
+    def rotate_xy(self, angle, origin):
+        """Apply a rotation in the XY plane to extension attributes.
+
+        This is useful in cases where extension attributes possess geometric data
+        that should be rotated alongside the host object. For example, dynamic
+        geometry within the honeybee-radiance state of an aperture should be
+        rotated if the host aperture is rotated.
+
+        Args:
+            angle: An angle in degrees.
+            origin: A ladybug_geometry Point3D for the origin around which the
+                object will be rotated.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'rotate_xy'):
+                continue
+            try:
+                var.rotate_xy(angle, origin)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to rotate {}: {}'.format(var, e))
+
+    def reflect(self, plane):
+        """Apply a reflection transform to extension attributes.
+
+        This is useful in cases where extension attributes possess geometric data
+        that should be reflected alongside the host object. For example, dynamic
+        geometry within the honeybee-radiance state of an aperture should be
+        reflected if the host aperture is reflected.
+
+        Args:
+            plane: A ladybug_geometry Plane across which the object will
+                be reflected.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'reflect'):
+                continue
+            try:
+                var.reflect(plane)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to reflect {}: {}'.format(var, e))
+
+    def scale(self, factor, origin=None):
+        """Apply a scale transform to extension attributes.
+
+        This is useful in cases where extension attributes possess geometric data
+        that should be scaled alongside the host object. For example, dynamic
+        geometry within the honeybee-radiance state of an aperture should be
+        scaled if the host aperture is scaled.
+
+        Args:
+            factor: A number representing how much the object should be scaled.
+            origin: A ladybug_geometry Point3D representing the origin from which
+                to scale. If None, it will be scaled from the World origin (0, 0, 0).
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'scale'):
+                continue
+            try:
+                var.scale(factor, origin)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to scale {}: {}'.format(var, e))
+
+    def is_equivalent(self, other_properties):
+        """Get a dictionary noting the equivalency of these properties to other ones.
+
+        The keys of this dictionary will note the name of each extension (eg.
+        energy, radiance) and the values will be a boolean for whether the
+        extension properties are equivalent or not.
+
+        Args:
+            other_properties: Properties of another object for which equivalency
+                will be tested.
+        """
+        eq_dict = {}
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'is_equivalent'):
+                continue
+            other_var = getattr(other_properties, atr)
+            try:
+                eq_dict[atr] = var.is_equivalent(other_var)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed test is_equivalent for {}: {}'.format(var, e))
+        return eq_dict
+
+    def _update_by_sync(self, change, existing_prop, updated_prop):
+        """Update properties using change instructions and existing/updated objects."""
+        for atr in self._extension_attributes:
+            up_atr = 'update_{}'.format(atr)
+            if up_atr in change:
+                var = getattr(updated_prop, atr) if change[up_atr] \
+                    else getattr(existing_prop, atr)
+                if not hasattr(var, 'duplicate'):
+                    continue
+                try:
+                    setattr(self, '_' + atr, var.duplicate(self.host))
+                except Exception as e:
+                    import traceback
+                    traceback.print_exc()
+                    raise Exception('Failed to duplicate {}: {}'.format(var, e))
+
+    def _duplicate_extension_attr(self, original_properties):
+        """Duplicate the attributes added by extensions.
+
+        This method should be called within the duplicate or __copy__ methods of
+        each honeybee-core geometry object after the core object has been duplicated.
+        This method only needs to be called on the new (duplicated) core object and
+        the extension properties of the original core object should be passed to
+        this method as the original_properties.
+
+        Args:
+            original_properties: The properties object of the original core
+                object from which the duplicate was derived.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(original_properties, atr)
+            if not hasattr(var, 'duplicate'):
+                continue
+            try:
+                setattr(self, '_' + atr, var.duplicate(self.host))
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to duplicate {}: {}'.format(var, e))
+
+    def _add_prefix_extension_attr(self, prefix):
+        """Change the name extension attributes unique to this object by adding a prefix.
+
+        This is particularly useful in workflows where you duplicate and edit
+        a starting object and then want to combine it with the original object
+        into one Model (like making a model of repeated rooms).
+
+        Notably, this method only adds the prefix to extension attributes that must
+        be unique to the object and does not add the prefix to attributes that are
+        shared across several objects.
+
+        Args:
+            prefix: Text that will be inserted at the start of the extension attributes'
+                name. It is recommended that this name be short to avoid maxing
+                out the 100 allowable characters for honeybee names.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'add_prefix'):
+                continue
+            try:
+                var.add_prefix(prefix)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to add prefix to {}: {}'.format(var, e))
+
+    def _reset_extension_attr_to_default(self):
+        """Reset all extension attributes for the object to be default.
+
+        This is useful in cases where properties that are hard-assigned to a specific
+        object might be illegal in combination with other properties and so they
+        should be reset upon the setting of the other properties. For example,
+        setting a Face type to AirBoundary typically makes certain types of energy
+        and radiance properties illegal. So calling this function whenever setting
+        Face type to AirBoundary will reset the extension attributes to the legal
+        default values.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'reset_to_default'):
+                continue
+            try:
+                var.reset_to_default()
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to reset_to_default for {}: {}'.format(var, e))
+
+    def _add_extension_attr_to_dict(self, base, abridged, include):
+        """Add attributes for extensions to the base dictionary.
+
+        This method should be called within the to_dict method of each honeybee-core
+        geometry object.
+
+        Args:
+            base: The dictionary of the core object without any extension attributes.
+                This method will add extension attributes to this dictionary. For
+                example, energy properties will appear under base['properties']['energy'].
+            abridged: Boolean to note whether the attributes of the extensions should
+                be abridged (True) or full (False). For example, if a Room's energy
+                properties are abridged, the program_type attribute under the energy
+                properties dictionary will just be the identifier of the program_type. If
+                it is full (not abridged), the program_type will be a complete
+                dictionary following the ProgramType schema. Abridged dictionaries
+                should be used within the Model.to_dict but full dictionaries should
+                be used within the to_dict methods of individual objects.
+            include: List of properties to filter keys that must be included in
+                output dictionary. For example ['energy'] will include 'energy' key if
+                available in properties to_dict. By default all the keys will be
+                included. To exclude all the keys from extensions use an empty list.
+        """
+        attr = include if include is not None else self._extension_attributes
+        for atr in attr:
+            var = getattr(self, atr)
+            if not hasattr(var, 'to_dict'):
+                continue
+            try:
+                base.update(var.to_dict(abridged))
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise Exception('Failed to convert {} to a dict: {}'.format(var, e))
+        return base
+
+    def _load_extension_attr_from_dict(self, property_dict):
+        """Get attributes for extensions from a dictionary of the properties.
+
+        This method should be called within the from_dict method of each honeybee-core
+        geometry object. Specifically, this method should be called on the core
+        object after it has been created from a dictionary but lacks any of the
+        extension attributes in the dictionary.
+
+        Args:
+            property_dict: A dictionary of properties for the object (ie.
+                FaceProperties, RoomProperties). These will be used to load
+                attributes from the dictionary and assign them to the object on
+                which this method is called.
+        """
+        for atr in self._extension_attributes:
+            var = getattr(self, atr)
+            if not hasattr(var, 'from_dict'):
+                continue
+            try:
+                setattr(self, '_' + atr, var.__class__.from_dict(
+                    property_dict[atr], self.host))
+            except KeyError:
+                pass  # the property_dict possesses no properties for that extension
+
+    def ToString(self):
+        """Overwrite .NET ToString method."""
+        return self.__repr__()
+
+    def __repr__(self):
+        """Properties representation."""
+        return 'BaseProperties'
+
+
+
[docs]class ModelProperties(_Properties): + """Honeybee Model Properties. + + This class will be extended by extensions. + + Usage: + + .. code-block:: python + + model = Model('New Elementary School', list_of_rooms) + model.properties -> ModelProperties + model.properties.radiance -> ModelRadianceProperties + model.properties.energy -> ModelEnergyProperties + """ + +
[docs] def to_dict(self, include=None): + """Convert properties to dictionary. + + Args: + include: A list of keys to be included in dictionary. + If None all the available keys will be included. + """ + base = {'type': 'ModelProperties'} + attr = include if include is not None else self._extension_attributes + for atr in attr: + var = getattr(self, atr) + if not hasattr(var, 'to_dict'): + continue + try: + base.update(var.to_dict()) # no abridged dictionary for model + except Exception as e: + import traceback + traceback.print_exc() + raise Exception('Failed to convert {} to a dict: {}'.format(var, e)) + return base
+ +
[docs] def apply_properties_from_dict(self, data): + """Apply extension properties from a Model dictionary to the host Model. + + Args: + data: A dictionary representation of an entire honeybee-core Model. + """ + for atr in self._extension_attributes: + if atr not in data['properties'] or data['properties'][atr] is None: + continue + var = getattr(self, atr) + if var and not hasattr(var, 'apply_properties_from_dict'): + continue + try: + var.apply_properties_from_dict(data) + except Exception as e: + import traceback + traceback.print_exc() + raise Exception( + 'Failed to apply {} properties to the Model: {}'.format(atr, e))
+ + def _check_extension_attr(self, detailed=False): + """Check the attributes of extensions. + + This method should be called within the check_all method of the Model object + to ensure that the check_all functions of any extension model properties + are also called. + + Args: + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + """ + msgs = [] + for atr in self._extension_attributes: + check_msg = None + var = getattr(self, atr) + if not hasattr(var, 'check_all'): + continue + try: + try: + check_msg = var.check_all(raise_exception=False, detailed=detailed) + except TypeError: # no option available for detailed error message + check_msg = var.check_all(raise_exception=False) + if detailed and check_msg is not None: + msgs.append(check_msg) + elif check_msg != '': + f_msg = 'Attributes for {} are invalid.\n{}'.format(atr, check_msg) + msgs.append(f_msg) + except Exception as e: + import traceback + traceback.print_exc() + raise Exception('Failed to check_all for {}: {}'.format(var, e)) + return msgs + + def __repr__(self): + """Properties representation.""" + return 'ModelProperties: {}'.format(self.host.display_name)
+ + +
[docs]class RoomProperties(_Properties): + """Honeybee Room Properties. + + This class will be extended by extensions. + + Usage: + + .. code-block:: python + + room = Room('Bedroom', geometry) + room.properties -> RoomProperties + room.properties.radiance -> RoomRadianceProperties + room.properties.energy -> RoomEnergyProperties + """ + +
[docs] def to_dict(self, abridged=False, include=None): + """Convert properties to dictionary. + + Args: + abridged: Boolean to note whether the full dictionary describing the + object should be returned (False) or just an abridged version (True). + Default: False. + include: A list of keys to be included in dictionary. + If None all the available keys will be included. + """ + base = {'type': 'RoomProperties'} if not abridged else \ + {'type': 'RoomPropertiesAbridged'} + + base = self._add_extension_attr_to_dict(base, abridged, include) + return base
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier extension attributes unique to this object by adding a prefix. + + Notably, this method only adds the prefix to extension attributes that must + be unique to the Room (eg. single-room HVAC systems) and does not add the + prefix to attributes that are shared across several Rooms (eg. ConstructionSets). + + Args: + prefix: Text that will be inserted at the start of extension attribute identifiers. + """ + self._add_prefix_extension_attr(prefix)
+ +
[docs] def reset_to_default(self): + """Reset the extension properties assigned at the level of this Room to default. + + This typically means erasing any ConstructionSets or ModifierSets assigned + to this Room among other properties. + """ + self._reset_extension_attr_to_default()
+ + def __repr__(self): + """Properties representation.""" + return 'RoomProperties: {}'.format(self.host.display_name)
+ + +
[docs]class FaceProperties(_Properties): + """Honeybee Face Properties. + + This class will be extended by extensions. + + Usage: + + .. code-block:: python + + face = Face('South Bedroom Wall', geometry) + face.properties -> FaceProperties + face.properties.radiance -> FaceRadianceProperties + face.properties.energy -> FaceEnergyProperties + """ + +
[docs] def to_dict(self, abridged=False, include=None): + """Convert properties to dictionary. + + Args: + abridged: Boolean to note whether the full dictionary describing the + object should be returned (False) or just an abridged version (True). + Default: False. + include: A list of keys to be included in dictionary besides Face type + and boundary_condition. If None all the available keys will be included. + """ + base = {'type': 'FaceProperties'} if not abridged else \ + {'type': 'FacePropertiesAbridged'} + base = self._add_extension_attr_to_dict(base, abridged, include) + return base
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier extension attributes unique to this object by adding a prefix. + + Notably, this method only adds the prefix to extension attributes that must + be unique to the Face and does not add the prefix to attributes that are + shared across several Faces. + + Args: + prefix: Text that will be inserted at the start of extension attribute identifiers. + """ + self._add_prefix_extension_attr(prefix)
+ +
[docs] def reset_to_default(self): + """Reset the extension properties assigned at the level of this Face to default. + + This typically means erasing any Constructions or Modifiers assigned to this + Face (having them instead assigned by ConstructionSets and ModifierSets). + """ + self._reset_extension_attr_to_default()
+ + def __repr__(self): + """Properties representation.""" + return 'FaceProperties: {}'.format(self.host.display_name)
+ + +
[docs]class ApertureProperties(_Properties): + """Honeybee Aperture Properties. + + This class will be extended by extensions. + + Usage: + + .. code-block:: python + + aperture = Aperture('Window to My Soul', geometry) + aperture.properties -> ApertureProperties + aperture.properties.radiance -> ApertureRadianceProperties + aperture.properties.energy -> ApertureEnergyProperties + """ + +
[docs] def to_dict(self, abridged=False, include=None): + """Convert properties to dictionary. + + Args: + abridged: Boolean to note whether the full dictionary describing the + object should be returned (False) or just an abridged version (True). + Default: False. + include: A list of keys to be included in dictionary. + If None all the available keys will be included. + """ + base = {'type': 'ApertureProperties'} if not abridged else \ + {'type': 'AperturePropertiesAbridged'} + + base = self._add_extension_attr_to_dict(base, abridged, include) + return base
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier extension attributes unique to this object by adding a prefix. + + Notably, this method only adds the prefix to extension attributes that must + be unique to the Aperture and does not add the prefix to attributes that are + shared across several Apertures. + + Args: + prefix: Text that will be inserted at the start of extension attribute identifiers. + """ + self._add_prefix_extension_attr(prefix)
+ +
[docs] def reset_to_default(self): + """Reset the extension properties assigned to this Aperture to default. + + This typically means erasing any Constructions or Modifiers assigned to this + Aperture (having them instead assigned by ConstructionSets and ModifierSets). + """ + self._reset_extension_attr_to_default()
+ + def __repr__(self): + """Properties representation.""" + return 'ApertureProperties: {}'.format(self.host.display_name)
+ + +
[docs]class DoorProperties(_Properties): + """Honeybee Door Properties. + + This class will be extended by extensions. + + Usage: + + .. code-block:: python + + door = Door('Front Door', geometry) + door.properties -> DoorProperties + door.properties.radiance -> DoorRadianceProperties + door.properties.energy -> DoorEnergyProperties + """ + +
[docs] def to_dict(self, abridged=False, include=None): + """Convert properties to dictionary. + + Args: + abridged: Boolean to note whether the full dictionary describing the + object should be returned (False) or just an abridged version (True). + Default: False. + include: A list of keys to be included in dictionary. + If None all the available keys will be included. + """ + base = {'type': 'DoorProperties'} if not abridged else \ + {'type': 'DoorPropertiesAbridged'} + + base = self._add_extension_attr_to_dict(base, abridged, include) + return base
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier extension attributes unique to this object by adding a prefix. + + Notably, this method only adds the prefix to extension attributes that must + be unique to the Door and does not add the prefix to attributes that are + shared across several Doors. + + Args: + prefix: Text that will be inserted at the start of extension attribute identifiers. + """ + self._add_prefix_extension_attr(prefix)
+ +
[docs] def reset_to_default(self): + """Reset the extension properties assigned to this Door to default. + + This typically means erasing any Constructions or Modifiers assigned to this + Door (having them instead assigned by ConstructionSets and ModifierSets). + """ + self._reset_extension_attr_to_default()
+ + def __repr__(self): + """Properties representation.""" + return 'DoorProperties: {}'.format(self.host.display_name)
+ + +
[docs]class ShadeProperties(_Properties): + """Honeybee Shade Properties. + + This class will be extended by extensions. + + Usage: + + .. code-block:: python + + shade = Shade('Deep Overhang', geometry) + shade.properties -> ShadeProperties + shade.properties.radiance -> ShadeRadianceProperties + shade.properties.energy -> ShadeEnergyProperties + """ + +
[docs] def to_dict(self, abridged=False, include=None): + """Convert properties to dictionary. + + Args: + abridged: Boolean to note whether the full dictionary describing the + object should be returned (False) or just an abridged version (True). + Default: False. + include: A list of keys to be included in dictionary. + If None all the available keys will be included. + """ + base = {'type': 'ShadeProperties'} if not abridged else \ + {'type': 'ShadePropertiesAbridged'} + + base = self._add_extension_attr_to_dict(base, abridged, include) + return base
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier extension attributes unique to this object by adding a prefix. + + Notably, this method only adds the prefix to extension attributes that must + be unique to the Shade and does not add the prefix to attributes that are + shared across several Shades. + + Args: + prefix: Text that will be inserted at the start of extension attribute identifiers. + """ + self._add_prefix_extension_attr(prefix)
+ +
[docs] def reset_to_default(self): + """Reset the extension properties assigned at the level of this Shade to default. + + This typically means erasing any Constructions or Modifiers assigned to this + Shade (having them instead assigned by ConstructionSets and ModifierSets). + """ + self._reset_extension_attr_to_default()
+ + def __repr__(self): + """Properties representation.""" + return 'ShadeProperties: {}'.format(self.host.display_name)
+ + +
[docs]class ShadeMeshProperties(_Properties): + """Honeybee ShadeMesh Properties. + + This class will be extended by extensions. + + Usage: + + .. code-block:: python + + shade = ShadeMesh('Hilly Terrain', geometry) + shade.properties -> ShadeMeshProperties + shade.properties.radiance -> ShadeMeshRadianceProperties + shade.properties.energy -> ShadeMeshEnergyProperties + """ + +
[docs] def to_dict(self, abridged=False, include=None): + """Convert properties to dictionary. + + Args: + abridged: Boolean to note whether the full dictionary describing the + object should be returned (False) or just an abridged version (True). + Default: False. + include: A list of keys to be included in dictionary. + If None all the available keys will be included. + """ + base = {'type': 'ShadeMeshProperties'} if not abridged else \ + {'type': 'ShadeMeshPropertiesAbridged'} + + base = self._add_extension_attr_to_dict(base, abridged, include) + return base
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier extension attributes unique to this object by adding a prefix. + + Notably, this method only adds the prefix to extension attributes that must + be unique to the ShadeMesh and does not add the prefix to attributes that are + shared across several ShadeMeshes (eg. modifier). + + Args: + prefix: Text that will be inserted at the start of extension attribute identifiers. + """ + self._add_prefix_extension_attr(prefix)
+ +
[docs] def reset_to_default(self): + """Reset the extension properties assigned to this ShadeMesh to default. + + This typically means erasing any Constructions or Modifiers assigned to this + ShadeMesh. + """ + self._reset_extension_attr_to_default()
+ + def __repr__(self): + """Properties representation.""" + return 'ShadeMeshProperties: {}'.format(self.host.display_name)
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/room.html b/docs/_modules/honeybee/room.html new file mode 100644 index 00000000..8c59ec88 --- /dev/null +++ b/docs/_modules/honeybee/room.html @@ -0,0 +1,3734 @@ + + + + + + + honeybee.room — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.room

+# coding: utf-8
+"""Honeybee Room."""
+from __future__ import division
+import math
+import uuid
+
+from ladybug_geometry.geometry2d import Point2D, Vector2D, Polygon2D
+from ladybug_geometry.geometry3d import Point3D, Vector3D, Ray3D, Plane, Face3D, \
+    Mesh3D, Polyface3D
+from ladybug_geometry_polyskel.polysplit import perimeter_core_subpolygons
+
+import honeybee.writer.room as writer
+from ._basewithshade import _BaseWithShade
+from .typing import float_in_range, int_in_range, clean_string, \
+    invalid_dict_error
+from .properties import RoomProperties
+from .face import Face
+from .facetype import AirBoundary, Wall, Floor, RoofCeiling, get_type_from_normal
+from .boundarycondition import get_bc_from_position, Outdoors, Ground, Surface, \
+    boundary_conditions
+from .orientation import angles_from_num_orient, orient_index
+try:
+    ad_bc = boundary_conditions.adiabatic
+except AttributeError:  # honeybee_energy is not loaded and adiabatic does not exist
+    ad_bc = None
+
+
+
[docs]class Room(_BaseWithShade): + """A volume enclosed by faces, representing a single room or space. + + Note that, if zero is input for tolerance and angle_tolerance, no checks + will be performed to determine whether the room is a closed volume + and no attempt will be made to flip faces in the event that they are not + facing outward from the room volume. As such, an input tolerance of 0 + is intended for workflows where the solidity of the room volume has been + evaluated elsewhere. + + Args: + identifier: Text string for a unique Room ID. Must be < 100 characters and + not contain any spaces or special characters. + faces: A list or tuple of honeybee Face objects that together form the + closed volume of a room. + tolerance: The maximum difference between x, y, and z values + at which vertices of adjacent faces are considered equivalent. This is + used in determining whether the faces form a closed volume. Default + is 0, which makes no attempt to evaluate whether the Room volume + is closed. + angle_tolerance: The max angle difference in degrees that vertices are + allowed to differ from one another in order to consider them colinear. + Default is 0, which makes no attempt to evaluate whether the Room + volume is closed. + + Properties: + * identifier + * display_name + * faces + * multiplier + * story + * exclude_floor_area + * indoor_furniture + * indoor_shades + * outdoor_shades + * walls + * floors + * roof_ceilings + * air_boundaries + * doors + * apertures + * exterior_apertures + * geometry + * center + * min + * max + * volume + * floor_area + * exposed_area + * exterior_wall_area + * exterior_aperture_area + * exterior_wall_aperture_area + * exterior_skylight_aperture_area + * average_floor_height + * user_data + """ + __slots__ = ( + '_geometry', '_faces', + '_multiplier', '_story', '_exclude_floor_area', '_parent') + + def __init__(self, identifier, faces, tolerance=0, angle_tolerance=0): + """Initialize Room.""" + _BaseWithShade.__init__(self, identifier) # process the identifier + + # process the zone volume geometry + if not isinstance(faces, tuple): + faces = tuple(faces) + for face in faces: + assert isinstance(face, Face), \ + 'Expected honeybee Face. Got {}'.format(type(face)) + face._parent = self + + if tolerance == 0: + self._faces = faces + self._geometry = None # calculated later from faces or added by classmethods + else: + # try to get a closed volume between the faces + room_polyface = Polyface3D.from_faces( + tuple(face.geometry for face in faces), tolerance) + if not room_polyface.is_solid and angle_tolerance != 0: + ang_tol = math.radians(angle_tolerance) + room_polyface = room_polyface.merge_overlapping_edges(tolerance, ang_tol) + # replace honeybee face geometry with versions that are facing outwards + if room_polyface.is_solid: + for i, correct_face3d in enumerate(room_polyface.faces): + face = faces[i] + norm_init = face._geometry.normal + face._geometry = correct_face3d + if face.has_sub_faces: # flip sub-faces to align with parent Face + if norm_init.angle(face._geometry.normal) > (math.pi / 2): + for ap in face._apertures: + ap._geometry = ap._geometry.flip() + for dr in face._doors: + dr._geometry = dr._geometry.flip() + self._faces = faces + self._geometry = room_polyface + + self._multiplier = 1 # default value that can be overridden later + self._story = None # default value that can be overridden later + self._exclude_floor_area = False # default value that can be overridden later + self._parent = None # completely hidden as it is only used by Dragonfly + self._properties = RoomProperties(self) # properties for extensions + +
[docs] @classmethod + def from_dict(cls, data, tolerance=0, angle_tolerance=0): + """Initialize an Room from a dictionary. + + Args: + data: A dictionary representation of a Room object. + tolerance: The maximum difference between x, y, and z values + at which vertices of adjacent faces are considered equivalent. This is + used in determining whether the faces form a closed volume. Default + is 0, which makes no attempt to evaluate whether the Room volume + is closed. + angle_tolerance: The max angle difference in degrees that vertices are + allowed to differ from one another in order to consider them colinear. + Default is 0, which makes no attempt to evaluate whether the Room + volume is closed. + """ + try: + # check the type of dictionary + assert data['type'] == 'Room', 'Expected Room dictionary. ' \ + 'Got {}.'.format(data['type']) + + # create the room object and assign properties + faces = [] + for f_dict in data['faces']: + try: + faces.append(Face.from_dict(f_dict)) + except Exception as e: + invalid_dict_error(f_dict, e) + room = cls(data['identifier'], faces, tolerance, angle_tolerance) + if 'display_name' in data and data['display_name'] is not None: + room.display_name = data['display_name'] + if 'user_data' in data and data['user_data'] is not None: + room.user_data = data['user_data'] + if 'multiplier' in data and data['multiplier'] is not None: + room.multiplier = data['multiplier'] + if 'story' in data and data['story'] is not None: + room.story = data['story'] + if 'exclude_floor_area' in data and data['exclude_floor_area'] is not None: + room.exclude_floor_area = data['exclude_floor_area'] + room._recover_shades_from_dict(data) + + if data['properties']['type'] == 'RoomProperties': + room.properties._load_extension_attr_from_dict(data['properties']) + return room + except Exception as e: + cls._from_dict_error_message(data, e)
+ +
[docs] @classmethod + def from_polyface3d(cls, identifier, polyface, roof_angle=60, floor_angle=130, + ground_depth=0): + """Initialize a Room from a ladybug_geometry Polyface3D object. + + Args: + identifier: Text string for a unique Room ID. Must be < 100 characters and + not contain any spaces or special characters. + polyface: A ladybug_geometry Polyface3D object representing the closed + volume of a room. The Polyface3D.is_solid property can be used to + determine whether the polyface is a closed solid before input here. + roof_angle: A number between 0 and 90 to set the angle from the horizontal + plane below which faces will be considered roofs instead of + walls. 90 indicates that all vertical faces are roofs and 0 + indicates that all horizontal faces are walls. (Default: 60, + recommended by the ASHRAE 90.1 standard). + floor_angle: A number between 90 and 180 to set the angle from the horizontal + plane above which faces will be considered floors instead of + walls. 180 indicates that all vertical faces are floors and 0 + indicates that all horizontal faces are walls. (Default: 130, + recommended by the ASHRAE 90.1 standard). + ground_depth: The Z value above which faces are considered Outdoors + instead of Ground. Faces will have a Ground boundary condition if + all of their vertices lie at or below this value. Default: 0. + """ + assert isinstance(polyface, Polyface3D), \ + 'Expected ladybug_geometry Polyface3D. Got {}'.format(type(polyface)) + faces = [] + for i, face in enumerate(polyface.faces): + faces.append(Face('{}..Face{}'.format(identifier, i), face, + get_type_from_normal(face.normal, roof_angle, floor_angle), + get_bc_from_position(face.boundary, ground_depth))) + room = cls(identifier, faces) + room._geometry = polyface + return room
+ +
[docs] @classmethod + def from_box(cls, identifier, width=3.0, depth=6.0, height=3.2, + orientation_angle=0, origin=Point3D(0, 0, 0)): + """Initialize a Room from parameters describing a box. + + The resulting faces of the room will always be ordered as follows: + (Bottom, Front, Right, Back, Left, Top) where the front is facing the + cardinal direction of the orientation_angle. + + Args: + identifier: Text string for a unique Room ID. Must be < 100 characters and + not contain any spaces or special characters. + width: Number for the width of the box (in the X direction). Default: 3.0. + depth: Number for the depth of the box (in the Y direction). Default: 6.0. + height: Number for the height of the box (in the Z direction). Default: 3.2. + orientation_angle: A number between 0 and 360 for the clockwise + orientation of the box in degrees. + (0 = North, 90 = East, 180 = South, 270 = West) + origin: A ladybug_geometry Point3D for the origin of the room. + """ + # create a box Polyface3D + x_axis = Vector3D(1, 0, 0) + if orientation_angle != 0: + angle = -1 * math.radians( + float_in_range(orientation_angle, 0, 360, 'orientation_angle')) + x_axis = x_axis.rotate_xy(angle) + base_plane = Plane(Vector3D(0, 0, 1), origin, x_axis) + polyface = Polyface3D.from_box(width, depth, height, base_plane) + + # create the honeybee Faces + directions = ('Bottom', 'Front', 'Right', 'Back', 'Left', 'Top') + faces = [] + for face, dir in zip(polyface.faces, directions): + faces.append(Face('{}_{}'.format(identifier, dir), face, + get_type_from_normal(face.normal), + get_bc_from_position(face.boundary))) + room = cls(identifier, faces) + room._geometry = polyface + return room
+ + @property + def faces(self): + """Get a tuple of all honeybee Faces making up this room volume.""" + return self._faces + + @property + def multiplier(self): + """Get or set an integer noting how many times this Room is repeated. + + Multipliers are used to speed up the calculation when similar Rooms are + repeated more than once. Essentially, a given simulation with the + Room is run once and then the result is multiplied by the multiplier. + This means that the "repetition" isn't in a particular direction (it's + essentially in the exact same location) and this comes with some + inaccuracy. However, this error might not be too large if the Rooms + are similar enough and it can often be worth it since it can greatly + speed up the calculation. + """ + return self._multiplier + + @multiplier.setter + def multiplier(self, value): + self._multiplier = int_in_range(value, 1, input_name='room multiplier') + + @property + def story(self): + """Get or set text for the story identifier to which this Room belongs. + + Rooms sharing the same story identifier are considered part of the same + story in a Model. Note that the story identifier has no character + restrictions much like display_name. + """ + return self._story + + @story.setter + def story(self, value): + if value is not None: + try: + self._story = str(value) + except UnicodeEncodeError: # Python 2 machine lacking the character set + self._story = value # keep it as unicode + else: + self._story = value + + @property + def exclude_floor_area(self): + """Get or set a boolean for whether the floor area contributes to the Model. + + Note that this will not affect the floor_area property of this Room but + it will ensure the Room's floor area is excluded from any calculations + when the Room is part of a Model. + """ + return self._exclude_floor_area + + @exclude_floor_area.setter + def exclude_floor_area(self, value): + self._exclude_floor_area = bool(value) + + @property + def indoor_furniture(self): + """Array of all indoor furniture Shade objects assigned to this Room. + + Note that this property is identical to the indoor_shades property but + it is provided here under an alternate name to make it clear that indoor + furniture objects should be added here to the Room. + """ + return tuple(self._indoor_shades) + + @property + def walls(self): + """Get a tuple of all of the Wall Faces of the Room.""" + return tuple(face for face in self._faces if isinstance(face.type, Wall)) + + @property + def floors(self): + """Get a tuple of all of the Floor Faces of the Room.""" + return tuple(face for face in self._faces if isinstance(face.type, Floor)) + + @property + def roof_ceilings(self): + """Get a tuple of all of the RoofCeiling Faces of the Room.""" + return tuple(face for face in self._faces if isinstance(face.type, RoofCeiling)) + + @property + def air_boundaries(self): + """Get a tuple of all of the AirBoundary Faces of the Room.""" + return tuple(face for face in self._faces if isinstance(face.type, AirBoundary)) + + @property + def doors(self): + """Get a tuple of all Doors of the Room.""" + drs = [] + for face in self._faces: + if len(face._doors) > 0: + drs.extend(face._doors) + return tuple(drs) + + @property + def apertures(self): + """Get a tuple of all Apertures of the Room.""" + aps = [] + for face in self._faces: + if len(face._apertures) > 0: + aps.extend(face._apertures) + return tuple(aps) + + @property + def exterior_apertures(self): + """Get a tuple of all exterior Apertures of the Room.""" + aps = [] + for face in self._faces: + if isinstance(face.boundary_condition, Outdoors) and \ + len(face._apertures) > 0: + aps.extend(face._apertures) + return tuple(aps) + + @property + def geometry(self): + """Get a ladybug_geometry Polyface3D object representing the room.""" + if self._geometry is None: + self._geometry = Polyface3D.from_faces( + tuple(face.geometry for face in self._faces), 0) # use 0 tolerance + return self._geometry + + @property + def center(self): + """Get a ladybug_geometry Point3D for the center of the room. + + Note that this is the center of the bounding box around the room Polyface + geometry and not the volume centroid. Also note that shades assigned to + this room are not included in this center calculation. + """ + return self.geometry.center + + @property + def min(self): + """Get a Point3D for the minimum of the bounding box around the object. + + This includes any shades assigned to this object or its children. + """ + all_geo = self._outdoor_shades + self._indoor_shades + all_geo.extend(self._faces) + return self._calculate_min(all_geo) + + @property + def max(self): + """Get a Point3D for the maximum of the bounding box around the object. + + This includes any shades assigned to this object or its children. + """ + all_geo = self._outdoor_shades + self._indoor_shades + all_geo.extend(self._faces) + return self._calculate_max(all_geo) + + @property + def volume(self): + """Get the volume of the room. + + Note that, if this room faces do not form a closed solid the value of this + property will not be accurate. + """ + return self.geometry.volume + + @property + def floor_area(self): + """Get the combined area of all room floor faces.""" + return sum([face.area for face in self._faces if isinstance(face.type, Floor)]) + + @property + def exposed_area(self): + """Get the combined area of all room faces with outdoor boundary conditions. + + Useful for estimating infiltration, often expressed as a flow per + unit exposed envelope area. + """ + return sum([face.area for face in self._faces if + isinstance(face.boundary_condition, Outdoors)]) + + @property + def exterior_wall_area(self): + """Get the combined area of all exterior walls on the room. + + This is NOT the area of the wall's punched_geometry and it includes BOTH + the area of opaque and transparent parts of the walls. + """ + wall_areas = 0 + for f in self._faces: + if isinstance(f.boundary_condition, Outdoors) and isinstance(f.type, Wall): + wall_areas += f.area + return wall_areas + + @property + def exterior_roof_area(self): + """Get the combined area of all exterior roofs on the room. + + This is NOT the area of the roof's punched_geometry and it includes BOTH + the area of opaque and transparent parts of the roofs. + """ + wall_areas = 0 + for f in self._faces: + if isinstance(f.boundary_condition, Outdoors) and \ + isinstance(f.type, RoofCeiling): + wall_areas += f.area + return wall_areas + + @property + def exterior_aperture_area(self): + """Get the combined area of all exterior apertures on the room.""" + ap_areas = 0 + for face in self._faces: + if isinstance(face.boundary_condition, Outdoors) and \ + len(face._apertures) > 0: + ap_areas += sum(ap.area for ap in face._apertures) + return ap_areas + + @property + def exterior_wall_aperture_area(self): + """Get the combined area of all apertures on exterior walls of the room.""" + ap_areas = 0 + for face in self._faces: + if isinstance(face.boundary_condition, Outdoors) and \ + isinstance(face.type, Wall) and len(face._apertures) > 0: + ap_areas += sum(ap.area for ap in face._apertures) + return ap_areas + + @property + def exterior_skylight_aperture_area(self): + """Get the combined area of all apertures on exterior roofs of the room.""" + ap_areas = 0 + for face in self._faces: + if isinstance(face.boundary_condition, Outdoors) and \ + isinstance(face.type, RoofCeiling) and len(face._apertures) > 0: + ap_areas += sum(ap.area for ap in face._apertures) + return ap_areas + + @property + def average_floor_height(self): + """Get the height of the room floor averaged over all floor faces in the room. + + The resulting value is weighted by the area of each of the floor faces. + Will be the minimum Z value of the Room volume if the room possesses no floors. + """ + heights = 0 + areas = 0 + for face in self._faces: + if isinstance(face.type, Floor): + heights += face.center.z * face.area + areas += face.area + return heights / areas if areas != 0 else self.geometry.min.z + + @property + def has_parent(self): + """Always False as Rooms cannot have parents.""" + return False + +
[docs] def average_orientation(self, north_vector=Vector2D(0, 1)): + """Get a number between 0 and 360 for the average orientation of exposed walls. + + 0 = North, 90 = East, 180 = South, 270 = West. Will be None if the zone has + no exterior walls. Resulting value is weighted by the area of each of the + wall faces. + + Args: + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + """ + orientations = 0 + areas = 0 + for face in self._faces: + if isinstance(face.type, Wall) and \ + isinstance(face.boundary_condition, Outdoors): + orientations += face.horizontal_orientation(north_vector) * face.area + areas += face.area + return orientations / areas if areas != 0 else None
+ +
[docs] def add_prefix(self, prefix): + """Change the identifier of this object and child objects by inserting a prefix. + + This is particularly useful in workflows where you duplicate and edit + a starting object and then want to combine it with the original object + into one Model (like making a model of repeated rooms) since all objects + within a Model must have unique identifiers. + + Args: + prefix: Text that will be inserted at the start of this object's + (and child objects') identifier and display_name. It is recommended + that this prefix be short to avoid maxing out the 100 allowable + characters for honeybee identifiers. + """ + self._identifier = clean_string('{}_{}'.format(prefix, self.identifier)) + self.display_name = '{}_{}'.format(prefix, self.display_name) + self.properties.add_prefix(prefix) + for face in self._faces: + face.add_prefix(prefix) + self._add_prefix_shades(prefix)
+ +
[docs] def horizontal_boundary(self, match_walls=False, tolerance=0.01): + """Get a Face3D representing the horizontal boundary around the Room. + + This will be generated from all downward-facing Faces of the Room (essentially + the Floor faces but can also include overhanging slanted walls). So, for + a valid closed-volume Honeybee Room, the result should always represent + the Room in the XY plane. + + The Z height of the resulting Face3D will be at the minimum floor height. + + Note that, if this Room is not solid, the computation of the horizontal + boundary may fail with an exception. + + Args: + match_walls: Boolean to note whether vertices should be inserted into + the final Face3D that will help match the segments of the result + back to the walls that are adjacent to the floors. If False, the + result may lack some colinear vertices that relate the Face3D + to the Walls, though setting this to True does not guarantee that + all walls will relate to a segment in the result. (Default: False). + tolerance: The minimum difference between x, y, and z coordinate values + at which points are considered distinct. (Default: 0.01, + suitable for objects in Meters). + """ + # get the starting horizontal boundary + try: + horiz_bound = self._base_horiz_boundary(tolerance) + except Exception as e: + msg = 'Room "{}" is not solid and so a valid horizontal boundary for ' \ + 'the Room could not be established.\n{}'.format(self.full_id, e) + raise ValueError(msg) + if match_walls: # insert the wall vertices + return self._match_walls_to_horizontal_faces([horiz_bound], tolerance)[0] + return horiz_bound
+ +
[docs] def horizontal_floor_boundaries(self, match_walls=False, tolerance=0.01): + """Get a list of horizontal Face3D for the boundaries around the Room's Floors. + + Unlike the horizontal_boundary method, which uses all downward-pointing + geometries, this method will derive horizontal boundaries using only the + Floors. This is useful when the resulting geometry is used to specify the + floor area in the result. + + The Z height of the resulting Face3D will be at the minimum floor height. + + Args: + match_walls: Boolean to note whether vertices should be inserted into + the final Face3Ds that will help match the segments of the result + back to the walls that are adjacent to the floors. If False, the + result may lack some colinear vertices that relate the Face3Ds + to the Walls, though setting this to True does not guarantee that + all walls will relate to a segment in the result. (Default: False). + tolerance: The minimum difference between x, y, and z coordinate values + at which points are considered distinct. (Default: 0.01, + suitable for objects in Meters). + """ + # gather all of the floor geometries + flr_geo = [face.geometry for face in self.floors] + + # ensure that all geometries are horizontal with as few faces as possible + if len(flr_geo) == 0: # degenerate face + return [] + elif len(flr_geo) == 1: + if flr_geo[0].is_horizontal(tolerance): + horiz_bound = flr_geo + else: + floor_height = self.geometry.min.z + bound = [Point3D(p.x, p.y, floor_height) for p in flr_geo[0].boundary] + holes = None + if flr_geo[0].has_holes: + holes = [[Point3D(p.x, p.y, floor_height) for p in hole] + for hole in flr_geo[0].holes] + horiz_bound = [Face3D(bound, holes=holes)] + else: # multiple geometries to be joined together + floor_height = self.geometry.min.z + horiz_geo = [] + for fg in flr_geo: + if fg.is_horizontal(tolerance) and \ + abs(floor_height - fg.min.z) <= tolerance: + horiz_geo.append(fg) + else: # project the face geometry into the XY plane + bound = [Point3D(p.x, p.y, floor_height) for p in fg.boundary] + holes = None + if fg.has_holes: + holes = [[Point3D(p.x, p.y, floor_height) for p in hole] + for hole in fg.holes] + horiz_geo.append(Face3D(bound, holes=holes)) + # join the coplanar horizontal faces together + horiz_bound = Face3D.join_coplanar_faces(horiz_geo, tolerance) + + if match_walls: # insert the wall vertices + return self._match_walls_to_horizontal_faces(horiz_bound, tolerance) + return horiz_bound
+ +
[docs] def remove_indoor_furniture(self): + """Remove all indoor furniture assigned to this Room. + + Note that this method is identical to the remove_indoor_shade method but + it is provided here under an alternate name to make it clear that indoor + furniture objects should be added here to the Room. + """ + self.remove_indoor_shades()
+ +
[docs] def add_indoor_furniture(self, shade): + """Add a Shade object representing furniture to the Room. + + Note that this method is identical to the add_indoor_shade method but + it is provided here under an alternate name to make it clear that indoor + furniture objects should be added here to the Room. + + Args: + shade: A Shade object to add to the indoors of this Room, representing + furniture, desks, partitions, etc. + """ + self.add_indoor_shade(shade)
+ +
[docs] def generate_grid(self, x_dim, y_dim=None, offset=1.0): + """Get a gridded Mesh3D objects offset from the floors of this room. + + Note that the x_dim and y_dim refer to dimensions within the XY coordinate + system of the floor faces's planes. So rotating the planes of the floor faces + will result in rotated grid cells. + + Will be None if the Room has no floor faces. + + Args: + x_dim: The x dimension of the grid cells as a number. + y_dim: The y dimension of the grid cells as a number. Default is None, + which will assume the same cell dimension for y as is set for x. + offset: A number for how far to offset the grid from the base face. + Default is 1.0, which will offset the grid to be 1 unit above + the floor. + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + floor_mesh = room.generate_grid(0.5, 0.5, 1) + test_points = floor_mesh.face_centroids + """ + floor_grids = [] + for face in self._faces: + if isinstance(face.type, Floor): + try: + floor_grids.append( + face.geometry.mesh_grid(x_dim, y_dim, offset, True)) + except AssertionError: # grid tolerance not fine enough + pass + if len(floor_grids) == 1: + return floor_grids[0] + elif len(floor_grids) > 1: + return Mesh3D.join_meshes(floor_grids) + return None
+ +
[docs] def generate_exterior_face_grid( + self, dimension, offset=0.1, face_type='Wall', punched_geometry=False): + """Get a gridded Mesh3D offset from the exterior Faces of this Room. + + This will be None if the Room has no exterior Faces. + + Args: + dimension: The dimension of the grid cells as a number. + offset: A number for how far to offset the grid from the base face. + Positive numbers indicate an offset towards the exterior. (Default + is 0.1, which will offset the grid to be 0.1 unit from the faces). + face_type: Text to specify the type of face that will be used to + generate grids. Note that only Faces with Outdoors boundary + conditions will be used, meaning that most Floors will typically + be excluded unless they represent the underside of a cantilever. + Choose from the following. (Default: Wall). + + * Wall + * Roof + * Floor + * All + + punched_geometry: Boolean to note whether the punched_geometry of the faces + should be used (True) with the areas of sub-faces removed from the grid + or the full geometry should be used (False). (Default:False). + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + face_mesh = room.generate_exterior_face_grid(0.5) + test_points = face_mesh.face_centroids + """ + # select the correct face type based on the input + face_t = face_type.title() + if face_t == 'Wall': + ft = Wall + elif face_t in ('Roof', 'Roofceiling'): + ft = RoofCeiling + elif face_t == 'All': + ft = (Wall, RoofCeiling, Floor) + elif face_t == 'Floor': + ft = Floor + else: + raise ValueError('Unrecognized face_type "{}".'.format(face_type)) + face_attr = 'punched_geometry' if punched_geometry else 'geometry' + # loop through the faces and generate grids + face_grids = [] + for face in self._faces: + if isinstance(face.type, ft) and \ + isinstance(face.boundary_condition, Outdoors): + try: + f_geo = getattr(face, face_attr) + face_grids.append( + f_geo.mesh_grid(dimension, None, offset, False)) + except AssertionError: # grid tolerance not fine enough + pass + # join the grids together if there are several ones + if len(face_grids) == 1: + return face_grids[0] + elif len(face_grids) > 1: + return Mesh3D.join_meshes(face_grids) + return None
+ +
[docs] def generate_exterior_aperture_grid( + self, dimension, offset=0.1, aperture_type='All'): + """Get a gridded Mesh3D offset from the exterior Apertures of this room. + + Will be None if the Room has no exterior Apertures. + + Args: + dimension: The dimension of the grid cells as a number. + offset: A number for how far to offset the grid from the base aperture. + Positive numbers indicate an offset towards the exterior while + negative numbers indicate an offset towards the interior, essentially + modeling the value of sun on the building interior. (Default + is 0.1, which will offset the grid to be 0.1 unit from the apertures). + aperture_type: Text to specify the type of Aperture that will be used to + generate grids. Window indicates Apertures in Walls. Choose from + the following. (Default: All). + + * Window + * Skylight + * All + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + room[3].apertures_by_ratio(0.4) + aperture_mesh = room.generate_exterior_aperture_grid(0.5) + test_points = aperture_mesh.face_centroids + """ + # select the correct face type based on the input + ap_t = aperture_type.title() + if ap_t == 'Window': + ft = Wall + elif ap_t == 'Skylight': + ft = RoofCeiling + elif ap_t == 'All': + ft = (Wall, RoofCeiling, Floor) + else: + raise ValueError('Unrecognized aperture_type "{}".'.format(aperture_type)) + # loop through the faces and generate grids + ap_grids = [] + for face in self._faces: + if isinstance(face.type, ft) and \ + isinstance(face.boundary_condition, Outdoors): + for ap in face.apertures: + try: + ap_grids.append( + ap.geometry.mesh_grid(dimension, None, offset, False)) + except AssertionError: # grid tolerance not fine enough + pass + # join the grids together if there are several ones + if len(ap_grids) == 1: + return ap_grids[0] + elif len(ap_grids) > 1: + return Mesh3D.join_meshes(ap_grids) + return None
+ +
[docs] def wall_apertures_by_ratio(self, ratio, tolerance=0.01): + """Add apertures to all exterior walls given a ratio of aperture to face area. + + Note this method removes any existing apertures and doors on the Room's walls. + This method attempts to generate as few apertures as necessary to meet the ratio. + + Args: + ratio: A number between 0 and 1 (but not perfectly equal to 1) + for the desired ratio between aperture area and face area. + tolerance: The maximum difference between point values for them to be + considered equivalent. (Default: 0.01, suitable for objects in meters). + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + room.wall_apertures_by_ratio(0.4) + """ + for face in self._faces: + if isinstance(face.boundary_condition, Outdoors) and \ + isinstance(face.type, Wall): + face.apertures_by_ratio(ratio, tolerance)
+ +
[docs] def skylight_apertures_by_ratio(self, ratio, tolerance=0.01): + """Add apertures to all exterior roofs given a ratio of aperture to face area. + + Note this method removes any existing apertures and overhead doors on the + Room's roofs. This method attempts to generate as few apertures as + necessary to meet the ratio. + + Args: + ratio: A number between 0 and 1 (but not perfectly equal to 1) + for the desired ratio between aperture area and face area. + tolerance: The maximum difference between point values for them to be + considered equivalent. (Default: 0.01, suitable for objects in meters). + + Usage: + + .. code-block:: python + + room = Room.from_box(3.0, 6.0, 3.2, 180) + room.skylight_apertures_by_ratio(0.05) + """ + for face in self._faces: + if isinstance(face.boundary_condition, Outdoors) and \ + isinstance(face.type, RoofCeiling): + face.apertures_by_ratio(ratio, tolerance)
+ +
[docs] def simplify_apertures(self, tolerance=0.01): + """Convert all Apertures on this Room to be a simple window ratio. + + This is useful for studies where faster simulation times are desired and + the window ratio is the critical factor driving the results (as opposed + to the detailed geometry of the window). Apertures assigned to concave + Faces will not be simplified given that the apertures_by_ratio method + likely won't improve the cleanliness of the apertures for such cases. + + Args: + tolerance: The maximum difference between point values for them to be + considered equivalent. (Default: 0.01, suitable for objects in meters). + """ + for face in self.faces: + f_ap = face._apertures + if len(f_ap) != 0 and face.geometry.is_convex: + # reset boundary conditions to outdoors so new apertures can be added + if not isinstance(face.boundary_condition, Outdoors): + face.boundary_condition = boundary_conditions.outdoors + face.apertures_by_ratio(face.aperture_ratio, tolerance)
+ +
[docs] def rectangularize_apertures( + self, subdivision_distance=None, max_separation=None, merge_all=False, + tolerance=0.01, angle_tolerance=1.0): + """Convert all Apertures on this Room to be rectangular. + + This is useful when exporting to simulation engines that only accept + rectangular window geometry. This method will always result ing Rooms where + all Apertures are rectangular. However, if the subdivision_distance is not + set, some Apertures may extend past the parent Face or may collide with + one another. + + Args: + subdivision_distance: A number for the resolution at which the + non-rectangular Apertures will be subdivided into smaller + rectangular units. Specifying a number here ensures that the + resulting rectangular Apertures do not extend past the parent + Face or collide with one another. If None, all non-rectangular + Apertures will be rectangularized by taking the bounding rectangle + around the Aperture. (Default: None). + max_separation: A number for the maximum distance between non-rectangular + Apertures at which point the Apertures will be merged into a single + rectangular geometry. This is often helpful when there are several + triangular Apertures that together make a rectangle when they are + merged across their frames. In such cases, this max_separation + should be set to a value that is slightly larger than the window frame. + If None, no merging of Apertures will happen before they are + converted to rectangles. (Default: None). + merge_all: Boolean to note whether all apertures should be merged before + they are rectangularized. If False, only non-rectangular apertures + will be merged before rectangularization. Note that this argument + has no effect when the max_separation is None. (Default: False). + tolerance: The maximum difference between point values for them to be + considered equivalent. (Default: 0.01, suitable for objects in meters). + angle_tolerance: The max angle in degrees that the corners of the + rectangle can differ from a right angle before it is not + considered a rectangle. (Default: 1). + """ + for face in self._faces: + face.rectangularize_apertures( + subdivision_distance, max_separation, merge_all, + tolerance, angle_tolerance + )
+ +
[docs] def ground_by_custom_surface(self, ground_geometry, tolerance=0.01, + angle_tolerance=1.0): + """Set ground boundary conditions using an array of Face3D for the ground. + + Room faces that are coplanar with the ground surface or lie completely below it + will get a Ground boundary condition while those above will get an Outdoors + boundary condition. Existing Faces with an indoor boundary condition will + be unaffected. + + Args: + ground_geometry: An array of ladybug_geometry Face3D that together + represent the ground surface. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. (Default: 0.01, + suitable for objects in meters). + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered + coplanar. (Default: 1). + """ + select_faces = self.faces_by_guide_surface( + ground_geometry, Vector3D(0, 0, -1), tolerance, angle_tolerance) + for face in select_faces: + if face.can_be_ground and \ + isinstance(face.boundary_condition, (Outdoors, Ground)): + face.boundary_condition = boundary_conditions.ground
+ +
[docs] def faces_by_guide_surface(self, surface_geometry, directional_vector=None, + tolerance=0.01, angle_tolerance=1.0): + """Get the Faces of the Room that are touching and coplanar with a given surface. + + This is useful in workflows were one would like to set the properties + of a group of Faces using a guide surface, like setting a series of faces + along a given stretch of a parti wall to be adiabatic. + + Args: + surface_geometry: An array of ladybug_geometry Face3D that together + represent the guide surface. + directional_vector: An optional Vector3D to select the room Faces that + lie on a certain side of the surface_geometry. For example, using + (0, 0, -1) will include all Faces that lie below the surface_geometry + in the resulting selection. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. (Default: 0.01, + suitable for objects in meters). + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered + coplanar. (Default: 1). + """ + selected_faces, ang_tol = [], math.radians(angle_tolerance) + if directional_vector is None: # only check for co-planarity + for face in self.faces: + for srf_geo in surface_geometry: + pl1, pl2 = face.geometry.plane, srf_geo.plane + if pl1.is_coplanar_tolerance(pl2, tolerance, ang_tol): + pt_on_face = face.geometry._point_on_face(tolerance * 2) + if srf_geo.is_point_on_face(pt_on_face, tolerance): + selected_faces.append(face) + break + else: # first check to see if the Face is on the correct side of the surface + rev_vector = directional_vector.reverse() + for face in self.faces: + ray = Ray3D(face.center, rev_vector) + for srf_geo in surface_geometry: + if srf_geo.intersect_line_ray(ray): + selected_faces.append(face) + break + pl1, pl2 = face.geometry.plane, srf_geo.plane + if pl1.is_coplanar_tolerance(pl2, tolerance, ang_tol): + pt_on_face = face.geometry._point_on_face(tolerance * 2) + if srf_geo.is_point_on_face(pt_on_face, tolerance): + selected_faces.append(face) + break + return selected_faces
+ +
[docs] def move(self, moving_vec): + """Move this Room along a vector. + + Args: + moving_vec: A ladybug_geometry Vector3D with the direction and distance + to move the room. + """ + for face in self._faces: + face.move(moving_vec) + self.move_shades(moving_vec) + self.properties.move(moving_vec) + if self._geometry is not None: + self._geometry = self.geometry.move(moving_vec)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Room by a certain angle around an axis and origin. + + Args: + axis: A ladybug_geometry Vector3D axis representing the axis of rotation. + angle: An angle for rotation in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + for face in self._faces: + face.rotate(axis, angle, origin) + self.rotate_shades(axis, angle, origin) + self.properties.rotate(axis, angle, origin) + if self._geometry is not None: + self._geometry = self.geometry.rotate(axis, math.radians(angle), origin)
+ +
[docs] def rotate_xy(self, angle, origin): + """Rotate this Room counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + for face in self._faces: + face.rotate_xy(angle, origin) + self.rotate_xy_shades(angle, origin) + self.properties.rotate_xy(angle, origin) + if self._geometry is not None: + self._geometry = self.geometry.rotate_xy(math.radians(angle), origin)
+ +
[docs] def reflect(self, plane): + """Reflect this Room across a plane. + + Args: + plane: A ladybug_geometry Plane across which the object will + be reflected. + """ + for face in self._faces: + face.reflect(plane) + self.reflect_shades(plane) + self.properties.reflect(plane) + if self._geometry is not None: + self._geometry = self.geometry.reflect(plane.n, plane.o)
+ +
[docs] def scale(self, factor, origin=None): + """Scale this Room by a factor from an origin point. + + Args: + factor: A number representing how much the object should be scaled. + origin: A ladybug_geometry Point3D representing the origin from which + to scale. If None, it will be scaled from the World origin (0, 0, 0). + """ + for face in self._faces: + face.scale(factor, origin) + self.scale_shades(factor, origin) + self.properties.scale(factor, origin) + if self._geometry is not None: + self._geometry = self.geometry.scale(factor, origin)
+ +
[docs] def remove_colinear_vertices_envelope(self, tolerance=0.01, delete_degenerate=False): + """Remove colinear and duplicate vertices from this object's Faces and Sub-faces. + + If degenerate geometry is found in the process of removing colinear vertices, + an exception will be raised. Note that this does not affect the assigned Shades. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + delete_degenerate: Boolean to note whether degenerate Faces, Apertures and + Doors (objects that evaluate to less than 3 vertices at the tolerance) + should be deleted from the Room instead of raising a ValueError. + (Default: False). + """ + if delete_degenerate: + new_faces, i_to_remove = list(self._faces), [] + for i, face in enumerate(new_faces): + try: + face.remove_colinear_vertices(tolerance) + face.remove_degenerate_sub_faces(tolerance) + except ValueError: # degenerate face found! + i_to_remove.append(i) + for i in reversed(i_to_remove): + new_faces.pop(i) + self._faces = tuple(new_faces) + else: + try: + for face in self._faces: + face.remove_colinear_vertices(tolerance) + for ap in face._apertures: + ap.remove_colinear_vertices(tolerance) + for dr in face._doors: + dr.remove_colinear_vertices(tolerance) + except ValueError as e: + raise ValueError( + 'Room "{}" contains invalid geometry.\n {}'.format( + self.full_id, str(e).replace('\n', '\n '))) + if self._geometry is not None: + self._geometry = Polyface3D.from_faces( + tuple(face.geometry for face in self._faces), tolerance)
+ +
[docs] def clean_envelope(self, adjacency_dict, tolerance=0.01): + """Remove colinear and duplicate vertices from this object's Faces and Sub-faces. + + This method also automatically removes degenerate Faces and coordinates + adjacent Faces with the adjacency_dict to ensure matching numbers of + vertices, which is a requirement for engines like EnergyPlus. + + Args: + adjacency_dict: A dictionary containing the identifiers of Room Faces as + keys and Honeybee Face objects as values. This is used to indicate the + target number of vertices that each Face should have after colinear + vertices are removed. This can be used to ensure adjacent Faces + have matching numbers of vertices, which is a requirement for + certain interfaces like EnergyPlus. + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + + Returns: + A dictionary containing the identifiers of adjacent Faces as keys and + Honeybee Face objects as values. This can be used as an input in future + Rooms on which this method is run to ensure adjacent Faces have matching + numbers of vertices, which is a requirement for certain interfaces + like EnergyPlus. + """ + adj_dict = {} + new_faces, i_to_remove = list(self._faces), [] + for i, face in enumerate(new_faces): + try: # first make sure that the geometry is not degenerate + new_geo = face.geometry.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate face found! + i_to_remove.append(i) + continue + # see if the geometry matches its adjacent geometry + if isinstance(face.boundary_condition, Surface): + try: + adj_face = adjacency_dict[face.identifier] + if len(new_geo) != len(adj_face.geometry): + new_geo = adj_face.geometry.flip() + except KeyError: # the adjacent object has not been found yet + pass + adj_dict[face.boundary_condition.boundary_condition_object] = face + # update geometry and remove degenerate Apertures and Doors + face._geometry = new_geo + face._punched_geometry = None # reset so that it can be re-computed + face.remove_degenerate_sub_faces(tolerance) + # remove any degenerate Faces from the Room + for i in reversed(i_to_remove): + new_faces.pop(i) + self._faces = tuple(new_faces) + if self._geometry is not None: + self._geometry = Polyface3D.from_faces( + tuple(face.geometry for face in self._faces), tolerance) + return adj_dict
+ +
[docs] def is_geo_equivalent(self, room, tolerance=0.01): + """Get a boolean for whether this object is geometrically equivalent to another. + + This will also check all child Faces, Apertures, Doors and Shades + for equivalency. + + Args: + room: Another Room for which geometric equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered geometrically equivalent. + + Returns: + True if geometrically equivalent. False if not geometrically equivalent. + """ + met_1 = (self.display_name, self.multiplier, self.story, self.exclude_floor_area) + met_2 = (room.display_name, room.multiplier, room.story, room.exclude_floor_area) + if met_1 != met_2: + return False + if len(self._faces) != len(room._faces): + return False + for f1, f2 in zip(self._faces, room._faces): + if not f1.is_geo_equivalent(f2, tolerance): + return False + if not self._are_shades_equivalent(room, tolerance): + return False + return True
+ +
[docs] def check_solid(self, tolerance=0.01, angle_tolerance=1, raise_exception=True, + detailed=False): + """Check whether the Room is a closed solid to within the input tolerances. + + Args: + tolerance: tolerance: The maximum difference between x, y, and z values + at which face vertices are considered equivalent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle difference in degrees that vertices are + allowed to differ from one another in order to consider them colinear. + Default: 1 degree. + raise_exception: Boolean to note whether a ValueError should be raised + if the room geometry does not form a closed solid. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + if self._geometry is not None and self.geometry.is_solid: + return [] if detailed else '' + face_geometries = tuple(face.geometry for face in self._faces) + self._geometry = Polyface3D.from_faces(face_geometries, tolerance) + if self.geometry.is_solid: + return [] if detailed else '' + ang_tol = math.radians(angle_tolerance) + self._geometry = self.geometry.merge_overlapping_edges(tolerance, ang_tol) + if self.geometry.is_solid: + return [] if detailed else '' + msg = 'Room "{}" is not closed to within {} tolerance and {} angle ' \ + 'tolerance.\n {} naked edges found\n {} non-manifold edges found'.format( + self.full_id, tolerance, angle_tolerance, + len(self._geometry.naked_edges), len(self._geometry.non_manifold_edges)) + full_msg = self._validation_message( + msg, raise_exception, detailed, '000106', + error_type='Non-Solid Room Geometry') + if detailed: # add the naked and non-manifold edges to helper_geometry + help_edges = [ln.to_dict() for ln in self.geometry.naked_edges] + help_edges.extend([ln.to_dict() for ln in self.geometry.non_manifold_edges]) + full_msg[0]['helper_geometry'] = help_edges + return full_msg
+ +
[docs] def check_sub_faces_valid(self, tolerance=0.01, angle_tolerance=1, + raise_exception=True, detailed=False): + """Check that room's sub-faces are co-planar with faces and in the face boundary. + + Note this does not check the planarity of the sub-faces themselves, whether + they self-intersect, or whether they have a non-zero area. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered coplanar. + Default: 1 degree. + raise_exception: Boolean to note whether a ValueError should be raised + if an sub-face is not valid. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + detailed = False if raise_exception else detailed + msgs = [] + for f in self._faces: + msg = f.check_sub_faces_valid(tolerance, angle_tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if len(msgs) == 0: + return [] if detailed else '' + elif detailed: + return msgs + full_msg = 'Room "{}" contains invalid sub-faces (Apertures and Doors).' \ + '\n {}'.format(self.full_id, '\n '.join(msgs)) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_sub_faces_overlapping( + self, tolerance=0.01, raise_exception=True, detailed=False): + """Check that this Room's sub-faces do not overlap with one another. + + Args: + tolerance: The minimum distance that two sub-faces must overlap in order + for them to be considered overlapping and invalid. (Default: 0.01, + suitable for objects in meters). + raise_exception: Boolean to note whether a ValueError should be raised + if a sub-faces overlap with one another. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + detailed = False if raise_exception else detailed + msgs = [] + for f in self._faces: + msg = f.check_sub_faces_overlapping(tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if len(msgs) == 0: + return [] if detailed else '' + elif detailed: + return msgs + full_msg = 'Room "{}" contains overlapping sub-faces.' \ + '\n {}'.format(self.full_id, '\n '.join(msgs)) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_upside_down_faces( + self, angle_tolerance=1, raise_exception=True, detailed=False): + """Check whether the Room's Faces have the correct direction for the face type. + + This method will only report Floors that are pointing upwards or RoofCeilings + that are pointed downwards. These cases are likely modeling errors and are in + danger of having their vertices flipped by EnergyPlus, causing them to + not see the sun. + + Args: + angle_tolerance: The max angle in degrees that the Face normal can + differ from up or down before it is considered a case of a downward + pointing RoofCeiling or upward pointing Floor. Default: 1 degree. + raise_exception: Boolean to note whether an ValueError should be + raised if the Face is an an upward pointing Floor or a downward + pointing RoofCeiling. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + detailed = False if raise_exception else detailed + msgs = [] + for f in self._faces: + msg = f.check_upside_down(angle_tolerance, False, detailed) + if detailed: + msgs.extend(msg) + elif msg != '': + msgs.append(msg) + if len(msgs) == 0: + return [] if detailed else '' + elif detailed: + return msgs + full_msg = 'Room "{}" contains upside down Faces.' \ + '\n {}'.format(self.full_id, '\n '.join(msgs)) + if raise_exception and len(msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_planar(self, tolerance=0.01, raise_exception=True, detailed=False): + """Check that all of the Room's geometry components are planar. + + This includes all of the Room's Faces, Apertures, Doors and Shades. + + Args: + tolerance: The minimum distance between a given vertex and a the + object's plane at which the vertex is said to lie in the plane. + Default: 0.01, suitable for objects in meters. + raise_exception: Boolean to note whether an ValueError should be + raised if a vertex does not lie within the object's plane. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + detailed = False if raise_exception else detailed + msgs = [self._check_planar_shades(tolerance, detailed)] + for face in self._faces: + msgs.append(face.check_planar(tolerance, False, detailed)) + msgs.append(face._check_planar_shades(tolerance, detailed)) + for ap in face._apertures: + msgs.append(ap.check_planar(tolerance, False, detailed)) + msgs.append(ap._check_planar_shades(tolerance, detailed)) + for dr in face._doors: + msgs.append(dr.check_planar(tolerance, False, detailed)) + msgs.append(dr._check_planar_shades(tolerance, detailed)) + full_msgs = [msg for msg in msgs if msg] + if len(full_msgs) == 0: + return [] if detailed else '' + elif detailed: + return [m for megs in full_msgs for m in megs] + full_msg = 'Room "{}" contains non-planar geometry.' \ + '\n {}'.format(self.full_id, '\n '.join(full_msgs)) + if raise_exception and len(full_msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_self_intersecting(self, tolerance=0.01, raise_exception=True, + detailed=False): + """Check that no edges of the Room's geometry components self-intersect. + + This includes all of the Room's Faces, Apertures, Doors and Shades. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + raise_exception: If True, a ValueError will be raised if an object + intersects with itself (like a bow tie). (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + detailed = False if raise_exception else detailed + msgs = [self._check_self_intersecting_shades(tolerance, detailed)] + for face in self._faces: + msgs.append(face.check_self_intersecting(tolerance, False, detailed)) + msgs.append(face._check_self_intersecting_shades(tolerance, detailed)) + for ap in face._apertures: + msgs.append(ap.check_self_intersecting(tolerance, False, detailed)) + msgs.append(ap._check_self_intersecting_shades(tolerance, detailed)) + for dr in face._doors: + msgs.append(dr.check_self_intersecting(tolerance, False, detailed)) + msgs.append(dr._check_self_intersecting_shades(tolerance, detailed)) + full_msgs = [msg for msg in msgs if msg] + if len(full_msgs) == 0: + return [] if detailed else '' + elif detailed: + return [m for megs in full_msgs for m in megs] + full_msg = 'Room "{}" contains self-intersecting geometry.' \ + '\n {}'.format(self.full_id, '\n '.join(full_msgs)) + if raise_exception and len(full_msgs) != 0: + raise ValueError(full_msg) + return full_msg
+ +
[docs] def check_degenerate(self, tolerance=0.01, raise_exception=True, detailed=False): + """Check whether the Room is degenerate with zero volume. + + Args: + tolerance: tolerance: The maximum difference between x, y, and z values + at which face vertices are considered equivalent. (Default: 0.01, + suitable for objects in meters). + raise_exception: Boolean to note whether a ValueError should be raised + if the room geometry is degenerate. (Default: True). + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + if len(self._faces) >= 4 and self.volume > tolerance: + return [] if detailed else '' + msg = 'Room "{}" is degenerate with zero volume. It should be deleted'.format( + self.full_id) + return self._validation_message( + msg, raise_exception, detailed, '000107', + error_type='Degenerate Room Volume')
+ +
[docs] def merge_coplanar_faces( + self, tolerance=0.01, angle_tolerance=1, orthogonal_only=False): + """Merge coplanar Faces of this Room. + + This is often useful before running Room.intersect_adjacency between + multiple Rooms as it will ensure the result is clean with any previous + intersections erased. + + This method attempts to preserve as many properties as possible for the + split Faces but, when Faces are merged, the properties of one of the + merged faces will determine the face type and boundary condition. Also, all + Face extension attributes will be removed (reset to default) and, if merged + Faces originally had Surface boundary conditions, they will be reset + to Outdoors. + + Args: + tolerance: The minimum difference between the coordinate values of two + faces at which they can be considered adjacent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered + coplanar. (Default: 1 degree). + orthogonal_only: A boolean to note whether only vertical and horizontal + coplanar faces should be merged, leaving faces with any other tilt + intact. Useful for cases where alignment of walls with the + Room.horizontal_boundary is desired without disrupting the roof + geometry. (Default: False). + + Returns: + A list containing only the new Faces that were created as part of the + merging process. These new Faces will have as many properties of the + original Face assigned to them as possible but they will not have a + Surface boundary condition if the original Face had one. Having + the new Faces here can be used in operations like setting new Surface + boundary conditions or re-assigning extension attributes. + """ + # group the Faces of the Room by their co-planarity + tol, a_tol = tolerance, math.radians(angle_tolerance) + coplanar_dict = {self._faces[0].geometry.plane: [self._faces[0]]} + if not orthogonal_only: + for face in self._faces[1:]: + for pln, f_list in coplanar_dict.items(): + if face.geometry.plane.is_coplanar_tolerance(pln, tol, a_tol): + f_list.append(face) + break + else: # the first face with this type of plane + coplanar_dict[face.geometry.plane] = [face] + else: + up_vec = Vector3D(0, 0, 1) + min_ang, max_ang = (math.pi / 2) - a_tol, (math.pi / 2) + a_tol + max_h_ang = math.pi + a_tol + for face in self._faces[1:]: + v_ang = up_vec.angle(face.normal) + if v_ang < a_tol or min_ang < v_ang < max_ang or v_ang > max_h_ang: + for pln, f_list in coplanar_dict.items(): + if face.geometry.plane.is_coplanar_tolerance(pln, tol, a_tol): + f_list.append(face) + break + else: # the first face with this type of plane + coplanar_dict[face.geometry.plane] = [face] + else: + coplanar_dict[face.geometry.plane] = [face] + + # merge any of the coplanar Faces together + all_faces, new_faces = [], [] + for face_list in coplanar_dict.values(): + if len(face_list) == 1: # no faces to merge + all_faces.append(face_list[0]) + else: # there are faces to merge + f_geos = [f.geometry for f in face_list] + joined_geos = Face3D.join_coplanar_faces(f_geos, tolerance) + if len(joined_geos) < len(f_geos): # faces were merged + prop_f = face_list[0] + apertures, doors, in_shades, out_shades = [], [], [], [] + for f in face_list: + apertures.extend(f._apertures) + doors.extend(f._doors) + in_shades.extend(f._indoor_shades) + out_shades.extend(f._outdoor_shades) + for i, new_geo in enumerate(joined_geos): + fid = prop_f.identifier if i == 0 else \ + '{}_{}'.format(prop_f.identifier, i) + fbc = prop_f.boundary_condition if not \ + isinstance(prop_f.boundary_condition, Surface) \ + else boundary_conditions.outdoors + nf = Face(fid, new_geo, prop_f.type, fbc) + for ap in apertures: + if nf.geometry.is_sub_face(ap.geometry, tol, a_tol): + nf.add_aperture(ap) + for dr in doors: + if nf.geometry.is_sub_face(dr.geometry, tol, a_tol): + nf.add_door(dr) + if i == 0: # add all assigned shades to this face + nf.add_indoor_shades(in_shades) + nf.add_outdoor_shades(out_shades) + nf._parent = self + all_faces.append(nf) + new_faces.append(nf) + else: # faces don't overlap and were not merged + all_faces.extend(face_list) + if len(new_faces) == 0: + return new_faces # nothing has been merged + + # make a new polyface from the updated faces + room_polyface = Polyface3D.from_faces( + tuple(face.geometry for face in all_faces), tolerance) + if not room_polyface.is_solid: + room_polyface = room_polyface.merge_overlapping_edges(tolerance, a_tol) + # replace honeybee face geometry with versions that are facing outwards + if room_polyface.is_solid: + for i, correct_face3d in enumerate(room_polyface.faces): + face = all_faces[i] + norm_init = face._geometry.normal + face._geometry = correct_face3d + if face.has_sub_faces: # flip sub-faces to align with parent Face + if norm_init.angle(face._geometry.normal) > (math.pi / 2): + for ap in face._apertures: + ap._geometry = ap._geometry.flip() + for dr in face._doors: + dr._geometry = dr._geometry.flip() + # reset the faces and geometry of the room with the new faces + self._faces = tuple(all_faces) + self._geometry = room_polyface + return new_faces
+ +
[docs] def coplanar_split(self, geometry, tolerance=0.01, angle_tolerance=1): + """Split the Faces of this Room with coplanar geometry (Polyface3D or Face3D). + + This method attempts to preserve as many properties as possible for the + split Faces, including all extension attributes and sub-faces (as long + as they don't fall in the path of the intersection). + + Args: + geometry: A list of coplanar geometry (either Polyface3D or Face3D) + that will be used to split the Faces of this Room. Typically, these + are Polyface3D of other Room geometries to be intersected with this + one but they can also be Face3D if only one intersection is desired. + tolerance: The minimum difference between the coordinate values of two + faces at which they can be considered adjacent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered + coplanar. (Default: 1 degree). + + Returns: + A list containing only the new Faces that were created as part of the + splitting process. These new Faces will have as many properties of the + original Face assigned to them as possible but they will not have a + Surface boundary condition if the original Face had one. Having just + the new Faces here can be used in operations like setting new Surface + boundary conditions. + """ + # make a dictionary of all face geometry to be intersected + geo_dict = {f.identifier: [f.geometry] for f in self.faces} + + # loop through the polyface geometries and intersect this room's geometry + ang_tol = math.radians(angle_tolerance) + for s_geo in geometry: + if isinstance(s_geo, Polyface3D) and not \ + Polyface3D.overlapping_bounding_boxes( + self.geometry, s_geo, tolerance): + continue # no overlap in bounding box; intersection impossible + s_geos = s_geo.faces if isinstance(s_geo, Polyface3D) else [s_geo] + for face_1 in self.faces: + for face_2 in s_geos: + if not face_1.geometry.plane.is_coplanar_tolerance( + face_2.plane, tolerance, ang_tol): + continue # not coplanar; intersection impossible + if face_1.geometry.is_centered_adjacent(face_2, tolerance): + tol_area = math.sqrt(face_1.geometry.area) * tolerance + if abs(face_1.geometry.area - face_2.area) < tol_area: + continue # already intersected; no need to re-do + new_geo = [] + for f_geo in geo_dict[face_1.identifier]: + f_split, _ = Face3D.coplanar_split( + f_geo, face_2, tolerance, ang_tol) + for sp_g in f_split: + try: + sp_g = sp_g.remove_colinear_vertices(tolerance) + new_geo.append(sp_g) + except AssertionError: # degenerate geometry to ignore + pass + geo_dict[face_1.identifier] = new_geo + + # use the intersected geometry to remake this room's faces + all_faces, new_faces = [], [] + for face in self.faces: + int_faces = geo_dict[face.identifier] + if len(int_faces) == 1: # just use the old Face object + all_faces.append(face) + else: # make new Face objects + new_bc = face.boundary_condition \ + if not isinstance(face.boundary_condition, Surface) \ + else boundary_conditions.outdoors + new_aps = [ap.duplicate() for ap in face.apertures] + new_drs = [dr.duplicate() for dr in face.doors] + for x, nf_geo in enumerate(int_faces): + new_id = '{}_{}'.format(face.identifier, x) + new_face = Face(new_id, nf_geo, face.type, new_bc) + new_face._display_name = face._display_name + new_face._user_data = None if face.user_data is None \ + else face.user_data.copy() + for ap in new_aps: + if nf_geo.is_sub_face(ap.geometry, tolerance, ang_tol): + new_face.add_aperture(ap) + for dr in new_drs: + if nf_geo.is_sub_face(dr.geometry, tolerance, ang_tol): + new_face.add_door(dr) + if x == 0: + face._duplicate_child_shades(new_face) + new_face._parent = face._parent + new_face._properties._duplicate_extension_attr(face._properties) + new_faces.append(new_face) + all_faces.append(new_face) + if len(new_faces) == 0: + return new_faces # nothing has been intersected + + # make a new polyface from the updated faces + room_polyface = Polyface3D.from_faces( + tuple(face.geometry for face in all_faces), tolerance) + if not room_polyface.is_solid: + room_polyface = room_polyface.merge_overlapping_edges(tolerance, ang_tol) + # replace honeybee face geometry with versions that are facing outwards + if room_polyface.is_solid: + for i, correct_face3d in enumerate(room_polyface.faces): + face = all_faces[i] + norm_init = face._geometry.normal + face._geometry = correct_face3d + if face.has_sub_faces: # flip sub-faces to align with parent Face + if norm_init.angle(face._geometry.normal) > (math.pi / 2): + for ap in face._apertures: + ap._geometry = ap._geometry.flip() + for dr in face._doors: + dr._geometry = dr._geometry.flip() + # reset the faces and geometry of the room with the new faces + self._faces = tuple(all_faces) + self._geometry = room_polyface + return new_faces
+ +
[docs] @staticmethod + def intersect_adjacency(rooms, tolerance=0.01, angle_tolerance=1): + """Intersect the Faces of an array of Rooms to ensure matching adjacencies. + + Note that this method may remove Apertures and Doors if they align with + an intersection so it is typically recommended that this method be used + before sub-faces are assigned (if possible). Sub-faces that do not fall + along an intersection will be preserved. + + Also note that this method does not actually set the walls that are next to one + another to be adjacent. The solve_adjacency method must be used for this after + running this method. + + Args: + rooms: A list of Rooms for which adjacent Faces will be intersected. + tolerance: The minimum difference between the coordinate values of two + faces at which they can be considered adjacent. Default: 0.01, + suitable for objects in meters. + angle_tolerance: The max angle in degrees that the plane normals can + differ from one another in order for them to be considered + coplanar. (Default: 1 degree). + + Returns: + An array of Rooms that have been intersected with one another. + """ + # get all of the room polyfaces + room_geos = [r.geometry for r in rooms] + # intersect all adjacencies between rooms + for i, room in enumerate(rooms): + other_rooms = room_geos[:i] + room_geos[i + 1:] + room.coplanar_split(other_rooms, tolerance, angle_tolerance)
+ +
[docs] @staticmethod + def solve_adjacency(rooms, tolerance=0.01): + """Solve for adjacencies between a list of rooms. + + Note that this method will mutate the input rooms by setting Surface + boundary conditions for any adjacent objects. However, it does NOT overwrite + existing Surface boundary conditions and only adds new ones if faces are + found to be adjacent with equivalent areas. + + Args: + rooms: A list of rooms for which adjacencies will be solved. + tolerance: The minimum difference between the coordinate values of two + faces at which they can be considered centered adjacent. Default: 0.01, + suitable for objects in meters. + + Returns: + A dictionary of information about the objects that had their adjacency set. + The dictionary has the following keys. + + - adjacent_faces - A list of tuples with each tuple containing 2 objects + for Faces paired in the process of solving adjacency. This data can + be used to assign custom properties to the new adjacent Faces (like + making all adjacencies an AirBoundary face type or assigning custom + materials/constructions). + + - adjacent_apertures - A list of tuples with each tuple containing 2 + objects for Apertures paired in the process of solving adjacency. + + - adjacent_doors - A list of tuples with each tuple containing 2 objects + for Doors paired in the process of solving adjacency. + """ + # lists of adjacencies to track + adj_info = {'adjacent_faces': [], 'adjacent_apertures': [], + 'adjacent_doors': []} + + # solve all adjacencies between rooms + for i, room_1 in enumerate(rooms): + try: + for room_2 in rooms[i + 1:]: + if not Polyface3D.overlapping_bounding_boxes( + room_1.geometry, room_2.geometry, tolerance): + continue # no overlap in bounding box; adjacency impossible + for face_1 in room_1._faces: + for face_2 in room_2._faces: + if not isinstance(face_2.boundary_condition, Surface): + if face_1.geometry.is_centered_adjacent( + face_2.geometry, tolerance): + face_info = face_1.set_adjacency(face_2) + adj_info['adjacent_faces'].append((face_1, face_2)) + adj_info['adjacent_apertures'].extend( + face_info['adjacent_apertures']) + adj_info['adjacent_doors'].extend( + face_info['adjacent_doors']) + break + except IndexError: + pass # we have reached the end of the list of rooms + return adj_info
+ +
[docs] @staticmethod + def find_adjacency(rooms, tolerance=0.01): + """Get a list with all adjacent pairs of Faces between input rooms. + + Note that this method does not change any boundary conditions of the input + rooms or mutate them in any way. It's purely a geometric analysis of the + faces between rooms. + + Args: + rooms: A list of rooms for which adjacencies will be solved. + tolerance: The minimum difference between the coordinate values of two + faces at which they can be considered centered adjacent. Default: 0.01, + suitable for objects in meters. + + Returns: + A list of tuples with each tuple containing 2 objects for Faces that + are adjacent to one another. + """ + adj_faces = [] # lists of adjacencies to track + for i, room_1 in enumerate(rooms): + try: + for room_2 in rooms[i + 1:]: + if not Polyface3D.overlapping_bounding_boxes( + room_1.geometry, room_2.geometry, tolerance): + continue # no overlap in bounding box; adjacency impossible + for face_1 in room_1._faces: + for face_2 in room_2._faces: + if face_1.geometry.is_centered_adjacent( + face_2.geometry, tolerance): + adj_faces.append((face_1, face_2)) + break + except IndexError: + pass # we have reached the end of the list of zones + return adj_faces
+ +
[docs] @staticmethod + def group_by_adjacency(rooms): + """Group Rooms together that are connected by adjacencies. + + This is useful for separating rooms in the case where a Model contains + multiple buildings or sections that are separated by adiabatic or + outdoor boundary conditions. + + Args: + rooms: A list of rooms to be grouped by their adjacency. + + Returns: + A list of list with each sub-list containing rooms that share adjacencies. + """ + return Room._adjacency_grouping(rooms, Room._find_adjacent_rooms)
+ +
[docs] @staticmethod + def group_by_air_boundary_adjacency(rooms): + """Group Rooms together that share air boundaries. + + This is useful for understanding the radiant enclosures that will exist + when a model is exported to EnergyPlus. + + Args: + rooms: A list of rooms to be grouped by their air boundary adjacency. + + Returns: + A list of list with each sub-list containing rooms that share adjacent + air boundaries. If a Room has no air boundaries it will the the only + item within its sub-list. + """ + return Room._adjacency_grouping(rooms, Room._find_adjacent_air_boundary_rooms)
+ +
[docs] @staticmethod + def group_by_orientation(rooms, group_count=None, north_vector=Vector2D(0, 1)): + """Group Rooms with the same average outdoor wall orientation. + + Args: + rooms: A list of honeybee rooms to be grouped by orientation. + group_count: An optional positive integer to set the number of orientation + groups to use. For example, setting this to 4 will result in rooms + being grouped by four orientations (North, East, South, West). If None, + the maximum number of unique groups will be used. + north_vector: A ladybug_geometry Vector2D for the north direction. + Default is the Y-axis (0, 1). + + Returns: + A tuple with three items. + + - grouped_rooms - A list of lists of honeybee rooms with each sub-list + representing a different orientation. + + - core_rooms - A list of honeybee rooms with no identifiable orientation. + + - orientations - A list of numbers between 0 and 360 with one orientation + for each branch of the output grouped_rooms. This will be a list of + angle ranges if a value is input for group_count. + """ + # loop through each of the rooms and get the orientation + orient_dict = {} + core_rooms = [] + for room in rooms: + ori = room.average_orientation(north_vector) + if ori is None: + core_rooms.append(room) + else: + try: + orient_dict[ori].append(room) + except KeyError: + orient_dict[ori] = [] + orient_dict[ori].append(room) + + # sort the rooms by orientation values + room_mtx = sorted(orient_dict.items(), key=lambda d: float(d[0])) + orientations = [r_tup[0] for r_tup in room_mtx] + grouped_rooms = [r_tup[1] for r_tup in room_mtx] + + # group orientations if there is an input group_count + if group_count is not None: + angs = angles_from_num_orient(group_count) + p_rooms = [[] for i in range(group_count)] + for ori, rm in zip(orientations, grouped_rooms): + or_ind = orient_index(ori, angs) + p_rooms[or_ind].extend(rm) + orientations = ['{} - {}'.format(int(angs[i - 1]), int(angs[i])) + for i in range(group_count)] + grouped_rooms = p_rooms + return grouped_rooms, core_rooms, orientations
+ +
[docs] @staticmethod + def group_by_floor_height(rooms, min_difference=0.01): + """Group Rooms according to their average floor height. + + Args: + rooms: A list of honeybee rooms to be grouped by floor height. + min_difference: An float value to denote the minimum difference + in floor heights that is considered meaningful. This can be used + to ensure rooms like those representing stair landings are grouped + with those below them. Default: 0.01, which means that virtually + any minor difference in floor heights will result in a new group. + This assumption is suitable for models in meters. + + Returns: + A tuple with two items. + + - grouped_rooms - A list of lists of honeybee rooms with each sub-list + representing a different floor height. + + - floor_heights - A list of floor heights with one floor height for each + sub-list of the output grouped_rooms. + """ + # loop through each of the rooms and get the floor height + flrhgt_dict = {} + for room in rooms: + flrhgt = room.average_floor_height + try: # assume there is already a story with the room's floor height + flrhgt_dict[flrhgt].append(room) + except KeyError: # this is the first room with this floor height + flrhgt_dict[flrhgt] = [] + flrhgt_dict[flrhgt].append(room) + + # sort the rooms by floor heights + room_mtx = sorted(flrhgt_dict.items(), key=lambda d: float(d[0])) + flr_hgts = [r_tup[0] for r_tup in room_mtx] + rooms = [r_tup[1] for r_tup in room_mtx] + + # group floor heights if they differ by less than the min_difference + floor_heights = [flr_hgts[0]] + grouped_rooms = [rooms[0]] + for flrh, rm in zip(flr_hgts[1:], rooms[1:]): + if flrh - floor_heights[-1] < min_difference: + grouped_rooms[-1].extend(rm) + else: + grouped_rooms.append(rm) + floor_heights.append(flrh) + return grouped_rooms, floor_heights
+ +
[docs] @staticmethod + def stories_by_floor_height(rooms, min_difference=2.0): + """Assign story properties to a set of Rooms using their floor heights. + + Stories will be named with a standard convention ('Floor1', 'Floor2', etc.). + Note that this method will only assign stories to Rooms that do not have + a story identifier already assigned to them. + + Args: + rooms: A list of rooms for which story properties will be automatically + assigned. + min_difference: An float value to denote the minimum difference + in floor heights that is considered meaningful. This can be used + to ensure rooms like those representing stair landings are grouped + with those below them. Default: 2.0, which means that any difference + in floor heights less than 2.0 will be considered a part of the + same story. This assumption is suitable for models in meters. + + Returns: + A list of the unique story names that were assigned to the input rooms. + """ + # group the rooms by floor height + new_rooms, _ = Room.group_by_floor_height(rooms, min_difference) + + # assign the story property to each of the groups + story_names = [] + for i, room_list in enumerate(new_rooms): + story_name = 'Floor{}'.format(i + 1) + story_names.append(story_name) + for room in room_list: + if room.story is not None: + continue # preserve any existing user-assigned story values + room.story = story_name + return story_names
+ +
[docs] @staticmethod + def check_room_volume_collisions(rooms, tolerance=0.01, detailed=False): + """Check whether the volumes of Rooms collide with one another beyond tolerance. + + At the moment, this method only checks for the case where coplanar Floor + Faces of different Rooms overlap with one another, which clearly indicates + that there is definitely a collision between the Room volumes. In the + future, this method may be amended to sense more complex cases of + colliding Room volumes. For now, it is designed to only detect the most + common cases. + + Args: + rooms: A list of rooms that will be checked for volumetric collisions. + For this method to run most efficiently, these input Rooms should + be at the same horizontal floor level. The Room.group_by_floor_height() + method can be used to group the Rooms of a model according to their + height before running this method. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. (Default: 0.01, + suitable for objects in meters. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + # create Polygon2Ds from the floors of the rooms + polys = [ + [(Polygon2D(Point2D(p.x, p.y) for p in flr.vertices), flr.geometry[0].z) + for flr in room.floors if flr.geometry.is_horizontal(tolerance)] + for room in rooms + ] + + # find the number of overlaps across the Rooms + msgs = [] + for i, (room_1, polys_1) in enumerate(zip(rooms, polys)): + overlap_rooms = [] + if len(polys_1) == 0: + continue + try: + for room_2, polys_2 in zip(rooms[i + 1:], polys[i + 1:]): + collision_found = False + for ply_1, z1 in polys_1: + if collision_found: + break + for ply_2, z2 in polys_2: + if collision_found: + break + if abs(z1 - z2) < tolerance: + if ply_1.polygon_relationship(ply_2, tolerance) >= 0: + overlap_rooms.append(room_2) + collision_found = True + break + except IndexError: + pass # we have reached the end of the list + + # of colliding rooms were found, create error messages + if len(overlap_rooms) != 0: + for room_2 in overlap_rooms: + msg = 'Room "{}" has a volume that collides with the volume ' \ + 'of Room "{}" more than the tolerance ({}).'.format( + room_1.display_name, room_2.display_name, tolerance) + msg = Room._validation_message_child( + msg, room_1, detailed, '000108', + error_type='Colliding Room Volumes') + if detailed: + msg['element_id'].append(room_2.identifier) + msg['element_name'].append(room_2.display_name) + msg['parents'].append(msg['parents'][0]) + msgs.append(msg) + # report any errors + if detailed: + return msgs + full_msg = '\n '.join(msgs) + return full_msg
+ +
[docs] @staticmethod + def grouped_horizontal_boundary( + rooms, min_separation=0, tolerance=0.01, floors_only=True): + """Get a list of Face3D for the horizontal boundary around several Rooms. + + This method will attempt to produce a boundary that follows along the + outer parts of the Floors of the Rooms so it is not suitable for groups + of Rooms that overlap one another in plan. This method may return an empty + list if the min_separation is so large that a continuous boundary could not + be determined. + + Args: + rooms: A list of Honeybee Rooms for which the horizontal boundary will + be computed. + min_separation: A number for the minimum distance between Rooms that + is considered a meaningful separation. Gaps between Rooms that + are less than this distance will be ignored and the boundary + will continue across the gap. When the input rooms represent + volumes of interior Faces, this input can be thought of as the + maximum interior wall thickness, which should be ignored in + the calculation of the overall boundary of the Rooms. When Rooms + are touching one another (with Room volumes representing center lines + of walls), this value can be set to zero or anything less than + or equal to the tolerance. Doing so will yield a cleaner result for + the boundary, which will be faster. Note that care should be taken + not to set this value higher than the length of any meaningful + exterior wall segments. Otherwise, the exterior segments + will be ignored in the result. This can be particularly dangerous + around curved exterior walls that have been planarized through + subdivision into small segments. (Default: 0). + tolerance: The maximum difference between coordinate values of two + vertices at which they can be considered equivalent. (Default: 0.01, + suitable for objects in meters). + floors_only: A boolean to note whether the grouped boundary should only + surround the Floor geometries of the Rooms (True) or if they should + surround the entirety of the Room volumes in plan (False). + """ + # get the horizontal boundary geometry of each room + floor_geos = [] + if floors_only: + for room in rooms: + floor_geos.extend(room.horizontal_floor_boundaries(tolerance=tolerance)) + else: + for room in rooms: + floor_geos.append(room.horizontal_boundary(tolerance=tolerance)) + + # remove colinear vertices and degenerate faces + clean_floor_geos = [] + for geo in floor_geos: + try: + clean_floor_geos.append(geo.remove_colinear_vertices(tolerance)) + except AssertionError: # degenerate geometry to ignore + pass + if len(clean_floor_geos) == 0: + return [] # no Room boundary to be found + + # convert the floor Face3Ds into counterclockwise Polygon2Ds + floor_polys, z_vals = [], [] + for flr_geo in clean_floor_geos: + z_vals.append(flr_geo.min.z) + b_poly = Polygon2D([Point2D(pt.x, pt.y) for pt in flr_geo.boundary]) + floor_polys.append(b_poly) + if flr_geo.has_holes: + for hole in flr_geo.holes: + h_poly = Polygon2D([Point2D(pt.x, pt.y) for pt in hole]) + floor_polys.append(h_poly) + z_min = min(z_vals) + + # if the min_separation is small, use the more reliable intersection method + if min_separation <= tolerance: + closed_polys = Polygon2D.joined_intersected_boundary(floor_polys, tolerance) + else: # otherwise, use the more intense and less reliable gap crossing method + closed_polys = Polygon2D.gap_crossing_boundary( + floor_polys, min_separation, tolerance) + + # remove colinear vertices from the resulting polygons + clean_polys = [] + for poly in closed_polys: + try: + clean_polys.append(poly.remove_colinear_vertices(tolerance)) + except AssertionError: + pass # degenerate polygon to ignore + + # figure out if polygons represent holes in the others and make Face3D + if len(clean_polys) == 0: + return [] + elif len(clean_polys) == 1: # can be represented with a single Face3D + pts3d = [Point3D(pt.x, pt.y, z_min) for pt in clean_polys[0]] + return [Face3D(pts3d)] + else: # need to separate holes from distinct Face3Ds + bound_faces = [] + for poly in clean_polys: + pts3d = tuple(Point3D(pt.x, pt.y, z_min) for pt in poly) + bound_faces.append(Face3D(pts3d)) + return Face3D.merge_faces_to_holes(bound_faces, tolerance)
+ +
[docs] @staticmethod + def rooms_from_rectangle_plan( + width, length, floor_to_floor_height, perimeter_offset=0, story_count=1, + orientation_angle=0, outdoor_roof=True, ground_floor=True, + unique_id=None, tolerance=0.01): + """Create a Rooms that represent a rectangular floor plan. + + Note that the resulting Rooms won't have any windows or solved adjacencies. + These can be added by using the Room.solve_adjacency method and the + various Face.apertures_by_XXX methods. + + Args: + width: Number for the width of the plan (in the X direction). + length: Number for the length of the plan (in the Y direction). + floor_to_floor_height: Number for the height of each floor of the model + (in the Z direction). + perimeter_offset: An optional positive number that will be used to offset + the perimeter to create core/perimeter Rooms. If this value is 0, + no offset will occur and each floor will have one Room. (Default: 0). + story_count: An integer for the number of stories to generate. (Default: 1). + orientation_angle: A number between 0 and 360 for the counterclockwise + orientation that the width of the box faces. (0=North, 90=East, + 180=South, 270=West). (Default: 0). + outdoor_roof: Boolean to note whether the roof faces of the top floor + should be outdoor or adiabatic. (Default: True). + ground_floor: Boolean to note whether the floor faces of the bottom + floor should be ground or adiabatic. (Default: True). + unique_id: Text for a unique identifier to be incorporated into all + of the Room identifiers. If None, a default one will be generated. + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. (Default: 0.01, suitable + for objects in meters). + """ + footprint = [Face3D.from_rectangle(width, length)] + if perimeter_offset != 0: # use the straight skeleton methods + assert perimeter_offset > 0, 'perimeter_offset cannot be less than than 0.' + try: + footprint = [] + base = Polygon2D.from_rectangle(Point2D(), Vector2D(0, 1), width, length) + sub_polys_perim, sub_polys_core = perimeter_core_subpolygons( + polygon=base, distance=perimeter_offset, tolerance=tolerance) + for s_poly in sub_polys_perim + sub_polys_core: + sub_face = Face3D([Point3D(pt.x, pt.y, 0) for pt in s_poly]) + footprint.append(sub_face) + except RuntimeError: + pass + # create the honeybee rooms + if unique_id is None: + unique_id = str(uuid.uuid4())[:8] # unique identifier for the rooms + rm_ids = ['Room'] if len(footprint) == 1 else ['Front', 'Right', 'Back', 'Left'] + if len(footprint) == 5: + rm_ids.append('Core') + return Room.rooms_from_footprint( + footprint, floor_to_floor_height, rm_ids, unique_id, orientation_angle, + story_count, outdoor_roof, ground_floor)
+ +
[docs] @staticmethod + def rooms_from_l_shaped_plan( + width_1, length_1, width_2, length_2, floor_to_floor_height, + perimeter_offset=0, story_count=1, orientation_angle=0, + outdoor_roof=True, ground_floor=True, unique_id=None, tolerance=0.01): + """Create a Rooms that represent an L-shaped floor plan. + + Note that the resulting Rooms in the model won't have any windows or solved + adjacencies. These can be added by using the Room.solve_adjacency method + and the various Face.apertures_by_XXX methods. + + Args: + width_1: Number for the width of the lower part of the L segment. + length_1: Number for the length of the lower part of the L segment, not + counting the overlap between the upper and lower segments. + width_2: Number for the width of the upper (left) part of the L segment. + length_2: Number for the length of the upper (left) part of the L segment, + not counting the overlap between the upper and lower segments. + floor_to_floor_height: Number for the height of each floor of the model + (in the Z direction). + perimeter_offset: An optional positive number that will be used to offset + the perimeter to create core/perimeter Rooms. If this value is 0, + no offset will occur and each floor will have one Room. (Default: 0). + story_count: An integer for the number of stories to generate. (Default: 1). + orientation_angle: A number between 0 and 360 for the counterclockwise + orientation that the width of the box faces. (0=North, 90=East, + 180=South, 270=West). (Default: 0). + outdoor_roof: Boolean to note whether the roof faces of the top floor + should be outdoor or adiabatic. (Default: True). + ground_floor: Boolean to note whether the floor faces of the bottom + floor should be ground or adiabatic. (Default: True). + unique_id: Text for a unique identifier to be incorporated into all + of the Room identifiers. If None, a default one will be generated. + tolerance: The maximum difference between x, y, and z values at which + vertices are considered equivalent. (Default: 0.01, suitable + for objects in meters). + """ + # create the geometry of the rooms for the first floor + max_x, max_y = width_2 + length_1, width_1 + length_2 + pts = [(0, 0), (max_x, 0), (max_x, width_1), (width_2, width_1), + (width_2, max_y), (0, max_y)] + footprint = Face3D(tuple(Point3D(*pt) for pt in pts)) + if perimeter_offset != 0: # use the straight skeleton methods + assert perimeter_offset > 0, 'perimeter_offset cannot be less than than 0.' + try: + footprint = [] + base = Polygon2D(tuple(Point2D(*pt) for pt in pts)) + sub_polys_perim, sub_polys_core = perimeter_core_subpolygons( + polygon=base, distance=perimeter_offset, tolerance=tolerance) + for s_poly in sub_polys_perim + sub_polys_core: + sub_face = Face3D([Point3D(pt.x, pt.y, 0) for pt in s_poly]) + footprint.append(sub_face) + except RuntimeError: + pass + # create the honeybee rooms + unique_id = '' if unique_id is None else '_{}'.format(unique_id) + rm_ids = ['Room'] if len(footprint) == 1 else \ + ['LongEdge1', 'End1', 'ShortEdge1', 'ShortEdge2', 'End2', 'LongEdge2'] + if len(footprint) == 7: + rm_ids.append('Core') + return Room.rooms_from_footprint( + footprint, floor_to_floor_height, rm_ids, unique_id, orientation_angle, + story_count, outdoor_roof, ground_floor)
+ +
[docs] @staticmethod + def rooms_from_footprint( + footprints, floor_to_floor_height, room_ids=None, unique_id=None, + orientation_angle=0, story_count=1, outdoor_roof=True, ground_floor=True): + """Create several Honeybee Rooms from footprint Face3Ds. + + Args: + footprints: A list of Face3Ds representing the floors of Rooms. + floor_to_floor_height: Number for the height of each floor of the model + (in the Z direction). + room_ids: A list of strings for the identifiers of the Rooms to be generated. + If None, default unique IDs will be generated. (Default: None) + unique_id: Text for a unique identifier to be incorporated into all + Room identifiers. (Default: None). + orientation_angle: A number between 0 and 360 for the counterclockwise + orientation that the width of the box faces. (0=North, 90=East, + 180=South, 270=West). (Default: 0). + story_count: An integer for the number of stories to generate. (Default: 1). + outdoor_roof: Boolean to note whether the roof faces of the top floor + should be outdoor or adiabatic. (Default: True). + ground_floor: Boolean to note whether the floor faces of the bottom + floor should be ground or adiabatic. (Default: True). + """ + # set default identifiers if not provided + if room_ids is None: + room_ids = ['Room_{}'.format(str(uuid.uuid4())[:8]) for _ in footprints] + # extrude the footprint into solids + first_floor = [Polyface3D.from_offset_face(geo, floor_to_floor_height) + for geo in footprints] + # rotate the geometries if an orientation angle is specified + if orientation_angle != 0: + angle, origin = math.radians(orientation_angle), Point3D() + first_floor = [geo.rotate_xy(angle, origin) for geo in first_floor] + # create the initial rooms for the first floor + rooms = [] + unique_id = '' if unique_id is None else '_{}'.format(unique_id) + for polyface, rmid in zip(first_floor, room_ids): + rooms.append(Room.from_polyface3d('{}{}'.format(rmid, unique_id), polyface)) + # if there are multiple stories, duplicate the first floor rooms + if story_count != 1: + all_rooms = [] + for i in range(story_count): + for room in rooms: + new_room = room.duplicate() + new_room.add_prefix('Floor{}'.format(i + 1)) + m_vec = Vector3D(0, 0, floor_to_floor_height * i) + new_room.move(m_vec) + all_rooms.append(new_room) + rooms = all_rooms + # assign readable names for the display_name (without the UUID) + for room in rooms: + room.display_name = room.identifier[:-9] + # assign adiabatic boundary conditions if requested + if not outdoor_roof and ad_bc: + for room in rooms[-len(first_floor):]: + room[-1].boundary_condition = ad_bc # make the roof adiabatic + if not ground_floor and ad_bc: + for room in rooms[:len(first_floor)]: + room[0].boundary_condition = ad_bc # make the floor adiabatic + return rooms
+ +
[docs] def display_dict(self): + """Get a list of DisplayFace3D dictionaries for visualizing the object.""" + base = [] + for f in self._faces: + base.extend(f.display_dict()) + for shd in self.shades: + base.extend(shd.display_dict()) + return base
+ + @property + def to(self): + """Room writer object. + + Use this method to access Writer class to write the room in other formats. + + Usage: + + .. code-block:: python + + room.to.idf(room) -> idf string. + room.to.radiance(room) -> Radiance string. + """ + return writer + +
[docs] def to_dict(self, abridged=False, included_prop=None, include_plane=True): + """Return Room as a dictionary. + + Args: + abridged: Boolean to note whether the extension properties of the + object (ie. construction sets) should be included in detail + (False) or just referenced by identifier (True). Default: False. + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + include_plane: Boolean to note wether the planes of the Face3Ds should be + included in the output. This can preserve the orientation of the + X/Y axes of the planes but is not required and can be removed to + keep the dictionary smaller. (Default: True). + """ + base = {'type': 'Room'} + base['identifier'] = self.identifier + base['display_name'] = self.display_name + base['properties'] = self.properties.to_dict(abridged, included_prop) + base['faces'] = [f.to_dict(abridged, included_prop, include_plane) + for f in self._faces] + self._add_shades_to_dict(base, abridged, included_prop, include_plane) + if self.multiplier != 1: + base['multiplier'] = self.multiplier + if self.story is not None: + base['story'] = self.story + if self.exclude_floor_area: + base['exclude_floor_area'] = self.exclude_floor_area + if self.user_data is not None: + base['user_data'] = self.user_data + return base
+ + def _base_horiz_boundary(self, tolerance=0.01): + """Get a starting horizontal boundary for the Room. + + This is the raw result obtained by merging all downward-facing Faces of the Room. + + Args: + tolerance: The minimum difference between x, y, and z coordinate values + at which points are considered distinct. (Default: 0.01, + suitable for objects in Meters). + """ + z_axis = Vector3D(0, 0, 1) + flr_geo = [] + for face in self.faces: + if math.degrees(z_axis.angle(face.normal)) >= 91: + flr_geo.append(face.geometry) + if len(flr_geo) == 1: + if flr_geo[0].is_horizontal(tolerance): + return flr_geo[0] + else: + floor_height = self.geometry.min.z + bound = [Point3D(p.x, p.y, floor_height) for p in flr_geo[0].boundary] + holes = None + if flr_geo[0].has_holes: + holes = [[Point3D(p.x, p.y, floor_height) for p in hole] + for hole in flr_geo[0].holes] + return Face3D(bound, holes=holes) + else: # multiple geometries to be joined together + floor_height = self.geometry.min.z + horiz_geo = [] + for fg in flr_geo: + if fg.is_horizontal(tolerance) and \ + abs(floor_height - fg.min.z) <= tolerance: + horiz_geo.append(fg) + else: # project the face geometry into the XY plane + bound = [Point3D(p.x, p.y, floor_height) for p in fg.boundary] + holes = None + if fg.has_holes: + holes = [[Point3D(p.x, p.y, floor_height) for p in hole] + for hole in fg.holes] + horiz_geo.append(Face3D(bound, holes=holes)) + # sense if there are overlapping geometries to be boolean unioned + overlap_groups = Face3D.group_by_coplanar_overlap(horiz_geo, tolerance) + if all(len(g) == 1 for g in overlap_groups): # no overlaps; just join + return Face3D.join_coplanar_faces(horiz_geo, tolerance)[0] + # we must do a boolean union + clean_geo = [] + for og in overlap_groups: + if len(og) == 1: + clean_geo.extend(og) + else: + a_tol = math.radians(1) + union = Face3D.coplanar_union_all(og, tolerance, a_tol) + if len(union) == 1: + clean_geo.extend(union) + else: + sort_geo = sorted(union, key=lambda x: x.area, reverse=True) + clean_geo.append(sort_geo[0]) + if len(clean_geo) == 1: + return clean_geo[0] + return Face3D.join_coplanar_faces(clean_geo, tolerance)[0] + + def _match_walls_to_horizontal_faces(self, faces, tolerance): + """Insert vertices to horizontal faces so they align with the Room's Walls. + + Args: + faces: A list of Face3D into which the vertices of the walls will + be inserted. + tolerance: The minimum difference between x, y, and z coordinate values + at which points are considered distinct. (Default: 0.01, + suitable for objects in Meters). + """ + # get 2D vertices for all of the walls + wall_st_pts = [ + f.geometry.lower_left_counter_clockwise_vertices[0] for f in self.walls] + wall_st_pts_2d = [Point2D(v[0], v[1]) for v in wall_st_pts] + # insert the wall points into each of the faces + wall_faces = [] + for horiz_bound in faces: + # get 2D polygons for the horizontal boundary + z_val = horiz_bound[0].z + polys = [Polygon2D([Point2D(v.x, v.y) for v in horiz_bound.boundary])] + if horiz_bound.holes is not None: + for hole in horiz_bound.holes: + polys.append(Polygon2D([Point2D(v.x, v.y) for v in hole])) + # insert the wall vertices into the polygon + wall_polys = [] + for st_poly in polys: + st_poly = st_poly.remove_colinear_vertices(tolerance) + polygon_update = [] + for pt in wall_st_pts_2d: + for v in st_poly.vertices: # check if pt is already included + if pt.is_equivalent(v, tolerance): + break + else: + values = [seg.distance_to_point(pt) for seg in st_poly.segments] + if min(values) < tolerance: + index_min = min(range(len(values)), key=values.__getitem__) + polygon_update.append((index_min, pt)) + if polygon_update: + end_poly = Polygon2D._insert_updates_in_order(st_poly, polygon_update) + wall_polys.append(end_poly) + else: + wall_polys.append(st_poly) + # rebuild the Face3D from the polygons + pts_3d = [[Point3D(p.x, p.y, z_val) for p in poly] for poly in wall_polys] + wall_faces.append(Face3D(pts_3d[0], holes=pts_3d[1:])) + return wall_faces + + @staticmethod + def _adjacency_grouping(rooms, adj_finding_function): + """Group Rooms together according to an adjacency finding function. + + Args: + rooms: A list of rooms to be grouped by their adjacency. + adj_finding_function: A function that denotes which rooms are adjacent + to another. + + Returns: + A list of list with each sub-list containing rooms that share adjacencies. + """ + # create a room lookup table and duplicate the list of rooms + room_lookup = {rm.identifier: rm for rm in rooms} + all_rooms = list(rooms) + adj_network = [] + + # loop through the rooms and find air boundary adjacencies + for room in all_rooms: + adj_ids = adj_finding_function(room) + if len(adj_ids) == 0: # a room that is its own solar enclosure + adj_network.append([room]) + else: # there are other adjacent rooms to find + local_network = [room] + local_ids, first_id = set(adj_ids), room.identifier + while len(adj_ids) != 0: + # add the current rooms to the local network + adj_objs = [room_lookup[rm_id] for rm_id in adj_ids] + local_network.extend(adj_objs) + adj_ids = [] # reset the list of new adjacencies + # find any rooms that are adjacent to the adjacent rooms + for obj in adj_objs: + all_new_ids = adj_finding_function(obj) + new_ids = [rid for rid in all_new_ids + if rid not in local_ids and rid != first_id] + for rm_id in new_ids: + local_ids.add(rm_id) + adj_ids.extend(new_ids) + # after the local network is understood, clean up duplicated rooms + adj_network.append(local_network) + i_to_remove = [i for i, room_obj in enumerate(all_rooms) + if room_obj.identifier in local_ids] + for i in reversed(i_to_remove): + all_rooms.pop(i) + return adj_network + + @staticmethod + def _find_adjacent_rooms(room): + """Find the identifiers of all rooms with adjacency to a room.""" + adj_rooms = [] + for face in room._faces: + if isinstance(face.boundary_condition, Surface): + adj_rooms.append(face.boundary_condition.boundary_condition_objects[-1]) + return adj_rooms + + @staticmethod + def _find_adjacent_air_boundary_rooms(room): + """Find the identifiers of all rooms with air boundary adjacency to a room.""" + adj_rooms = [] + for face in room._faces: + if isinstance(face.type, AirBoundary) and \ + isinstance(face.boundary_condition, Surface): + adj_rooms.append(face.boundary_condition.boundary_condition_objects[-1]) + return adj_rooms + + def __copy__(self): + new_r = Room(self.identifier, tuple(face.duplicate() for face in self._faces)) + new_r._display_name = self._display_name + new_r._user_data = None if self.user_data is None else self.user_data.copy() + new_r._multiplier = self.multiplier + new_r._story = self.story + new_r._exclude_floor_area = self.exclude_floor_area + self._duplicate_child_shades(new_r) + new_r._geometry = self._geometry + new_r._properties._duplicate_extension_attr(self._properties) + return new_r + + def __len__(self): + return len(self._faces) + + def __getitem__(self, key): + return self._faces[key] + + def __iter__(self): + return iter(self._faces) + + def __repr__(self): + return 'Room: %s' % self.display_name
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/search.html b/docs/_modules/honeybee/search.html new file mode 100644 index 00000000..4e1eee5b --- /dev/null +++ b/docs/_modules/honeybee/search.html @@ -0,0 +1,1223 @@ + + + + + + + honeybee.search — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.search

+"""Collection of methods for searching for keywords and filtering lists by keywords.
+This module also included methods to get nested attributes of objects.
+
+This is useful for cases like the following:
+
+* Searching through the honeybee-radiance modifier library.
+* Searching through the honeybee-energy material, construction, schedule,
+  constructionset, or programtype libraries.
+* Searching through EnergyPlus IDD or RDD to find possible output variables
+  to request form the simulation.
+"""
+
+
+
[docs]def filter_array_by_keywords(array, keywords, parse_phrases=True): + """Filter an array of strings to get only those containing the given keywords. + + This method is case insensitive, allowing the searching of keywords across + different cases of letters. + + Args: + array: An array of strings which will be filtered to get only those containing + the given keywords. + keywords: An array of strings representing keywords. + parse_phrases: If True, this method will automatically parse any strings of + multiple keywords (separated by spaces) into separate keywords for + searching. This results in a greater likelihood that someone finds what + they are searching for in large arrays but it may not be appropriate for + all cases. You may want to set it to False when you are searching for a + specific phrase that includes spaces. Default: True. + """ + # split any keywords separated by spaces + if parse_phrases: + keywords = [kw for words in keywords for kw in words.upper().split()] + else: + keywords = [kw.upper() for kw in keywords] + + # filter the input array + return [item for item in array if any_keywords_in_string(item.upper(), keywords)]
+ + +
[docs]def any_keywords_in_string(name, keywords): + """Check whether any keywords in an array exist within a given string. + + Args: + name: A string which will be tested for whether it possesses any of the keywords. + keywords: An array of strings representing keywords, which will be searched for + in the name. + """ + return all(kw in name for kw in keywords)
+ + +
[docs]def get_attr_nested(obj_instance, attr_name, decimal_count=None, cast_to_str=True): + """Get the attribute of an object while allowing the request of nested attributes. + + Args: + obj_instance: An instance of a Python object. Typically, this is a honeybee + object like a Model, Room, Face, Aperture, Door, or Shade. + attr_name: A string of an attribute that the input obj_instance should have. + This can have '.' that separate the nested attributes from one another. + For example, 'properties.energy.construction'. + decimal_count: An optional integer to be used to round the property to a + number of decimal places if it is a float. (Default: None). + cast_to_str: Boolean to note whether attributes with a type other than + float should be cast to strings. If False, the attribute will be + returned with the original object type. (Default: True). + + Returns: + A string or number for tha attribute assigned ot the obj_instance. If the + input attr_name is a valid attribute for the object but None is assigned, + the output will be 'None'. If the input attr_name is not valid for + the input object, 'N/A' will be returned. + """ + if '.' in attr_name: # nested attribute + attributes = attr_name.split('.') # get all the sub-attributes + current_obj = obj_instance + try: + for attribute in attributes: + if current_obj is None: + raise AttributeError + elif isinstance(current_obj, dict): + current_obj = current_obj.get(attribute, None) + else: + current_obj = getattr(current_obj, attribute) + if isinstance(current_obj, float) and decimal_count: + return round(current_obj, decimal_count) + else: + return str(current_obj) if cast_to_str else current_obj + except AttributeError as e: + if 'NoneType' in str(e): # it's a valid attribute but it's not assigned + return 'None' + else: # it's not a valid attribute + return 'N/A' + else: # honeybee-core attribute + try: + current_obj = getattr(obj_instance, attr_name) + if isinstance(current_obj, float) and decimal_count: + return round(current_obj, decimal_count) + else: + return str(current_obj) if cast_to_str else current_obj + except AttributeError: + return 'N/A'
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/shade.html b/docs/_modules/honeybee/shade.html new file mode 100644 index 00000000..ca7ebfce --- /dev/null +++ b/docs/_modules/honeybee/shade.html @@ -0,0 +1,1608 @@ + + + + + + + honeybee.shade — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.shade

+# coding: utf-8
+"""Honeybee Shade."""
+from __future__ import division
+import math
+
+from ladybug_geometry.geometry3d.pointvector import Point3D
+from ladybug_geometry.geometry3d.face import Face3D
+from ladybug.color import Color
+
+from ._base import _Base
+from .typing import clean_string
+from .properties import ShadeProperties
+import honeybee.writer.shade as writer
+
+
+
[docs]class Shade(_Base): + """A single planar shade. + + Args: + identifier: Text string for a unique Shade ID. Must be < 100 characters and + not contain any spaces or special characters. + geometry: A ladybug-geometry Face3D. + is_detached: Boolean to note whether this object is detached from other + geometry. Cases where this should be True include shade representing + surrounding buildings or context. (Default: False). + + Properties: + * identifier + * display_name + * is_detached + * parent + * top_level_parent + * has_parent + * is_indoor + * geometry + * vertices + * upper_left_vertices + * normal + * center + * area + * perimeter + * min + * max + * tilt + * altitude + * azimuth + * type_color + * bc_color + * user_data + """ + __slots__ = ('_geometry', '_parent', '_is_indoor', '_is_detached') + TYPE_COLORS = { + (False, False): Color(120, 75, 190), + (False, True): Color(80, 50, 128), + (True, False): Color(159, 99, 255), + (True, True): Color(159, 99, 255) + } + BC_COLOR = Color(120, 75, 190) + + def __init__(self, identifier, geometry, is_detached=False): + """A single planar shade.""" + _Base.__init__(self, identifier) # process the identifier + + # process the geometry and basic properties + assert isinstance(geometry, Face3D), \ + 'Expected ladybug_geometry Face3D. Got {}'.format(type(geometry)) + self._geometry = geometry + self._parent = None # _parent will be set when the Shade is added to an object + self._is_indoor = False # this will be set by the _parent + self.is_detached = is_detached + + # initialize properties for extensions + self._properties = ShadeProperties(self) + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize an Shade from a dictionary. + + Args: + data: A dictionary representation of an Shade object. + """ + try: + # check the type of dictionary + assert data['type'] == 'Shade', 'Expected Shade dictionary. ' \ + 'Got {}.'.format(data['type']) + + is_detached = data['is_detached'] if 'is_detached' in data else False + shade = cls( + data['identifier'], Face3D.from_dict(data['geometry']), is_detached) + if 'display_name' in data and data['display_name'] is not None: + shade.display_name = data['display_name'] + if 'user_data' in data and data['user_data'] is not None: + shade.user_data = data['user_data'] + + if data['properties']['type'] == 'ShadeProperties': + shade.properties._load_extension_attr_from_dict(data['properties']) + return shade + except Exception as e: + cls._from_dict_error_message(data, e)
+ +
[docs] @classmethod + def from_vertices(cls, identifier, vertices, is_detached=False): + """Create a Shade from vertices with each vertex as an iterable of 3 floats. + + Note that this method is not recommended for a shade with one or more holes + since the distinction between hole vertices and boundary vertices cannot + be derived from a single list of vertices. + + Args: + identifier: Text string for a unique Shade ID. Must be < 100 characters and + not contain any spaces or special characters. + vertices: A flattened list of 3 or more vertices as (x, y, z). + is_detached: Boolean to note whether this object is detached from other + geometry. Cases where this should be True include shade representing + surrounding buildings or context. (Default: False). + """ + geometry = Face3D(tuple(Point3D(*v) for v in vertices)) + return cls(identifier, geometry, is_detached)
+ + @property + def is_detached(self): + """Get or set a boolean for whether this object is detached from other geometry. + + This will automatically be set to False if the shade is assigned to + parent objects. + """ + return self._is_detached + + @is_detached.setter + def is_detached(self, value): + try: + self._is_detached = bool(value) + if self._is_detached: + assert not self.has_parent, 'Shade cannot be detached when it has ' \ + 'a parent Room, Face, Aperture or Door.' + except TypeError: + raise TypeError( + 'Expected boolean for Shade.is_detached. Got {}.'.format(value)) + + @property + def parent(self): + """Get the parent object if assigned. None if not assigned. + + The parent object can be either a Room, Face, Aperture or Door. + """ + return self._parent + + @property + def top_level_parent(self): + """Get the top-level parent object if assigned. + + This will be the highest-level parent in the hierarchy of the parent-child + chain. Will be None if no parent is assigned. + """ + if self.has_parent: + if self._parent.has_parent: + if self._parent._parent.has_parent: + return self._parent._parent._parent + return self._parent._parent + return self._parent + return None + + @property + def has_parent(self): + """Get a boolean noting whether this Shade has a parent object.""" + return self._parent is not None + + @property + def is_indoor(self): + """Get a boolean for whether this Shade is on the indoors of its parent object. + + Note that, if there is no parent assigned to this Shade, this property will + be False. + """ + return self._is_indoor + + @property + def geometry(self): + """Get a ladybug_geometry Face3D object representing the Shade.""" + return self._geometry + + @property + def vertices(self): + """Get a list of vertices for the shade (in counter-clockwise order).""" + return self._geometry.vertices + + @property + def upper_left_vertices(self): + """Get a list of vertices starting from the upper-left corner. + + This property should be used when exporting to EnergyPlus / OpenStudio. + """ + return self._geometry.upper_left_counter_clockwise_vertices + + @property + def normal(self): + """Get a ladybug_geometry Vector3D for the direction the shade is pointing. + """ + return self._geometry.normal + + @property + def center(self): + """Get a ladybug_geometry Point3D for the center of the shade. + + Note that this is the center of the bounding rectangle around this geometry + and not the area centroid. + """ + return self._geometry.center + + @property + def area(self): + """Get the area of the shade.""" + return self._geometry.area + + @property + def perimeter(self): + """Get the perimeter of the shade.""" + return self._geometry.perimeter + + @property + def min(self): + """Get a Point3D for the minimum of the bounding box around the object.""" + return self._geometry.min + + @property + def max(self): + """Get a Point3D for the maximum of the bounding box around the object.""" + return self._geometry.max + + @property + def tilt(self): + """Get the tilt of the geometry between 0 (up) and 180 (down).""" + return math.degrees(self._geometry.tilt) + + @property + def altitude(self): + """Get the altitude of the geometry between +90 (up) and -90 (down).""" + return math.degrees(self._geometry.altitude) + + @property + def azimuth(self): + """Get the azimuth of the geometry, between 0 and 360. + + Given Y-axis as North, 0 = North, 90 = East, 180 = South, 270 = West + This will be zero if the Face3D is perfectly horizontal. + """ + return math.degrees(self._geometry.azimuth) + + @property + def type_color(self): + """Get a Color to be used in visualizations by type.""" + return self.TYPE_COLORS[(self.is_indoor, self.is_detached)] + + @property + def bc_color(self): + """Get a Color to be used in visualizations by boundary condition.""" + return self.BC_COLOR + +
[docs] def add_prefix(self, prefix): + """Change the identifier of this object by inserting a prefix. + + This is particularly useful in workflows where you duplicate and edit + a starting object and then want to combine it with the original object + into one Model (like making a model of repeated rooms) since all objects + within a Model must have unique identifiers. + + Args: + prefix: Text that will be inserted at the start of this object's identifier + and display_name. It is recommended that this prefix be short to + avoid maxing out the 100 allowable characters for honeybee identifiers. + """ + self._identifier = clean_string('{}_{}'.format(prefix, self.identifier)) + self.display_name = '{}_{}'.format(prefix, self.display_name) + self.properties.add_prefix(prefix)
+ +
[docs] def move(self, moving_vec): + """Move this Shade along a vector. + + Args: + moving_vec: A ladybug_geometry Vector3D with the direction and distance + to move the face. + """ + self._geometry = self.geometry.move(moving_vec) + self.properties.move(moving_vec)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Shade by a certain angle around an axis and origin. + + Args: + axis: A ladybug_geometry Vector3D axis representing the axis of rotation. + angle: An angle for rotation in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate(axis, math.radians(angle), origin) + self.properties.rotate(axis, angle, origin)
+ +
[docs] def rotate_xy(self, angle, origin): + """Rotate this Shade counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate_xy(math.radians(angle), origin) + self.properties.rotate_xy(angle, origin)
+ +
[docs] def reflect(self, plane): + """Reflect this Shade across a plane. + + Args: + plane: A ladybug_geometry Plane across which the object will + be reflected. + """ + self._geometry = self.geometry.reflect(plane.n, plane.o) + self.properties.reflect(plane)
+ +
[docs] def scale(self, factor, origin=None): + """Scale this Shade by a factor from an origin point. + + Args: + factor: A number representing how much the object should be scaled. + origin: A ladybug_geometry Point3D representing the origin from which + to scale. If None, it will be scaled from the World origin (0, 0, 0). + """ + self._geometry = self.geometry.scale(factor, origin) + self.properties.scale(factor, origin)
+ +
[docs] def remove_colinear_vertices(self, tolerance=0.01): + """Remove all colinear and duplicate vertices from this object's geometry. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + """ + try: + self._geometry = self.geometry.remove_colinear_vertices(tolerance) + except AssertionError as e: # usually a sliver face of some kind + raise ValueError( + 'Shade "{}" is invalid with dimensions less than the ' + 'tolerance.\n{}'.format(self.full_id, e))
+ +
[docs] def is_geo_equivalent(self, shade, tolerance=0.01): + """Get a boolean for whether this object is geometrically equivalent to another. + + The total number of vertices and the ordering of these vertices can be + different but the geometries must share the same center point and be + next to one another to within the tolerance. + + Args: + shade: Another Shade for which geometric equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered geometrically equivalent. + + Returns: + True if geometrically equivalent. False if not geometrically equivalent. + """ + meta_1 = (self.display_name, self.is_detached) + meta_2 = (shade.display_name, shade.is_detached) + if meta_1 != meta_2: + return False + if abs(self.area - shade.area) > tolerance * self.area: + return False + return self.geometry.is_centered_adjacent(shade.geometry, tolerance)
+ +
[docs] def check_planar(self, tolerance=0.01, raise_exception=True, detailed=False): + """Check whether all of the Shade's vertices lie within the same plane. + + Args: + tolerance: The minimum distance between a given vertex and a the + object's plane at which the vertex is said to lie in the plane. + Default: 0.01, suitable for objects in meters. + raise_exception: Boolean to note whether an ValueError should be + raised if a vertex does not lie within the object's plane. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + try: + self.geometry.check_planar(tolerance, raise_exception=True) + except ValueError as e: + msg = 'Shade "{}" is not planar.\n{}'.format(self.full_id, e) + full_msg = self._validation_message( + msg, raise_exception, detailed, '000101', + error_type='Non-Planar Geometry') + if detailed: # add the out-of-plane points to helper_geometry + help_pts = [ + p.to_dict() for p in self.geometry.non_planar_vertices(tolerance) + ] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def check_self_intersecting(self, tolerance=0.01, raise_exception=True, + detailed=False): + """Check whether the edges of the Shade intersect one another (like a bowtie). + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. Default: 0.01, + suitable for objects in meters. + raise_exception: If True, a ValueError will be raised if the object + intersects with itself. Default: True. + detailed: Boolean for whether the returned object is a detailed list of + dicts with error info or a string with a message. (Default: False). + + Returns: + A string with the message or a list with a dictionary if detailed is True. + """ + if self.geometry.is_self_intersecting: + msg = 'Shade "{}" has self-intersecting edges.'.format(self.full_id) + try: # see if it is self-intersecting because of a duplicate vertex + new_geo = self.geometry.remove_duplicate_vertices(tolerance) + if not new_geo.is_self_intersecting: + return [] if detailed else '' # valid with removed dup vertex + except AssertionError: + return [] if detailed else '' # degenerate geometry + full_msg = self._validation_message( + msg, raise_exception, detailed, '000102', + error_type='Self-Intersecting Geometry') + if detailed: # add the self-intersection points to helper_geometry + help_pts = [p.to_dict() for p in self.geometry.self_intersection_points] + full_msg[0]['helper_geometry'] = help_pts + return full_msg + return [] if detailed else ''
+ +
[docs] def display_dict(self): + """Get a list of DisplayFace3D dictionaries for visualizing the object.""" + return [self._display_face(self.geometry, self.type_color)]
+ + @property + def to(self): + """Shade writer object. + + Use this method to access Writer class to write the shade in different formats. + + Usage: + + .. code-block:: python + + shade.to.idf(shade) -> idf string. + shade.to.radiance(shade) -> Radiance string. + """ + return writer + +
[docs] def to_dict(self, abridged=False, included_prop=None, include_plane=True): + """Return Shade as a dictionary. + + Args: + abridged: Boolean to note whether the extension properties of the + object (ie. modifiers, transmittance schedule) should be included in + detail (False) or just referenced by identifier (True). Default: False. + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + include_plane: Boolean to note wether the plane of the Face3D should be + included in the output. This can preserve the orientation of the + X/Y axes of the plane but is not required and can be removed to + keep the dictionary smaller. (Default: True). + """ + base = {'type': 'Shade'} + base['identifier'] = self.identifier + base['display_name'] = self.display_name + base['properties'] = self.properties.to_dict(abridged, included_prop) + enforce_upper_left = True if 'energy' in base['properties'] else False + base['geometry'] = self._geometry.to_dict(include_plane, enforce_upper_left) + if self.is_detached: + base['is_detached'] = self.is_detached + if self.user_data is not None: + base['user_data'] = self.user_data + return base
+ + def __copy__(self): + new_shade = Shade(self.identifier, self.geometry, self.is_detached) + new_shade._display_name = self._display_name + new_shade._user_data = None if self.user_data is None else self.user_data.copy() + new_shade._properties._duplicate_extension_attr(self._properties) + return new_shade + + def __repr__(self): + return 'Shade: %s' % self.display_name
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/shademesh.html b/docs/_modules/honeybee/shademesh.html new file mode 100644 index 00000000..24df6dca --- /dev/null +++ b/docs/_modules/honeybee/shademesh.html @@ -0,0 +1,1466 @@ + + + + + + + honeybee.shademesh — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.shademesh

+# coding: utf-8
+"""Honeybee ShadeMesh."""
+from __future__ import division
+import math
+
+from ladybug_geometry.geometry3d import Mesh3D, Face3D
+from ladybug.color import Color
+
+from ._base import _Base
+from .typing import clean_string
+from .properties import ShadeMeshProperties
+import honeybee.writer.shademesh as writer
+
+
+
[docs]class ShadeMesh(_Base): + """A single planar shade. + + Args: + identifier: Text string for a unique Shade ID. Must be < 100 characters and + not contain any spaces or special characters. + geometry: A ladybug-geometry Mesh3D. + is_detached: Boolean to note whether this object is detached from other + geometry. Cases where this should be True include shade representing + surrounding buildings or context. (Default: True). + + Properties: + * identifier + * display_name + * is_detached + * geometry + * vertices + * faces + * center + * area + * min + * max + * type_color + * bc_color + * user_data + """ + __slots__ = ('_geometry', '_is_detached') + TYPE_COLORS = { + False: Color(120, 75, 190), + True: Color(80, 50, 128) + } + BC_COLOR = Color(120, 75, 190) + + def __init__(self, identifier, geometry, is_detached=True): + """A single planar shade.""" + _Base.__init__(self, identifier) # process the identifier + + # process the geometry and basic properties + assert isinstance(geometry, Mesh3D), \ + 'Expected ladybug_geometry Mesh3D. Got {}'.format(type(geometry)) + self._geometry = geometry + self.is_detached = is_detached + + # initialize properties for extensions + self._properties = ShadeMeshProperties(self) + +
[docs] @classmethod + def from_dict(cls, data): + """Initialize an ShadeMesh from a dictionary. + + Args: + data: A dictionary representation of an ShadeMesh object. + """ + try: + # check the type of dictionary + assert data['type'] == 'ShadeMesh', 'Expected ShadeMesh dictionary. ' \ + 'Got {}.'.format(data['type']) + + is_detached = data['is_detached'] if 'is_detached' in data else True + shade = cls( + data['identifier'], Mesh3D.from_dict(data['geometry']), is_detached) + if 'display_name' in data and data['display_name'] is not None: + shade.display_name = data['display_name'] + if 'user_data' in data and data['user_data'] is not None: + shade.user_data = data['user_data'] + + if data['properties']['type'] == 'ShadeMeshProperties': + shade.properties._load_extension_attr_from_dict(data['properties']) + return shade + except Exception as e: + cls._from_dict_error_message(data, e)
+ + @property + def is_detached(self): + """Get or set a boolean for whether this object is detached from other geometry. + """ + return self._is_detached + + @is_detached.setter + def is_detached(self, value): + try: + self._is_detached = bool(value) + except TypeError: + raise TypeError( + 'Expected boolean for ShadeMesh.is_detached. Got {}.'.format(value)) + + @property + def geometry(self): + """Get a ladybug_geometry Mesh3D object representing the Shade.""" + return self._geometry + + @property + def vertices(self): + """Get a tuple of ladybug_geometry Point3D for the vertices of the mesh.""" + return self._geometry.vertices + + @property + def faces(self): + """Get a tuple of tuples for the faces of the mesh.""" + return self._geometry.faces + + @property + def center(self): + """Get a ladybug_geometry Point3D for the center of the shade. + + Note that this is the center of the bounding box around this geometry + and not the area or volume centroid. + """ + return self._geometry.center + + @property + def area(self): + """Get the surface area of the shade mesh.""" + return self._geometry.area + + @property + def min(self): + """Get a Point3D for the minimum of the bounding box around the object.""" + return self._geometry.min + + @property + def max(self): + """Get a Point3D for the maximum of the bounding box around the object.""" + return self._geometry.max + + @property + def type_color(self): + """Get a Color to be used in visualizations by type.""" + return self.TYPE_COLORS[self.is_detached] + + @property + def bc_color(self): + """Get a Color to be used in visualizations by boundary condition.""" + return self.BC_COLOR + +
[docs] def add_prefix(self, prefix): + """Change the identifier of this object by inserting a prefix. + + This is particularly useful in workflows where you duplicate and edit + a starting object and then want to combine it with the original object + into one Model (like making a model of repeated rooms) since all objects + within a Model must have unique identifiers. + + Args: + prefix: Text that will be inserted at the start of this object's identifier + and display_name. It is recommended that this prefix be short to + avoid maxing out the 100 allowable characters for honeybee identifiers. + """ + self._identifier = clean_string('{}_{}'.format(prefix, self.identifier)) + self.display_name = '{}_{}'.format(prefix, self.display_name) + self.properties.add_prefix(prefix)
+ +
[docs] def move(self, moving_vec): + """Move this Shade along a vector. + + Args: + moving_vec: A ladybug_geometry Vector3D with the direction and distance + to move the face. + """ + self._geometry = self.geometry.move(moving_vec) + self.properties.move(moving_vec)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Shade by a certain angle around an axis and origin. + + Args: + axis: A ladybug_geometry Vector3D axis representing the axis of rotation. + angle: An angle for rotation in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate(axis, math.radians(angle), origin) + self.properties.rotate(axis, angle, origin)
+ +
[docs] def rotate_xy(self, angle, origin): + """Rotate this Shade counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in degrees. + origin: A ladybug_geometry Point3D for the origin around which the + object will be rotated. + """ + self._geometry = self.geometry.rotate_xy(math.radians(angle), origin) + self.properties.rotate_xy(angle, origin)
+ +
[docs] def reflect(self, plane): + """Reflect this Shade across a plane. + + Args: + plane: A ladybug_geometry Plane across which the object will + be reflected. + """ + self._geometry = self.geometry.reflect(plane.n, plane.o) + self.properties.reflect(plane)
+ +
[docs] def scale(self, factor, origin=None): + """Scale this Shade by a factor from an origin point. + + Args: + factor: A number representing how much the object should be scaled. + origin: A ladybug_geometry Point3D representing the origin from which + to scale. If None, it will be scaled from the World origin (0, 0, 0). + """ + self._geometry = self.geometry.scale(factor, origin) + self.properties.scale(factor, origin)
+ +
[docs] def triangulate_and_remove_degenerate_faces(self, tolerance=0.01): + """Triangulate non-planar faces in the mesh and remove all degenerate faces. + + This is helpful for certain geometry interfaces that require perfectly + planar geometry without duplicate or colinear vertices. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. Default: 0.01, + suitable for objects in meters. + """ + new_faces, verts = [], self.geometry.vertices + for shd in self.faces: + shd_verts = [verts[v] for v in shd] + shf = Face3D(shd_verts) + if not shf.check_planar(tolerance, raise_exception=False): + shades = ((shd[0], shd[1], shd[2]), (shd[2], shd[3], shd[0])) + for shade in shades: + shd_verts = [verts[v] for v in shade] + shade_face = Face3D(shd_verts) + try: + shade_face.remove_colinear_vertices(tolerance) + except AssertionError: + continue # degenerate face to remove + new_faces.append(shade) + else: + try: + new_face = shf.remove_colinear_vertices(tolerance) + except AssertionError: + continue # degenerate face to remove + if len(new_face.vertices) == len(shd): + new_faces.append(shd) + else: # quad face with duplicate or colinear verts + new_sh = tuple(shd[shd_verts.index(v)] for v in new_face.vertices) + new_faces.append(new_sh) + self._geometry = Mesh3D(verts, new_faces)
+ +
[docs] def is_geo_equivalent(self, shade_mesh, tolerance=0.01): + """Get a boolean for whether this object is geometrically equivalent to another. + + The total number of vertices and the ordering of these vertices can be + different but the geometries must share the same center point and be + next to one another to within the tolerance. + + Args: + shade_mesh: Another ShadeMesh for which geometric equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered geometrically equivalent. + + Returns: + True if geometrically equivalent. False if not geometrically equivalent. + """ + meta_1 = (self.display_name, self.is_detached) + meta_2 = (shade_mesh.display_name, shade_mesh.is_detached) + if meta_1 != meta_2: + return False + if len(self.geometry.vertices) != len(shade_mesh.geometry.vertices): + return False + if len(self.geometry.faces) != len(shade_mesh.geometry.faces): + return False + return all(pt.is_equivalent(o_pt, tolerance) for pt, o_pt in + zip(self.geometry.vertices, shade_mesh.geometry.vertices))
+ +
[docs] def display_dict(self): + """Get a list of DisplayMesh3D dictionaries for visualizing the object.""" + return [self._display_mesh(self.geometry, self.type_color)]
+ + @property + def to(self): + """ShadeMesh writer object. + + Use this method to access Writer class to write the shade in different formats. + + Usage: + + .. code-block:: python + + shade_mesh.to.idf(shade) -> idf string. + shade_mesh.to.radiance(shade) -> Radiance string. + """ + return writer + +
[docs] def to_dict(self, abridged=False, included_prop=None): + """Return Shade as a dictionary. + + Args: + abridged: Boolean to note whether the extension properties of the + object (ie. modifiers, transmittance schedule) should be included in + detail (False) or just referenced by identifier (True). Default: False. + included_prop: List of properties to filter keys that must be included in + output dictionary. For example ['energy'] will include 'energy' key if + available in properties to_dict. By default all the keys will be + included. To exclude all the keys from extensions use an empty list. + """ + base = {'type': 'ShadeMesh'} + base['identifier'] = self.identifier + base['display_name'] = self.display_name + base['properties'] = self.properties.to_dict(abridged, included_prop) + base['geometry'] = self._geometry.to_dict() + if not self.is_detached: + base['is_detached'] = self.is_detached + if self.user_data is not None: + base['user_data'] = self.user_data + return base
+ + @staticmethod + def _display_mesh(mesh3d, color): + """Create a DisplayMesh3D dictionary from a Mesh3D and color.""" + return { + 'type': 'DisplayMesh3D', + 'geometry': mesh3d.to_dict(), + 'color': color.to_dict(), + 'display_mode': 'SurfaceWithEdges' + } + + def __copy__(self): + new_shade = ShadeMesh(self.identifier, self.geometry, self.is_detached) + new_shade._display_name = self._display_name + new_shade._user_data = None if self.user_data is None else self.user_data.copy() + new_shade._properties._duplicate_extension_attr(self._properties) + return new_shade + + def __repr__(self): + return 'ShadeMesh: %s' % self.display_name
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/typing.html b/docs/_modules/honeybee/typing.html new file mode 100644 index 00000000..c3b00b1b --- /dev/null +++ b/docs/_modules/honeybee/typing.html @@ -0,0 +1,1606 @@ + + + + + + + honeybee.typing — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.typing

+"""Collection of methods for type input checking."""
+import re
+import os
+import math
+import uuid
+
+try:
+    INFPOS = math.inf
+    INFNEG = -1 * math.inf
+except AttributeError:
+    # python 2
+    INFPOS = float('inf')
+    INFNEG = float('-inf')
+
+
+
[docs]def valid_string(value, input_name=''): + """Check that a string is valid for both Radiance and EnergyPlus. + + This is used for honeybee geometry object names. + """ + try: + illegal_match = re.search(r'[^.A-Za-z0-9_-]', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + assert illegal_match is None, 'Illegal character "{}" found in {}'.format( + illegal_match.group(0), input_name) + assert len(value) > 0, 'Input {} "{}" contains no characters.'.format( + input_name, value) + assert len(value) <= 100, 'Input {} "{}" must be less than 100 characters.'.format( + input_name, value) + return value
+ + +
[docs]def valid_rad_string(value, input_name=''): + """Check that a string is valid for Radiance. + + This is used for radiance modifier names, etc. + """ + try: + illegal_match = re.search(r'[^.A-Za-z0-9_-]', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + assert illegal_match is None, 'Illegal character "{}" found in {}'.format( + illegal_match.group(0), input_name) + assert len(value) > 0, 'Input {} "{}" contains no characters.'.format( + input_name, value) + return value
+ + +
[docs]def valid_ep_string(value, input_name=''): + """Check that a string is valid for EnergyPlus. + + This is used for energy material names, schedule names, etc. + """ + try: + non_ascii = tuple(i for i in value if ord(i) >= 128) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + assert non_ascii == (), 'Illegal characters {} found in {}'.format( + non_ascii, input_name) + illegal_match = re.search(r'[,;!\n\t]', value) + assert illegal_match is None, 'Illegal character "{}" found in {}'.format( + illegal_match.group(0), input_name) + assert len(value) > 0, 'Input {} "{}" contains no characters.'.format( + input_name, value) + assert len(value) <= 100, 'Input {} "{}" must be less than 100 characters.'.format( + input_name, value) + return value
+ + +def _number_check(value, input_name): + """Check if value is a number.""" + try: + number = float(value) + except (ValueError, TypeError): + raise TypeError('Input {} must be a number. Got {}: {}.'.format( + input_name, type(value), value)) + return number + + +
[docs]def float_in_range(value, mi=INFNEG, ma=INFPOS, input_name=''): + """Check a float value to be between minimum and maximum.""" + number = _number_check(value, input_name) + assert mi <= number <= ma, 'Input number {} must be between {} and {}. ' \ + 'Got {}'.format(input_name, mi, ma, value) + return number
+ + +
[docs]def float_in_range_excl(value, mi=INFNEG, ma=INFPOS, input_name=''): + """Check a float value to be greater than minimum and less than maximum.""" + number = _number_check(value, input_name) + assert mi < number < ma, 'Input number {} must be greater than {} ' \ + 'and less than {}. Got {}'.format(input_name, mi, ma, value) + return number
+ + +
[docs]def float_in_range_excl_incl(value, mi=INFNEG, ma=INFPOS, input_name=''): + """Check a float value to be greater than minimum and less than/equal to maximum.""" + number = _number_check(value, input_name) + assert mi < number <= ma, 'Input number {} must be greater than {} and less than ' \ + 'or equal to {}. Got {}'.format(input_name, mi, ma, value) + return number
+ + +
[docs]def float_in_range_incl_excl(value, mi=INFNEG, ma=INFPOS, input_name=''): + """Check a float value to be greater than/equal to minimum and less than maximum.""" + number = _number_check(value, input_name) + assert mi <= number < ma, 'Input number {} must be greater than or equal to {} ' \ + 'and less than {}. Got {}'.format(input_name, mi, ma, value) + return number
+ + +
[docs]def int_in_range(value, mi=INFNEG, ma=INFPOS, input_name=''): + """Check an integer value to be between minimum and maximum.""" + try: + number = int(value) + except ValueError: + # try to convert to float and then digit if possible + try: + number = int(float(value)) + except (ValueError, TypeError): + raise TypeError('Input {} must be an integer. Got {}: {}.'.format( + input_name, type(value), value)) + except (ValueError, TypeError): + raise TypeError('Input {} must be an integer. Got {}: {}.'.format( + input_name, type(value), value)) + assert mi <= number <= ma, 'Input integer {} must be between {} and {}. ' \ + 'Got {}.'.format(input_name, mi, ma, value) + return number
+ + +
[docs]def float_positive(value, input_name=''): + """Check a float value to be positive.""" + return float_in_range(value, 0, INFPOS, input_name)
+ + +
[docs]def int_positive(value, input_name=''): + """Check if an integer value is positive.""" + return int_in_range(value, 0, INFPOS, input_name)
+ + +
[docs]def tuple_with_length(value, length=3, item_type=float, input_name=''): + """Try to create a tuple with a certain value.""" + try: + value = tuple(item_type(v) for v in value) + except (ValueError, TypeError): + raise TypeError('Input {} must be a {}.'.format( + input_name, item_type)) + assert len(value) == length, 'Input {} length must be {} not {}'.format( + input_name, length, len(value)) + return value
+ + +
[docs]def list_with_length(value, length=3, item_type=float, input_name=''): + """Try to create a list with a certain value.""" + try: + value = [item_type(v) for v in value] + except (ValueError, TypeError): + raise TypeError('Input {} must be a {}.'.format( + input_name, item_type)) + assert len(value) == length, 'Input {} length must be {} not {}'.format( + input_name, length, len(value)) + return value
+ + +
[docs]def clean_string(value, input_name=''): + """Clean a string so that it is valid for both Radiance and EnergyPlus. + + This will strip out spaces and special characters and raise an error if the + string is empty after stripping or has more than 100 characters. + """ + try: + value = value.replace(' ', '_') # spaces > underscores for readability + val = re.sub(r'[^.A-Za-z0-9_-]', '', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + assert len(val) > 0, 'Input {} "{}" contains no valid characters.'.format( + input_name, value) + assert len(val) <= 100, 'Input {} "{}" must be less than 100 characters.'.format( + input_name, value) + return val
+ + +
[docs]def clean_rad_string(value, input_name=''): + """Clean a string for Radiance that can be used for rad material names. + + This includes stripping out illegal characters and white spaces as well as + raising an error if no legal characters are found. + """ + try: + value = value.replace(' ', '_') # spaces > underscores for readability + val = re.sub(r'[^.A-Za-z0-9_-]', '', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + assert len(val) > 0, 'Input {} "{}" contains no valid characters.'.format( + input_name, value) + return val
+ + +
[docs]def clean_ep_string(value, input_name=''): + """Clean a string for EnergyPlus that can be used for energy material names. + + This includes stripping out all illegal characters, removing trailing spaces, + and rasing an error if the name is not longer than 100 characters or no legal + characters found. + """ + try: + val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii + val = re.sub(r'[,;!\n\t]', '', val) # strip out E+ special characters + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + val = val.strip() + assert len(val) > 0, 'Input {} "{}" contains no valid characters.'.format( + input_name, value) + assert len(val) <= 100, 'Input {} "{}" must be less than 100 characters.'.format( + input_name, value) + return val
+ + +
[docs]def clean_and_id_string(value, input_name=''): + """Clean a string and add 8 unique characters to it to make it unique. + + Strings longer than 50 characters will be truncated before adding the ID. + The resulting string will be valid for both Radiance and EnergyPlus. + """ + try: + value = value.replace(' ', '_') # spaces > underscores for readability + val = re.sub(r'[^.A-Za-z0-9_-]', '', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + if len(val) > 50: + val = val[:50] + return val + '_' + str(uuid.uuid4())[:8]
+ + +
[docs]def clean_and_id_rad_string(value, input_name=''): + """Clean a string and add 8 unique characters to it to make it unique for Radiance. + + This includes stripping out illegal characters and white spaces. + """ + try: + value = value.replace(' ', '_') # spaces > underscores for readability + val = re.sub(r'[^.A-Za-z0-9_-]', '', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + return val + '_' + str(uuid.uuid4())[:8]
+ + +
[docs]def clean_and_id_ep_string(value, input_name=''): + """Clean a string and add 8 unique characters to it to make it unique for EnergyPlus. + + This includes stripping out all illegal characters and removing trailing white spaces. + Strings longer than 50 characters will be truncated before adding the ID. + """ + try: + val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii + val = re.sub(r'[,;!\n\t]', '', val) # strip out E+ special characters + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + val = val.strip() + if len(val) > 50: + val = val[:50] + return val + '_' + str(uuid.uuid4())[:8]
+ + +
[docs]def clean_and_number_string(value, existing_dict, input_name=''): + """Clean a string and add an integer to it if it is found in the existing_dict. + + The resulting string will be valid for both Radiance and EnergyPlus. + + Args: + value: The text string to be cleaned and possibly given a unique integer. + existing_dict: A dictionary where the keys are text strings of existing items + and the values are the number of times that the item has appeared in + the model already. + """ + try: + value = value.replace(' ', '_') # spaces > underscores for readability + val = re.sub(r'[^.A-Za-z0-9_-]', '_', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + if len(val) > 95: + val = val[:95] + if val in existing_dict: + existing_dict[val] += 1 + return val + '_' + str(existing_dict[val]) + else: + existing_dict[val] = 1 + return val
+ + +
[docs]def clean_and_number_rad_string(value, existing_dict, input_name=''): + """Clean a string for Radiance and add an integer if found in the existing_dict. + + This includes stripping out illegal characters and white spaces. + + Args: + value: The text string to be cleaned and possibly given a unique integer. + existing_dict: A dictionary where the keys are text strings of existing items + and the values are the number of times that the item has appeared in + the model already. + """ + try: + value = value.replace(' ', '_') # spaces > underscores for readability + val = re.sub(r'[^.A-Za-z0-9_-]', '_', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + if val in existing_dict: + existing_dict[val] += 1 + return val + '_' + str(existing_dict[val]) + else: + existing_dict[val] = 1 + return val
+ + +
[docs]def clean_and_number_ep_string(value, existing_dict, input_name=''): + """Clean a string for EnergyPlus and add an integer if found in the existing_dict. + + This includes stripping out all illegal characters and removing trailing white spaces. + Strings longer than 95 characters will be truncated before adding the integer. + + Args: + value: The text string to be cleaned and possibly given a unique integer. + existing_dict: A dictionary where the keys are text strings of existing items + and the values are the number of times that the item has appeared in + the model already. + """ + try: + val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii + val = re.sub(r'[,;!\n\t]', '', val) # strip out E+ special characters + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + val = val.strip() + if len(val) > 95: + val = val[:95] + if val in existing_dict: + existing_dict[val] += 1 + return val + ' ' + str(existing_dict[val]) + else: + existing_dict[val] = 1 + return val
+ + +
[docs]def truncate_and_id_string(value, truncate_len=32, uuid_len=0, input_name=''): + """Truncate a string to a length with an option to add unique characters at the end. + + Note that all outputs will always be the truncate_len or less and the uuid_len + just specifies the number of characters to replace at the end with unique ones. + + The result will be valid for EnergyPlus, Radiance, and likely many more engines + with different types of character restrictions. + """ + try: + value = value.replace(' ', '_') # spaces > underscores for readability + val = re.sub(r'[^.A-Za-z0-9_-]', '', value) + except TypeError: + raise TypeError('Input {} must be a text string. Got {}: {}.'.format( + input_name, type(value), value)) + final_len = truncate_len - uuid_len + if len(val) > final_len: + val = val[:final_len] + if uuid_len > 0: + return val + str(uuid.uuid4())[:uuid_len] + return val
+ + +
[docs]def fixed_string_length(value, target_len=32): + """Truncate a string or add trailing spaces to hit a target character length. + + This is useful when trying to construct human-readable tables of text. + """ + if len(value) > target_len: + return value[:target_len] + elif len(value) < target_len: + return value + ' ' * (target_len - len(value)) + else: + return value
+ + +
[docs]def readable_short_name(value, max_length=24): + """Convert a string into a shorter but readable version of itself. + + This is useful when dealing with interfaces of file formats that have very + strict character limits on names or identifiers (like in DOE-2/eQuest). + + When ths input is less than or equal to the max length, the string will be + left as-is. If not, then the lower-case vowels will be removed from the name, + making the result abbreviated but still readable/recognizable. If the result + is still not shorter than the max length, then spaces will be removed. Lastly, + if all else fails to meet the max length, the middle characters will be, + removed leaving the beginning and end as they are, which should typically + help preserve the uniqueness of the name. + + Note that this method does not do any check for illegal characters and presumes + that the input is already composed of legal characters. + """ + # perform an initial check to see if it passes + if len(value) <= max_length: + return value + # strip out lowercase vowels and special characters like dashes + try: + value = re.sub(r'[aeiouy_\-]', '', value) + except TypeError: + raise TypeError('Input must be a text string. Got {}: {}.'.format( + type(value), value)) + if len(value) <= max_length: + return value + # remove spaces from the string to see if it gets short enough + value = value.replace(' ', '') + if len(value) <= max_length: + return value + # lastly, remove some characters from the middle to get it to fit + mid_ind = int(max_length * 0.5) + assert mid_ind > 3, \ + 'Max character length of {} is too restrictive.'.format(max_length) + end_length = max_length - mid_ind - 1 + value = '{}_{}'.format(value[:mid_ind], value[-end_length:]) + return value
+ + +
[docs]def clean_doe2_string(value, max_length=24): + """Clean and shorten a string for DOE-2 so that it can be a U-name. + + This includes stripping out all illegal characters (including both non-ASCII + and DOE-2 specific characters), removing trailing spaces, and passing the + result through the readable_short_name function to hit the target max_length. + Note that white spaces can be in the result with the assumption that + the name will be enclosed in double quotes. + + Note that this method does not do any check to ensure the string is unique. + """ + try: + val = ''.join(i for i in value if ord(i) < 128) # strip out non-ascii + val = re.sub(r'["\(\)\[\]\,\=\n\t]', '', val) # remove DOE-2 special characters + val = val.replace('_', ' ') # put back white spaces + except TypeError: + raise TypeError('Input must be a text string. Got {}: {}.'.format( + type(value), value)) + val = val.strip() + return readable_short_name(val, max_length)
+ + +
[docs]def invalid_dict_error(invalid_dict, error): + """Raise a ValueError for an invalid dictionary that failed to serialize. + + This error message will include the identifier (and display_name) if they are + present within the invalid_dict, making it easier for ens users to find the + invalid object within large objects like Models. + + Args: + invalid_dict: A dictionary of an invalid honeybee object that failed + to serialize. + error: + """ + obj_type = invalid_dict['type'].replace('Abridged', '') \ + if 'type' in invalid_dict else 'Honeybee Object' + obj_id = invalid_dict['identifier'] if 'identifier' in invalid_dict else '' + full_id = '{}[{}]'.format(invalid_dict['display_name'], obj_id) \ + if 'display_name' in invalid_dict else obj_id + raise ValueError('{} "{}" is invalid:\n{}'.format(obj_type, full_id, error))
+ + +wrapper = '"' if os.name == 'nt' else '\'' +"""String wrapper.""" + + +
[docs]def normpath(value): + """Normalize path eliminating double slashes, etc and put it in quotes if needed.""" + value = os.path.normpath(value) + if ' ' in value: + value = '{0}{1}{0}'.format(wrapper, value) + return value
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/honeybee/units.html b/docs/_modules/honeybee/units.html new file mode 100644 index 00000000..b1f0d30e --- /dev/null +++ b/docs/_modules/honeybee/units.html @@ -0,0 +1,1210 @@ + + + + + + + honeybee.units — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

Source code for honeybee.units

+"""Utility functions for converting and parsing units of length."""
+
+# global properties to set all supported units
+UNITS = ('Meters', 'Millimeters', 'Feet', 'Inches', 'Centimeters')
+UNITS_ABBREVIATIONS = ('m', 'mm', 'ft', 'in', 'cm')
+UNITS_TOLERANCES = {
+    'Meters': 0.01,
+    'Millimeters': 1.0,
+    'Feet': 0.01,
+    'Inches': 0.1,
+    'Centimeters': 1.0
+}
+
+
+
[docs]def conversion_factor_to_meters(units): + """Get the conversion factor to meters based on input units. + + Args: + units: Text for the units. Choose from the following: + + * Meters + * Millimeters + * Feet + * Inches + * Centimeters + + Returns: + A number for the conversion factor, which should be multiplied by + all distance units taken from Rhino geometry in order to convert + them to meters. + """ + if units == 'Meters': + return 1.0 + elif units == 'Millimeters': + return 0.001 + elif units == 'Feet': + return 0.3048 + elif units == 'Inches': + return 0.0254 + elif units == 'Centimeters': + return 0.01 + else: + raise ValueError( + 'You are kidding me! What units are you using? {}?\n' + 'Please use one of the following: {}'.format(units, ' '.join(UNITS)) + )
+ + +
[docs]def parse_distance_string(distance_string, destination_units='Meters'): + """Parse a string of a distance value into a destination units system. + + Args: + distance_string: Text for a distance value to be parsed into the + destination units. This can have the units at the end of + it (eg. "3ft"). If no units are included, the number will be + assumed to be in the destination units system. + destination_units: The destination units system to which the distance + string will be computed. (Default: Meters). + + Returns: + A number for the distance in the destination_units. + """ + # separate the distance string into a number and a unit abbreviation + distance_string = distance_string.strip().replace(',', '.') + try: # check if the distance string is just a number + return float(distance_string) + except ValueError: # it must have some units attached to it + for i, ua in enumerate(UNITS_ABBREVIATIONS): + try: # see if replacing the units yields a float + distance = float(distance_string.replace(ua, '', 1)) + u_sys = UNITS[i] + break + except ValueError: # not the right type of units + pass + else: # we could not match the units system + raise ValueError( + 'Text string "{}" could not be decoded into a distance and a unit.\n' + 'Make sure your units are one of the following: {}'.format( + distance_string, ' '.join(UNITS_ABBREVIATIONS)) + ) + + # process the number into the destination units system + if u_sys == destination_units: + return distance + con_factor = 1 / conversion_factor_to_meters(destination_units) + if u_sys != 'Meters': + con_factor = con_factor * conversion_factor_to_meters(u_sys) + return distance * con_factor
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/docs/_modules/index.html b/docs/_modules/index.html new file mode 100644 index 00000000..e9b9cc6f --- /dev/null +++ b/docs/_modules/index.html @@ -0,0 +1,1143 @@ + + + + + + + Overview: module code — honeybee documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + +
+
+ + + \ No newline at end of file diff --git a/docs/_sources/cli/compare.rst.txt b/docs/_sources/cli/compare.rst.txt new file mode 100644 index 00000000..dc2967d3 --- /dev/null +++ b/docs/_sources/cli/compare.rst.txt @@ -0,0 +1,6 @@ +compare +======= + +.. click:: honeybee.cli.compare:compare + :prog: honeybee compare + :show-nested: diff --git a/docs/_sources/cli/create.rst.txt b/docs/_sources/cli/create.rst.txt new file mode 100644 index 00000000..1a6e1885 --- /dev/null +++ b/docs/_sources/cli/create.rst.txt @@ -0,0 +1,6 @@ +create +====== + +.. click:: honeybee.cli.create:create + :prog: honeybee create + :show-nested: diff --git a/docs/_sources/cli/edit.rst.txt b/docs/_sources/cli/edit.rst.txt new file mode 100644 index 00000000..96b50ad5 --- /dev/null +++ b/docs/_sources/cli/edit.rst.txt @@ -0,0 +1,6 @@ +edit +==== + +.. click:: honeybee.cli.edit:edit + :prog: honeybee edit + :show-nested: diff --git a/docs/_sources/cli/index.rst.txt b/docs/_sources/cli/index.rst.txt new file mode 100644 index 00000000..57f0bc78 --- /dev/null +++ b/docs/_sources/cli/index.rst.txt @@ -0,0 +1,21 @@ +CLI Docs +======== + +Installation +------------ + +To check if the Honeybee command line interface is installed correctly try ``honeybee viz`` and you +should get a ``viiiiiiiiiiiiizzzzzzzzz!`` back in response! + +Commands +-------- +.. toctree:: + :maxdepth: 1 + + main + setconfig + edit + compare + lib + validate + create diff --git a/docs/_sources/cli/lib.rst.txt b/docs/_sources/cli/lib.rst.txt new file mode 100644 index 00000000..3e7028fc --- /dev/null +++ b/docs/_sources/cli/lib.rst.txt @@ -0,0 +1,6 @@ +lib +=== + +.. click:: honeybee.cli.lib:lib + :prog: honeybee lib + :show-nested: diff --git a/docs/_sources/cli/main.rst.txt b/docs/_sources/cli/main.rst.txt new file mode 100644 index 00000000..d9c70d8d --- /dev/null +++ b/docs/_sources/cli/main.rst.txt @@ -0,0 +1,7 @@ +main +==== + +.. click:: honeybee.cli.__init__:main + :prog: honeybee + :show-nested: + :commands: config ,viz diff --git a/docs/_sources/cli/setconfig.rst.txt b/docs/_sources/cli/setconfig.rst.txt new file mode 100644 index 00000000..b98d5236 --- /dev/null +++ b/docs/_sources/cli/setconfig.rst.txt @@ -0,0 +1,6 @@ +setconfig +========= + +.. click:: honeybee.cli.setconfig:set_config + :prog: honeybee set-config + :show-nested: diff --git a/docs/_sources/cli/validate.rst.txt b/docs/_sources/cli/validate.rst.txt new file mode 100644 index 00000000..05a5e701 --- /dev/null +++ b/docs/_sources/cli/validate.rst.txt @@ -0,0 +1,6 @@ +validate +======== + +.. click:: honeybee.cli.validate:validate + :prog: honeybee validate + :show-nested: diff --git a/docs/_sources/honeybee.altnumber.rst.txt b/docs/_sources/honeybee.altnumber.rst.txt new file mode 100644 index 00000000..6eb0819b --- /dev/null +++ b/docs/_sources/honeybee.altnumber.rst.txt @@ -0,0 +1,7 @@ +honeybee.altnumber module +========================= + +.. automodule:: honeybee.altnumber + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.aperture.rst.txt b/docs/_sources/honeybee.aperture.rst.txt new file mode 100644 index 00000000..72d88f3d --- /dev/null +++ b/docs/_sources/honeybee.aperture.rst.txt @@ -0,0 +1,7 @@ +honeybee.aperture module +======================== + +.. automodule:: honeybee.aperture + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.boundarycondition.rst.txt b/docs/_sources/honeybee.boundarycondition.rst.txt new file mode 100644 index 00000000..c2801234 --- /dev/null +++ b/docs/_sources/honeybee.boundarycondition.rst.txt @@ -0,0 +1,7 @@ +honeybee.boundarycondition module +================================= + +.. automodule:: honeybee.boundarycondition + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.checkdup.rst.txt b/docs/_sources/honeybee.checkdup.rst.txt new file mode 100644 index 00000000..a2dcbb75 --- /dev/null +++ b/docs/_sources/honeybee.checkdup.rst.txt @@ -0,0 +1,7 @@ +honeybee.checkdup module +======================== + +.. automodule:: honeybee.checkdup + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.cli.compare.rst.txt b/docs/_sources/honeybee.cli.compare.rst.txt new file mode 100644 index 00000000..98f89305 --- /dev/null +++ b/docs/_sources/honeybee.cli.compare.rst.txt @@ -0,0 +1,7 @@ +honeybee.cli.compare module +=========================== + +.. automodule:: honeybee.cli.compare + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.cli.create.rst.txt b/docs/_sources/honeybee.cli.create.rst.txt new file mode 100644 index 00000000..1a6e3df7 --- /dev/null +++ b/docs/_sources/honeybee.cli.create.rst.txt @@ -0,0 +1,7 @@ +honeybee.cli.create module +========================== + +.. automodule:: honeybee.cli.create + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.cli.edit.rst.txt b/docs/_sources/honeybee.cli.edit.rst.txt new file mode 100644 index 00000000..697b1c68 --- /dev/null +++ b/docs/_sources/honeybee.cli.edit.rst.txt @@ -0,0 +1,7 @@ +honeybee.cli.edit module +======================== + +.. automodule:: honeybee.cli.edit + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.cli.lib.rst.txt b/docs/_sources/honeybee.cli.lib.rst.txt new file mode 100644 index 00000000..c96be7f2 --- /dev/null +++ b/docs/_sources/honeybee.cli.lib.rst.txt @@ -0,0 +1,7 @@ +honeybee.cli.lib module +======================= + +.. automodule:: honeybee.cli.lib + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.cli.rst.txt b/docs/_sources/honeybee.cli.rst.txt new file mode 100644 index 00000000..e0dc2233 --- /dev/null +++ b/docs/_sources/honeybee.cli.rst.txt @@ -0,0 +1,23 @@ +honeybee.cli package +==================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + honeybee.cli.compare + honeybee.cli.create + honeybee.cli.edit + honeybee.cli.lib + honeybee.cli.setconfig + honeybee.cli.validate + +Module contents +--------------- + +.. automodule:: honeybee.cli + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.cli.setconfig.rst.txt b/docs/_sources/honeybee.cli.setconfig.rst.txt new file mode 100644 index 00000000..bcecb98c --- /dev/null +++ b/docs/_sources/honeybee.cli.setconfig.rst.txt @@ -0,0 +1,7 @@ +honeybee.cli.setconfig module +============================= + +.. automodule:: honeybee.cli.setconfig + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.cli.validate.rst.txt b/docs/_sources/honeybee.cli.validate.rst.txt new file mode 100644 index 00000000..4d366837 --- /dev/null +++ b/docs/_sources/honeybee.cli.validate.rst.txt @@ -0,0 +1,7 @@ +honeybee.cli.validate module +============================ + +.. automodule:: honeybee.cli.validate + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.colorobj.rst.txt b/docs/_sources/honeybee.colorobj.rst.txt new file mode 100644 index 00000000..e7bc72e1 --- /dev/null +++ b/docs/_sources/honeybee.colorobj.rst.txt @@ -0,0 +1,7 @@ +honeybee.colorobj module +======================== + +.. automodule:: honeybee.colorobj + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.config.rst.txt b/docs/_sources/honeybee.config.rst.txt new file mode 100644 index 00000000..3655f4fe --- /dev/null +++ b/docs/_sources/honeybee.config.rst.txt @@ -0,0 +1,7 @@ +honeybee.config module +====================== + +.. automodule:: honeybee.config + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.dictutil.rst.txt b/docs/_sources/honeybee.dictutil.rst.txt new file mode 100644 index 00000000..378e8f70 --- /dev/null +++ b/docs/_sources/honeybee.dictutil.rst.txt @@ -0,0 +1,7 @@ +honeybee.dictutil module +======================== + +.. automodule:: honeybee.dictutil + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.door.rst.txt b/docs/_sources/honeybee.door.rst.txt new file mode 100644 index 00000000..a40420b7 --- /dev/null +++ b/docs/_sources/honeybee.door.rst.txt @@ -0,0 +1,7 @@ +honeybee.door module +==================== + +.. automodule:: honeybee.door + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.extensionutil.rst.txt b/docs/_sources/honeybee.extensionutil.rst.txt new file mode 100644 index 00000000..5e667967 --- /dev/null +++ b/docs/_sources/honeybee.extensionutil.rst.txt @@ -0,0 +1,7 @@ +honeybee.extensionutil module +============================= + +.. automodule:: honeybee.extensionutil + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.face.rst.txt b/docs/_sources/honeybee.face.rst.txt new file mode 100644 index 00000000..60b575c1 --- /dev/null +++ b/docs/_sources/honeybee.face.rst.txt @@ -0,0 +1,7 @@ +honeybee.face module +==================== + +.. automodule:: honeybee.face + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.facetype.rst.txt b/docs/_sources/honeybee.facetype.rst.txt new file mode 100644 index 00000000..8ba4400a --- /dev/null +++ b/docs/_sources/honeybee.facetype.rst.txt @@ -0,0 +1,7 @@ +honeybee.facetype module +======================== + +.. automodule:: honeybee.facetype + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.logutil.rst.txt b/docs/_sources/honeybee.logutil.rst.txt new file mode 100644 index 00000000..da74223a --- /dev/null +++ b/docs/_sources/honeybee.logutil.rst.txt @@ -0,0 +1,7 @@ +honeybee.logutil module +======================= + +.. automodule:: honeybee.logutil + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.model.rst.txt b/docs/_sources/honeybee.model.rst.txt new file mode 100644 index 00000000..181a4721 --- /dev/null +++ b/docs/_sources/honeybee.model.rst.txt @@ -0,0 +1,7 @@ +honeybee.model module +===================== + +.. automodule:: honeybee.model + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.orientation.rst.txt b/docs/_sources/honeybee.orientation.rst.txt new file mode 100644 index 00000000..c56148c0 --- /dev/null +++ b/docs/_sources/honeybee.orientation.rst.txt @@ -0,0 +1,7 @@ +honeybee.orientation module +=========================== + +.. automodule:: honeybee.orientation + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.properties.rst.txt b/docs/_sources/honeybee.properties.rst.txt new file mode 100644 index 00000000..b5124d91 --- /dev/null +++ b/docs/_sources/honeybee.properties.rst.txt @@ -0,0 +1,7 @@ +honeybee.properties module +========================== + +.. automodule:: honeybee.properties + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.room.rst.txt b/docs/_sources/honeybee.room.rst.txt new file mode 100644 index 00000000..3ba67b21 --- /dev/null +++ b/docs/_sources/honeybee.room.rst.txt @@ -0,0 +1,7 @@ +honeybee.room module +==================== + +.. automodule:: honeybee.room + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.rst.txt b/docs/_sources/honeybee.rst.txt new file mode 100644 index 00000000..cb5ff286 --- /dev/null +++ b/docs/_sources/honeybee.rst.txt @@ -0,0 +1,47 @@ +honeybee package +================ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + honeybee.cli + honeybee.writer + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + honeybee.altnumber + honeybee.aperture + honeybee.boundarycondition + honeybee.checkdup + honeybee.colorobj + honeybee.config + honeybee.dictutil + honeybee.door + honeybee.extensionutil + honeybee.face + honeybee.facetype + honeybee.logutil + honeybee.model + honeybee.orientation + honeybee.properties + honeybee.room + honeybee.search + honeybee.shade + honeybee.shademesh + honeybee.typing + honeybee.units + +Module contents +--------------- + +.. automodule:: honeybee + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.search.rst.txt b/docs/_sources/honeybee.search.rst.txt new file mode 100644 index 00000000..d293d647 --- /dev/null +++ b/docs/_sources/honeybee.search.rst.txt @@ -0,0 +1,7 @@ +honeybee.search module +====================== + +.. automodule:: honeybee.search + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.shade.rst.txt b/docs/_sources/honeybee.shade.rst.txt new file mode 100644 index 00000000..b973ba61 --- /dev/null +++ b/docs/_sources/honeybee.shade.rst.txt @@ -0,0 +1,7 @@ +honeybee.shade module +===================== + +.. automodule:: honeybee.shade + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.shademesh.rst.txt b/docs/_sources/honeybee.shademesh.rst.txt new file mode 100644 index 00000000..4405624b --- /dev/null +++ b/docs/_sources/honeybee.shademesh.rst.txt @@ -0,0 +1,7 @@ +honeybee.shademesh module +========================= + +.. automodule:: honeybee.shademesh + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.typing.rst.txt b/docs/_sources/honeybee.typing.rst.txt new file mode 100644 index 00000000..fdcfafd1 --- /dev/null +++ b/docs/_sources/honeybee.typing.rst.txt @@ -0,0 +1,7 @@ +honeybee.typing module +====================== + +.. automodule:: honeybee.typing + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.units.rst.txt b/docs/_sources/honeybee.units.rst.txt new file mode 100644 index 00000000..b01c11f4 --- /dev/null +++ b/docs/_sources/honeybee.units.rst.txt @@ -0,0 +1,7 @@ +honeybee.units module +===================== + +.. automodule:: honeybee.units + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.aperture.rst.txt b/docs/_sources/honeybee.writer.aperture.rst.txt new file mode 100644 index 00000000..75ccaa31 --- /dev/null +++ b/docs/_sources/honeybee.writer.aperture.rst.txt @@ -0,0 +1,7 @@ +honeybee.writer.aperture module +=============================== + +.. automodule:: honeybee.writer.aperture + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.door.rst.txt b/docs/_sources/honeybee.writer.door.rst.txt new file mode 100644 index 00000000..c859008f --- /dev/null +++ b/docs/_sources/honeybee.writer.door.rst.txt @@ -0,0 +1,7 @@ +honeybee.writer.door module +=========================== + +.. automodule:: honeybee.writer.door + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.face.rst.txt b/docs/_sources/honeybee.writer.face.rst.txt new file mode 100644 index 00000000..8f2a572f --- /dev/null +++ b/docs/_sources/honeybee.writer.face.rst.txt @@ -0,0 +1,7 @@ +honeybee.writer.face module +=========================== + +.. automodule:: honeybee.writer.face + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.model.rst.txt b/docs/_sources/honeybee.writer.model.rst.txt new file mode 100644 index 00000000..d39a00ff --- /dev/null +++ b/docs/_sources/honeybee.writer.model.rst.txt @@ -0,0 +1,7 @@ +honeybee.writer.model module +============================ + +.. automodule:: honeybee.writer.model + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.room.rst.txt b/docs/_sources/honeybee.writer.room.rst.txt new file mode 100644 index 00000000..d46e1c9d --- /dev/null +++ b/docs/_sources/honeybee.writer.room.rst.txt @@ -0,0 +1,7 @@ +honeybee.writer.room module +=========================== + +.. automodule:: honeybee.writer.room + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.rst.txt b/docs/_sources/honeybee.writer.rst.txt new file mode 100644 index 00000000..6bb09d43 --- /dev/null +++ b/docs/_sources/honeybee.writer.rst.txt @@ -0,0 +1,24 @@ +honeybee.writer package +======================= + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + honeybee.writer.aperture + honeybee.writer.door + honeybee.writer.face + honeybee.writer.model + honeybee.writer.room + honeybee.writer.shade + honeybee.writer.shademesh + +Module contents +--------------- + +.. automodule:: honeybee.writer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.shade.rst.txt b/docs/_sources/honeybee.writer.shade.rst.txt new file mode 100644 index 00000000..7c54f0e3 --- /dev/null +++ b/docs/_sources/honeybee.writer.shade.rst.txt @@ -0,0 +1,7 @@ +honeybee.writer.shade module +============================ + +.. automodule:: honeybee.writer.shade + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/honeybee.writer.shademesh.rst.txt b/docs/_sources/honeybee.writer.shademesh.rst.txt new file mode 100644 index 00000000..a146bd89 --- /dev/null +++ b/docs/_sources/honeybee.writer.shademesh.rst.txt @@ -0,0 +1,7 @@ +honeybee.writer.shademesh module +================================ + +.. automodule:: honeybee.writer.shademesh + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt new file mode 100644 index 00000000..9d73970e --- /dev/null +++ b/docs/_sources/index.rst.txt @@ -0,0 +1,62 @@ + +Welcome to Honeybee's documentation! +========================================= + +.. image:: http://www.ladybug.tools/assets/img/honeybee.png + +Honeybee is a collection of Python libraries to create representations of buildings +following `honeybee-schema `_. + +This package is the core library that provides honeybee's common functionalities. +To extend these functionalities you should install available Honeybee extensions or write +your own. + +Installation +============ + +To install the core library try ``pip install -U honeybee-core``. + +To check if the Honeybee command line interface is installed correctly try ``honeybee viz`` and you +should get a ``viiiiiiiiiiiiizzzzzzzzz!`` back in response! + + +Documentation +============= + +This document includes `Honeybee API documentation <#honeybee>`_ and +`Honeybee Command Line Interface <#cli-docs>`_ documentation for ``honeybee core`` and does +not include the documentation for honeybee extensions. For each extension refer to +extension's documentation page. + +Here are a number of Honeybee popular extensions: + +- `honeybee-energy `_ +- `honeybee-radiance `_ + + +CLI Docs +============= + +For command line interface documentation and API documentation see the pages below. + + +.. toctree:: + :maxdepth: 2 + + cli/index + + +honeybee +============= + +.. toctree:: + :maxdepth: 4 + + modules + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_sources/modules.rst.txt b/docs/_sources/modules.rst.txt new file mode 100644 index 00000000..b1e490b0 --- /dev/null +++ b/docs/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +honeybee +======== + +.. toctree:: + :maxdepth: 4 + + honeybee diff --git a/docs/_static/_sphinx_javascript_frameworks_compat.js b/docs/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000..8549469d --- /dev/null +++ b/docs/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,134 @@ +/* + * _sphinx_javascript_frameworks_compat.js + * ~~~~~~~~~~ + * + * Compatability shim for jQuery and underscores.js. + * + * WILL BE REMOVED IN Sphinx 6.0 + * xref RemovedInSphinx60Warning + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/docs/_static/basic.css b/docs/_static/basic.css new file mode 100644 index 00000000..eeb0519a --- /dev/null +++ b/docs/_static/basic.css @@ -0,0 +1,899 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} +dl.field-list > dt:after { + content: ":"; +} + + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.css b/docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.css new file mode 100644 index 00000000..09e88ce3 --- /dev/null +++ b/docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.css @@ -0,0 +1,1109 @@ +/*! + * Bootstrap Responsive v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +.clearfix { + *zoom: 1; +} + +.clearfix:before, +.clearfix:after { + display: table; + line-height: 0; + content: ""; +} + +.clearfix:after { + clear: both; +} + +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.input-block-level { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +@-ms-viewport { + width: device-width; +} + +.hidden { + display: none; + visibility: hidden; +} + +.visible-phone { + display: none !important; +} + +.visible-tablet { + display: none !important; +} + +.hidden-desktop { + display: none !important; +} + +.visible-desktop { + display: inherit !important; +} + +@media (min-width: 768px) and (max-width: 979px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important ; + } + .visible-tablet { + display: inherit !important; + } + .hidden-tablet { + display: none !important; + } +} + +@media (max-width: 767px) { + .hidden-desktop { + display: inherit !important; + } + .visible-desktop { + display: none !important; + } + .visible-phone { + display: inherit !important; + } + .hidden-phone { + display: none !important; + } +} + +.visible-print { + display: none !important; +} + +@media print { + .visible-print { + display: inherit !important; + } + .hidden-print { + display: none !important; + } +} + +@media (min-width: 1200px) { + .row { + margin-left: -30px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 30px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 1170px; + } + .span12 { + width: 1170px; + } + .span11 { + width: 1070px; + } + .span10 { + width: 970px; + } + .span9 { + width: 870px; + } + .span8 { + width: 770px; + } + .span7 { + width: 670px; + } + .span6 { + width: 570px; + } + .span5 { + width: 470px; + } + .span4 { + width: 370px; + } + .span3 { + width: 270px; + } + .span2 { + width: 170px; + } + .span1 { + width: 70px; + } + .offset12 { + margin-left: 1230px; + } + .offset11 { + margin-left: 1130px; + } + .offset10 { + margin-left: 1030px; + } + .offset9 { + margin-left: 930px; + } + .offset8 { + margin-left: 830px; + } + .offset7 { + margin-left: 730px; + } + .offset6 { + margin-left: 630px; + } + .offset5 { + margin-left: 530px; + } + .offset4 { + margin-left: 430px; + } + .offset3 { + margin-left: 330px; + } + .offset2 { + margin-left: 230px; + } + .offset1 { + margin-left: 130px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.564102564102564%; + *margin-left: 2.5109110747408616%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .controls-row [class*="span"] + [class*="span"] { + margin-left: 2.564102564102564%; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.45299145299145%; + *width: 91.39979996362975%; + } + .row-fluid .span10 { + width: 82.90598290598291%; + *width: 82.8527914166212%; + } + .row-fluid .span9 { + width: 74.35897435897436%; + *width: 74.30578286961266%; + } + .row-fluid .span8 { + width: 65.81196581196582%; + *width: 65.75877432260411%; + } + .row-fluid .span7 { + width: 57.26495726495726%; + *width: 57.21176577559556%; + } + .row-fluid .span6 { + width: 48.717948717948715%; + *width: 48.664757228587014%; + } + .row-fluid .span5 { + width: 40.17094017094017%; + *width: 40.11774868157847%; + } + .row-fluid .span4 { + width: 31.623931623931625%; + *width: 31.570740134569924%; + } + .row-fluid .span3 { + width: 23.076923076923077%; + *width: 23.023731587561375%; + } + .row-fluid .span2 { + width: 14.52991452991453%; + *width: 14.476723040552828%; + } + .row-fluid .span1 { + width: 5.982905982905983%; + *width: 5.929714493544281%; + } + .row-fluid .offset12 { + margin-left: 105.12820512820512%; + *margin-left: 105.02182214948171%; + } + .row-fluid .offset12:first-child { + margin-left: 102.56410256410257%; + *margin-left: 102.45771958537915%; + } + .row-fluid .offset11 { + margin-left: 96.58119658119658%; + *margin-left: 96.47481360247316%; + } + .row-fluid .offset11:first-child { + margin-left: 94.01709401709402%; + *margin-left: 93.91071103837061%; + } + .row-fluid .offset10 { + margin-left: 88.03418803418803%; + *margin-left: 87.92780505546462%; + } + .row-fluid .offset10:first-child { + margin-left: 85.47008547008548%; + *margin-left: 85.36370249136206%; + } + .row-fluid .offset9 { + margin-left: 79.48717948717949%; + *margin-left: 79.38079650845607%; + } + .row-fluid .offset9:first-child { + margin-left: 76.92307692307693%; + *margin-left: 76.81669394435352%; + } + .row-fluid .offset8 { + margin-left: 70.94017094017094%; + *margin-left: 70.83378796144753%; + } + .row-fluid .offset8:first-child { + margin-left: 68.37606837606839%; + *margin-left: 68.26968539734497%; + } + .row-fluid .offset7 { + margin-left: 62.393162393162385%; + *margin-left: 62.28677941443899%; + } + .row-fluid .offset7:first-child { + margin-left: 59.82905982905982%; + *margin-left: 59.72267685033642%; + } + .row-fluid .offset6 { + margin-left: 53.84615384615384%; + *margin-left: 53.739770867430444%; + } + .row-fluid .offset6:first-child { + margin-left: 51.28205128205128%; + *margin-left: 51.175668303327875%; + } + .row-fluid .offset5 { + margin-left: 45.299145299145295%; + *margin-left: 45.1927623204219%; + } + .row-fluid .offset5:first-child { + margin-left: 42.73504273504273%; + *margin-left: 42.62865975631933%; + } + .row-fluid .offset4 { + margin-left: 36.75213675213675%; + *margin-left: 36.645753773413354%; + } + .row-fluid .offset4:first-child { + margin-left: 34.18803418803419%; + *margin-left: 34.081651209310785%; + } + .row-fluid .offset3 { + margin-left: 28.205128205128204%; + *margin-left: 28.0987452264048%; + } + .row-fluid .offset3:first-child { + margin-left: 25.641025641025642%; + *margin-left: 25.53464266230224%; + } + .row-fluid .offset2 { + margin-left: 19.65811965811966%; + *margin-left: 19.551736679396257%; + } + .row-fluid .offset2:first-child { + margin-left: 17.094017094017094%; + *margin-left: 16.98763411529369%; + } + .row-fluid .offset1 { + margin-left: 11.11111111111111%; + *margin-left: 11.004728132387708%; + } + .row-fluid .offset1:first-child { + margin-left: 8.547008547008547%; + *margin-left: 8.440625568285142%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 30px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 1156px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 1056px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 956px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 856px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 756px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 656px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 556px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 456px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 356px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 256px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 156px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 56px; + } + .thumbnails { + margin-left: -30px; + } + .thumbnails > li { + margin-left: 30px; + } + .row-fluid .thumbnails { + margin-left: 0; + } +} + +@media (min-width: 768px) and (max-width: 979px) { + .row { + margin-left: -20px; + *zoom: 1; + } + .row:before, + .row:after { + display: table; + line-height: 0; + content: ""; + } + .row:after { + clear: both; + } + [class*="span"] { + float: left; + min-height: 1px; + margin-left: 20px; + } + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + width: 724px; + } + .span12 { + width: 724px; + } + .span11 { + width: 662px; + } + .span10 { + width: 600px; + } + .span9 { + width: 538px; + } + .span8 { + width: 476px; + } + .span7 { + width: 414px; + } + .span6 { + width: 352px; + } + .span5 { + width: 290px; + } + .span4 { + width: 228px; + } + .span3 { + width: 166px; + } + .span2 { + width: 104px; + } + .span1 { + width: 42px; + } + .offset12 { + margin-left: 764px; + } + .offset11 { + margin-left: 702px; + } + .offset10 { + margin-left: 640px; + } + .offset9 { + margin-left: 578px; + } + .offset8 { + margin-left: 516px; + } + .offset7 { + margin-left: 454px; + } + .offset6 { + margin-left: 392px; + } + .offset5 { + margin-left: 330px; + } + .offset4 { + margin-left: 268px; + } + .offset3 { + margin-left: 206px; + } + .offset2 { + margin-left: 144px; + } + .offset1 { + margin-left: 82px; + } + .row-fluid { + width: 100%; + *zoom: 1; + } + .row-fluid:before, + .row-fluid:after { + display: table; + line-height: 0; + content: ""; + } + .row-fluid:after { + clear: both; + } + .row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.7624309392265194%; + *margin-left: 2.709239449864817%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="span"]:first-child { + margin-left: 0; + } + .row-fluid .controls-row [class*="span"] + [class*="span"] { + margin-left: 2.7624309392265194%; + } + .row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; + } + .row-fluid .span11 { + width: 91.43646408839778%; + *width: 91.38327259903608%; + } + .row-fluid .span10 { + width: 82.87292817679558%; + *width: 82.81973668743387%; + } + .row-fluid .span9 { + width: 74.30939226519337%; + *width: 74.25620077583166%; + } + .row-fluid .span8 { + width: 65.74585635359117%; + *width: 65.69266486422946%; + } + .row-fluid .span7 { + width: 57.18232044198895%; + *width: 57.12912895262725%; + } + .row-fluid .span6 { + width: 48.61878453038674%; + *width: 48.56559304102504%; + } + .row-fluid .span5 { + width: 40.05524861878453%; + *width: 40.00205712942283%; + } + .row-fluid .span4 { + width: 31.491712707182323%; + *width: 31.43852121782062%; + } + .row-fluid .span3 { + width: 22.92817679558011%; + *width: 22.87498530621841%; + } + .row-fluid .span2 { + width: 14.3646408839779%; + *width: 14.311449394616199%; + } + .row-fluid .span1 { + width: 5.801104972375691%; + *width: 5.747913483013988%; + } + .row-fluid .offset12 { + margin-left: 105.52486187845304%; + *margin-left: 105.41847889972962%; + } + .row-fluid .offset12:first-child { + margin-left: 102.76243093922652%; + *margin-left: 102.6560479605031%; + } + .row-fluid .offset11 { + margin-left: 96.96132596685082%; + *margin-left: 96.8549429881274%; + } + .row-fluid .offset11:first-child { + margin-left: 94.1988950276243%; + *margin-left: 94.09251204890089%; + } + .row-fluid .offset10 { + margin-left: 88.39779005524862%; + *margin-left: 88.2914070765252%; + } + .row-fluid .offset10:first-child { + margin-left: 85.6353591160221%; + *margin-left: 85.52897613729868%; + } + .row-fluid .offset9 { + margin-left: 79.8342541436464%; + *margin-left: 79.72787116492299%; + } + .row-fluid .offset9:first-child { + margin-left: 77.07182320441989%; + *margin-left: 76.96544022569647%; + } + .row-fluid .offset8 { + margin-left: 71.2707182320442%; + *margin-left: 71.16433525332079%; + } + .row-fluid .offset8:first-child { + margin-left: 68.50828729281768%; + *margin-left: 68.40190431409427%; + } + .row-fluid .offset7 { + margin-left: 62.70718232044199%; + *margin-left: 62.600799341718584%; + } + .row-fluid .offset7:first-child { + margin-left: 59.94475138121547%; + *margin-left: 59.838368402492065%; + } + .row-fluid .offset6 { + margin-left: 54.14364640883978%; + *margin-left: 54.037263430116376%; + } + .row-fluid .offset6:first-child { + margin-left: 51.38121546961326%; + *margin-left: 51.27483249088986%; + } + .row-fluid .offset5 { + margin-left: 45.58011049723757%; + *margin-left: 45.47372751851417%; + } + .row-fluid .offset5:first-child { + margin-left: 42.81767955801105%; + *margin-left: 42.71129657928765%; + } + .row-fluid .offset4 { + margin-left: 37.01657458563536%; + *margin-left: 36.91019160691196%; + } + .row-fluid .offset4:first-child { + margin-left: 34.25414364640884%; + *margin-left: 34.14776066768544%; + } + .row-fluid .offset3 { + margin-left: 28.45303867403315%; + *margin-left: 28.346655695309746%; + } + .row-fluid .offset3:first-child { + margin-left: 25.69060773480663%; + *margin-left: 25.584224756083227%; + } + .row-fluid .offset2 { + margin-left: 19.88950276243094%; + *margin-left: 19.783119783707537%; + } + .row-fluid .offset2:first-child { + margin-left: 17.12707182320442%; + *margin-left: 17.02068884448102%; + } + .row-fluid .offset1 { + margin-left: 11.32596685082873%; + *margin-left: 11.219583872105325%; + } + .row-fluid .offset1:first-child { + margin-left: 8.56353591160221%; + *margin-left: 8.457152932878806%; + } + input, + textarea, + .uneditable-input { + margin-left: 0; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 20px; + } + input.span12, + textarea.span12, + .uneditable-input.span12 { + width: 710px; + } + input.span11, + textarea.span11, + .uneditable-input.span11 { + width: 648px; + } + input.span10, + textarea.span10, + .uneditable-input.span10 { + width: 586px; + } + input.span9, + textarea.span9, + .uneditable-input.span9 { + width: 524px; + } + input.span8, + textarea.span8, + .uneditable-input.span8 { + width: 462px; + } + input.span7, + textarea.span7, + .uneditable-input.span7 { + width: 400px; + } + input.span6, + textarea.span6, + .uneditable-input.span6 { + width: 338px; + } + input.span5, + textarea.span5, + .uneditable-input.span5 { + width: 276px; + } + input.span4, + textarea.span4, + .uneditable-input.span4 { + width: 214px; + } + input.span3, + textarea.span3, + .uneditable-input.span3 { + width: 152px; + } + input.span2, + textarea.span2, + .uneditable-input.span2 { + width: 90px; + } + input.span1, + textarea.span1, + .uneditable-input.span1 { + width: 28px; + } +} + +@media (max-width: 767px) { + body { + padding-right: 20px; + padding-left: 20px; + } + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin-right: -20px; + margin-left: -20px; + } + .container-fluid { + padding: 0; + } + .dl-horizontal dt { + float: none; + width: auto; + clear: none; + text-align: left; + } + .dl-horizontal dd { + margin-left: 0; + } + .container { + width: auto; + } + .row-fluid { + width: 100%; + } + .row, + .thumbnails { + margin-left: 0; + } + .thumbnails > li { + float: none; + margin-left: 0; + } + [class*="span"], + .uneditable-input[class*="span"], + .row-fluid [class*="span"] { + display: block; + float: none; + width: 100%; + margin-left: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .span12, + .row-fluid .span12 { + width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .row-fluid [class*="offset"]:first-child { + margin-left: 0; + } + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; + width: auto; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + .modal { + position: fixed; + top: 20px; + right: 20px; + left: 20px; + width: auto; + margin: 0; + } + .modal.fade { + top: -100px; + } + .modal.fade.in { + top: 20px; + } +} + +@media (max-width: 480px) { + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); + } + .page-header h1 small { + display: block; + line-height: 20px; + } + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + .form-horizontal .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + .form-horizontal .controls { + margin-left: 0; + } + .form-horizontal .control-list { + padding-top: 0; + } + .form-horizontal .form-actions { + padding-right: 10px; + padding-left: 10px; + } + .media .pull-left, + .media .pull-right { + display: block; + float: none; + margin-bottom: 10px; + } + .media-object { + margin-right: 0; + margin-left: 0; + } + .modal { + top: 10px; + right: 10px; + left: 10px; + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + .carousel-caption { + position: static; + } +} + +@media (max-width: 979px) { + body { + padding-top: 0; + } + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: 20px; + } + .navbar-fixed-bottom { + margin-top: 20px; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + .navbar .brand { + padding-right: 10px; + padding-left: 10px; + margin: 0 0 0 -5px; + } + .nav-collapse { + clear: both; + } + .nav-collapse .nav { + float: none; + margin: 0 0 10px; + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: #777777; + text-shadow: none; + } + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: #777777; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + } + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .nav > li > a:focus, + .nav-collapse .dropdown-menu a:hover, + .nav-collapse .dropdown-menu a:focus { + background-color: #f2f2f2; + } + .navbar-inverse .nav-collapse .nav > li > a, + .navbar-inverse .nav-collapse .dropdown-menu a { + color: #999999; + } + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .nav > li > a:focus, + .navbar-inverse .nav-collapse .dropdown-menu a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:focus { + background-color: #111111; + } + .nav-collapse.in .btn-group { + padding: 0; + margin-top: 5px; + } + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + display: none; + float: none; + max-width: none; + padding: 0; + margin: 0 15px; + background-color: transparent; + border: none; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + } + .nav-collapse .open > .dropdown-menu { + display: block; + } + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + .nav-collapse .nav > li > .dropdown-menu:before, + .nav-collapse .nav > li > .dropdown-menu:after { + display: none; + } + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: 10px 15px; + margin: 10px 0; + border-top: 1px solid #f2f2f2; + border-bottom: 1px solid #f2f2f2; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + } + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: #111111; + border-bottom-color: #111111; + } + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + .nav-collapse, + .nav-collapse.collapse { + height: 0; + overflow: hidden; + } + .navbar .btn-navbar { + display: block; + } + .navbar-static .navbar-inner { + padding-right: 10px; + padding-left: 10px; + } +} + +@media (min-width: 980px) { + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } +} diff --git a/docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.min.css b/docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.min.css new file mode 100644 index 00000000..f4ede63f --- /dev/null +++ b/docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap Responsive v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/docs/_static/bootstrap-2.3.2/css/bootstrap.css b/docs/_static/bootstrap-2.3.2/css/bootstrap.css new file mode 100644 index 00000000..b725064a --- /dev/null +++ b/docs/_static/bootstrap-2.3.2/css/bootstrap.css @@ -0,0 +1,6167 @@ +/*! + * Bootstrap v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +.clearfix { + *zoom: 1; +} + +.clearfix:before, +.clearfix:after { + display: table; + line-height: 0; + content: ""; +} + +.clearfix:after { + clear: both; +} + +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.input-block-level { + display: block; + width: 100%; + min-height: 30px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +audio:not([controls]) { + display: none; +} + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +a:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +a:hover, +a:active { + outline: 0; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +img { + width: auto\9; + height: auto; + max-width: 100%; + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; +} + +#map_canvas img, +.google-maps img { + max-width: none; +} + +button, +input, +select, +textarea { + margin: 0; + font-size: 100%; + vertical-align: middle; +} + +button, +input { + *overflow: visible; + line-height: normal; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; +} + +label, +select, +button, +input[type="button"], +input[type="reset"], +input[type="submit"], +input[type="radio"], +input[type="checkbox"] { + cursor: pointer; +} + +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} + +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +textarea { + overflow: auto; + vertical-align: top; +} + +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page { + margin: 0.5cm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } +} + +body { + margin: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 20px; + color: #333333; + background-color: #ffffff; +} + +a { + color: #0088cc; + text-decoration: none; +} + +a:hover, +a:focus { + color: #005580; + text-decoration: underline; +} + +.img-rounded { + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.img-polaroid { + padding: 4px; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.img-circle { + -webkit-border-radius: 500px; + -moz-border-radius: 500px; + border-radius: 500px; +} + +.row { + margin-left: -20px; + *zoom: 1; +} + +.row:before, +.row:after { + display: table; + line-height: 0; + content: ""; +} + +.row:after { + clear: both; +} + +[class*="span"] { + float: left; + min-height: 1px; + margin-left: 20px; +} + +.container, +.navbar-static-top .container, +.navbar-fixed-top .container, +.navbar-fixed-bottom .container { + width: 940px; +} + +.span12 { + width: 940px; +} + +.span11 { + width: 860px; +} + +.span10 { + width: 780px; +} + +.span9 { + width: 700px; +} + +.span8 { + width: 620px; +} + +.span7 { + width: 540px; +} + +.span6 { + width: 460px; +} + +.span5 { + width: 380px; +} + +.span4 { + width: 300px; +} + +.span3 { + width: 220px; +} + +.span2 { + width: 140px; +} + +.span1 { + width: 60px; +} + +.offset12 { + margin-left: 980px; +} + +.offset11 { + margin-left: 900px; +} + +.offset10 { + margin-left: 820px; +} + +.offset9 { + margin-left: 740px; +} + +.offset8 { + margin-left: 660px; +} + +.offset7 { + margin-left: 580px; +} + +.offset6 { + margin-left: 500px; +} + +.offset5 { + margin-left: 420px; +} + +.offset4 { + margin-left: 340px; +} + +.offset3 { + margin-left: 260px; +} + +.offset2 { + margin-left: 180px; +} + +.offset1 { + margin-left: 100px; +} + +.row-fluid { + width: 100%; + *zoom: 1; +} + +.row-fluid:before, +.row-fluid:after { + display: table; + line-height: 0; + content: ""; +} + +.row-fluid:after { + clear: both; +} + +.row-fluid [class*="span"] { + display: block; + float: left; + width: 100%; + min-height: 30px; + margin-left: 2.127659574468085%; + *margin-left: 2.074468085106383%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.row-fluid [class*="span"]:first-child { + margin-left: 0; +} + +.row-fluid .controls-row [class*="span"] + [class*="span"] { + margin-left: 2.127659574468085%; +} + +.row-fluid .span12 { + width: 100%; + *width: 99.94680851063829%; +} + +.row-fluid .span11 { + width: 91.48936170212765%; + *width: 91.43617021276594%; +} + +.row-fluid .span10 { + width: 82.97872340425532%; + *width: 82.92553191489361%; +} + +.row-fluid .span9 { + width: 74.46808510638297%; + *width: 74.41489361702126%; +} + +.row-fluid .span8 { + width: 65.95744680851064%; + *width: 65.90425531914893%; +} + +.row-fluid .span7 { + width: 57.44680851063829%; + *width: 57.39361702127659%; +} + +.row-fluid .span6 { + width: 48.93617021276595%; + *width: 48.88297872340425%; +} + +.row-fluid .span5 { + width: 40.42553191489362%; + *width: 40.37234042553192%; +} + +.row-fluid .span4 { + width: 31.914893617021278%; + *width: 31.861702127659576%; +} + +.row-fluid .span3 { + width: 23.404255319148934%; + *width: 23.351063829787233%; +} + +.row-fluid .span2 { + width: 14.893617021276595%; + *width: 14.840425531914894%; +} + +.row-fluid .span1 { + width: 6.382978723404255%; + *width: 6.329787234042553%; +} + +.row-fluid .offset12 { + margin-left: 104.25531914893617%; + *margin-left: 104.14893617021275%; +} + +.row-fluid .offset12:first-child { + margin-left: 102.12765957446808%; + *margin-left: 102.02127659574467%; +} + +.row-fluid .offset11 { + margin-left: 95.74468085106382%; + *margin-left: 95.6382978723404%; +} + +.row-fluid .offset11:first-child { + margin-left: 93.61702127659574%; + *margin-left: 93.51063829787232%; +} + +.row-fluid .offset10 { + margin-left: 87.23404255319149%; + *margin-left: 87.12765957446807%; +} + +.row-fluid .offset10:first-child { + margin-left: 85.1063829787234%; + *margin-left: 84.99999999999999%; +} + +.row-fluid .offset9 { + margin-left: 78.72340425531914%; + *margin-left: 78.61702127659572%; +} + +.row-fluid .offset9:first-child { + margin-left: 76.59574468085106%; + *margin-left: 76.48936170212764%; +} + +.row-fluid .offset8 { + margin-left: 70.2127659574468%; + *margin-left: 70.10638297872339%; +} + +.row-fluid .offset8:first-child { + margin-left: 68.08510638297872%; + *margin-left: 67.9787234042553%; +} + +.row-fluid .offset7 { + margin-left: 61.70212765957446%; + *margin-left: 61.59574468085106%; +} + +.row-fluid .offset7:first-child { + margin-left: 59.574468085106375%; + *margin-left: 59.46808510638297%; +} + +.row-fluid .offset6 { + margin-left: 53.191489361702125%; + *margin-left: 53.085106382978715%; +} + +.row-fluid .offset6:first-child { + margin-left: 51.063829787234035%; + *margin-left: 50.95744680851063%; +} + +.row-fluid .offset5 { + margin-left: 44.68085106382979%; + *margin-left: 44.57446808510638%; +} + +.row-fluid .offset5:first-child { + margin-left: 42.5531914893617%; + *margin-left: 42.4468085106383%; +} + +.row-fluid .offset4 { + margin-left: 36.170212765957444%; + *margin-left: 36.06382978723405%; +} + +.row-fluid .offset4:first-child { + margin-left: 34.04255319148936%; + *margin-left: 33.93617021276596%; +} + +.row-fluid .offset3 { + margin-left: 27.659574468085104%; + *margin-left: 27.5531914893617%; +} + +.row-fluid .offset3:first-child { + margin-left: 25.53191489361702%; + *margin-left: 25.425531914893618%; +} + +.row-fluid .offset2 { + margin-left: 19.148936170212764%; + *margin-left: 19.04255319148936%; +} + +.row-fluid .offset2:first-child { + margin-left: 17.02127659574468%; + *margin-left: 16.914893617021278%; +} + +.row-fluid .offset1 { + margin-left: 10.638297872340425%; + *margin-left: 10.53191489361702%; +} + +.row-fluid .offset1:first-child { + margin-left: 8.51063829787234%; + *margin-left: 8.404255319148938%; +} + +[class*="span"].hide, +.row-fluid [class*="span"].hide { + display: none; +} + +[class*="span"].pull-right, +.row-fluid [class*="span"].pull-right { + float: right; +} + +.container { + margin-right: auto; + margin-left: auto; + *zoom: 1; +} + +.container:before, +.container:after { + display: table; + line-height: 0; + content: ""; +} + +.container:after { + clear: both; +} + +.container-fluid { + padding-right: 20px; + padding-left: 20px; + *zoom: 1; +} + +.container-fluid:before, +.container-fluid:after { + display: table; + line-height: 0; + content: ""; +} + +.container-fluid:after { + clear: both; +} + +p { + margin: 0 0 10px; +} + +.lead { + margin-bottom: 20px; + font-size: 21px; + font-weight: 200; + line-height: 30px; +} + +small { + font-size: 85%; +} + +strong { + font-weight: bold; +} + +em { + font-style: italic; +} + +cite { + font-style: normal; +} + +.muted { + color: #999999; +} + +a.muted:hover, +a.muted:focus { + color: #808080; +} + +.text-warning { + color: #c09853; +} + +a.text-warning:hover, +a.text-warning:focus { + color: #a47e3c; +} + +.text-error { + color: #b94a48; +} + +a.text-error:hover, +a.text-error:focus { + color: #953b39; +} + +.text-info { + color: #3a87ad; +} + +a.text-info:hover, +a.text-info:focus { + color: #2d6987; +} + +.text-success { + color: #468847; +} + +a.text-success:hover, +a.text-success:focus { + color: #356635; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 10px 0; + font-family: inherit; + font-weight: bold; + line-height: 20px; + color: inherit; + text-rendering: optimizelegibility; +} + +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small { + font-weight: normal; + line-height: 1; + color: #999999; +} + +h1, +h2, +h3 { + line-height: 40px; +} + +h1 { + font-size: 38.5px; +} + +h2 { + font-size: 31.5px; +} + +h3 { + font-size: 24.5px; +} + +h4 { + font-size: 17.5px; +} + +h5 { + font-size: 14px; +} + +h6 { + font-size: 11.9px; +} + +h1 small { + font-size: 24.5px; +} + +h2 small { + font-size: 17.5px; +} + +h3 small { + font-size: 14px; +} + +h4 small { + font-size: 14px; +} + +.page-header { + padding-bottom: 9px; + margin: 20px 0 30px; + border-bottom: 1px solid #eeeeee; +} + +ul, +ol { + padding: 0; + margin: 0 0 10px 25px; +} + +ul ul, +ul ol, +ol ol, +ol ul { + margin-bottom: 0; +} + +li { + line-height: 20px; +} + +ul.unstyled, +ol.unstyled { + margin-left: 0; + list-style: none; +} + +ul.inline, +ol.inline { + margin-left: 0; + list-style: none; +} + +ul.inline > li, +ol.inline > li { + display: inline-block; + *display: inline; + padding-right: 5px; + padding-left: 5px; + *zoom: 1; +} + +dl { + margin-bottom: 20px; +} + +dt, +dd { + line-height: 20px; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 10px; +} + +.dl-horizontal { + *zoom: 1; +} + +.dl-horizontal:before, +.dl-horizontal:after { + display: table; + line-height: 0; + content: ""; +} + +.dl-horizontal:after { + clear: both; +} + +.dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dl-horizontal dd { + margin-left: 180px; +} + +hr { + margin: 20px 0; + border: 0; + border-top: 1px solid #eeeeee; + border-bottom: 1px solid #ffffff; +} + +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999999; +} + +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +blockquote { + padding: 0 0 0 15px; + margin: 0 0 20px; + border-left: 5px solid #eeeeee; +} + +blockquote p { + margin-bottom: 0; + font-size: 17.5px; + font-weight: 300; + line-height: 1.25; +} + +blockquote small { + display: block; + line-height: 20px; + color: #999999; +} + +blockquote small:before { + content: '\2014 \00A0'; +} + +blockquote.pull-right { + float: right; + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; +} + +blockquote.pull-right p, +blockquote.pull-right small { + text-align: right; +} + +blockquote.pull-right small:before { + content: ''; +} + +blockquote.pull-right small:after { + content: '\00A0 \2014'; +} + +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +address { + display: block; + margin-bottom: 20px; + font-style: normal; + line-height: 20px; +} + +code, +pre { + padding: 0 3px 2px; + font-family: Monaco, Menlo, Consolas, "Courier New", monospace; + font-size: 12px; + color: #333333; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +code { + padding: 2px 4px; + color: #d14; + white-space: nowrap; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 20px; + word-break: break-all; + word-wrap: break-word; + white-space: pre; + white-space: pre-wrap; + background-color: #f5f5f5; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.15); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +pre.prettyprint { + margin-bottom: 20px; +} + +pre code { + padding: 0; + color: inherit; + white-space: pre; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +form { + margin: 0 0 20px; +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: 40px; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} + +legend small { + font-size: 15px; + color: #999999; +} + +label, +input, +button, +select, +textarea { + font-size: 14px; + font-weight: normal; + line-height: 20px; +} + +input, +button, +select, +textarea { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +label { + display: block; + margin-bottom: 5px; +} + +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: 20px; + padding: 4px 6px; + margin-bottom: 10px; + font-size: 14px; + line-height: 20px; + color: #555555; + vertical-align: middle; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +input, +textarea, +.uneditable-input { + width: 206px; +} + +textarea { + height: auto; +} + +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: #ffffff; + border: 1px solid #cccccc; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; + -moz-transition: border linear 0.2s, box-shadow linear 0.2s; + -o-transition: border linear 0.2s, box-shadow linear 0.2s; + transition: border linear 0.2s, box-shadow linear 0.2s; +} + +textarea:focus, +input[type="text"]:focus, +input[type="password"]:focus, +input[type="datetime"]:focus, +input[type="datetime-local"]:focus, +input[type="date"]:focus, +input[type="month"]:focus, +input[type="time"]:focus, +input[type="week"]:focus, +input[type="number"]:focus, +input[type="email"]:focus, +input[type="url"]:focus, +input[type="search"]:focus, +input[type="tel"]:focus, +input[type="color"]:focus, +.uneditable-input:focus { + border-color: rgba(82, 168, 236, 0.8); + outline: 0; + outline: thin dotted \9; + /* IE6-9 */ + + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); +} + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + *margin-top: 0; + line-height: normal; +} + +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; +} + +select, +input[type="file"] { + height: 30px; + /* In IE7, the height of the select element cannot be changed by height, only font-size */ + + *margin-top: 4px; + /* For IE7, add top margin to align select with labels */ + + line-height: 30px; +} + +select { + width: 220px; + background-color: #ffffff; + border: 1px solid #cccccc; +} + +select[multiple], +select[size] { + height: auto; +} + +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.uneditable-input, +.uneditable-textarea { + color: #999999; + cursor: not-allowed; + background-color: #fcfcfc; + border-color: #cccccc; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); +} + +.uneditable-input { + overflow: hidden; + white-space: nowrap; +} + +.uneditable-textarea { + width: auto; + height: auto; +} + +input:-moz-placeholder, +textarea:-moz-placeholder { + color: #999999; +} + +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + color: #999999; +} + +input::-webkit-input-placeholder, +textarea::-webkit-input-placeholder { + color: #999999; +} + +.radio, +.checkbox { + min-height: 20px; + padding-left: 20px; +} + +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: -20px; +} + +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; +} + +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; +} + +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; +} + +.input-mini { + width: 60px; +} + +.input-small { + width: 90px; +} + +.input-medium { + width: 150px; +} + +.input-large { + width: 210px; +} + +.input-xlarge { + width: 270px; +} + +.input-xxlarge { + width: 530px; +} + +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; +} + +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; +} + +input, +textarea, +.uneditable-input { + margin-left: 0; +} + +.controls-row [class*="span"] + [class*="span"] { + margin-left: 20px; +} + +input.span12, +textarea.span12, +.uneditable-input.span12 { + width: 926px; +} + +input.span11, +textarea.span11, +.uneditable-input.span11 { + width: 846px; +} + +input.span10, +textarea.span10, +.uneditable-input.span10 { + width: 766px; +} + +input.span9, +textarea.span9, +.uneditable-input.span9 { + width: 686px; +} + +input.span8, +textarea.span8, +.uneditable-input.span8 { + width: 606px; +} + +input.span7, +textarea.span7, +.uneditable-input.span7 { + width: 526px; +} + +input.span6, +textarea.span6, +.uneditable-input.span6 { + width: 446px; +} + +input.span5, +textarea.span5, +.uneditable-input.span5 { + width: 366px; +} + +input.span4, +textarea.span4, +.uneditable-input.span4 { + width: 286px; +} + +input.span3, +textarea.span3, +.uneditable-input.span3 { + width: 206px; +} + +input.span2, +textarea.span2, +.uneditable-input.span2 { + width: 126px; +} + +input.span1, +textarea.span1, +.uneditable-input.span1 { + width: 46px; +} + +.controls-row { + *zoom: 1; +} + +.controls-row:before, +.controls-row:after { + display: table; + line-height: 0; + content: ""; +} + +.controls-row:after { + clear: both; +} + +.controls-row [class*="span"], +.row-fluid .controls-row [class*="span"] { + float: left; +} + +.controls-row .checkbox[class*="span"], +.controls-row .radio[class*="span"] { + padding-top: 5px; +} + +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: #eeeeee; +} + +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; +} + +.control-group.warning .control-label, +.control-group.warning .help-block, +.control-group.warning .help-inline { + color: #c09853; +} + +.control-group.warning .checkbox, +.control-group.warning .radio, +.control-group.warning input, +.control-group.warning select, +.control-group.warning textarea { + color: #c09853; +} + +.control-group.warning input, +.control-group.warning select, +.control-group.warning textarea { + border-color: #c09853; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.warning input:focus, +.control-group.warning select:focus, +.control-group.warning textarea:focus { + border-color: #a47e3c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; +} + +.control-group.warning .input-prepend .add-on, +.control-group.warning .input-append .add-on { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; +} + +.control-group.error .control-label, +.control-group.error .help-block, +.control-group.error .help-inline { + color: #b94a48; +} + +.control-group.error .checkbox, +.control-group.error .radio, +.control-group.error input, +.control-group.error select, +.control-group.error textarea { + color: #b94a48; +} + +.control-group.error input, +.control-group.error select, +.control-group.error textarea { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.error input:focus, +.control-group.error select:focus, +.control-group.error textarea:focus { + border-color: #953b39; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; +} + +.control-group.error .input-prepend .add-on, +.control-group.error .input-append .add-on { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; +} + +.control-group.success .control-label, +.control-group.success .help-block, +.control-group.success .help-inline { + color: #468847; +} + +.control-group.success .checkbox, +.control-group.success .radio, +.control-group.success input, +.control-group.success select, +.control-group.success textarea { + color: #468847; +} + +.control-group.success input, +.control-group.success select, +.control-group.success textarea { + border-color: #468847; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.success input:focus, +.control-group.success select:focus, +.control-group.success textarea:focus { + border-color: #356635; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; +} + +.control-group.success .input-prepend .add-on, +.control-group.success .input-append .add-on { + color: #468847; + background-color: #dff0d8; + border-color: #468847; +} + +.control-group.info .control-label, +.control-group.info .help-block, +.control-group.info .help-inline { + color: #3a87ad; +} + +.control-group.info .checkbox, +.control-group.info .radio, +.control-group.info input, +.control-group.info select, +.control-group.info textarea { + color: #3a87ad; +} + +.control-group.info input, +.control-group.info select, +.control-group.info textarea { + border-color: #3a87ad; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.control-group.info input:focus, +.control-group.info select:focus, +.control-group.info textarea:focus { + border-color: #2d6987; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; +} + +.control-group.info .input-prepend .add-on, +.control-group.info .input-append .add-on { + color: #3a87ad; + background-color: #d9edf7; + border-color: #3a87ad; +} + +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: #b94a48; + border-color: #ee5f5b; +} + +input:focus:invalid:focus, +textarea:focus:invalid:focus, +select:focus:invalid:focus { + border-color: #e9322d; + -webkit-box-shadow: 0 0 6px #f8b9b7; + -moz-box-shadow: 0 0 6px #f8b9b7; + box-shadow: 0 0 6px #f8b9b7; +} + +.form-actions { + padding: 19px 20px 20px; + margin-top: 20px; + margin-bottom: 20px; + background-color: #f5f5f5; + border-top: 1px solid #e5e5e5; + *zoom: 1; +} + +.form-actions:before, +.form-actions:after { + display: table; + line-height: 0; + content: ""; +} + +.form-actions:after { + clear: both; +} + +.help-block, +.help-inline { + color: #595959; +} + +.help-block { + display: block; + margin-bottom: 10px; +} + +.help-inline { + display: inline-block; + *display: inline; + padding-left: 5px; + vertical-align: middle; + *zoom: 1; +} + +.input-append, +.input-prepend { + display: inline-block; + margin-bottom: 10px; + font-size: 0; + white-space: nowrap; + vertical-align: middle; +} + +.input-append input, +.input-prepend input, +.input-append select, +.input-prepend select, +.input-append .uneditable-input, +.input-prepend .uneditable-input, +.input-append .dropdown-menu, +.input-prepend .dropdown-menu, +.input-append .popover, +.input-prepend .popover { + font-size: 14px; +} + +.input-append input, +.input-prepend input, +.input-append select, +.input-prepend select, +.input-append .uneditable-input, +.input-prepend .uneditable-input { + position: relative; + margin-bottom: 0; + *margin-left: 0; + vertical-align: top; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-append input:focus, +.input-prepend input:focus, +.input-append select:focus, +.input-prepend select:focus, +.input-append .uneditable-input:focus, +.input-prepend .uneditable-input:focus { + z-index: 2; +} + +.input-append .add-on, +.input-prepend .add-on { + display: inline-block; + width: auto; + height: 20px; + min-width: 16px; + padding: 4px 5px; + font-size: 14px; + font-weight: normal; + line-height: 20px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + background-color: #eeeeee; + border: 1px solid #ccc; +} + +.input-append .add-on, +.input-prepend .add-on, +.input-append .btn, +.input-prepend .btn, +.input-append .btn-group > .dropdown-toggle, +.input-prepend .btn-group > .dropdown-toggle { + vertical-align: top; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.input-append .active, +.input-prepend .active { + background-color: #a9dba9; + border-color: #46a546; +} + +.input-prepend .add-on, +.input-prepend .btn { + margin-right: -1px; +} + +.input-prepend .add-on:first-child, +.input-prepend .btn:first-child { + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.input-append input, +.input-append select, +.input-append .uneditable-input { + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.input-append input + .btn-group .btn:last-child, +.input-append select + .btn-group .btn:last-child, +.input-append .uneditable-input + .btn-group .btn:last-child { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-append .add-on, +.input-append .btn, +.input-append .btn-group { + margin-left: -1px; +} + +.input-append .add-on:last-child, +.input-append .btn:last-child, +.input-append .btn-group:last-child > .dropdown-toggle { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-prepend.input-append input, +.input-prepend.input-append select, +.input-prepend.input-append .uneditable-input { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.input-prepend.input-append input + .btn-group .btn, +.input-prepend.input-append select + .btn-group .btn, +.input-prepend.input-append .uneditable-input + .btn-group .btn { + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-prepend.input-append .add-on:first-child, +.input-prepend.input-append .btn:first-child { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.input-prepend.input-append .add-on:last-child, +.input-prepend.input-append .btn:last-child { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.input-prepend.input-append .btn-group:first-child { + margin-left: 0; +} + +input.search-query { + padding-right: 14px; + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; + /* IE7-8 doesn't have border-radius, so don't indent the padding */ + + margin-bottom: 0; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} + +/* Allow for input prepend/append in search forms */ + +.form-search .input-append .search-query, +.form-search .input-prepend .search-query { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.form-search .input-append .search-query { + -webkit-border-radius: 14px 0 0 14px; + -moz-border-radius: 14px 0 0 14px; + border-radius: 14px 0 0 14px; +} + +.form-search .input-append .btn { + -webkit-border-radius: 0 14px 14px 0; + -moz-border-radius: 0 14px 14px 0; + border-radius: 0 14px 14px 0; +} + +.form-search .input-prepend .search-query { + -webkit-border-radius: 0 14px 14px 0; + -moz-border-radius: 0 14px 14px 0; + border-radius: 0 14px 14px 0; +} + +.form-search .input-prepend .btn { + -webkit-border-radius: 14px 0 0 14px; + -moz-border-radius: 14px 0 0 14px; + border-radius: 14px 0 0 14px; +} + +.form-search input, +.form-inline input, +.form-horizontal input, +.form-search textarea, +.form-inline textarea, +.form-horizontal textarea, +.form-search select, +.form-inline select, +.form-horizontal select, +.form-search .help-inline, +.form-inline .help-inline, +.form-horizontal .help-inline, +.form-search .uneditable-input, +.form-inline .uneditable-input, +.form-horizontal .uneditable-input, +.form-search .input-prepend, +.form-inline .input-prepend, +.form-horizontal .input-prepend, +.form-search .input-append, +.form-inline .input-append, +.form-horizontal .input-append { + display: inline-block; + *display: inline; + margin-bottom: 0; + vertical-align: middle; + *zoom: 1; +} + +.form-search .hide, +.form-inline .hide, +.form-horizontal .hide { + display: none; +} + +.form-search label, +.form-inline label, +.form-search .btn-group, +.form-inline .btn-group { + display: inline-block; +} + +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; +} + +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; +} + +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; +} + +.control-group { + margin-bottom: 10px; +} + +legend + .control-group { + margin-top: 20px; + -webkit-margin-top-collapse: separate; +} + +.form-horizontal .control-group { + margin-bottom: 20px; + *zoom: 1; +} + +.form-horizontal .control-group:before, +.form-horizontal .control-group:after { + display: table; + line-height: 0; + content: ""; +} + +.form-horizontal .control-group:after { + clear: both; +} + +.form-horizontal .control-label { + float: left; + width: 160px; + padding-top: 5px; + text-align: right; +} + +.form-horizontal .controls { + *display: inline-block; + *padding-left: 20px; + margin-left: 180px; + *margin-left: 0; +} + +.form-horizontal .controls:first-child { + *padding-left: 180px; +} + +.form-horizontal .help-block { + margin-bottom: 0; +} + +.form-horizontal input + .help-block, +.form-horizontal select + .help-block, +.form-horizontal textarea + .help-block, +.form-horizontal .uneditable-input + .help-block, +.form-horizontal .input-prepend + .help-block, +.form-horizontal .input-append + .help-block { + margin-top: 10px; +} + +.form-horizontal .form-actions { + padding-left: 180px; +} + +table { + max-width: 100%; + background-color: transparent; + border-collapse: collapse; + border-spacing: 0; +} + +.table { + width: 100%; + margin-bottom: 20px; +} + +.table th, +.table td { + padding: 8px; + line-height: 20px; + text-align: left; + vertical-align: top; + border-top: 1px solid #dddddd; +} + +.table th { + font-weight: bold; +} + +.table thead th { + vertical-align: bottom; +} + +.table caption + thead tr:first-child th, +.table caption + thead tr:first-child td, +.table colgroup + thead tr:first-child th, +.table colgroup + thead tr:first-child td, +.table thead:first-child tr:first-child th, +.table thead:first-child tr:first-child td { + border-top: 0; +} + +.table tbody + tbody { + border-top: 2px solid #dddddd; +} + +.table .table { + background-color: #ffffff; +} + +.table-condensed th, +.table-condensed td { + padding: 4px 5px; +} + +.table-bordered { + border: 1px solid #dddddd; + border-collapse: separate; + *border-collapse: collapse; + border-left: 0; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.table-bordered th, +.table-bordered td { + border-left: 1px solid #dddddd; +} + +.table-bordered caption + thead tr:first-child th, +.table-bordered caption + tbody tr:first-child th, +.table-bordered caption + tbody tr:first-child td, +.table-bordered colgroup + thead tr:first-child th, +.table-bordered colgroup + tbody tr:first-child th, +.table-bordered colgroup + tbody tr:first-child td, +.table-bordered thead:first-child tr:first-child th, +.table-bordered tbody:first-child tr:first-child th, +.table-bordered tbody:first-child tr:first-child td { + border-top: 0; +} + +.table-bordered thead:first-child tr:first-child > th:first-child, +.table-bordered tbody:first-child tr:first-child > td:first-child, +.table-bordered tbody:first-child tr:first-child > th:first-child { + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; +} + +.table-bordered thead:first-child tr:first-child > th:last-child, +.table-bordered tbody:first-child tr:first-child > td:last-child, +.table-bordered tbody:first-child tr:first-child > th:last-child { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; +} + +.table-bordered thead:last-child tr:last-child > th:first-child, +.table-bordered tbody:last-child tr:last-child > td:first-child, +.table-bordered tbody:last-child tr:last-child > th:first-child, +.table-bordered tfoot:last-child tr:last-child > td:first-child, +.table-bordered tfoot:last-child tr:last-child > th:first-child { + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; +} + +.table-bordered thead:last-child tr:last-child > th:last-child, +.table-bordered tbody:last-child tr:last-child > td:last-child, +.table-bordered tbody:last-child tr:last-child > th:last-child, +.table-bordered tfoot:last-child tr:last-child > td:last-child, +.table-bordered tfoot:last-child tr:last-child > th:last-child { + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; +} + +.table-bordered tfoot + tbody:last-child tr:last-child td:first-child { + -webkit-border-bottom-left-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomleft: 0; +} + +.table-bordered tfoot + tbody:last-child tr:last-child td:last-child { + -webkit-border-bottom-right-radius: 0; + border-bottom-right-radius: 0; + -moz-border-radius-bottomright: 0; +} + +.table-bordered caption + thead tr:first-child th:first-child, +.table-bordered caption + tbody tr:first-child td:first-child, +.table-bordered colgroup + thead tr:first-child th:first-child, +.table-bordered colgroup + tbody tr:first-child td:first-child { + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; +} + +.table-bordered caption + thead tr:first-child th:last-child, +.table-bordered caption + tbody tr:first-child td:last-child, +.table-bordered colgroup + thead tr:first-child th:last-child, +.table-bordered colgroup + tbody tr:first-child td:last-child { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; +} + +.table-striped tbody > tr:nth-child(odd) > td, +.table-striped tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} + +.table-hover tbody tr:hover > td, +.table-hover tbody tr:hover > th { + background-color: #f5f5f5; +} + +table td[class*="span"], +table th[class*="span"], +.row-fluid table td[class*="span"], +.row-fluid table th[class*="span"] { + display: table-cell; + float: none; + margin-left: 0; +} + +.table td.span1, +.table th.span1 { + float: none; + width: 44px; + margin-left: 0; +} + +.table td.span2, +.table th.span2 { + float: none; + width: 124px; + margin-left: 0; +} + +.table td.span3, +.table th.span3 { + float: none; + width: 204px; + margin-left: 0; +} + +.table td.span4, +.table th.span4 { + float: none; + width: 284px; + margin-left: 0; +} + +.table td.span5, +.table th.span5 { + float: none; + width: 364px; + margin-left: 0; +} + +.table td.span6, +.table th.span6 { + float: none; + width: 444px; + margin-left: 0; +} + +.table td.span7, +.table th.span7 { + float: none; + width: 524px; + margin-left: 0; +} + +.table td.span8, +.table th.span8 { + float: none; + width: 604px; + margin-left: 0; +} + +.table td.span9, +.table th.span9 { + float: none; + width: 684px; + margin-left: 0; +} + +.table td.span10, +.table th.span10 { + float: none; + width: 764px; + margin-left: 0; +} + +.table td.span11, +.table th.span11 { + float: none; + width: 844px; + margin-left: 0; +} + +.table td.span12, +.table th.span12 { + float: none; + width: 924px; + margin-left: 0; +} + +.table tbody tr.success > td { + background-color: #dff0d8; +} + +.table tbody tr.error > td { + background-color: #f2dede; +} + +.table tbody tr.warning > td { + background-color: #fcf8e3; +} + +.table tbody tr.info > td { + background-color: #d9edf7; +} + +.table-hover tbody tr.success:hover > td { + background-color: #d0e9c6; +} + +.table-hover tbody tr.error:hover > td { + background-color: #ebcccc; +} + +.table-hover tbody tr.warning:hover > td { + background-color: #faf2cc; +} + +.table-hover tbody tr.info:hover > td { + background-color: #c4e3f3; +} + +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + margin-top: 1px; + *margin-right: .3em; + line-height: 14px; + vertical-align: text-top; + background-image: url("../img/glyphicons-halflings.png"); + background-position: 14px 14px; + background-repeat: no-repeat; +} + +/* White icons with optional class, or on hover/focus/active states of certain elements */ + +.icon-white, +.nav-pills > .active > a > [class^="icon-"], +.nav-pills > .active > a > [class*=" icon-"], +.nav-list > .active > a > [class^="icon-"], +.nav-list > .active > a > [class*=" icon-"], +.navbar-inverse .nav > .active > a > [class^="icon-"], +.navbar-inverse .nav > .active > a > [class*=" icon-"], +.dropdown-menu > li > a:hover > [class^="icon-"], +.dropdown-menu > li > a:focus > [class^="icon-"], +.dropdown-menu > li > a:hover > [class*=" icon-"], +.dropdown-menu > li > a:focus > [class*=" icon-"], +.dropdown-menu > .active > a > [class^="icon-"], +.dropdown-menu > .active > a > [class*=" icon-"], +.dropdown-submenu:hover > a > [class^="icon-"], +.dropdown-submenu:focus > a > [class^="icon-"], +.dropdown-submenu:hover > a > [class*=" icon-"], +.dropdown-submenu:focus > a > [class*=" icon-"] { + background-image: url("../img/glyphicons-halflings-white.png"); +} + +.icon-glass { + background-position: 0 0; +} + +.icon-music { + background-position: -24px 0; +} + +.icon-search { + background-position: -48px 0; +} + +.icon-envelope { + background-position: -72px 0; +} + +.icon-heart { + background-position: -96px 0; +} + +.icon-star { + background-position: -120px 0; +} + +.icon-star-empty { + background-position: -144px 0; +} + +.icon-user { + background-position: -168px 0; +} + +.icon-film { + background-position: -192px 0; +} + +.icon-th-large { + background-position: -216px 0; +} + +.icon-th { + background-position: -240px 0; +} + +.icon-th-list { + background-position: -264px 0; +} + +.icon-ok { + background-position: -288px 0; +} + +.icon-remove { + background-position: -312px 0; +} + +.icon-zoom-in { + background-position: -336px 0; +} + +.icon-zoom-out { + background-position: -360px 0; +} + +.icon-off { + background-position: -384px 0; +} + +.icon-signal { + background-position: -408px 0; +} + +.icon-cog { + background-position: -432px 0; +} + +.icon-trash { + background-position: -456px 0; +} + +.icon-home { + background-position: 0 -24px; +} + +.icon-file { + background-position: -24px -24px; +} + +.icon-time { + background-position: -48px -24px; +} + +.icon-road { + background-position: -72px -24px; +} + +.icon-download-alt { + background-position: -96px -24px; +} + +.icon-download { + background-position: -120px -24px; +} + +.icon-upload { + background-position: -144px -24px; +} + +.icon-inbox { + background-position: -168px -24px; +} + +.icon-play-circle { + background-position: -192px -24px; +} + +.icon-repeat { + background-position: -216px -24px; +} + +.icon-refresh { + background-position: -240px -24px; +} + +.icon-list-alt { + background-position: -264px -24px; +} + +.icon-lock { + background-position: -287px -24px; +} + +.icon-flag { + background-position: -312px -24px; +} + +.icon-headphones { + background-position: -336px -24px; +} + +.icon-volume-off { + background-position: -360px -24px; +} + +.icon-volume-down { + background-position: -384px -24px; +} + +.icon-volume-up { + background-position: -408px -24px; +} + +.icon-qrcode { + background-position: -432px -24px; +} + +.icon-barcode { + background-position: -456px -24px; +} + +.icon-tag { + background-position: 0 -48px; +} + +.icon-tags { + background-position: -25px -48px; +} + +.icon-book { + background-position: -48px -48px; +} + +.icon-bookmark { + background-position: -72px -48px; +} + +.icon-print { + background-position: -96px -48px; +} + +.icon-camera { + background-position: -120px -48px; +} + +.icon-font { + background-position: -144px -48px; +} + +.icon-bold { + background-position: -167px -48px; +} + +.icon-italic { + background-position: -192px -48px; +} + +.icon-text-height { + background-position: -216px -48px; +} + +.icon-text-width { + background-position: -240px -48px; +} + +.icon-align-left { + background-position: -264px -48px; +} + +.icon-align-center { + background-position: -288px -48px; +} + +.icon-align-right { + background-position: -312px -48px; +} + +.icon-align-justify { + background-position: -336px -48px; +} + +.icon-list { + background-position: -360px -48px; +} + +.icon-indent-left { + background-position: -384px -48px; +} + +.icon-indent-right { + background-position: -408px -48px; +} + +.icon-facetime-video { + background-position: -432px -48px; +} + +.icon-picture { + background-position: -456px -48px; +} + +.icon-pencil { + background-position: 0 -72px; +} + +.icon-map-marker { + background-position: -24px -72px; +} + +.icon-adjust { + background-position: -48px -72px; +} + +.icon-tint { + background-position: -72px -72px; +} + +.icon-edit { + background-position: -96px -72px; +} + +.icon-share { + background-position: -120px -72px; +} + +.icon-check { + background-position: -144px -72px; +} + +.icon-move { + background-position: -168px -72px; +} + +.icon-step-backward { + background-position: -192px -72px; +} + +.icon-fast-backward { + background-position: -216px -72px; +} + +.icon-backward { + background-position: -240px -72px; +} + +.icon-play { + background-position: -264px -72px; +} + +.icon-pause { + background-position: -288px -72px; +} + +.icon-stop { + background-position: -312px -72px; +} + +.icon-forward { + background-position: -336px -72px; +} + +.icon-fast-forward { + background-position: -360px -72px; +} + +.icon-step-forward { + background-position: -384px -72px; +} + +.icon-eject { + background-position: -408px -72px; +} + +.icon-chevron-left { + background-position: -432px -72px; +} + +.icon-chevron-right { + background-position: -456px -72px; +} + +.icon-plus-sign { + background-position: 0 -96px; +} + +.icon-minus-sign { + background-position: -24px -96px; +} + +.icon-remove-sign { + background-position: -48px -96px; +} + +.icon-ok-sign { + background-position: -72px -96px; +} + +.icon-question-sign { + background-position: -96px -96px; +} + +.icon-info-sign { + background-position: -120px -96px; +} + +.icon-screenshot { + background-position: -144px -96px; +} + +.icon-remove-circle { + background-position: -168px -96px; +} + +.icon-ok-circle { + background-position: -192px -96px; +} + +.icon-ban-circle { + background-position: -216px -96px; +} + +.icon-arrow-left { + background-position: -240px -96px; +} + +.icon-arrow-right { + background-position: -264px -96px; +} + +.icon-arrow-up { + background-position: -289px -96px; +} + +.icon-arrow-down { + background-position: -312px -96px; +} + +.icon-share-alt { + background-position: -336px -96px; +} + +.icon-resize-full { + background-position: -360px -96px; +} + +.icon-resize-small { + background-position: -384px -96px; +} + +.icon-plus { + background-position: -408px -96px; +} + +.icon-minus { + background-position: -433px -96px; +} + +.icon-asterisk { + background-position: -456px -96px; +} + +.icon-exclamation-sign { + background-position: 0 -120px; +} + +.icon-gift { + background-position: -24px -120px; +} + +.icon-leaf { + background-position: -48px -120px; +} + +.icon-fire { + background-position: -72px -120px; +} + +.icon-eye-open { + background-position: -96px -120px; +} + +.icon-eye-close { + background-position: -120px -120px; +} + +.icon-warning-sign { + background-position: -144px -120px; +} + +.icon-plane { + background-position: -168px -120px; +} + +.icon-calendar { + background-position: -192px -120px; +} + +.icon-random { + width: 16px; + background-position: -216px -120px; +} + +.icon-comment { + background-position: -240px -120px; +} + +.icon-magnet { + background-position: -264px -120px; +} + +.icon-chevron-up { + background-position: -288px -120px; +} + +.icon-chevron-down { + background-position: -313px -119px; +} + +.icon-retweet { + background-position: -336px -120px; +} + +.icon-shopping-cart { + background-position: -360px -120px; +} + +.icon-folder-close { + width: 16px; + background-position: -384px -120px; +} + +.icon-folder-open { + width: 16px; + background-position: -408px -120px; +} + +.icon-resize-vertical { + background-position: -432px -119px; +} + +.icon-resize-horizontal { + background-position: -456px -118px; +} + +.icon-hdd { + background-position: 0 -144px; +} + +.icon-bullhorn { + background-position: -24px -144px; +} + +.icon-bell { + background-position: -48px -144px; +} + +.icon-certificate { + background-position: -72px -144px; +} + +.icon-thumbs-up { + background-position: -96px -144px; +} + +.icon-thumbs-down { + background-position: -120px -144px; +} + +.icon-hand-right { + background-position: -144px -144px; +} + +.icon-hand-left { + background-position: -168px -144px; +} + +.icon-hand-up { + background-position: -192px -144px; +} + +.icon-hand-down { + background-position: -216px -144px; +} + +.icon-circle-arrow-right { + background-position: -240px -144px; +} + +.icon-circle-arrow-left { + background-position: -264px -144px; +} + +.icon-circle-arrow-up { + background-position: -288px -144px; +} + +.icon-circle-arrow-down { + background-position: -312px -144px; +} + +.icon-globe { + background-position: -336px -144px; +} + +.icon-wrench { + background-position: -360px -144px; +} + +.icon-tasks { + background-position: -384px -144px; +} + +.icon-filter { + background-position: -408px -144px; +} + +.icon-briefcase { + background-position: -432px -144px; +} + +.icon-fullscreen { + background-position: -456px -144px; +} + +.dropup, +.dropdown { + position: relative; +} + +.dropdown-toggle { + *margin-bottom: -3px; +} + +.dropdown-toggle:active, +.open .dropdown-toggle { + outline: 0; +} + +.caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid #000000; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; +} + +.dropdown .caret { + margin-top: 8px; + margin-left: 2px; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + *border-right-width: 2px; + *border-bottom-width: 2px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.dropdown-menu .divider { + *width: 100%; + height: 1px; + margin: 9px 1px; + *margin: -5px 0 5px; + overflow: hidden; + background-color: #e5e5e5; + border-bottom: 1px solid #ffffff; +} + +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 20px; + color: #333333; + white-space: nowrap; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-submenu:hover > a, +.dropdown-submenu:focus > a { + color: #ffffff; + text-decoration: none; + background-color: #0081c2; + background-image: -moz-linear-gradient(top, #0088cc, #0077b3); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); + background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); + background-image: -o-linear-gradient(top, #0088cc, #0077b3); + background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); +} + +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #0081c2; + background-image: -moz-linear-gradient(top, #0088cc, #0077b3); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); + background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); + background-image: -o-linear-gradient(top, #0088cc, #0077b3); + background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-repeat: repeat-x; + outline: 0; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); +} + +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999999; +} + +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: default; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.open { + *z-index: 1000; +} + +.open > .dropdown-menu { + display: block; +} + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} + +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px solid #000000; + content: ""; +} + +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} + +.dropdown-submenu { + position: relative; +} + +.dropdown-submenu > .dropdown-menu { + top: 0; + left: 100%; + margin-top: -6px; + margin-left: -1px; + -webkit-border-radius: 0 6px 6px 6px; + -moz-border-radius: 0 6px 6px 6px; + border-radius: 0 6px 6px 6px; +} + +.dropdown-submenu:hover > .dropdown-menu { + display: block; +} + +.dropup .dropdown-submenu > .dropdown-menu { + top: auto; + bottom: 0; + margin-top: 0; + margin-bottom: -2px; + -webkit-border-radius: 5px 5px 5px 0; + -moz-border-radius: 5px 5px 5px 0; + border-radius: 5px 5px 5px 0; +} + +.dropdown-submenu > a:after { + display: block; + float: right; + width: 0; + height: 0; + margin-top: 5px; + margin-right: -10px; + border-color: transparent; + border-left-color: #cccccc; + border-style: solid; + border-width: 5px 0 5px 5px; + content: " "; +} + +.dropdown-submenu:hover > a:after { + border-left-color: #ffffff; +} + +.dropdown-submenu.pull-left { + float: none; +} + +.dropdown-submenu.pull-left > .dropdown-menu { + left: -100%; + margin-left: 10px; + -webkit-border-radius: 6px 0 6px 6px; + -moz-border-radius: 6px 0 6px 6px; + border-radius: 6px 0 6px 6px; +} + +.dropdown .dropdown-menu .nav-header { + padding-right: 20px; + padding-left: 20px; +} + +.typeahead { + z-index: 1051; + margin-top: 2px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} + +.well-large { + padding: 24px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.well-small { + padding: 9px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -moz-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.in { + opacity: 1; +} + +.collapse { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + -moz-transition: height 0.35s ease; + -o-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +.collapse.in { + height: auto; +} + +.close { + float: right; + font-size: 20px; + font-weight: bold; + line-height: 20px; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} + +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.4; + filter: alpha(opacity=40); +} + +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.btn { + display: inline-block; + *display: inline; + padding: 4px 12px; + margin-bottom: 0; + *margin-left: .3em; + font-size: 14px; + line-height: 20px; + color: #333333; + text-align: center; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + vertical-align: middle; + cursor: pointer; + background-color: #f5f5f5; + *background-color: #e6e6e6; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); + background-repeat: repeat-x; + border: 1px solid #cccccc; + *border: 0; + border-color: #e6e6e6 #e6e6e6 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + border-bottom-color: #b3b3b3; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + *zoom: 1; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn:hover, +.btn:focus, +.btn:active, +.btn.active, +.btn.disabled, +.btn[disabled] { + color: #333333; + background-color: #e6e6e6; + *background-color: #d9d9d9; +} + +.btn:active, +.btn.active { + background-color: #cccccc \9; +} + +.btn:first-child { + *margin-left: 0; +} + +.btn:hover, +.btn:focus { + color: #333333; + text-decoration: none; + background-position: 0 -15px; + -webkit-transition: background-position 0.1s linear; + -moz-transition: background-position 0.1s linear; + -o-transition: background-position 0.1s linear; + transition: background-position 0.1s linear; +} + +.btn:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.btn.active, +.btn:active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn.disabled, +.btn[disabled] { + cursor: default; + background-image: none; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +.btn-large { + padding: 11px 19px; + font-size: 17.5px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.btn-large [class^="icon-"], +.btn-large [class*=" icon-"] { + margin-top: 4px; +} + +.btn-small { + padding: 2px 10px; + font-size: 11.9px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.btn-small [class^="icon-"], +.btn-small [class*=" icon-"] { + margin-top: 0; +} + +.btn-mini [class^="icon-"], +.btn-mini [class*=" icon-"] { + margin-top: -1px; +} + +.btn-mini { + padding: 0 6px; + font-size: 10.5px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.btn-block + .btn-block { + margin-top: 5px; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255, 255, 255, 0.75); +} + +.btn-primary { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #006dcc; + *background-color: #0044cc; + background-image: -moz-linear-gradient(top, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); + background-image: -o-linear-gradient(top, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.btn-primary.disabled, +.btn-primary[disabled] { + color: #ffffff; + background-color: #0044cc; + *background-color: #003bb3; +} + +.btn-primary:active, +.btn-primary.active { + background-color: #003399 \9; +} + +.btn-warning { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #faa732; + *background-color: #f89406; + background-image: -moz-linear-gradient(top, #fbb450, #f89406); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); + background-image: -webkit-linear-gradient(top, #fbb450, #f89406); + background-image: -o-linear-gradient(top, #fbb450, #f89406); + background-image: linear-gradient(to bottom, #fbb450, #f89406); + background-repeat: repeat-x; + border-color: #f89406 #f89406 #ad6704; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.btn-warning.disabled, +.btn-warning[disabled] { + color: #ffffff; + background-color: #f89406; + *background-color: #df8505; +} + +.btn-warning:active, +.btn-warning.active { + background-color: #c67605 \9; +} + +.btn-danger { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #da4f49; + *background-color: #bd362f; + background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); + background-image: linear-gradient(to bottom, #ee5f5b, #bd362f); + background-repeat: repeat-x; + border-color: #bd362f #bd362f #802420; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.btn-danger.disabled, +.btn-danger[disabled] { + color: #ffffff; + background-color: #bd362f; + *background-color: #a9302a; +} + +.btn-danger:active, +.btn-danger.active { + background-color: #942a25 \9; +} + +.btn-success { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #5bb75b; + *background-color: #51a351; + background-image: -moz-linear-gradient(top, #62c462, #51a351); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); + background-image: -webkit-linear-gradient(top, #62c462, #51a351); + background-image: -o-linear-gradient(top, #62c462, #51a351); + background-image: linear-gradient(to bottom, #62c462, #51a351); + background-repeat: repeat-x; + border-color: #51a351 #51a351 #387038; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.btn-success.disabled, +.btn-success[disabled] { + color: #ffffff; + background-color: #51a351; + *background-color: #499249; +} + +.btn-success:active, +.btn-success.active { + background-color: #408140 \9; +} + +.btn-info { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #49afcd; + *background-color: #2f96b4; + background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); + background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); + background-image: linear-gradient(to bottom, #5bc0de, #2f96b4); + background-repeat: repeat-x; + border-color: #2f96b4 #2f96b4 #1f6377; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.btn-info.disabled, +.btn-info[disabled] { + color: #ffffff; + background-color: #2f96b4; + *background-color: #2a85a0; +} + +.btn-info:active, +.btn-info.active { + background-color: #24748c \9; +} + +.btn-inverse { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #363636; + *background-color: #222222; + background-image: -moz-linear-gradient(top, #444444, #222222); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222)); + background-image: -webkit-linear-gradient(top, #444444, #222222); + background-image: -o-linear-gradient(top, #444444, #222222); + background-image: linear-gradient(to bottom, #444444, #222222); + background-repeat: repeat-x; + border-color: #222222 #222222 #000000; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.btn-inverse:hover, +.btn-inverse:focus, +.btn-inverse:active, +.btn-inverse.active, +.btn-inverse.disabled, +.btn-inverse[disabled] { + color: #ffffff; + background-color: #222222; + *background-color: #151515; +} + +.btn-inverse:active, +.btn-inverse.active { + background-color: #080808 \9; +} + +button.btn, +input[type="submit"].btn { + *padding-top: 3px; + *padding-bottom: 3px; +} + +button.btn::-moz-focus-inner, +input[type="submit"].btn::-moz-focus-inner { + padding: 0; + border: 0; +} + +button.btn.btn-large, +input[type="submit"].btn.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; +} + +button.btn.btn-small, +input[type="submit"].btn.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; +} + +button.btn.btn-mini, +input[type="submit"].btn.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; +} + +.btn-link, +.btn-link:active, +.btn-link[disabled] { + background-color: transparent; + background-image: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +.btn-link { + color: #0088cc; + cursor: pointer; + border-color: transparent; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.btn-link:hover, +.btn-link:focus { + color: #005580; + text-decoration: underline; + background-color: transparent; +} + +.btn-link[disabled]:hover, +.btn-link[disabled]:focus { + color: #333333; + text-decoration: none; +} + +.btn-group { + position: relative; + display: inline-block; + *display: inline; + *margin-left: .3em; + font-size: 0; + white-space: nowrap; + vertical-align: middle; + *zoom: 1; +} + +.btn-group:first-child { + *margin-left: 0; +} + +.btn-group + .btn-group { + margin-left: 5px; +} + +.btn-toolbar { + margin-top: 10px; + margin-bottom: 10px; + font-size: 0; +} + +.btn-toolbar > .btn + .btn, +.btn-toolbar > .btn-group + .btn, +.btn-toolbar > .btn + .btn-group { + margin-left: 5px; +} + +.btn-group > .btn { + position: relative; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.btn-group > .btn + .btn { + margin-left: -1px; +} + +.btn-group > .btn, +.btn-group > .dropdown-menu, +.btn-group > .popover { + font-size: 14px; +} + +.btn-group > .btn-mini { + font-size: 10.5px; +} + +.btn-group > .btn-small { + font-size: 11.9px; +} + +.btn-group > .btn-large { + font-size: 17.5px; +} + +.btn-group > .btn:first-child { + margin-left: 0; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-topleft: 4px; +} + +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-bottomright: 4px; +} + +.btn-group > .btn.large:first-child { + margin-left: 0; + -webkit-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -webkit-border-top-left-radius: 6px; + border-top-left-radius: 6px; + -moz-border-radius-bottomleft: 6px; + -moz-border-radius-topleft: 6px; +} + +.btn-group > .btn.large:last-child, +.btn-group > .large.dropdown-toggle { + -webkit-border-top-right-radius: 6px; + border-top-right-radius: 6px; + -webkit-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + -moz-border-radius-topright: 6px; + -moz-border-radius-bottomright: 6px; +} + +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active { + z-index: 2; +} + +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + +.btn-group > .btn + .dropdown-toggle { + *padding-top: 5px; + padding-right: 8px; + *padding-bottom: 5px; + padding-left: 8px; + -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn-group > .btn-mini + .dropdown-toggle { + *padding-top: 2px; + padding-right: 5px; + *padding-bottom: 2px; + padding-left: 5px; +} + +.btn-group > .btn-small + .dropdown-toggle { + *padding-top: 5px; + *padding-bottom: 4px; +} + +.btn-group > .btn-large + .dropdown-toggle { + *padding-top: 7px; + padding-right: 12px; + *padding-bottom: 7px; + padding-left: 12px; +} + +.btn-group.open .dropdown-toggle { + background-image: none; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.btn-group.open .btn.dropdown-toggle { + background-color: #e6e6e6; +} + +.btn-group.open .btn-primary.dropdown-toggle { + background-color: #0044cc; +} + +.btn-group.open .btn-warning.dropdown-toggle { + background-color: #f89406; +} + +.btn-group.open .btn-danger.dropdown-toggle { + background-color: #bd362f; +} + +.btn-group.open .btn-success.dropdown-toggle { + background-color: #51a351; +} + +.btn-group.open .btn-info.dropdown-toggle { + background-color: #2f96b4; +} + +.btn-group.open .btn-inverse.dropdown-toggle { + background-color: #222222; +} + +.btn .caret { + margin-top: 8px; + margin-left: 0; +} + +.btn-large .caret { + margin-top: 6px; +} + +.btn-large .caret { + border-top-width: 5px; + border-right-width: 5px; + border-left-width: 5px; +} + +.btn-mini .caret, +.btn-small .caret { + margin-top: 8px; +} + +.dropup .btn-large .caret { + border-bottom-width: 5px; +} + +.btn-primary .caret, +.btn-warning .caret, +.btn-danger .caret, +.btn-info .caret, +.btn-success .caret, +.btn-inverse .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.btn-group-vertical { + display: inline-block; + *display: inline; + /* IE7 inline-block hack */ + + *zoom: 1; +} + +.btn-group-vertical > .btn { + display: block; + float: none; + max-width: 100%; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.btn-group-vertical > .btn + .btn { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:first-child { + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +.btn-group-vertical > .btn:last-child { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +.btn-group-vertical > .btn-large:first-child { + -webkit-border-radius: 6px 6px 0 0; + -moz-border-radius: 6px 6px 0 0; + border-radius: 6px 6px 0 0; +} + +.btn-group-vertical > .btn-large:last-child { + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; +} + +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: 20px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + background-color: #fcf8e3; + border: 1px solid #fbeed5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.alert, +.alert h4 { + color: #c09853; +} + +.alert h4 { + margin: 0; +} + +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: 20px; +} + +.alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.alert-success h4 { + color: #468847; +} + +.alert-danger, +.alert-error { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.alert-danger h4, +.alert-error h4 { + color: #b94a48; +} + +.alert-info { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.alert-info h4 { + color: #3a87ad; +} + +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} + +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; +} + +.alert-block p + p { + margin-top: 5px; +} + +.nav { + margin-bottom: 20px; + margin-left: 0; + list-style: none; +} + +.nav > li > a { + display: block; +} + +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.nav > li > a > img { + max-width: none; +} + +.nav > .pull-right { + float: right; +} + +.nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: 20px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; +} + +.nav li + .nav-header { + margin-top: 9px; +} + +.nav-list { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 0; +} + +.nav-list > li > a, +.nav-list .nav-header { + margin-right: -15px; + margin-left: -15px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); +} + +.nav-list > li > a { + padding: 3px 15px; +} + +.nav-list > .active > a, +.nav-list > .active > a:hover, +.nav-list > .active > a:focus { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + background-color: #0088cc; +} + +.nav-list [class^="icon-"], +.nav-list [class*=" icon-"] { + margin-right: 2px; +} + +.nav-list .divider { + *width: 100%; + height: 1px; + margin: 9px 1px; + *margin: -5px 0 5px; + overflow: hidden; + background-color: #e5e5e5; + border-bottom: 1px solid #ffffff; +} + +.nav-tabs, +.nav-pills { + *zoom: 1; +} + +.nav-tabs:before, +.nav-pills:before, +.nav-tabs:after, +.nav-pills:after { + display: table; + line-height: 0; + content: ""; +} + +.nav-tabs:after, +.nav-pills:after { + clear: both; +} + +.nav-tabs > li, +.nav-pills > li { + float: left; +} + +.nav-tabs > li > a, +.nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; +} + +.nav-tabs { + border-bottom: 1px solid #ddd; +} + +.nav-tabs > li { + margin-bottom: -1px; +} + +.nav-tabs > li > a { + padding-top: 8px; + padding-bottom: 8px; + line-height: 20px; + border: 1px solid transparent; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} + +.nav-tabs > li > a:hover, +.nav-tabs > li > a:focus { + border-color: #eeeeee #eeeeee #dddddd; +} + +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover, +.nav-tabs > .active > a:focus { + color: #555555; + cursor: default; + background-color: #ffffff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} + +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.nav-pills > .active > a, +.nav-pills > .active > a:hover, +.nav-pills > .active > a:focus { + color: #ffffff; + background-color: #0088cc; +} + +.nav-stacked > li { + float: none; +} + +.nav-stacked > li > a { + margin-right: 0; +} + +.nav-tabs.nav-stacked { + border-bottom: 0; +} + +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.nav-tabs.nav-stacked > li:first-child > a { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-topleft: 4px; +} + +.nav-tabs.nav-stacked > li:last-child > a { + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -moz-border-radius-bottomright: 4px; + -moz-border-radius-bottomleft: 4px; +} + +.nav-tabs.nav-stacked > li > a:hover, +.nav-tabs.nav-stacked > li > a:focus { + z-index: 2; + border-color: #ddd; +} + +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} + +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; +} + +.nav-tabs .dropdown-menu { + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; +} + +.nav-pills .dropdown-menu { + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.nav .dropdown-toggle .caret { + margin-top: 6px; + border-top-color: #0088cc; + border-bottom-color: #0088cc; +} + +.nav .dropdown-toggle:hover .caret, +.nav .dropdown-toggle:focus .caret { + border-top-color: #005580; + border-bottom-color: #005580; +} + +/* move down carets for tabs */ + +.nav-tabs .dropdown-toggle .caret { + margin-top: 8px; +} + +.nav .active .dropdown-toggle .caret { + border-top-color: #fff; + border-bottom-color: #fff; +} + +.nav-tabs .active .dropdown-toggle .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} + +.nav > .dropdown.active > a:hover, +.nav > .dropdown.active > a:focus { + cursor: pointer; +} + +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover, +.nav > li.dropdown.open.active > a:focus { + color: #ffffff; + background-color: #999999; + border-color: #999999; +} + +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret, +.nav li.dropdown.open a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; + opacity: 1; + filter: alpha(opacity=100); +} + +.tabs-stacked .open > a:hover, +.tabs-stacked .open > a:focus { + border-color: #999999; +} + +.tabbable { + *zoom: 1; +} + +.tabbable:before, +.tabbable:after { + display: table; + line-height: 0; + content: ""; +} + +.tabbable:after { + clear: both; +} + +.tab-content { + overflow: auto; +} + +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} + +.tab-content > .active, +.pill-content > .active { + display: block; +} + +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; +} + +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} + +.tabs-below > .nav-tabs > li > a { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +.tabs-below > .nav-tabs > li > a:hover, +.tabs-below > .nav-tabs > li > a:focus { + border-top-color: #ddd; + border-bottom-color: transparent; +} + +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover, +.tabs-below > .nav-tabs > .active > a:focus { + border-color: transparent #ddd #ddd #ddd; +} + +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} + +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} + +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.tabs-left > .nav-tabs > li > a:hover, +.tabs-left > .nav-tabs > li > a:focus { + border-color: #eeeeee #dddddd #eeeeee #eeeeee; +} + +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover, +.tabs-left > .nav-tabs .active > a:focus { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: #ffffff; +} + +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} + +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.tabs-right > .nav-tabs > li > a:hover, +.tabs-right > .nav-tabs > li > a:focus { + border-color: #eeeeee #eeeeee #eeeeee #dddddd; +} + +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover, +.tabs-right > .nav-tabs .active > a:focus { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: #ffffff; +} + +.nav > .disabled > a { + color: #999999; +} + +.nav > .disabled > a:hover, +.nav > .disabled > a:focus { + text-decoration: none; + cursor: default; + background-color: transparent; +} + +.navbar { + *position: relative; + *z-index: 2; + margin-bottom: 20px; + overflow: visible; +} + +.navbar-inner { + min-height: 40px; + padding-right: 20px; + padding-left: 20px; + background-color: #fafafa; + background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2)); + background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2); + background-image: -o-linear-gradient(top, #ffffff, #f2f2f2); + background-image: linear-gradient(to bottom, #ffffff, #f2f2f2); + background-repeat: repeat-x; + border: 1px solid #d4d4d4; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0); + *zoom: 1; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); +} + +.navbar-inner:before, +.navbar-inner:after { + display: table; + line-height: 0; + content: ""; +} + +.navbar-inner:after { + clear: both; +} + +.navbar .container { + width: auto; +} + +.nav-collapse.collapse { + height: auto; + overflow: visible; +} + +.navbar .brand { + display: block; + float: left; + padding: 10px 20px 10px; + margin-left: -20px; + font-size: 20px; + font-weight: 200; + color: #777777; + text-shadow: 0 1px 0 #ffffff; +} + +.navbar .brand:hover, +.navbar .brand:focus { + text-decoration: none; +} + +.navbar-text { + margin-bottom: 0; + line-height: 40px; + color: #777777; +} + +.navbar-link { + color: #777777; +} + +.navbar-link:hover, +.navbar-link:focus { + color: #333333; +} + +.navbar .divider-vertical { + height: 40px; + margin: 0 9px; + border-right: 1px solid #ffffff; + border-left: 1px solid #f2f2f2; +} + +.navbar .btn, +.navbar .btn-group { + margin-top: 5px; +} + +.navbar .btn-group .btn, +.navbar .input-prepend .btn, +.navbar .input-append .btn, +.navbar .input-prepend .btn-group, +.navbar .input-append .btn-group { + margin-top: 0; +} + +.navbar-form { + margin-bottom: 0; + *zoom: 1; +} + +.navbar-form:before, +.navbar-form:after { + display: table; + line-height: 0; + content: ""; +} + +.navbar-form:after { + clear: both; +} + +.navbar-form input, +.navbar-form select, +.navbar-form .radio, +.navbar-form .checkbox { + margin-top: 5px; +} + +.navbar-form input, +.navbar-form select, +.navbar-form .btn { + display: inline-block; + margin-bottom: 0; +} + +.navbar-form input[type="image"], +.navbar-form input[type="checkbox"], +.navbar-form input[type="radio"] { + margin-top: 3px; +} + +.navbar-form .input-append, +.navbar-form .input-prepend { + margin-top: 5px; + white-space: nowrap; +} + +.navbar-form .input-append input, +.navbar-form .input-prepend input { + margin-top: 0; +} + +.navbar-search { + position: relative; + float: left; + margin-top: 5px; + margin-bottom: 0; +} + +.navbar-search .search-query { + padding: 4px 14px; + margin-bottom: 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 1; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} + +.navbar-static-top { + position: static; + margin-bottom: 0; +} + +.navbar-static-top .navbar-inner { + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; + margin-bottom: 0; +} + +.navbar-fixed-top .navbar-inner, +.navbar-static-top .navbar-inner { + border-width: 0 0 1px; +} + +.navbar-fixed-bottom .navbar-inner { + border-width: 1px 0 0; +} + +.navbar-fixed-top .navbar-inner, +.navbar-fixed-bottom .navbar-inner { + padding-right: 0; + padding-left: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.navbar-static-top .container, +.navbar-fixed-top .container, +.navbar-fixed-bottom .container { + width: 940px; +} + +.navbar-fixed-top { + top: 0; +} + +.navbar-fixed-top .navbar-inner, +.navbar-static-top .navbar-inner { + -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); +} + +.navbar-fixed-bottom { + bottom: 0; +} + +.navbar-fixed-bottom .navbar-inner { + -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); + box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); +} + +.navbar .nav { + position: relative; + left: 0; + display: block; + float: left; + margin: 0 10px 0 0; +} + +.navbar .nav.pull-right { + float: right; + margin-right: 0; +} + +.navbar .nav > li { + float: left; +} + +.navbar .nav > li > a { + float: none; + padding: 10px 15px 10px; + color: #777777; + text-decoration: none; + text-shadow: 0 1px 0 #ffffff; +} + +.navbar .nav .dropdown-toggle .caret { + margin-top: 8px; +} + +.navbar .nav > li > a:focus, +.navbar .nav > li > a:hover { + color: #333333; + text-decoration: none; + background-color: transparent; +} + +.navbar .nav > .active > a, +.navbar .nav > .active > a:hover, +.navbar .nav > .active > a:focus { + color: #555555; + text-decoration: none; + background-color: #e5e5e5; + -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); + -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); +} + +.navbar .btn-navbar { + display: none; + float: right; + padding: 7px 10px; + margin-right: 5px; + margin-left: 5px; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #ededed; + *background-color: #e5e5e5; + background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5)); + background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5); + background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5); + background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5); + background-repeat: repeat-x; + border-color: #e5e5e5 #e5e5e5 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); +} + +.navbar .btn-navbar:hover, +.navbar .btn-navbar:focus, +.navbar .btn-navbar:active, +.navbar .btn-navbar.active, +.navbar .btn-navbar.disabled, +.navbar .btn-navbar[disabled] { + color: #ffffff; + background-color: #e5e5e5; + *background-color: #d9d9d9; +} + +.navbar .btn-navbar:active, +.navbar .btn-navbar.active { + background-color: #cccccc \9; +} + +.navbar .btn-navbar .icon-bar { + display: block; + width: 18px; + height: 2px; + background-color: #f5f5f5; + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); + -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); +} + +.btn-navbar .icon-bar + .icon-bar { + margin-top: 3px; +} + +.navbar .nav > li > .dropdown-menu:before { + position: absolute; + top: -7px; + left: 9px; + display: inline-block; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.navbar .nav > li > .dropdown-menu:after { + position: absolute; + top: -6px; + left: 10px; + display: inline-block; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-left: 6px solid transparent; + content: ''; +} + +.navbar-fixed-bottom .nav > li > .dropdown-menu:before { + top: auto; + bottom: -7px; + border-top: 7px solid #ccc; + border-bottom: 0; + border-top-color: rgba(0, 0, 0, 0.2); +} + +.navbar-fixed-bottom .nav > li > .dropdown-menu:after { + top: auto; + bottom: -6px; + border-top: 6px solid #ffffff; + border-bottom: 0; +} + +.navbar .nav li.dropdown > a:hover .caret, +.navbar .nav li.dropdown > a:focus .caret { + border-top-color: #333333; + border-bottom-color: #333333; +} + +.navbar .nav li.dropdown.open > .dropdown-toggle, +.navbar .nav li.dropdown.active > .dropdown-toggle, +.navbar .nav li.dropdown.open.active > .dropdown-toggle { + color: #555555; + background-color: #e5e5e5; +} + +.navbar .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: #777777; + border-bottom-color: #777777; +} + +.navbar .nav li.dropdown.open > .dropdown-toggle .caret, +.navbar .nav li.dropdown.active > .dropdown-toggle .caret, +.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} + +.navbar .pull-right > li > .dropdown-menu, +.navbar .nav > li > .dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.navbar .pull-right > li > .dropdown-menu:before, +.navbar .nav > li > .dropdown-menu.pull-right:before { + right: 12px; + left: auto; +} + +.navbar .pull-right > li > .dropdown-menu:after, +.navbar .nav > li > .dropdown-menu.pull-right:after { + right: 13px; + left: auto; +} + +.navbar .pull-right > li > .dropdown-menu .dropdown-menu, +.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu { + right: 100%; + left: auto; + margin-right: -1px; + margin-left: 0; + -webkit-border-radius: 6px 0 6px 6px; + -moz-border-radius: 6px 0 6px 6px; + border-radius: 6px 0 6px 6px; +} + +.navbar-inverse .navbar-inner { + background-color: #1b1b1b; + background-image: -moz-linear-gradient(top, #222222, #111111); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); + background-image: -webkit-linear-gradient(top, #222222, #111111); + background-image: -o-linear-gradient(top, #222222, #111111); + background-image: linear-gradient(to bottom, #222222, #111111); + background-repeat: repeat-x; + border-color: #252525; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); +} + +.navbar-inverse .brand, +.navbar-inverse .nav > li > a { + color: #999999; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} + +.navbar-inverse .brand:hover, +.navbar-inverse .nav > li > a:hover, +.navbar-inverse .brand:focus, +.navbar-inverse .nav > li > a:focus { + color: #ffffff; +} + +.navbar-inverse .brand { + color: #999999; +} + +.navbar-inverse .navbar-text { + color: #999999; +} + +.navbar-inverse .nav > li > a:focus, +.navbar-inverse .nav > li > a:hover { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .nav .active > a, +.navbar-inverse .nav .active > a:hover, +.navbar-inverse .nav .active > a:focus { + color: #ffffff; + background-color: #111111; +} + +.navbar-inverse .navbar-link { + color: #999999; +} + +.navbar-inverse .navbar-link:hover, +.navbar-inverse .navbar-link:focus { + color: #ffffff; +} + +.navbar-inverse .divider-vertical { + border-right-color: #222222; + border-left-color: #111111; +} + +.navbar-inverse .nav li.dropdown.open > .dropdown-toggle, +.navbar-inverse .nav li.dropdown.active > .dropdown-toggle, +.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle { + color: #ffffff; + background-color: #111111; +} + +.navbar-inverse .nav li.dropdown > a:hover .caret, +.navbar-inverse .nav li.dropdown > a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: #999999; + border-bottom-color: #999999; +} + +.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret, +.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret, +.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.navbar-inverse .navbar-search .search-query { + color: #ffffff; + background-color: #515151; + border-color: #111111; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; +} + +.navbar-inverse .navbar-search .search-query:-moz-placeholder { + color: #cccccc; +} + +.navbar-inverse .navbar-search .search-query:-ms-input-placeholder { + color: #cccccc; +} + +.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder { + color: #cccccc; +} + +.navbar-inverse .navbar-search .search-query:focus, +.navbar-inverse .navbar-search .search-query.focused { + padding: 5px 15px; + color: #333333; + text-shadow: 0 1px 0 #ffffff; + background-color: #ffffff; + border: 0; + outline: 0; + -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); +} + +.navbar-inverse .btn-navbar { + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #0e0e0e; + *background-color: #040404; + background-image: -moz-linear-gradient(top, #151515, #040404); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404)); + background-image: -webkit-linear-gradient(top, #151515, #040404); + background-image: -o-linear-gradient(top, #151515, #040404); + background-image: linear-gradient(to bottom, #151515, #040404); + background-repeat: repeat-x; + border-color: #040404 #040404 #000000; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.navbar-inverse .btn-navbar:hover, +.navbar-inverse .btn-navbar:focus, +.navbar-inverse .btn-navbar:active, +.navbar-inverse .btn-navbar.active, +.navbar-inverse .btn-navbar.disabled, +.navbar-inverse .btn-navbar[disabled] { + color: #ffffff; + background-color: #040404; + *background-color: #000000; +} + +.navbar-inverse .btn-navbar:active, +.navbar-inverse .btn-navbar.active { + background-color: #000000 \9; +} + +.breadcrumb { + padding: 8px 15px; + margin: 0 0 20px; + list-style: none; + background-color: #f5f5f5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.breadcrumb > li { + display: inline-block; + *display: inline; + text-shadow: 0 1px 0 #ffffff; + *zoom: 1; +} + +.breadcrumb > li > .divider { + padding: 0 5px; + color: #ccc; +} + +.breadcrumb > .active { + color: #999999; +} + +.pagination { + margin: 20px 0; +} + +.pagination ul { + display: inline-block; + *display: inline; + margin-bottom: 0; + margin-left: 0; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + *zoom: 1; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.pagination ul > li { + display: inline; +} + +.pagination ul > li > a, +.pagination ul > li > span { + float: left; + padding: 4px 12px; + line-height: 20px; + text-decoration: none; + background-color: #ffffff; + border: 1px solid #dddddd; + border-left-width: 0; +} + +.pagination ul > li > a:hover, +.pagination ul > li > a:focus, +.pagination ul > .active > a, +.pagination ul > .active > span { + background-color: #f5f5f5; +} + +.pagination ul > .active > a, +.pagination ul > .active > span { + color: #999999; + cursor: default; +} + +.pagination ul > .disabled > span, +.pagination ul > .disabled > a, +.pagination ul > .disabled > a:hover, +.pagination ul > .disabled > a:focus { + color: #999999; + cursor: default; + background-color: transparent; +} + +.pagination ul > li:first-child > a, +.pagination ul > li:first-child > span { + border-left-width: 1px; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-topleft: 4px; +} + +.pagination ul > li:last-child > a, +.pagination ul > li:last-child > span { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-bottomright: 4px; +} + +.pagination-centered { + text-align: center; +} + +.pagination-right { + text-align: right; +} + +.pagination-large ul > li > a, +.pagination-large ul > li > span { + padding: 11px 19px; + font-size: 17.5px; +} + +.pagination-large ul > li:first-child > a, +.pagination-large ul > li:first-child > span { + -webkit-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -webkit-border-top-left-radius: 6px; + border-top-left-radius: 6px; + -moz-border-radius-bottomleft: 6px; + -moz-border-radius-topleft: 6px; +} + +.pagination-large ul > li:last-child > a, +.pagination-large ul > li:last-child > span { + -webkit-border-top-right-radius: 6px; + border-top-right-radius: 6px; + -webkit-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + -moz-border-radius-topright: 6px; + -moz-border-radius-bottomright: 6px; +} + +.pagination-mini ul > li:first-child > a, +.pagination-small ul > li:first-child > a, +.pagination-mini ul > li:first-child > span, +.pagination-small ul > li:first-child > span { + -webkit-border-bottom-left-radius: 3px; + border-bottom-left-radius: 3px; + -webkit-border-top-left-radius: 3px; + border-top-left-radius: 3px; + -moz-border-radius-bottomleft: 3px; + -moz-border-radius-topleft: 3px; +} + +.pagination-mini ul > li:last-child > a, +.pagination-small ul > li:last-child > a, +.pagination-mini ul > li:last-child > span, +.pagination-small ul > li:last-child > span { + -webkit-border-top-right-radius: 3px; + border-top-right-radius: 3px; + -webkit-border-bottom-right-radius: 3px; + border-bottom-right-radius: 3px; + -moz-border-radius-topright: 3px; + -moz-border-radius-bottomright: 3px; +} + +.pagination-small ul > li > a, +.pagination-small ul > li > span { + padding: 2px 10px; + font-size: 11.9px; +} + +.pagination-mini ul > li > a, +.pagination-mini ul > li > span { + padding: 0 6px; + font-size: 10.5px; +} + +.pager { + margin: 20px 0; + text-align: center; + list-style: none; + *zoom: 1; +} + +.pager:before, +.pager:after { + display: table; + line-height: 0; + content: ""; +} + +.pager:after { + clear: both; +} + +.pager li { + display: inline; +} + +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} + +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #f5f5f5; +} + +.pager .next > a, +.pager .next > span { + float: right; +} + +.pager .previous > a, +.pager .previous > span { + float: left; +} + +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #999999; + cursor: default; + background-color: #fff; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000000; +} + +.modal-backdrop.fade { + opacity: 0; +} + +.modal-backdrop, +.modal-backdrop.fade.in { + opacity: 0.8; + filter: alpha(opacity=80); +} + +.modal { + position: fixed; + top: 10%; + left: 50%; + z-index: 1050; + width: 560px; + margin-left: -280px; + background-color: #ffffff; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, 0.3); + *border: 1px solid #999; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -webkit-background-clip: padding-box; + -moz-background-clip: padding-box; + background-clip: padding-box; +} + +.modal.fade { + top: -25%; + -webkit-transition: opacity 0.3s linear, top 0.3s ease-out; + -moz-transition: opacity 0.3s linear, top 0.3s ease-out; + -o-transition: opacity 0.3s linear, top 0.3s ease-out; + transition: opacity 0.3s linear, top 0.3s ease-out; +} + +.modal.fade.in { + top: 10%; +} + +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; +} + +.modal-header .close { + margin-top: 2px; +} + +.modal-header h3 { + margin: 0; + line-height: 30px; +} + +.modal-body { + position: relative; + max-height: 400px; + padding: 15px; + overflow-y: auto; +} + +.modal-form { + margin-bottom: 0; +} + +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + *zoom: 1; + -webkit-box-shadow: inset 0 1px 0 #ffffff; + -moz-box-shadow: inset 0 1px 0 #ffffff; + box-shadow: inset 0 1px 0 #ffffff; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + line-height: 0; + content: ""; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} + +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} + +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} + +.tooltip { + position: absolute; + z-index: 1030; + display: block; + font-size: 11px; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); + visibility: visible; +} + +.tooltip.in { + opacity: 0.8; + filter: alpha(opacity=80); +} + +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} + +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} + +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} + +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} + +.tooltip-inner { + max-width: 200px; + padding: 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-right-color: #000000; + border-width: 5px 5px 5px 0; +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-left-color: #000000; + border-width: 5px 0 5px 5px; +} + +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.popover.top { + margin-top: -10px; +} + +.popover.right { + margin-left: 10px; +} + +.popover.bottom { + margin-top: 10px; +} + +.popover.left { + margin-left: -10px; +} + +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + -webkit-border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + border-radius: 5px 5px 0 0; +} + +.popover-title:empty { + display: none; +} + +.popover-content { + padding: 9px 14px; +} + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.popover .arrow { + border-width: 11px; +} + +.popover .arrow:after { + border-width: 10px; + content: ""; +} + +.popover.top .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, 0.25); + border-bottom-width: 0; +} + +.popover.top .arrow:after { + bottom: 1px; + margin-left: -10px; + border-top-color: #ffffff; + border-bottom-width: 0; +} + +.popover.right .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, 0.25); + border-left-width: 0; +} + +.popover.right .arrow:after { + bottom: -10px; + left: 1px; + border-right-color: #ffffff; + border-left-width: 0; +} + +.popover.bottom .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, 0.25); + border-top-width: 0; +} + +.popover.bottom .arrow:after { + top: 1px; + margin-left: -10px; + border-bottom-color: #ffffff; + border-top-width: 0; +} + +.popover.left .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, 0.25); + border-right-width: 0; +} + +.popover.left .arrow:after { + right: 1px; + bottom: -10px; + border-left-color: #ffffff; + border-right-width: 0; +} + +.thumbnails { + margin-left: -20px; + list-style: none; + *zoom: 1; +} + +.thumbnails:before, +.thumbnails:after { + display: table; + line-height: 0; + content: ""; +} + +.thumbnails:after { + clear: both; +} + +.row-fluid .thumbnails { + margin-left: 0; +} + +.thumbnails > li { + float: left; + margin-bottom: 20px; + margin-left: 20px; +} + +.thumbnail { + display: block; + padding: 4px; + line-height: 20px; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +a.thumbnail:hover, +a.thumbnail:focus { + border-color: #0088cc; + -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); + box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); +} + +.thumbnail > img { + display: block; + max-width: 100%; + margin-right: auto; + margin-left: auto; +} + +.thumbnail .caption { + padding: 9px; + color: #555555; +} + +.media, +.media-body { + overflow: hidden; + *overflow: visible; + zoom: 1; +} + +.media, +.media .media { + margin-top: 15px; +} + +.media:first-child { + margin-top: 0; +} + +.media-object { + display: block; +} + +.media-heading { + margin: 0 0 5px; +} + +.media > .pull-left { + margin-right: 10px; +} + +.media > .pull-right { + margin-left: 10px; +} + +.media-list { + margin-left: 0; + list-style: none; +} + +.label, +.badge { + display: inline-block; + padding: 2px 4px; + font-size: 11.844px; + font-weight: bold; + line-height: 14px; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + white-space: nowrap; + vertical-align: baseline; + background-color: #999999; +} + +.label { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +.badge { + padding-right: 9px; + padding-left: 9px; + -webkit-border-radius: 9px; + -moz-border-radius: 9px; + border-radius: 9px; +} + +.label:empty, +.badge:empty { + display: none; +} + +a.label:hover, +a.label:focus, +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.label-important, +.badge-important { + background-color: #b94a48; +} + +.label-important[href], +.badge-important[href] { + background-color: #953b39; +} + +.label-warning, +.badge-warning { + background-color: #f89406; +} + +.label-warning[href], +.badge-warning[href] { + background-color: #c67605; +} + +.label-success, +.badge-success { + background-color: #468847; +} + +.label-success[href], +.badge-success[href] { + background-color: #356635; +} + +.label-info, +.badge-info { + background-color: #3a87ad; +} + +.label-info[href], +.badge-info[href] { + background-color: #2d6987; +} + +.label-inverse, +.badge-inverse { + background-color: #333333; +} + +.label-inverse[href], +.badge-inverse[href] { + background-color: #1a1a1a; +} + +.btn .label, +.btn .badge { + position: relative; + top: -1px; +} + +.btn-mini .label, +.btn-mini .badge { + top: 0; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-moz-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-ms-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-o-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f7f7f7; + background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); + background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); + background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9); + background-repeat: repeat-x; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.progress .bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + color: #ffffff; + text-align: center; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + background-color: #0e90d2; + background-image: -moz-linear-gradient(top, #149bdf, #0480be); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); + background-image: -webkit-linear-gradient(top, #149bdf, #0480be); + background-image: -o-linear-gradient(top, #149bdf, #0480be); + background-image: linear-gradient(to bottom, #149bdf, #0480be); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-transition: width 0.6s ease; + -moz-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress .bar + .bar { + -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); +} + +.progress-striped .bar { + background-color: #149bdf; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + -moz-background-size: 40px 40px; + -o-background-size: 40px 40px; + background-size: 40px 40px; +} + +.progress.active .bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + +.progress-danger .bar, +.progress .bar-danger { + background-color: #dd514c; + background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); + background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); + background-image: linear-gradient(to bottom, #ee5f5b, #c43c35); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0); +} + +.progress-danger.progress-striped .bar, +.progress-striped .bar-danger { + background-color: #ee5f5b; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-success .bar, +.progress .bar-success { + background-color: #5eb95e; + background-image: -moz-linear-gradient(top, #62c462, #57a957); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957)); + background-image: -webkit-linear-gradient(top, #62c462, #57a957); + background-image: -o-linear-gradient(top, #62c462, #57a957); + background-image: linear-gradient(to bottom, #62c462, #57a957); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0); +} + +.progress-success.progress-striped .bar, +.progress-striped .bar-success { + background-color: #62c462; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-info .bar, +.progress .bar-info { + background-color: #4bb1cf; + background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9)); + background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); + background-image: -o-linear-gradient(top, #5bc0de, #339bb9); + background-image: linear-gradient(to bottom, #5bc0de, #339bb9); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0); +} + +.progress-info.progress-striped .bar, +.progress-striped .bar-info { + background-color: #5bc0de; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-warning .bar, +.progress .bar-warning { + background-color: #faa732; + background-image: -moz-linear-gradient(top, #fbb450, #f89406); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); + background-image: -webkit-linear-gradient(top, #fbb450, #f89406); + background-image: -o-linear-gradient(top, #fbb450, #f89406); + background-image: linear-gradient(to bottom, #fbb450, #f89406); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); +} + +.progress-warning.progress-striped .bar, +.progress-striped .bar-warning { + background-color: #fbb450; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.accordion { + margin-bottom: 20px; +} + +.accordion-group { + margin-bottom: 2px; + border: 1px solid #e5e5e5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.accordion-heading { + border-bottom: 0; +} + +.accordion-heading .accordion-toggle { + display: block; + padding: 8px 15px; +} + +.accordion-toggle { + cursor: pointer; +} + +.accordion-inner { + padding: 9px 15px; + border-top: 1px solid #e5e5e5; +} + +.carousel { + position: relative; + margin-bottom: 20px; + line-height: 1; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: 0.6s ease-in-out left; + -moz-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} + +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + line-height: 1; +} + +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} + +.carousel-inner > .active { + left: 0; +} + +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} + +.carousel-inner > .next { + left: 100%; +} + +.carousel-inner > .prev { + left: -100%; +} + +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} + +.carousel-inner > .active.left { + left: -100%; +} + +.carousel-inner > .active.right { + left: 100%; +} + +.carousel-control { + position: absolute; + top: 40%; + left: 15px; + width: 40px; + height: 40px; + margin-top: -20px; + font-size: 60px; + font-weight: 100; + line-height: 30px; + color: #ffffff; + text-align: center; + background: #222222; + border: 3px solid #ffffff; + -webkit-border-radius: 23px; + -moz-border-radius: 23px; + border-radius: 23px; + opacity: 0.5; + filter: alpha(opacity=50); +} + +.carousel-control.right { + right: 15px; + left: auto; +} + +.carousel-control:hover, +.carousel-control:focus { + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} + +.carousel-indicators { + position: absolute; + top: 15px; + right: 15px; + z-index: 5; + margin: 0; + list-style: none; +} + +.carousel-indicators li { + display: block; + float: left; + width: 10px; + height: 10px; + margin-left: 5px; + text-indent: -999px; + background-color: #ccc; + background-color: rgba(255, 255, 255, 0.25); + border-radius: 5px; +} + +.carousel-indicators .active { + background-color: #fff; +} + +.carousel-caption { + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 15px; + background: #333333; + background: rgba(0, 0, 0, 0.75); +} + +.carousel-caption h4, +.carousel-caption p { + line-height: 20px; + color: #ffffff; +} + +.carousel-caption h4 { + margin: 0 0 5px; +} + +.carousel-caption p { + margin-bottom: 0; +} + +.hero-unit { + padding: 60px; + margin-bottom: 30px; + font-size: 18px; + font-weight: 200; + line-height: 30px; + color: inherit; + background-color: #eeeeee; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.hero-unit h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + letter-spacing: -1px; + color: inherit; +} + +.hero-unit li { + line-height: 30px; +} + +.pull-right { + float: right; +} + +.pull-left { + float: left; +} + +.hide { + display: none; +} + +.show { + display: block; +} + +.invisible { + visibility: hidden; +} + +.affix { + position: fixed; +} diff --git a/docs/_static/bootstrap-2.3.2/css/bootstrap.min.css b/docs/_static/bootstrap-2.3.2/css/bootstrap.min.css new file mode 100644 index 00000000..b6428e69 --- /dev/null +++ b/docs/_static/bootstrap-2.3.2/css/bootstrap.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/docs/_static/bootstrap-2.3.2/img/glyphicons-halflings-white.png b/docs/_static/bootstrap-2.3.2/img/glyphicons-halflings-white.png new file mode 100644 index 00000000..3bf6484a Binary files /dev/null and b/docs/_static/bootstrap-2.3.2/img/glyphicons-halflings-white.png differ diff --git a/docs/_static/bootstrap-2.3.2/img/glyphicons-halflings.png b/docs/_static/bootstrap-2.3.2/img/glyphicons-halflings.png new file mode 100644 index 00000000..a9969993 Binary files /dev/null and b/docs/_static/bootstrap-2.3.2/img/glyphicons-halflings.png differ diff --git a/docs/_static/bootstrap-2.3.2/js/bootstrap.js b/docs/_static/bootstrap-2.3.2/js/bootstrap.js new file mode 100644 index 00000000..638bb187 --- /dev/null +++ b/docs/_static/bootstrap-2.3.2/js/bootstrap.js @@ -0,0 +1,2287 @@ +/* =================================================== + * bootstrap-transition.js v2.3.2 + * http://twitter.github.com/bootstrap/javascript.html#transitions + * =================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) + * ======================================================= */ + + $(function () { + + $.support.transition = (function () { + + var transitionEnd = (function () { + + var el = document.createElement('bootstrap') + , transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd' + , 'MozTransition' : 'transitionend' + , 'OTransition' : 'oTransitionEnd otransitionend' + , 'transition' : 'transitionend' + } + , name + + for (name in transEndEventNames){ + if (el.style[name] !== undefined) { + return transEndEventNames[name] + } + } + + }()) + + return transitionEnd && { + end: transitionEnd + } + + })() + + }) + +}(window.$jqTheme || window.jQuery); +/* ========================================================== + * bootstrap-alert.js v2.3.2 + * http://twitter.github.com/bootstrap/javascript.html#alerts + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* ALERT CLASS DEFINITION + * ====================== */ + + var dismiss = '[data-dismiss="alert"]' + , Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + + e && e.preventDefault() + + $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) + + $parent.trigger(e = $.Event('close')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent + .trigger('closed') + .remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent.on($.support.transition.end, removeElement) : + removeElement() + } + + + /* ALERT PLUGIN DEFINITION + * ======================= */ + + var old = $.fn.alert + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('alert') + if (!data) $this.data('alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + /* ALERT NO CONFLICT + * ================= */ + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + /* ALERT DATA-API + * ============== */ + + $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) + +}(window.$jqTheme || window.jQuery); +/* ============================================================ + * bootstrap-button.js v2.3.2 + * http://twitter.github.com/bootstrap/javascript.html#buttons + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* BUTTON PUBLIC CLASS DEFINITION + * ============================== */ + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.button.defaults, options) + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + , $el = this.$element + , data = $el.data() + , val = $el.is('input') ? 'val' : 'html' + + state = state + 'Text' + data.resetText || $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d) + }, 0) + } + + Button.prototype.toggle = function () { + var $parent = this.$element.closest('[data-toggle="buttons-radio"]') + + $parent && $parent + .find('.active') + .removeClass('active') + + this.$element.toggleClass('active') + } + + + /* BUTTON PLUGIN DEFINITION + * ======================== */ + + var old = $.fn.button + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('button') + , options = typeof option == 'object' && option + if (!data) $this.data('button', (data = new Button(this, options))) + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.defaults = { + loadingText: 'loading...' + } + + $.fn.button.Constructor = Button + + + /* BUTTON NO CONFLICT + * ================== */ + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + /* BUTTON DATA-API + * =============== */ + + $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + }) + +}(window.$jqTheme || window.jQuery); +/* ========================================================== + * bootstrap-carousel.js v2.3.2 + * http://twitter.github.com/bootstrap/javascript.html#carousel + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* CAROUSEL CLASS DEFINITION + * ========================= */ + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.prototype = { + + cycle: function (e) { + if (!e) this.paused = false + if (this.interval) clearInterval(this.interval); + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + return this + } + + , getActiveIndex: function () { + this.$active = this.$element.find('.item.active') + this.$items = this.$active.parent().children() + return this.$items.index(this.$active) + } + + , to: function (pos) { + var activeIndex = this.getActiveIndex() + , that = this + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) { + return this.$element.one('slid', function () { + that.to(pos) + }) + } + + if (activeIndex == pos) { + return this.pause().cycle() + } + + return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) + } + + , pause: function (e) { + if (!e) this.paused = true + if (this.$element.find('.next, .prev').length && $.support.transition.end) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + clearInterval(this.interval) + this.interval = null + return this + } + + , next: function () { + if (this.sliding) return + return this.slide('next') + } + + , prev: function () { + if (this.sliding) return + return this.slide('prev') + } + + , slide: function (type, next) { + var $active = this.$element.find('.item.active') + , $next = next || $active[type]() + , isCycling = this.interval + , direction = type == 'next' ? 'left' : 'right' + , fallback = type == 'next' ? 'first' : 'last' + , that = this + , e + + this.sliding = true + + isCycling && this.pause() + + $next = $next.length ? $next : this.$element.find('.item')[fallback]() + + e = $.Event('slide', { + relatedTarget: $next[0] + , direction: direction + }) + + if ($next.hasClass('active')) return + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + this.$element.one('slid', function () { + var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) + $nextIndicator && $nextIndicator.addClass('active') + }) + } + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + this.$element.one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + } + + + /* CAROUSEL PLUGIN DEFINITION + * ========================== */ + + var old = $.fn.carousel + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('carousel') + , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) + , action = typeof option == 'string' ? option : options.slide + if (!data) $this.data('carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + $.fn.carousel.defaults = { + interval: 5000 + , pause: 'hover' + } + + $.fn.carousel.Constructor = Carousel + + + /* CAROUSEL NO CONFLICT + * ==================== */ + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + /* CAROUSEL DATA-API + * ================= */ + + $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { + var $this = $(this), href + , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + , options = $.extend({}, $target.data(), $this.data()) + , slideIndex + + $target.carousel(options) + + if (slideIndex = $this.attr('data-slide-to')) { + $target.data('carousel').pause().to(slideIndex).cycle() + } + + e.preventDefault() + }) + +}(window.$jqTheme || window.jQuery); +/* ============================================================= + * bootstrap-collapse.js v2.3.2 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* COLLAPSE PUBLIC CLASS DEFINITION + * ================================ */ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options.parent) { + this.$parent = $(this.options.parent) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension + , scroll + , actives + , hasData + + if (this.transitioning || this.$element.hasClass('in')) return + + dimension = this.dimension() + scroll = $.camelCase(['scroll', dimension].join('-')) + actives = this.$parent && this.$parent.find('> .accordion-group > .in') + + if (actives && actives.length) { + hasData = actives.data('collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', $.Event('show'), 'shown') + $.support.transition && this.$element[dimension](this.$element[0][scroll]) + } + + , hide: function () { + var dimension + if (this.transitioning || !this.$element.hasClass('in')) return + dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', $.Event('hide'), 'hidden') + this.$element[dimension](0) + } + + , reset: function (size) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') + + return this + } + + , transition: function (method, startEvent, completeEvent) { + var that = this + , complete = function () { + if (startEvent.type == 'show') that.reset() + that.transitioning = 0 + that.$element.trigger(completeEvent) + } + + this.$element.trigger(startEvent) + + if (startEvent.isDefaultPrevented()) return + + this.transitioning = 1 + + this.$element[method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + + /* COLLAPSE PLUGIN DEFINITION + * ========================== */ + + var old = $.fn.collapse + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSE NO CONFLICT + * ==================== */ + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + /* COLLAPSE DATA-API + * ================= */ + + $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + $(target).collapse(option) + }) + +}(window.$jqTheme || window.jQuery); +/* ============================================================ + * bootstrap-dropdown.js v2.3.2 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle=dropdown]' + , Dropdown = function (element) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function (e) { + var $this = $(this) + , $parent + , isActive + + if ($this.is('.disabled, :disabled')) return + + $parent = getParent($this) + + isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement) { + // if mobile we we use a backdrop because click events don't delegate + $('