From a16ba707e1bb85c36b51ce410f1dcf3751d73cd8 Mon Sep 17 00:00:00 2001 From: chriswmackey Date: Tue, 9 Apr 2024 17:20:07 +0000 Subject: [PATCH] deploy: update docs --- .nojekyll | 1 + README.md | 1 + docs/.buildinfo | 4 + docs/.doctrees/environment.pickle | Bin 0 -> 1354247 bytes docs/.doctrees/index.doctree | Bin 0 -> 6084 bytes .../ladybug_geometry.boolean.doctree | Bin 0 -> 75478 bytes .../ladybug_geometry.bounding.doctree | Bin 0 -> 36601 bytes .../ladybug_geometry.dictutil.doctree | Bin 0 -> 9055 bytes docs/.doctrees/ladybug_geometry.doctree | Bin 0 -> 4358 bytes .../ladybug_geometry.geometry2d.arc.doctree | Bin 0 -> 93195 bytes .../ladybug_geometry.geometry2d.doctree | Bin 0 -> 4359 bytes .../ladybug_geometry.geometry2d.line.doctree | Bin 0 -> 100714 bytes .../ladybug_geometry.geometry2d.mesh.doctree | Bin 0 -> 111947 bytes ...ug_geometry.geometry2d.pointvector.doctree | Bin 0 -> 124608 bytes ...adybug_geometry.geometry2d.polygon.doctree | Bin 0 -> 271795 bytes ...dybug_geometry.geometry2d.polyline.doctree | Bin 0 -> 77502 bytes .../ladybug_geometry.geometry2d.ray.doctree | Bin 0 -> 58016 bytes .../ladybug_geometry.geometry3d.arc.doctree | Bin 0 -> 100587 bytes .../ladybug_geometry.geometry3d.cone.doctree | Bin 0 -> 52062 bytes ...dybug_geometry.geometry3d.cylinder.doctree | Bin 0 -> 60012 bytes .../ladybug_geometry.geometry3d.doctree | Bin 0 -> 4567 bytes .../ladybug_geometry.geometry3d.face.doctree | Bin 0 -> 352721 bytes .../ladybug_geometry.geometry3d.line.doctree | Bin 0 -> 106188 bytes .../ladybug_geometry.geometry3d.mesh.doctree | Bin 0 -> 124699 bytes .../ladybug_geometry.geometry3d.plane.doctree | Bin 0 -> 101349 bytes ...ug_geometry.geometry3d.pointvector.doctree | Bin 0 -> 139021 bytes ...dybug_geometry.geometry3d.polyface.doctree | Bin 0 -> 118250 bytes ...dybug_geometry.geometry3d.polyline.doctree | Bin 0 -> 79391 bytes .../ladybug_geometry.geometry3d.ray.doctree | Bin 0 -> 68490 bytes ...ladybug_geometry.geometry3d.sphere.doctree | Bin 0 -> 53236 bytes .../ladybug_geometry.interop.doctree | Bin 0 -> 3802 bytes .../ladybug_geometry.interop.obj.doctree | Bin 0 -> 49843 bytes .../ladybug_geometry.interop.stl.doctree | Bin 0 -> 26522 bytes .../ladybug_geometry.intersection2d.doctree | Bin 0 -> 56727 bytes .../ladybug_geometry.intersection3d.doctree | Bin 0 -> 44082 bytes .../ladybug_geometry.triangulation.doctree | Bin 0 -> 11138 bytes docs/.doctrees/modules.doctree | Bin 0 -> 2760 bytes docs/.nojekyll | 0 docs/README.md | 1 + docs/_modules/index.html | 1115 ++ docs/_modules/ladybug_geometry/boolean.html | 2293 ++++ docs/_modules/ladybug_geometry/bounding.html | 1294 ++ docs/_modules/ladybug_geometry/dictutil.html | 1154 ++ .../ladybug_geometry/geometry2d/arc.html | 1581 +++ .../ladybug_geometry/geometry2d/line.html | 1377 ++ .../ladybug_geometry/geometry2d/mesh.html | 1827 +++ .../geometry2d/pointvector.html | 1563 +++ .../ladybug_geometry/geometry2d/polygon.html | 3576 +++++ .../ladybug_geometry/geometry2d/polyline.html | 1415 ++ .../ladybug_geometry/geometry2d/ray.html | 1191 ++ .../ladybug_geometry/geometry3d/arc.html | 1523 +++ .../ladybug_geometry/geometry3d/cone.html | 1322 ++ .../ladybug_geometry/geometry3d/cylinder.html | 1348 ++ .../ladybug_geometry/geometry3d/face.html | 3987 ++++++ .../ladybug_geometry/geometry3d/line.html | 1379 ++ .../ladybug_geometry/geometry3d/mesh.html | 1850 +++ .../ladybug_geometry/geometry3d/plane.html | 1542 +++ .../geometry3d/pointvector.html | 1626 +++ .../ladybug_geometry/geometry3d/polyface.html | 1980 +++ .../ladybug_geometry/geometry3d/polyline.html | 1416 ++ .../ladybug_geometry/geometry3d/ray.html | 1228 ++ .../ladybug_geometry/geometry3d/sphere.html | 1302 ++ .../ladybug_geometry/interop/obj.html | 1609 +++ .../ladybug_geometry/interop/stl.html | 1398 ++ .../ladybug_geometry/intersection2d.html | 1461 +++ .../ladybug_geometry/intersection3d.html | 1333 ++ .../ladybug_geometry/triangulation.html | 1795 +++ docs/_sources/index.rst.txt | 27 + .../_sources/ladybug_geometry.boolean.rst.txt | 7 + .../ladybug_geometry.bounding.rst.txt | 7 + .../ladybug_geometry.dictutil.rst.txt | 7 + .../ladybug_geometry.geometry2d.arc.rst.txt | 7 + .../ladybug_geometry.geometry2d.line.rst.txt | 7 + .../ladybug_geometry.geometry2d.mesh.rst.txt | 7 + ...ug_geometry.geometry2d.pointvector.rst.txt | 7 + ...adybug_geometry.geometry2d.polygon.rst.txt | 7 + ...dybug_geometry.geometry2d.polyline.rst.txt | 7 + .../ladybug_geometry.geometry2d.ray.rst.txt | 7 + .../ladybug_geometry.geometry2d.rst.txt | 24 + .../ladybug_geometry.geometry3d.arc.rst.txt | 7 + .../ladybug_geometry.geometry3d.cone.rst.txt | 7 + ...dybug_geometry.geometry3d.cylinder.rst.txt | 7 + .../ladybug_geometry.geometry3d.face.rst.txt | 7 + .../ladybug_geometry.geometry3d.line.rst.txt | 7 + .../ladybug_geometry.geometry3d.mesh.rst.txt | 7 + .../ladybug_geometry.geometry3d.plane.rst.txt | 7 + ...ug_geometry.geometry3d.pointvector.rst.txt | 7 + ...dybug_geometry.geometry3d.polyface.rst.txt | 7 + ...dybug_geometry.geometry3d.polyline.rst.txt | 7 + .../ladybug_geometry.geometry3d.ray.rst.txt | 7 + .../ladybug_geometry.geometry3d.rst.txt | 29 + ...ladybug_geometry.geometry3d.sphere.rst.txt | 7 + .../ladybug_geometry.interop.obj.rst.txt | 7 + .../_sources/ladybug_geometry.interop.rst.txt | 19 + .../ladybug_geometry.interop.stl.rst.txt | 7 + .../ladybug_geometry.intersection2d.rst.txt | 7 + .../ladybug_geometry.intersection3d.rst.txt | 7 + docs/_sources/ladybug_geometry.rst.txt | 33 + .../ladybug_geometry.triangulation.rst.txt | 7 + docs/_sources/modules.rst.txt | 7 + .../_sphinx_javascript_frameworks_compat.js | 134 + docs/_static/basic.css | 899 ++ .../css/bootstrap-responsive.css | 1109 ++ .../css/bootstrap-responsive.min.css | 9 + .../_static/bootstrap-2.3.2/css/bootstrap.css | 6167 +++++++++ .../bootstrap-2.3.2/css/bootstrap.min.css | 9 + .../img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes .../img/glyphicons-halflings.png | Bin 0 -> 12799 bytes docs/_static/bootstrap-2.3.2/js/bootstrap.js | 2287 ++++ .../bootstrap-2.3.2/js/bootstrap.min.js | 6 + .../bootstrap-3.4.1/css/bootstrap-theme.css | 587 + .../css/bootstrap-theme.css.map | 1 + .../css/bootstrap-theme.min.css | 6 + .../css/bootstrap-theme.min.css.map | 1 + .../_static/bootstrap-3.4.1/css/bootstrap.css | 6834 ++++++++++ .../bootstrap-3.4.1/css/bootstrap.css.map | 1 + .../bootstrap-3.4.1/css/bootstrap.min.css | 6 + .../bootstrap-3.4.1/css/bootstrap.min.css.map | 1 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes docs/_static/bootstrap-3.4.1/js/bootstrap.js | 2580 ++++ .../bootstrap-3.4.1/js/bootstrap.min.js | 6 + docs/_static/bootstrap-3.4.1/js/npm.js | 13 + docs/_static/bootstrap-sphinx.css | 223 + docs/_static/bootstrap-sphinx.js | 175 + .../bootswatch-2.3.2/amelia/bootstrap.min.css | 9 + .../cerulean/bootstrap.min.css | 9 + .../bootswatch-2.3.2/cosmo/bootstrap.min.css | 9 + .../bootswatch-2.3.2/cyborg/bootstrap.min.css | 9 + .../bootswatch-2.3.2/flatly/bootstrap.min.css | 9 + .../img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes .../img/glyphicons-halflings.png | Bin 0 -> 12799 bytes .../journal/bootstrap.min.css | 9 + .../readable/bootstrap.min.css | 9 + .../simplex/bootstrap.min.css | 9 + .../bootswatch-2.3.2/slate/bootstrap.min.css | 9 + .../spacelab/bootstrap.min.css | 9 + .../bootswatch-2.3.2/spruce/bootstrap.min.css | 9 + .../superhero/bootstrap.min.css | 9 + .../bootswatch-2.3.2/united/bootstrap.min.css | 9 + .../cerulean/bootstrap.min.css | 11 + .../bootswatch-3.4.1/cosmo/bootstrap.min.css | 11 + .../bootswatch-3.4.1/cyborg/bootstrap.min.css | 11 + .../bootswatch-3.4.1/darkly/bootstrap.min.css | 11 + .../bootswatch-3.4.1/flatly/bootstrap.min.css | 11 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../journal/bootstrap.min.css | 11 + .../bootswatch-3.4.1/lumen/bootstrap.min.css | 11 + .../bootswatch-3.4.1/paper/bootstrap.min.css | 11 + .../readable/bootstrap.min.css | 11 + .../sandstone/bootstrap.min.css | 11 + .../simplex/bootstrap.min.css | 11 + .../bootswatch-3.4.1/slate/bootstrap.min.css | 11 + .../spacelab/bootstrap.min.css | 11 + .../superhero/bootstrap.min.css | 11 + .../bootswatch-3.4.1/united/bootstrap.min.css | 11 + .../bootswatch-3.4.1/yeti/bootstrap.min.css | 11 + docs/_static/custom.css | 48 + docs/_static/doctools.js | 156 + docs/_static/documentation_options.js | 14 + docs/_static/file.png | Bin 0 -> 286 bytes docs/_static/jquery-3.6.0.js | 10881 ++++++++++++++++ docs/_static/jquery.js | 2 + docs/_static/js/jquery-1.12.4.min.js | 5 + docs/_static/js/jquery-fix.js | 2 + docs/_static/language_data.js | 199 + docs/_static/minus.png | Bin 0 -> 90 bytes docs/_static/plus.png | Bin 0 -> 90 bytes docs/_static/pygments.css | 84 + docs/_static/searchtools.js | 566 + docs/_static/sphinx_highlight.js | 144 + docs/_static/underscore-1.13.1.js | 2042 +++ docs/_static/underscore.js | 6 + docs/genindex.html | 3409 +++++ docs/index.html | 1207 ++ docs/ladybug_geometry.boolean.html | 1455 +++ docs/ladybug_geometry.bounding.html | 1296 ++ docs/ladybug_geometry.dictutil.html | 1159 ++ docs/ladybug_geometry.geometry2d.arc.html | 1619 +++ docs/ladybug_geometry.geometry2d.html | 1480 +++ docs/ladybug_geometry.geometry2d.line.html | 1650 +++ docs/ladybug_geometry.geometry2d.mesh.html | 1687 +++ ...dybug_geometry.geometry2d.pointvector.html | 1771 +++ docs/ladybug_geometry.geometry2d.polygon.html | 2478 ++++ .../ladybug_geometry.geometry2d.polyline.html | 1529 +++ docs/ladybug_geometry.geometry2d.ray.html | 1445 ++ docs/ladybug_geometry.geometry3d.arc.html | 1661 +++ docs/ladybug_geometry.geometry3d.cone.html | 1422 ++ .../ladybug_geometry.geometry3d.cylinder.html | 1449 ++ docs/ladybug_geometry.geometry3d.face.html | 2785 ++++ docs/ladybug_geometry.geometry3d.html | 1685 +++ docs/ladybug_geometry.geometry3d.line.html | 1682 +++ docs/ladybug_geometry.geometry3d.mesh.html | 1768 +++ docs/ladybug_geometry.geometry3d.plane.html | 1661 +++ ...dybug_geometry.geometry3d.pointvector.html | 1848 +++ .../ladybug_geometry.geometry3d.polyface.html | 1724 +++ .../ladybug_geometry.geometry3d.polyline.html | 1544 +++ docs/ladybug_geometry.geometry3d.ray.html | 1505 +++ docs/ladybug_geometry.geometry3d.sphere.html | 1427 ++ docs/ladybug_geometry.html | 1330 ++ docs/ladybug_geometry.interop.html | 1179 ++ docs/ladybug_geometry.interop.obj.html | 1345 ++ docs/ladybug_geometry.interop.stl.html | 1254 ++ docs/ladybug_geometry.intersection2d.html | 1400 ++ docs/ladybug_geometry.intersection3d.html | 1335 ++ docs/ladybug_geometry.triangulation.html | 1161 ++ docs/modules.html | 1182 ++ docs/objects.inv | Bin 0 -> 5697 bytes docs/py-modindex.html | 1258 ++ docs/search.html | 1115 ++ docs/searchindex.js | 1 + 218 files changed, 139798 insertions(+) create mode 100644 .nojekyll create mode 100644 README.md create mode 100644 docs/.buildinfo create mode 100644 docs/.doctrees/environment.pickle create mode 100644 docs/.doctrees/index.doctree create mode 100644 docs/.doctrees/ladybug_geometry.boolean.doctree create mode 100644 docs/.doctrees/ladybug_geometry.bounding.doctree create mode 100644 docs/.doctrees/ladybug_geometry.dictutil.doctree create mode 100644 docs/.doctrees/ladybug_geometry.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.arc.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.line.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.mesh.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.pointvector.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.polygon.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.polyline.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry2d.ray.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.arc.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.cone.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.cylinder.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.face.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.line.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.mesh.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.plane.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.pointvector.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.polyface.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.polyline.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.ray.doctree create mode 100644 docs/.doctrees/ladybug_geometry.geometry3d.sphere.doctree create mode 100644 docs/.doctrees/ladybug_geometry.interop.doctree create mode 100644 docs/.doctrees/ladybug_geometry.interop.obj.doctree create mode 100644 docs/.doctrees/ladybug_geometry.interop.stl.doctree create mode 100644 docs/.doctrees/ladybug_geometry.intersection2d.doctree create mode 100644 docs/.doctrees/ladybug_geometry.intersection3d.doctree create mode 100644 docs/.doctrees/ladybug_geometry.triangulation.doctree create mode 100644 docs/.doctrees/modules.doctree create mode 100644 docs/.nojekyll create mode 100644 docs/README.md create mode 100644 docs/_modules/index.html create mode 100644 docs/_modules/ladybug_geometry/boolean.html create mode 100644 docs/_modules/ladybug_geometry/bounding.html create mode 100644 docs/_modules/ladybug_geometry/dictutil.html create mode 100644 docs/_modules/ladybug_geometry/geometry2d/arc.html create mode 100644 docs/_modules/ladybug_geometry/geometry2d/line.html create mode 100644 docs/_modules/ladybug_geometry/geometry2d/mesh.html create mode 100644 docs/_modules/ladybug_geometry/geometry2d/pointvector.html create mode 100644 docs/_modules/ladybug_geometry/geometry2d/polygon.html create mode 100644 docs/_modules/ladybug_geometry/geometry2d/polyline.html create mode 100644 docs/_modules/ladybug_geometry/geometry2d/ray.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/arc.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/cone.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/cylinder.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/face.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/line.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/mesh.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/plane.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/pointvector.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/polyface.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/polyline.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/ray.html create mode 100644 docs/_modules/ladybug_geometry/geometry3d/sphere.html create mode 100644 docs/_modules/ladybug_geometry/interop/obj.html create mode 100644 docs/_modules/ladybug_geometry/interop/stl.html create mode 100644 docs/_modules/ladybug_geometry/intersection2d.html create mode 100644 docs/_modules/ladybug_geometry/intersection3d.html create mode 100644 docs/_modules/ladybug_geometry/triangulation.html create mode 100644 docs/_sources/index.rst.txt create mode 100644 docs/_sources/ladybug_geometry.boolean.rst.txt create mode 100644 docs/_sources/ladybug_geometry.bounding.rst.txt create mode 100644 docs/_sources/ladybug_geometry.dictutil.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.arc.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.line.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.mesh.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.pointvector.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.polygon.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.polyline.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.ray.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry2d.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.arc.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.cone.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.cylinder.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.face.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.line.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.mesh.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.plane.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.pointvector.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.polyface.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.polyline.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.ray.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.rst.txt create mode 100644 docs/_sources/ladybug_geometry.geometry3d.sphere.rst.txt create mode 100644 docs/_sources/ladybug_geometry.interop.obj.rst.txt create mode 100644 docs/_sources/ladybug_geometry.interop.rst.txt create mode 100644 docs/_sources/ladybug_geometry.interop.stl.rst.txt create mode 100644 docs/_sources/ladybug_geometry.intersection2d.rst.txt create mode 100644 docs/_sources/ladybug_geometry.intersection3d.rst.txt create mode 100644 docs/_sources/ladybug_geometry.rst.txt create mode 100644 docs/_sources/ladybug_geometry.triangulation.rst.txt create mode 100644 docs/_sources/modules.rst.txt create mode 100644 docs/_static/_sphinx_javascript_frameworks_compat.js create mode 100644 docs/_static/basic.css create mode 100644 docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.css create mode 100644 docs/_static/bootstrap-2.3.2/css/bootstrap-responsive.min.css create mode 100644 docs/_static/bootstrap-2.3.2/css/bootstrap.css create mode 100644 docs/_static/bootstrap-2.3.2/css/bootstrap.min.css create mode 100644 docs/_static/bootstrap-2.3.2/img/glyphicons-halflings-white.png create mode 100644 docs/_static/bootstrap-2.3.2/img/glyphicons-halflings.png create mode 100644 docs/_static/bootstrap-2.3.2/js/bootstrap.js create mode 100644 docs/_static/bootstrap-2.3.2/js/bootstrap.min.js create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap-theme.css create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap-theme.css.map create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap-theme.min.css create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap-theme.min.css.map create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap.css create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap.css.map create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap.min.css create mode 100644 docs/_static/bootstrap-3.4.1/css/bootstrap.min.css.map create mode 100644 docs/_static/bootstrap-3.4.1/fonts/glyphicons-halflings-regular.eot create mode 100644 docs/_static/bootstrap-3.4.1/fonts/glyphicons-halflings-regular.svg create mode 100644 docs/_static/bootstrap-3.4.1/fonts/glyphicons-halflings-regular.ttf create mode 100644 docs/_static/bootstrap-3.4.1/fonts/glyphicons-halflings-regular.woff create mode 100644 docs/_static/bootstrap-3.4.1/fonts/glyphicons-halflings-regular.woff2 create mode 100644 docs/_static/bootstrap-3.4.1/js/bootstrap.js create mode 100644 docs/_static/bootstrap-3.4.1/js/bootstrap.min.js create mode 100644 docs/_static/bootstrap-3.4.1/js/npm.js create mode 100644 docs/_static/bootstrap-sphinx.css create mode 100644 docs/_static/bootstrap-sphinx.js create mode 100644 docs/_static/bootswatch-2.3.2/amelia/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/cerulean/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/cosmo/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/cyborg/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/flatly/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/img/glyphicons-halflings-white.png create mode 100644 docs/_static/bootswatch-2.3.2/img/glyphicons-halflings.png create mode 100644 docs/_static/bootswatch-2.3.2/journal/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/readable/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/simplex/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/slate/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/spacelab/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/spruce/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/superhero/bootstrap.min.css create mode 100644 docs/_static/bootswatch-2.3.2/united/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/cerulean/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/cosmo/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/cyborg/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/darkly/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/flatly/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/fonts/glyphicons-halflings-regular.eot create mode 100644 docs/_static/bootswatch-3.4.1/fonts/glyphicons-halflings-regular.svg create mode 100644 docs/_static/bootswatch-3.4.1/fonts/glyphicons-halflings-regular.ttf create mode 100644 docs/_static/bootswatch-3.4.1/fonts/glyphicons-halflings-regular.woff create mode 100644 docs/_static/bootswatch-3.4.1/fonts/glyphicons-halflings-regular.woff2 create mode 100644 docs/_static/bootswatch-3.4.1/journal/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/lumen/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/paper/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/readable/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/sandstone/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/simplex/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/slate/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/spacelab/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/superhero/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/united/bootstrap.min.css create mode 100644 docs/_static/bootswatch-3.4.1/yeti/bootstrap.min.css create mode 100644 docs/_static/custom.css create mode 100644 docs/_static/doctools.js create mode 100644 docs/_static/documentation_options.js create mode 100644 docs/_static/file.png create mode 100644 docs/_static/jquery-3.6.0.js create mode 100644 docs/_static/jquery.js create mode 100644 docs/_static/js/jquery-1.12.4.min.js create mode 100644 docs/_static/js/jquery-fix.js create mode 100644 docs/_static/language_data.js create mode 100644 docs/_static/minus.png create mode 100644 docs/_static/plus.png create mode 100644 docs/_static/pygments.css create mode 100644 docs/_static/searchtools.js create mode 100644 docs/_static/sphinx_highlight.js create mode 100644 docs/_static/underscore-1.13.1.js create mode 100644 docs/_static/underscore.js create mode 100644 docs/genindex.html create mode 100644 docs/index.html create mode 100644 docs/ladybug_geometry.boolean.html create mode 100644 docs/ladybug_geometry.bounding.html create mode 100644 docs/ladybug_geometry.dictutil.html create mode 100644 docs/ladybug_geometry.geometry2d.arc.html create mode 100644 docs/ladybug_geometry.geometry2d.html create mode 100644 docs/ladybug_geometry.geometry2d.line.html create mode 100644 docs/ladybug_geometry.geometry2d.mesh.html create mode 100644 docs/ladybug_geometry.geometry2d.pointvector.html create mode 100644 docs/ladybug_geometry.geometry2d.polygon.html create mode 100644 docs/ladybug_geometry.geometry2d.polyline.html create mode 100644 docs/ladybug_geometry.geometry2d.ray.html create mode 100644 docs/ladybug_geometry.geometry3d.arc.html create mode 100644 docs/ladybug_geometry.geometry3d.cone.html create mode 100644 docs/ladybug_geometry.geometry3d.cylinder.html create mode 100644 docs/ladybug_geometry.geometry3d.face.html create mode 100644 docs/ladybug_geometry.geometry3d.html create mode 100644 docs/ladybug_geometry.geometry3d.line.html create mode 100644 docs/ladybug_geometry.geometry3d.mesh.html create mode 100644 docs/ladybug_geometry.geometry3d.plane.html create mode 100644 docs/ladybug_geometry.geometry3d.pointvector.html create mode 100644 docs/ladybug_geometry.geometry3d.polyface.html create mode 100644 docs/ladybug_geometry.geometry3d.polyline.html create mode 100644 docs/ladybug_geometry.geometry3d.ray.html create mode 100644 docs/ladybug_geometry.geometry3d.sphere.html create mode 100644 docs/ladybug_geometry.html create mode 100644 docs/ladybug_geometry.interop.html create mode 100644 docs/ladybug_geometry.interop.obj.html create mode 100644 docs/ladybug_geometry.interop.stl.html create mode 100644 docs/ladybug_geometry.intersection2d.html create mode 100644 docs/ladybug_geometry.intersection3d.html create mode 100644 docs/ladybug_geometry.triangulation.html create mode 100644 docs/modules.html create mode 100644 docs/objects.inv create mode 100644 docs/py-modindex.html create mode 100644 docs/search.html create mode 100644 docs/searchindex.js diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ + 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..be70412c --- /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: 31cb97776c36a7ddac7569e660b7ba6a +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..6e452b7fa20caf3b669dfd18374f0801833038e7 GIT binary patch literal 1354247 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¨&G%QHvrGmz=?-6ZE2+-4>@N5ab9$vG$@ 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

gbS> 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Ξ1vAS}kF!kCn%#$Z)}oWkqa~ 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�Y8rDZXKg3D`*L6F7<8QOA8aG3DCRdt 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 `&(*QAR!M-_2!9&?BEeOsd~-HzDiui0f+XMC7V) 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.bounding.doctree b/docs/.doctrees/ladybug_geometry.bounding.doctree new file mode 100644 index 0000000000000000000000000000000000000000..bfb338704104ade2020b12989ac0eec32315fdea GIT binary patch literal 36601 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.dictutil.doctree b/docs/.doctrees/ladybug_geometry.dictutil.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c0edc0eb1f7b67c4b919c6ce7e4db7e6ca469ea8 GIT binary patch literal 9055 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry2d.arc.doctree b/docs/.doctrees/ladybug_geometry.geometry2d.arc.doctree new file mode 100644 index 0000000000000000000000000000000000000000..83e26a3c490f04cffd3c745839e3d126957a76af GIT binary patch literal 93195 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry2d.doctree b/docs/.doctrees/ladybug_geometry.geometry2d.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7786a4b3b9ae7fcda2b632aa0be283ce74de93fe GIT binary patch literal 4359 zcmd5<-H&586`$RiWG0!9%Z$#MZ3apH7_d=;xDMwmrDI>_(OQ$=lhk9-A#cfB1O7BK0fw2{{7B5{{7xx z{&l)j{9MmuT*P%0H=#=8Tm;yh*2`p^8g|KLCI7p|kpwNbf_nRh$Th`C6pX3~G; z-APclA~C~i{k~zb6-qYCd5gFC{zv|TcU;Q~o3NEB_eMhKVv^53jAl%w%<9Fk{&-0H z8HumRjQOx0%l96!c{o?IYG>=JPknKI-fHx>@8a4Sfm_z|GuMflIPhK9uG}BHy+lan z$N7Y~F0odNNp2Cg5T;#0O$f<;oDgFm#CJ=QwEQ}tdA1x(;FxC9pp(0OOJ-rp^B%v; zkN7#?<*&I9MjQbd>0C;tN7qVUjjC7;>IgkAz@beoeNk+N+Q7YgmpAhXKP<0zc|WJM zz;%C-Pf|tm1ZOW||H_)E2`v8s4*dP{z+-+dgoXDJ3w|H>^#=ZL;(vkvci<=gZsFgs zRObU6qlII104SAaDwlKx7$s)-4uYd%tJx}Os=1eK#P7OSt4O^l1h^pFu@=R(T#Vhl zExJRZWB(^VcKvNi1th*!3pn6~tq0p|WlBi<7!*>vP=0;8aHFF0knNyZo?l1QMd6E^R2SH|u zn)s)#%>|{%5l`K5WvUSz$UL1e&8WZJaor*%7$uVhbh}73%i9T%d56DNKxH3D`^hH0 zT(3gM;98R`W7?BV$o&}WRv-t6vIe5AYnC`CHDEWSEAZ_u>Dj<{3yXU178L5oN?JJ8 zQ0Y1Ol253`FZutYZOHfTeyKIs zf>O`hW9vz{%Rj@r>bUNY|4Vw?+-6i~!iRJeMM4T2MZ;|Ic1VMuxSaLXbgKhJ&O!?6 zAoG(MFvM5F=FWd!)ug_fd<;&SzL5Qg@AM7o5*yZ;@oTh}m-`R6|M&m=a{mV8{}oB{ zMjf?P=w9$=u2%)W!53>FTum*ZM^@ADCXNA{6;jkGrztbd$3r@(EAo;pwfc(^PgB63 z+kSTiwJ1g}FB?tm<%0mB8}1~|b*R!&QJ7gm1QfFmp9yT=#7y4qWM(m)I)L-V46iJfT86wF!^dJVF@cF(Y3HVsyhnoSS_ep zjIle?EK}M>346>Ee|gh&IZ2vf%bl?~SOITrZfd2LYf*-#3)`Js6D<)dXlO=KNIF&n z-N7mU#GMG4|5hs!+$i%of_!e95dMdZ;R$7G> z@6I=7Hcaq;x|7oA@&K)0KKj|GcwZW+=d_*_|jWV=qVva=SPDpN5v=q6U0*E@Md74EH>JZgJ zfJ)A^BuOyXD^ z%at4|JsWvwv~Ti^R3!f~Rf@rL;zS(<;dHV>ka0Jkfqhl1H@ zu?HwS;x{RQO7qluV^>A%~c{))^L#0CX8ymt#yR&AoQ}0?Vy0vB>RYu!T z*6wWDs%FscBX`Dl>wzHN9mXn204ex@;<^9RO8yyc`&0iO5(&C8q$IqY43J-L*rd=k e@nIt_zg_LC2VkHS+?XC@`d>@v}R|< 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 RLxLbvBsHouj^y6S{|7N?us#3) literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry2d.pointvector.doctree b/docs/.doctrees/ladybug_geometry.geometry2d.pointvector.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c4bef141bf743a293cbad5e4b48df0b174aada3b GIT binary patch literal 124608 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�EXJ_QLcMogLc2lVh4VC1-JZw!W}{q)lXbntBpc1Nmf{wuG9N z>JZTDO*%of)NINJ8?o0$pLbw}l(QOcPB&jxFHxP5QIx`AQYc-PQ?@Sqv7Z!IL_pX@btLIjy>VcBCp?+Hq@3u>=~l1+>9!!v^?1JRNjolrIF4z|&wAO@ z6Px|noIM@6OB#1a(3q+Ot$A4nTJ837YoUt5##^NsG=jNSxwChDcsf*SVcN7V!@zWN zkt+DUaI>VVQfp!kEXGx!b0noXsPcwJ>5%z}rguD93$%#Z0W`2`WuemQ+;H4E;c?C7 zTC21)U9KD`SMeYEyQW$v)!*1$D50U<*+#o=BEu8qZ}oG!TE}RP67A`;bX(+sNf%BNnM zPiv(Ghz5_5+r0TPMonr?K3_)<)xCHM;o60`G{bRv+*)3giRFuZ&GK2;M?$^_s_iO| zhhvHuYtHo^6XhapIB^zWXP5NST-P^TTWJEBI===;5pPFXSnB=22!^MmvkSD`sQTpP z+sgCe;iDm+57K2~H7>`|<;8S)8C`xvm!Ht(_BFT!bh&yhF4xoLLApFdmlMWtIfX9w z)8!3xS-lRIak|__ml?WTjFX4OPP&|h(}=}6bg56^(xS_G8*te{m!Ht(Kk2e>BQCel z<&I6b+)0(ME*H?{J#_gXUEXpE zF7Ker+Q;LvfiAD0%PZ-!`cz!T=_2S-r^}6};j*7Dbl#^t$yCre8uDxqLAxaj+D%!| z-j;H|PC5zv^aTHMtC E53?s!CjbBd literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry2d.polygon.doctree b/docs/.doctrees/ladybug_geometry.geometry2d.polygon.doctree new file mode 100644 index 0000000000000000000000000000000000000000..79ab217d9cecc11a949f759bed23b940b887cfb4 GIT binary patch literal 271795 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 C<0NouF#rR$K@ofounyNPfZu>wIk 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 Q!)&rdla$1Bp&hgTA38J}>i_@% literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.arc.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.arc.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2da4d85ec19c5979dd430ea42dd8cbc382a8467f GIT binary patch literal 100587 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.cone.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.cone.doctree new file mode 100644 index 0000000000000000000000000000000000000000..fd1952865c0b0f0bc31d69a98e0a1b4a014cb694 GIT binary patch literal 52062 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.cylinder.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.cylinder.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7a819306e157d077f7c57ee513ba58b9461965f5 GIT binary patch literal 60012 zcmdUY4YXXRB8*;Ub$SW zm;F{ZTvx7_I`e+5-F>`!`G>m?cBjJergySc?=(w(H(Y@drJzzRH~m`oSU226#oLu? zYg*S|+wx28O1);P2Wx_{VC}K)R4^W{X;<1+Kav&hta|0enaC+3^oK81gnA_p|?ALpmsMqwVL1DeX`y>zFW(17fY@; z0YtYrHC0yA%@+8)@Q!mjGr@{*4M~3?GG1yZg97*pF2Yz`jQ=jhe^dDH3gQuBYE~p? zQ{n1{S7~C9)ZkrL&}63e-wm%+#dl>Y9gH1mcfC%qGnkHu=+Qw$6h5BqZmVyhR7f{=AsF63<^cq1#qT#y3TFI4mZ;vDxg$-^7cQ+{oI3k<# z632jqabvI}xB;WMl7)i)cdOaV(=oArd<@50;1n}2yoSjvOi4X1RBD;Hzq}h}8p>&I zzuR|A71vP7)j+9IM1Bk|D*#2vDYhz@Gwn_@hB7<2@u?CN)Nk%Acng)*&O*~GS30fB zrR1m3_-5IJSF6?Ao&#dxzV}@J<~DHWK;beefXm*tvv56kS!^}Dl7FCJiWDw;+uJZF zB<^hw*T@=OJDy`0m}+!9(Yc{tH5TD9Tv=e{z*D@H6VX=US`n^1+4Z8+lT@QkJ$GjO z!fx8#93aCGJe` zXK|)k;2BHD2j#KkL758ILq&H%u9B{BBatc9L;m)o;}!gL5R%+KYULh0O4C|0I4&7< zxyuA6q<+t)(YTIiR0OW0ZjxzxrY~(?@A1-FO%Gd@iJDOm*J%3AN-`8DwR1(^86xx)kY?@;iUFi0;l=7y^Y za1`19v0vhLT_R54e@`Yb_}!kVP2~OIVC0P{BygO}S z*+bn*xf?tO0s6D>AG+biSult&S8Q0hVufW;nZbxmj(OyR!O6rV=!O$CZR;}vE_gSF zB;lC$pfmQ&7AqJF*UfgSRXM%Dz(lcKFBLI0+pVs+gh+etr@C;8DOHytz$(N4kU#~> zJyMSdBA4p0z>)BR9g?ZwkeL{-zsZB6e4uau{+7hX1MgQ{y4&dHEVbrhrG3-$k8S8)Buq7M&|%?PJ~`dgzgAO&g&-k zS1Ll9yF=;Rg>Qc7aVJ6&lp8Yz+r#UIVbu^kTroTWZZzzRQ$y$%Z^AVz> zu5({6S^OR`9I^H3;?X+uWhtH{ouS-llPLG_p~8sB$SUCI&;)IEWwx|7za+;XeYkRH z^K~jbQTkl#%+FxqxJR3Hnf?G4S3C}P7_j<9KjH_DrlfPDmG670%1615^6foT$jgw` zk^w4XI9)219?sYD4rgkU;u9o>Nzu*F>OWIO$%HQ9>xl7UH_dx%Q!5GP5v7v6C`M))97l*eO57I7Su)e=903UNa=6*F#e|H9Zf z@gC|=hik6Op)x1Mu{<15xWMF(gZy_(^3&(NMzQ;3(Ky+Wl_5)X(-N)nt@N*R_G&^_m*_vUM5}yDvdo$k-x`!;Z1aj&ha-7RSpk>mA6lZhge6;MPKtjR zl2DoLmC0OQ6iu0*JR=X2L?JXd93o55At3@KIeyF_)=G7(m ziYURzwgk(ZNwITKg13U;so@A-AMpfS!Vg)(W5p~3vc#k~JSYR(z`zZ|F)$$+u+B?l zxp*CCDaNpcZW1gD?ufB>o?tSY&F79_Tnh@SgC{z4d$K@rkL+3sYHkLCn@}0b7Yd zy^I3|Z^>0}ntP0&EHF!|Sp0o@G&i#&l*=O|?|1y)z~_W;8!rW$I#YbC;WMVtIx7j~ z5ZeSZPvipTQ>l|=D)?ORuVUL3ycPEhlYD@0$V$(6gsrZ+~ zwA`2%8++oE#KvMfDg*8+hFf|@z8l{XqPeej5hE148;PO|4%A+vxDXtO0{+CtSX_*h zjN6CeQj`sz6PHm-zk!y;6g~Yeo+8JaLaTlcb;T9bd|x#N1GJ-PgbdMtg5)i?L&Q$h z{;Tej*8x%^KIv{eK{ezl zEb)pVIJKI%65K@ii*W5OY)q*Q#4&}6?shM40saO04cB6ONNxd&zoeh?L`UTJ+8UPq z>73sq;RgLk$M_cUdvmR>_iI}^i3X!6y)L~ewL>U2>5SW;M&TwLg2Z;D*Y=073UAG4 z6-<}OX5pC;H472am$D1z8FVm}`WS|BlgS=bB*jbwX&TnARx_{vEJ!yEYfatoY!K}* zw!yM0UQb+~8QZY6cl48ugC)7gIIIK<%VQk&pk-wouEA5(I7Bp%twVohX7j*u27In2 zk!LUuZ@ATO7ra8L-dI#STG$P9w$jszn3*ANq7Em;jk@3dlf&7llO|#n)leqlpdmVw ziC`XKBAD|$CPMr*Oo4bO`V`-$r+3lQcg)k@;OP%6Iice)MOQBO=zE6ATWX+=;?Znv0lH6k+18}}P=J6%8 ztjy!@<0)nyEy=y+(d5C`WAn)R1?da4m1rKL=P)lmj0JGSPF{hbP95s-un#HU^78P$ z7Q2ku3LB*qUn43e#aA>j3||fqMcytVPO$fOmcR*E`y(so#hxft{F9!fDe)7LwAcYN zy@Q#2=IpQYh>_2n{cozYgP{jAT|6EkpeLH%Pq3nnS2Px}7FUa?0lg1IXMJYt)oT4D zogJF3H|IUPOa}OziXW_qpjv;dTD?XoXeONy`cTCRR7yStM!7O<)#n{?qUXv(h3sq? zz0Fq*THyAax=ITLpA>(OVM#+>Oa!5iJkSWHS!61zk39UZ46W)T53IN>e;jAn$Orpo zs`b+G$o71A>>p1Q@V|>_Md6|Yg^L&}E>g*A(-j$0d-v=t(El&rsdDI`1Es6vy_~EL zJ|JW7^qz?a8C{mS7c;!lrMge*~1~yI=vG6NVu-*)#f_h9FACzDTTs&u>p?B zRq3c)5pNC9i=Z?T`$-9Rh(D)kci|mtVi1w47Q8OR$ZaPr+?IIFv~?ln^#2H`P)`3* zdXi4Re9GYTYp;0Fg`PX5WoqF~iZ*S-5AF3|pKr}4-un=)|7EP?)A1&!8hdvZ8vEQg z3r|xdaGcaDvLTu+5@LK*a=ZavVh- zHhkv~46Mw=L)45L7;K6wnzFKy_sz~}>{ZBVBLtJQ9V^Fs?e4Thyr)uWO^6PjM5Q!4 z6^#y_kY!^O?(3ycClQ7F914GV5DH%yhC)8XBz-R{$5?X0OL5}mL8yHxH??u-?>7M{ z+Ul!Jr#;&GM+umjydr7o?g_bOAgbjFxn4udD&+bho}wX_JKH(#dVK<8k>HExFb1QH zRx<@(SKUr0OucI5gA{<({F8+eUxz_xRFGCwp2>$LEi~xWjg|?<2CHoFON@2-yT#-j`%1ys=J8z?q5cZ z&D>emUZArsU&%H=Etp`RR>~=L?($nhSIDBV6Usf>bAMwWYmY&me7aMhV_bAb%dMvT z7*}snyn^>RgPhK!)|>k!lS%LXP_Cml_mbY6a$c>*EKmE;6_=N#b>*E>HtAy2r`faU z${Bel=V50A)49<~@sp`aLAj1n4Decw9q|DwV>n$ZmLARz=N-<}Ci$J5%QCd87pTa9 zr3H$S1*&ZsTGb0wY`1xVDxGS5sD?c+YflGQJoyrcREsBHrYE_0BA=Fj@#Nrq{lqW_ zWcKA-ETP_pSR%1ru_POR=Pp4b>W7&rGCKXA5Os9+#V2(77TY&X z-SCqmy858lcSHOMD>4-*+a183EE}EnWZ^~iNJDYC{=Jb+zZHsvsQ>?>Qr4*dZ!?yq zDAf*>c#Y_i@;L2L>P8dH$x%t%4z$$v#HL>d^~)2RzIiRjrdQ%A8k?$F!x8D`7!#2= zlm!cM|1rrp>5NQt6L6n8B*RT262SEUf!8X`_v2Tl8Oq!Jl@xtlVys(m@03AubZ zMlY+O1Nd?u2a(E`dxD;%FDIXt-|z&q79tM@C(Ky zS89z;yPzftUsFW487XBn{_3f3*i1^X9Z~|5=g~j8yxFq>*1*^n&DiO1XfMwrM0RK| zrAj+^O80kY^KYd*qLm8UDG^`LY@F~VT5;9G$sy_PDlJt?J8?3ol$4|FHA_(4Y84R{ zwqT?M^~=+RdvCk_p2B^%9KQFiqlG(v^qzYQH{DV=a@Wy25542|`)|3quglD~LA%TR z(`cnMzc=TSrgoKYM5Sa`nR26A4p;fd&a`+8(`TL(Ocz%|$)<^SaSlp}$9*H$QP(T; z2V~LoU7VW~_NicvFaGP%TU|(QIBNy1vrhKcIBnlqe5r9YM?KP+(i$fuQ%Sov zlST#yGV}Uao&@hE{cmM;Xc&7eJ&6@jg#aGQ^Wl%OB$1(4?;j46|T=Rn7=c_BTX;cd=Q1(WKJ} zU3@Enz5tON~a$J7l!J*pv|7QMTMzpal1d&0cP z4PZ9NizN2H_3~9Cl^g-yAEQ#%0Pm}^Y$U*2BNf4o@1m)=fmM}>5B?7ZA@zI1kjhb} zdvq_0aoRSXrm6{>BAZbupJ0=bI~t2x-4|aWDV~{F)b1zfxt_Q!V(IM(MSlbIFHb0X zErhR*>>a{WG!$hp;z-n9mcig`-j8DO!fyW;t*=W4qBk9t^B%rDUL0odEX*t-04?EW zM_j_Lz!O(A);kC{J^ZE{cl(5%r=lq{>%<+T!;|86`X^Vgc@_XH_{6%Vp+{Y zD6b1tsL~FGSD^SQIbwN0PW1SyfK2KMMPw@OBW4Zk8&)mp4m^~LbsbXu1S%y5@st}y z4}@O(B0+psZj3q&R2v%P(vfUAIaL0Ti~Z0=MpU3?jX*VlBOw!U_Ot;7fE%_^I;X z8NkDKMPqOl?GT@m=AZ;FrAsTpALLbn)FyeIXq-GZ#rQ^GYswT#I3dszMwP(ic3gHg!k#u7ijm+%=DlF(LM>c0{eAUG9jc zCafadZKRT8!Q&mMlr=8DO_q%;c*Kz3(i%%nG6=yS)RuEon~X5gRRpV0;#Q*T%&a0r zwt=O&1!5%cXdD_$XRd3kgr)DY}plndiKgU~eNclOb}_lI0BZxQ|4h z<)r0NXJ1>Mw8V0OYQKzo%Hkm<-?@R0Kv&C!cfDKhdei!YcJ!1(0~c{Qnl^wZo*@o# z!M$dbm-*wE=Q!XaeU*zDQTZx3So@?M!)1R_@bh`Z$WiRSp-MY=dY`H&cKIWpmvb?z z9p{%&DLHPX+^Fe=`ez7cj5cQ}42^JmNk(FhN`G5YojNJ_HN4-HMbo3w5s7^Mk0b4L z{`3I8CizPWT`AWYz*7>c*dab8%|QuVN|#oG??}$mg`~q@r39%>a^&;t8Cunm4*^BS z$meqzTGf#c<0C_VI@LPzVZ&%~Ykzc9#BSW|E9`?t|c?^EIyB>9NjHQ#bt7 zh;Hs`0l0C_wm#zPiFU?RAr&r0LWpQGLJ0jcF4lP?nj8U71(mV}JiaU&33%441v9A$ znw1F6DpRqN6!v^%5Sl-%=vWbMk)MsH@43^L(L5+;^rEqq2f1%N#YOe;)NaF-^`4Kp zi^r+Tq|kkjhhvpmwNmq=9j>Ms!F{S9@ot|_?{*`;XXbUP?cxcq+VSHg;1lJYMx%~< zuv+uD4eJA)dfRVx>6%^fT$Hk8Amsp$?%q@*xpY}N-fU-_C{s^oCK%il$D-sw(30B| z2;K?umnRT>87-?o@W0_H8VG6tq%GA@RFN%vl1!efN+-!5$?GN9`pm zbkJ9C*7Tueb>oW(@CK=Blv(@?gHrV>AmH)B_Z{N)$LJr=( zO_g@=bUanT+pSt}!n<7~Z*rp9i?=z_r zU*kD}O3ASuJAfw z)RBEj(-6MY7j^6|h7qm~mF$HkYfWn^YBs+O9>}=)6VQh!NW~R7J#K#7)IAGCI?R`J zETiJH#PgX6SFE4ULz{R6))L$kusjL|mM38O4`^8hEWe4TXux80k3*Jz3QX<@ePWFz z49oEt2|Fohi5$DUfHtj+^C(YP_W3x#5~a+%5HC}=_;RoAo#8q@HTpy%;$u_;%7MyX zG^D3i<4d)f(+K~r{aN}_?N`yKLis&xd^0aV2L3 z>mRhvvchM(yL)V-nxtII(f9s$cT-ocf=6c*FD%hle7ZhWmcs?X_Kr+DPDurmIdcSM>!Sb?*jj#$j&?h*_m@!@insBX&L|Msr*XX5%j9C{VsZv*e;*4V7sDzIBchPMsj{EnPFxC zY)5a&{`;ujtwpENaae=oobt)G$n=rr;hvucY_B*fpAFkT;MB^5?bMa4;z`)UmVeZn5e8-?w*Qpjtl!RAI<{M>h>uV+F18nuxa`J41wL@ZGl@LSuyK?7y4sgg!Y&#Ns(>eo{QEdr5FUdM}3;9@S7t{~H4};`4)1B8veR2BWfPQVhV*`;+ zlX3mo5#4-`5x*3?+asB=YNrR~|3GUrPls$M|23zUGaLGQ@YI#7{z)kRis>LcJI2@n z`}OWn0`p^O2A3rCf5)(!ANsAR#J8y#7y4g9;_^U$f5lZ6V9lnu1&A@vhF}bZ*bp%7 zfaou4X)NXBAC@PNYlTD2(*B$6XCL_XB8^z3O0(3ddd&h2iFQ7Bm;^t2vV*9)r-ED&nnF zLxnCKBZAaw9J;W?&P;m~_fmGm-l@YiPEVNf^ICgZ^|M)#7o1v-=JyX%s4G|1ldQ<|rh}2aL34=W|6uQCr2FI zNJI|NBm+ni`}Y}M^JBl2lh{kmxY)lQiOYlieU(<>|J%`N6#t{FvkkZvW!MHV3Y{eah zoz!Y-F;wn9-qjIB6?yRL9R6c&2$Xdvgx68D&F zCq{Mc!BW1Im9W9Ot#f*8#D}yN^AyNtBOZ2YIWwTYji9bvl~1w}510-{c4f(qlmzBT zRItuwskp!<7zmQq;nRl8{MNxrNPLQ#ajnA%BrcD2=%cL4HvA&GjM@gvHd}@lP=YN3 zQwW9Jn8vB0KH)Y{9cpAGB3>(p{IzdzPfSFWljVg0vE2P3=w!{g*4{TOE`oadhgbR^2J zJ6E9_i#;yD^dm`DHexL`M>qw zH+t)B3Tyh{d%r9rXmKe?XHr~D|G<*;mvvMFiAY-Af0Zax=>8^MIpbb2^Mpp2Q&;7) zp!;>`RiXR!^d!+;K4n37Mg1`7e*L_+FoZL|H{Y@kI!ng&Cr5O>M-sghsBbU}J+k=e zLG=3^b~-qHCKeRbfd=^z~KF?E)9R`JkbO-DW(e33xjGHm5{ z*R4oIlbUgX{2fSK9w3h^rb6+D&|4J6C9@3S4+5<)Q#cjUzIDcDa5XC)jP>Z{Hp0-bd7`@(;g$C zhfW#^JR5wGM&cWWt^7v9ibQ;!nsJT9uOM;RjRZJSM&fVKTii$>i;cv0k;z7aX$7=? zfyf@vNK|U4&Pe2L%M7CUA+dnhA2laDYaIQLqrHqIUZfhzNPN$TA+;JC36>g+1k2K$ zMOlo*mMv@~w$hU{67ngFkxv!oFs5Zb_B*aL$4F*p9XZK<+$DO zm)~+&*@)Yy8P{@Lgv8~s96d^^jK?u_7&RU&0k$9SLq6LNrWmMu2@#*cer!Feu%_QS zQJ*X$Xfa0};MPRlPj+;_m})3=pD{!_IvzI)jPA@6(49GTRXz*4KY(6U2=E|1NpzP_ zS7l{t!_>PIOoOoqg!ebZ19*u5o(M{S#VyS@pA_`_oP>NAvrmJ9Xu% zdJ?)nZaNrQbmwvEL3hs3=&pYn(2?l=ONPJv=x${rK2OcK=>8}YmmA$RQwrUG2OY-H zUFR~oe-ZhN?o2UI_c0Y5dP2UNg}*_%7XBUdLX>Lj3l`DST{P|Fd~}b zz*GKR@TjXxR7cagrd0J>t!|%NG5*#N`gev<`IJUzuz`odQaBYH46oEX2)lUo&phle z?<(ofwe6a(m+@gw5TkiA)rwqgnjiL;r@2}|SM!1?h~kQ4@{_LEp5 zCvCqc9Bb7(%@Xc{-bD(%GTc<^G@Ce}R@CW7!VR1vFOfbHu50=2HdZ-XUEFG2FLm0L zYHJ!dZsK5bt&H1_@1rZBTlTi;%}2uVIlrcFy~d9+z8&6bd-IL%k#N20riiwWq!}_{11{90tE{X33BTIC}yp&3dy)AAzmibnAuSea0v!}K+;N)2w7DUfchMZ<5C8ZEz5t``^PNu|#97jWFtFGg!h zEh*tH^9s&k;$n8Rg}WtN-b|Gk(NwPG2@&iHH-MQIrhxpcE{bo|#fr0)DwsRg4L3FX zMiD&um`Ww3NR%R9$s^qkUl+97jn;wPyOG?QZY;KgdTqMioZH{qQV}JKZGRrp zZ9|&tkiP4ZJy$^-o0_vFr+G@T&0osRW3G9mamW4Ubj5Ga%04h_cL(kHDmoi)do!2> z<#y1$V_kRwRBFCxW|v{0*lN%KKN4=2bX96CY;NF_!q8ljQW>hezUiGbKQZ;T5L#eH z)aEe3s+IXlyL;!kmxbrH7V%-?g<^msH&r@z(+yWw>!kV{&+xJBOH9$&;4 zi*bFp6zv72zym`Vcs1WJl(H+7KHh17#!Xvp3I`s6_S8$2+AMT{fnh5_MXNL8 zl&j2>@>c5zzKEpN5|yAc^D}-KQ+N?11is%!C+Fkq$KLUxKkrqlMXy|L`Vuf64!2Ot zB$}3AomH^*NVq|@Ae+Zk_~9zQcA^Vg7Oqq9g82;9tYGs9lcim3d(Al?a0Nx$0wSSm zZJ;dL#I5_S?t=ipSZWPNR%J3;n>9biXiAl3`dUg<&*J})Mf)NCw8C*pZZ9@uWtnlL z61Ph4VFV_s9_}pkQzj9u7*^ENY0uHI)`DQ}YZ3Y@hG8rE^w<6EF<=TXEfReW_lps@VqTVC`Y`4LNA)gV?h;N+9et0ri+1Elg|G kp|KCKhO#i6MG(YD7J|8O#)c8tX&1YStg^bh zI#typauFi~*vSk~JD2}pkRb3E81Tk`m-!NZg-`X1kC~wyuPi_S>-Me3y>-sHxBjv7 z<^PVi@;}?vDHq9XKqyTlW!l+2D#I*hiE&@J*Y3N&yHndzg8xiS2Ie%BP&XqdV%x+RCB~US+I*U}1=T(!yJ1AMh7rFT35knedo)WIgE0bAY~p<~yKBhQe{#Ib zZ}9_u!ngSg_JbiuLWU|!5~hZiQe6zoT=lCAJ#N6Eb!B~0ZU#ytyxZs3voYT36O@v3X z*PwU#UH(4*fd7Sm$p6Ye=3`FzgmY&1VltyFHQb%=fWjorMGSk=K?3%RN3xIq=V@8puu1KtgSs-@vE`$8WcVuyeI zfBNv*l0G~Keb}-M&zsrf;_x&9SRuIGNr_O-zF6qlZ#Oa7**k?-e*?Z8Z;p&3{w}{n zbmejFXFo-**S8r~%6>+NK_C)gf?$x&-}15!$x5a@IoTLMuECJh7AhH;0z-T$ZT93( zWsU90@h70M?un##_I6LB&ay$J^jCN-F83>h|KA^fx&Hz3{|(W^M!>)EVl%g!tkK#kg#Jpp=_B(_CT>zDicKP z9*f-hb=&47s)r4G%w}K(qA{7SoEo-48Jb9J*SRDrL9XDT7>QwN83lCvpxgu75lNbv zfF+@%@N%}5nMwc6z83g>YxQHml#(joB%u+M7#D}wLgQ>#W@dGjM!t%(Cu?8Umf+vp zPO<3X0FC$V{;~fiHeda)Hq##Y05r11k)Oyn2=)*~dzzs=M!G4+*HNA9Gm3>$(Ez24 zK{b=m_cZPFxl$@h&rTMoCY=Okk#CP;?Gn4?ozsTq`PF*AoqXo7n}ivpkBfC^2~K)(}*~WipUzI&}7? z_w*}y&%m%4*)_=K9_;;>fBhSzqoXF_+VbAASpJo2c@T|wxLq(c5X?;SJwVwZH*ti` zT850l0P4GpJ$nK?#X(K2I)EV8V{{MC=I0WLX3ED7*!nC{;CLT>MLh)Xj=fZX4#^bk zibO1od*|p?d!*-yA+vysDUa|E@a{y?cRmt zXr?ImJI4_?V+Nbt4B>SVu$YJ_z*|+Z{DJkr9+$68fHj&Fn)c8h6f5M*Ys}te$vyvM zvwMZMfXsj!ZpjOVah_m~lj3Ph^F`#(v{-Lygf!qGvy$c~hR|ZMKn@j`w#fG(nu9)|0a}L)1 X^2q_ROg&E9)T?|ch`#g4#)BUK5q14P literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.face.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.face.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9c7bf0bb1b3ec901592567d59533fbf9386c2a43 GIT binary patch literal 352721 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 Pj1)+n(wc|jVq^awsylI2 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.line.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.line.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8030a1089c7fd04bc0a0f41766048256b240a7e1 GIT binary patch literal 106188 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 Ctd`LL literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.mesh.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.mesh.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f0aaed1f90f1142d032400e49f07ad551494bc1d GIT binary patch literal 124699 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 `_^F;;&A#FXszS*=Y` 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.pointvector.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.pointvector.doctree new file mode 100644 index 0000000000000000000000000000000000000000..513b7c3792bc6c0232bfb448c0d1a8cef14cded5 GIT binary patch literal 139021 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.polyface.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.polyface.doctree new file mode 100644 index 0000000000000000000000000000000000000000..586cb98288641d332751be41728f8eeafc34037e GIT binary patch literal 118250 zcmeHw37i~Pd8cJdBdIORw+!}p-1xvFBaJUuI|9p=ET6JvV+$h#qN$m#neI|w?!(d$ zL(Jv?hccLThlFq$ADD$~b~g}ANI1fhUr4e^mV_(GvI!2mIe>+*KtkA%{eRy(>vdIi zRrkzz#Nr>cYO3DxUGKZzb^qF5IOdo&$IyS_iJexZ+Bh=fmCKE0+3)nj4dv!sukJUx z{fGNoU)O(4e>&XQ_6~QNz4n~n5062Kxk|NGZu^b?!G5@zig&BE&Wx6{Q8gS@*l`#ToT*d3mPQtS!!abAGYj zEcZ~6+CejG_xnc(K3)5mOtpWu&-hf<>3Z3E4JTS2(CzjsCx;tLnm(1|!U;*1ec{?# zwc+>o^=7@W;B~w0>TItIauWHDtCl-Ns`Ya3D;E{W&eo9FTu)65qMS31uxFW|DTKhPvid=5sn~JyULhNhihA2 zwGAc-=Dn~e!A$MHA3jeM-=3*-Wny2q@AWF%Dl-;|9vp>4ZhsdmYF=KxSjpi`YjFpW z5;!&%ip}|KC{@mZTtI9$LX5nHw%4jy6b-lEHXC5b6c1OsmEx_vTDRJ&`Ni97Uc+k_ zuZQq=iq%H(@;${nTlhl^fh~D!Y0x{!L6`h-YG7+W%qGO9hkmSt2+SeiYoYVzEN&=O zbqo|6zLYvujKFTM9no1oVVa-5*Kc>L;L)}s>8TRNi}=egFVJ7}&34@*b$HdjX2ajg z+?z(fCvxjvqtWd0XA6YzvX{T;ye<^nj^c$(oeOtvE51lUQ|gcu>?rCY#S3@t#NcBE zyCq!5HTs6#6X09TW;Z(5sENkxzSk6$fZ-|H%Dr|gQK1Rf9PWFz9w5o-QqR3vKUq9B z9;w%MFg`FUt+wCs+xPl?svDs0ywrEUw5kU4jNv`0$u+DdHwkXShLW~cb`y5yf-F)! zqez~ybd)U*C9`EZJRb799h7Cb!V?Hg77+g1w>egMVbSCEf--55?(8|-vig1(ll zjOtDN5$*7l^bVmR<~_I|rF*?v&yRKo?bIbnU27d56=LU0Sp3-0dO$*-_j9H=6Cxj#s@ljbf$C$}?2u zvOPC7F5mMiIncL+>t#D;G?`bG$>2^$$l>yAYWeWY8VjW(V3j>MuNVgw48rxbDh!TS zv)T+^Mg2>AXuvnXLMSJLvIAsPjU#wsNW)`Wi{ub%Zme$;`8ydutR<~9!JUPCKFtQ_*<_U*-8MXE#jncJJyMi+So zVLUs4+2@JZK?4$lVluZ?3|b z9TFyz7t(Rm%uL}r{fULG-h;%E69l7L^t#2G?~w?iv2KqNdZ-Uk^cVbg2gTaRcC?!8 zk`%g4eBrba1Ayug!KBt_io43?D&>*9nrN>RX|V1s7Ur9^TJtdKLWrr`P7k1WD*%O> zo3A3VD?usyNIOAw)BD+GuTl2eixR&A;Z{711ivqE@APIn{yjZ-c1~rlSZSh=gqBGn z{IlTqEH*fCqZ}pC@KcK$E1mD4wk_7_0w71qw@@j0q|jHp$;y@kx8ao;EI;PHknZug zule|6>MqCFnhaby9b!Q&!*2<6bBe|hXN$GEu_(xbPvq-vs%PzgHgy^T}})X?E- ztyb)`e7{`mwMg$2=Gs0dzLj28sYGe1i&d!0ZV{@?yVtAMyjiFk%%~MRm1eJ2F3v(l zBVZ|bog!sCOzB?ZP@{Rc0snNKzicZGz{{|pfw^YAg*m!%VZPn0vo6%W06DV}TTyMG zXlJH)or7P&z$0)*dqn`{6`{|WCeW-rvC}==EFSU~@u^c3n!2;&LJ1q|{tI^$cEND8 z+a7c=hx#N$(B5oR?5`kauK%{oN-={?A(y?tjSB;%T_XaY5=+I7zd#eg zfVYjpOi9HqV+IQX3kn`P5gaUgr!6VV2V+H9Rrq~1F;Z+o6r{mxD8#7GONz>9dBuiz z2vnvj+XyU44k~qNL6;Km+hi}PBn)U{5vCDdU!}TG@!Kp~#YVHSz3w%t;D~5ZdVNtF zHkdY)c5Lv2)`4uxrZ8S?!;?M`r7b%Xm6B~4eYKl3*_8uVV6gvqB*N9bN)!%xO*;H%%Kpb$gqp#JgA$xTA29&bH!_;;Rn+ zC9^>PvR7YHf00v9&bxjAPral(b^X$g(PUF<1(y1ghZz2-R~z)}9l`G$f*QY#2dwJ! z@Xx)?8dkjGq1q|&bVfmFBFa9mMM*pH93F3Bqy}{4of!$gB1Xdd^NfVxeNn1 zaTYnig6KwZ#7aVoOy_Vf$)u(^deNRn@G0t1OjnvR1`Ga-1a2NIY;W#NpzujXA=Lp? zx!jL&(WsqU$HL-Rhv3f)+_HcHn!im3e=#c1X94IhjtMl&vJ3RLETBaJ12lh|48A=o z(31fAe~$$3%fftFkUGFXk*1BIOJUTx2rvM@8W|K*1Xig#v@d z7SToE)dm5(lmVX$Oa}Lj3jVnO|HydY9|s%m!u=KlH-9z&^Owos;ZXrT4*#y*@YbJmGdANmUa2H|B`*F+|40htBSV$f0MxG{BI!QsZ_G^{=5Cyh$*qYk*U$1^a-F zg`b7Bo86igi=Zcj?r|5ut7XiuN5A2E%$$>d+YjDAfAU@;>+kgqEC-|wtq+8o;xkyvZ^B~1C3{3BTA zHI_!cHLpfa1tn2U%dL|sitsXPY^3%vt*{@|l4qw@R4&GlQd+QCpFSxCGhV9G_32}} zZusFXWqV)ujvA4R1! zzfRdV>}Cr-oUtq!3B$YK*9k5*62T>d$~19PyS=4h#7#osbY@0Hw`!2zDE10jhzP1$ zhVETJY*`Fl@Kv-d?Aq7y6t!zZhMzKa7iC2qre9e=#VPRb5n>u|SBSBI_U@uvcQ>&j z6)S>hxzXVYJXI|7kqd-a+hX}_k@ai`3&^^MeP67!<;7jaTcC+)p?JG^hqrk79&I<5 zxK%w+g6|Ptlfic+dhu$>S@6soOpay^1cd*=@beu5gK#={Pt3dbTJIo&IkqrtTqlGl z6Lr-Vh6$=PrL+Z5L!6+S>O{qNKZdUPjAvs zZ^qLD!9)D}7Wy3whGZNtY9O=+#~xvCDKy?#LC;U*4ti9#i2+Xd&Q;;zJ9#KgE;#ZO zfTqM>TWo(zp&OHl+Gw>YQc>0$JtA2NJp%DaPS~c$PVdllS3hOPNk$nwgI^_--DK2T z@p4&w^zgJehRbZ!C(vuuYqyxJJoF@qQQo-12m1AGlAFWwQD@%ZcZ(jjB(&t#GTN2K z+ZwdRNGqbnFZg}xa5DIe>{lttRDI1ksb8WRA}94b8tBw&Dkr6Igl<)s=hv-&i9UsH z{VRH6-O5iHbZa1BA1Bdt6YXj#%{GsWqF(u|fiZIZs_#S{J3NVG9TGO9nwvgcEq5#1 zuoa|5|5SYJtQpgk}Sv?n@BNeYP(hk_{}P{P@1)~hJ|-BI9gG5i6Y^lCIx@=;X(Hnopn6Pg)N zN;r!uklmbYcQf=Ds)y1$JKODS=-E>ejA#H9ryD7IEQZ1GvXniJ)e$S6J+`MCwIWPR z6dRZa62;$wSy&b)G9(a(WTK{y784EJJ}q%W2&z|*hYOnYf~?BcV*@kX{=?YOT*0^=DZ7CBfP+F-s2*)3kEaa!xM(N65oHH2T+yx{u`<^rJNzS6)}nLp&Je06_d+mq~jX)bwqN! z(W}p5R)&({Bo5+>IDCQzV{uxZCvpHA=-c8T@vX5L@eJngKA=EB#YQgSYw zzS>Pf7O&V<91STgTcbAtnQ|dYwrVQt{X;N0&0$12wHM#K4e2L-i z-9x!M+ylMXVMTgmFS+}?RPNH(q1?@gR2p|PHUaLs5Dn+2|dUp9=t%1xKES?W(sG>)wAcG!`Ql;rSBQaMat9S&bPFn+V*rJ)T+Zv#AbVH(ck zJC>Hmb0+Q(e5SMKabT4=mY)45af%BL6fr zX}=4Cij?nX=!sLl{FEW(JLYuS%^XD0p+ifM@6A1_raK=a;d`cu*+jyZktus!PpuAI zp@i;NT(yuXq5F4EEk{4aCv>T+v;klzbib@S2p?VD30-2`U`BVNB_wjT(Y(;LoMp4oy{^do7)QhinMn@1v4b_Mve)E0v|F7R%rkU<5#Jrhl?V zzPzUJXi+IZ?goh*sJ!f1lu{l-sw~PYsM3^DKN#&XSrjWhayh9M;dr-jL><-yk^-fp zUU7DyI$o8}cBMp_IGIaKCqz%F8>+9mxsZ|ZR11}o^C9%r7Bu8Tp0}%*9StqH`Kmz* z+@4dtJYMHjXG|O=ZQWf)-llX+iGyi&*>X(p&d{oKOqJj&#}p@eoS=@Do~!>D+DNi#o6pUtd0b$+jZ>Z|k<=^rg--|4kq8UOXR^ zE!ZchP%GTbrQ3>^Z9TXRZC=i8Ub^+5Dla#;3n_$*qK<%)A+@)@pSwe}#qdyr=>9SZ zAO5|Z{-);95hG(7;0@}iE|35)^l*?1y{7OQIa~H)0woei4=Q%1o){var?c^As0U_f5^3$y#YR%W(i`h9eEcrn%>6}9sb>h zLu6}6_B7`7y>%-Tf&B%^l;yr;TT=7aPAx~%$46k)Rhp((?l>)}`J(P1d~9_`V9dY4 z5R6YcF3FD~2D4v9^~OJGZ1V?UhEl;lQZsH4c9JV#!k@7hrdUv#kjy>@3JX(2nJCzs zH%4X0k%IWGX9ZNs8k9|NSqs#SdYA~)WWaU5YzeJ5QLvvo3fSl720K|XD?z7NGM0TS z9!m}^5fJQ73?>cK1Ht5^du*v_@{D+eYc{^A@d7q{PL9B# z;pFr6^!wn-2Rh1IT!nHy4&0*+ZsKfl9bk@DSxay5N|M~k;1%?brL&!J7xl9_&}M)xl+ir7nc!P+nmP0;(ZaQ5aq< zfBgSbc>q=M|9wF_Y)o~jJhVvC2kX!>G{ z`?aq@38Vd(X2pr7)q1avoYDNekBffia8HKZ+k9kOaglCFC>LK{-Cdrwci7 z)s?vV%0I$4I^b3buN**Y(QOZ`ydW$%52I6sO2UFID&D~gx>&ifoEkZeYrWB2;9&IA z4ztq@`^Q)sdghGeH=QX!wkD59xF%FwjZ|e?_+=&97BjRe6K%>us6<;j)N%$`d0R$vkC2YoP*CZJjr7Fn2!2}rbi|7} zA<&$k@8B{$8pBJFl(=T3q=W^-7^#V^%2welh5^(FzirT5gzO4W51$LhM)Xal>EVl< zTF#h@Pfk!*X`@5MY?Pds(jA2Vesw1&BH|%317_dPNiM5px;GsBpcO7#ma8@X`O_7K zcEL_+#!Xjjvc+v6Rk4=7x}m@I5|hVVA`0(IQsGUQJbp1MWzAFE%4MxQ#e^Wd3u)vv zHqgjYQE2OU$8va=j)cqipev44D16*if4md z%af#d7%ht=#oO@|O;QYwE0wA^I|V^&{#%V0Fy<_@PO5Qe!UETM>#Z7|xItKlCT2GL zdWvI_51t?_aBP%BMmZ6V#*Rs79A$bJI6k5o zX3a;xn7rqNTm`s*-5hj$l46UTpw_GP@t%VVQh6??mZ4?Cwhsxm{aRkO1^=09h}1$X zJ%0Go3J!|$OwtCSQB}(?#YT7ms$jkZgcJ9yGWH_vQ?Q2-h(fo_;xa@-NU9~0I$W#Q zp?IrVpcoozv+>_q!Dx>c47s*{7Lz8oW%5AM*RLNeh*#Y6yztM&>_6vWO80|HIr zGR*#wZo9`75%M)?*9ArzHH+H@^)UPYoeW0OJ|!gWlcP%7L}cVGe-i6+c>q;Otj|)V zDdi8r>sXQ`)(cp2Ifr#U1QFUDlHT##lmMk`Cut?R-W=3YJH~~}<*at0mV_@8?_hm1 z7uGV>@HJFQPF2y@i0i*4+`daK;mU-F7NWcrAu?|7$AOfm6O79$11?8d7)uIvPR`Es4u9FlT7)rJt!hO^-E*~drepN^Si z>FKDH%u@R5u=J{d4I#OZGb|pnA+X$qZaB+NVQkY0BQrXJ<*7}ojLy>;1W9IeNN=RY ztX4+nk216>GdjvJD`zYnYMIed?x;~?BV=?Af}mo7$t&rJGdldV{284)7m&$m_PU3? zc3I5YkCM{Cxa?Nv%waCO^5%2~pco^mGh;bJ1MN$I%l+XDorZH76&>lo3l$V!mkI$tj(_ zCPDs`jv-(0Fg4?*be3@i%KKBnPRQ)M7nQPRb{^%jR%XYVZeja5(h8JMjRN+^bAz3% zxs~)&iYJzRE1u$+Ow(4$IUWQ2K#r#YdM!_m=Wo%n$nks&PthDt%t%v7o~bkxtt^ik zMBq&e?PtpJ?A?#!P2u&a1t_fEc>z1cmn!dr({SN3V!tU)Y;JSByNKYphHx8Siqib) zD0E5N)aoYqXToVR_$P^)ax)yQ9+UGq+H^CJ{I2GomGt>5y_RJg9rH@QyU9u5(3B>h z_{tdr9O_qVQj&tbdCp3JZ*?58sG?^i;|w7gr;jQb>mzz3CWW=s z=eTdp1F6bkJ(ns?DF+b#)sp0}t`Z`|SrBE~t{2HHxwcY{i6&fMPPtN^H7)s(Vd2%D zT=>h>$2F*wocf@zwpxI4e177>D>2IBG16i+VtvSx3rn(>sB&9ZF>a{_TIIHOaMASK zR;bMgEv&k&#ryAS$Y#kM5GE;!>!4v#yxAIz9UV1>TsusbT6853u~O@-q!&ZxR7ee( zFne896DMO)@)K<}7SJT^po&e9xjuf%TJna&*mNRESqYd(NsFxLt0OBv^-L4Vh}|;| z3MAO2&~OP}V2Y2d0ce(5oP)g1DVIwe`{uO zWdB|?XNyxh5#*f)k5Q!|+T8MJyV6?_nKQA!R;gH8lp8r8ady=y8%o!NQA6~lfqcq$KTd5P?YShmtdKuRo$ zKrV?*A2KqztXy0b^`%#m$z>G<8;Kktx%?I2Ba+Kc(-S9``Dyu+%eU%VX!IR)qhyzb zXN~(ncjrwj5B5JsUfJHqIN0#Cx-3U66*TM3P8lq%CKcm8=>f)S><3_4vKmW2r{o`X z-S7jeHoqH395<8dTuo#SRdGI;q@?p9`5L)XPDrC~LMC-HDrL>27BZHll%!!;u#wV_yh|EP~1i@a73)uAQ5`biQCIo75U6xS6DEGPsfc z$&gg=L&d}-6D;@hR%LU9@8yV&K3owQ zDRH)m;2 zb?n#|J;YdN@52T))C%kj^a`=n%JZ z30JCUtBdB8lFb$9D8p)mXM{lK&w!5zbpD*4IMCsz0_$2pTKOxq9>{htI(s zBgA=5tW(6TPV?_zaC4%Ax{7QD-s>U5Va@*mi)`f{mDu#K=9{{1_`ahctdUG{W14mH zJuOr*0ge6EL^JkVY*7_8qXaE%P9tbS95aDRS>u>vGnS<|#&9TDLvTGaag2T1t1G36 zL5u-?Ac*;U$ldY;F_(ZyB8a&XPthP|U?8ao#$Gw%pl5|IivHlr*X6hw8opc|7rv<3 z2Qk~35WTQuYWq8OJ6gnp-6Tes!=Zn21u93Eh5(9JNfcKkN_j~hda5Yp4yrVzMm7{k zdX#bv3xx<#ZjYS$K1N`YK_dpaaFZd)JSruJB=pr5KZGQ>nqxGiv}}*w1f<1b#4F=URX%ZLg+r8R=VOE@H`^gfwAE?G z4@>m?PPcR~wiq_sSKY<`U|3III&%q~@v48f#>Fzx22CvEyw~H5W0|jkm$p@iigtP| z^EbNgYK~)5FVBd}~H+@O>J*L9dZynHM%CgXEWTgLCJmonEBJI@UOojsNj1bWcSC^{ zn16gI52Pxxe~>CoDV;{<}nr!6M%IC)!^Qa^1Xd$)o`8`}TedY60mj6R?pqKRuFI^naAXZG6Ltd~0XK>oO zQ=rW`5tS@gJ*)X_pN^rVn|!I>p~qM|>|g92JBpLbC`pKU3V}&wDsASKTOhV0#b8 zt`r4aLfKD84~2;MZBpbS?==57IQYCHCFwMkg7;abDFiC^G)nsF2;TdS8j5P_ghvm| z7(y=1hYz7YVjiZmTQ0p8LnyULweZE~ovDzfERzw+pha*!Gg&6ipFz2qn^Y8Ez^#`Pi?U>&_KDRMh(W|uBOfJjR*opm{JkQwhYNCYFl1tsJ9vH#z5oVLY?@9!f4R;24%)u2$rz!=ipVquI z9_N88kP6vPr$5ZW=frAMb%s?+-pl2!-ioTgP|{C5aOpnnB%i9fH2EltBsJlY&H`|gt5@+%~j}>Me@Ms=$yq_ zzt%jwL(1rnNjL@x&3OEh7>{4bJ0642Qw@=eG!h;>jw_(Hz797oPFL22|F(D^U6LvP zyHq_Ci}w|F-B*hJ@4jmbcBS1`1NTL~*roKB%eV_s=W=?!Pbsw30E#9$dzfDZ{f2og z!3ROk2Z9gbH(b}L__bO;_%Qy6Txcm7u`x;2)Szl^n&(qLifYx&%q0xJ#L@gYy_RZ|%vv$|OI=_DJ=;!tXK z7wYcXEzb3&cH|){eCl$i;dW&ZtW5Ul(N7%q(;Mg{r#wm!4$Jp)VWBfc=KbpEneQ{wSw73#Y66?D5aAT!& zAPFF+aR%ON7V?n4)e)<7LP<%zi3yc7l|x@+C3Qtfq|ua;x}u#!Qe7$xm(=T-s_6`n zlPf|}Q=3$iD_b(OD%0eo$up+OkIm4kOp`0GTp5*gsO2dkD)C?p%Lr-mw*W|yCjWQz z#A$MVTK+Wo)pdAlW97u3pF1ax9>++4+t>0i@c5zZK4y&vv)gN_gz_iEA9}p8TKND7 zZtH-kRHx^ypVW23cdp*NH50&1QWp%-1a-_NxPeb5uRHK*OK87}Qub4tIyutPA4jEO zK^_@QS~K|3jAbdCYgiTh2Ep~rWOGk638fhp%*52Kfp;Kv`zYXFp49E%qGge~{T800 zsar92RG#)^2N7#Ypdv4b{y0%OOH%eSO3GTvJ9bAX=AQMDi6crO=f$@}kqb>r%T>@A zj#5?2Y4FbkAx;IBfGW)&RRYe^8(si-6pVM^^&7OtcJM}$Gt&x-t4Y#|Xl=^3p$a)c7L3r)PHQ(rb!Fmz%CW_IMsTd`!7kLbovO4DP z=Cam$5e>>^iD1hw7TA|L*Z6#56tI7DEU>kD7y#S(lCnDIi=%-3r@6sSGHAc>g+Dxo z|HDBiZ>)qAJ5f9oE14^P*5L)WORbC>z*EL-Vw7aC7>JS%fr85uCH)Ip7E#i=Q{5;j zO|7Wd$v(+68EGq`QmO-Nt4m?R+{JD1ft0a=qJmnRwmjtY@*2`!V4F+s{*x zn7mZ_0WXA~z-wWU)%A>B)N$6*HK>(Qa3bgjq)woJavdvj)S4+^Taeh=JFyO&s#dpP zmIpJ+NW=GPUKK=a{9lKBkDV!dfg`fkTIEJDnlOdv<7^mk(uYVD$%1 zv_>|+1BR;!D4Mc1T(4DeWh1YN=pc;ZIqG(iJE2z5OTjA#aG4r&i!()*CwfowR!Uyv zoVFugz=7_Y_3J(mxTjaeYMX`Xy}YCbpTv2>7j_6WH7S0lm`11OcO5|)Ocw#ix)Qzv zr~m&(o~Q4BTQ&lj!89cX(`4Sk6r4ph#JUtCo6A3idr2NZRlZ<5Rhm-HRD8Z5{}#Ee zYD`?JCdtGd&+0WTQ{ZtCn=#PN&85Oiuw0l+9T#SAE}Uh);09Dm&KJ;ETX`a1Fp1$K zZ>f&KKugS!a!^%n+etpg!M>Wq+{3Us5lVDzqMF0p#YNNSFfUT@U#1uXO@&o;T8U-k z%y8$j=a4ZS1<9Fv7=@(SMf&P+=4|3jG75$@Lkfa7ZtKH&(_m=R5tFlaS$MNgyTHVo zJ?eH~d3%@KUCQR;ERqbIt9w<{WJoj2~IL3q7*T8%->Z(%$V5I~`5Q zzqhCIkG?wm+ciF#8tiZh8iO3TP!5-aU&<>7sZFZc+npI&m9w{`#M5#UP$nwFcTt8` zWrk1LE|tSihgxR%ls|23$_N?0zXp&Z!}nEs;tU@@Eq{h@ciW$@!Fd~N0{4o%c|6(a z7)d*OTZ2NOfpyqk4NIll)rD$9CFIOTrS-a7k;2h`>mkLO;9tN4d)P)bL3+OC`?_xU z$g0hRX)?Ikq|H$Zvd*-VM-$?^o6Cs2-V&-mqf|ZZv^1(F8BGW| z7=YG(4QS<`QE0b~0_}8eXp>cpooX>c6&z1+Ju_3!r`&E*ElPqC^C|}9fxOChFhrIo zuW|>7Bl0RQ!&7wjS&wys z^;Dy{BuWe_UeYOKQAMrc61J$tJ1CO~O$M{{Pp+KF(V@_QT1lb?jxGD7_2E2ZR6%H$ zDov@uyi`Hxo1`GYnYQM8vFyI>cWAl2OufibViklwCR0eJ5Y&P>*+qp<`WP22)Yt}a z<4t!K@4D{V{kPpwy!9n_?l11XuDI{EJ8s=|%S|u4ZcnT~u1tfR7gv93;bi;!?{bk; zdg5-6VIphKvi}z(vzz_T54Y ziuD=lJe~nDmW6m5h?LhR_L#6U8!vm(&@0)Ke`*e}?ERJ_DCtN^+4~41l@!I(S4Z}) zH0KQE#7jdPj@|}%?7}ph$G^@vrjsMWCB8V-#r;xl`)?(6}e(Q z069x6AQ9!+J98gu*$=1j$ z!ic<13QoOIpgouy+GGuG2d75*gK2{6nF&tq<-~HlC&s1*;(^%oWMHs7vFYp4vWQLJ zjHhU9s#KB+O-~)vbmqtkMPyjs{T&qQAob4!JyEs#w)~wSy_72W{z-nVSsR-S8W`0|d1)FBs)me#inEN<%xQf$L zo%#)b0dhqE-evHS{?#FM zI_Q#nKVZ<4LSy>saPK+8!8gpxP#C;%dl}A~e`2W95tPAh7T)ZYxe6X>DxpE#;gwK4 zS%n?bY5>vRG|*q_>@%5{7ugHXv_2dVpvxsDHIXjSGql$BGyRyx#jep~sN#`=tq z>!^dEVt%_pPn_%Er{&Le>=6T}bYyX?8SbALE!}}`$H;h`HxLI98m#D((;XW%!pfPm z-srK#Nsre%<2Net=}C{D)^)>2S9#Kdnc(I+F#2JZL+4ZGYLkdf?puGo|#NSWL}uVW|E)8 zWP$;DAer!5AhA5jg#V6~MKa;5c#0+ytkJIW30q?Ih)F3ZYQtFGPlV5sQn=Zj>}FDx zu^=xRQj+6IZqDIm8|RcAxLZzvQ~E*rslp$Hi@20cYDE|PZ^8(dr%J?>@r&xZxJ1Lz zqSk=i-x;ng`-JzJGonskDj`+;`a=S6N)2o1x+OU{>+Hdp2ht`_BpNl$naqWs^pl^3 zO38jQeRcfg({z|)L#(AXjQ+JMV_?4v<#6_2%~+=sD8l~KCRH$VVun^_FhgTMV=(grE}9Oj z3}%$dQ~Ep|Y8lKZ>t&}rG;#RyILL30*r?p3hcA!ly5YN4ZTKQd z;D#=z#t?8IfSC}5L=0#}1@>!11@g}*VSiH-Cr1qP6e?wnVLr%Ztr+Hbks)JQkf>NN z%auB*3wQo>6qx^*;mCaF=2uetNWODZb`!aCb1jLz;0|Wb`Hv_QzM0we# z!89-hpwFd$axFDJ8gw!cJ5M6E5(|xAn1_fe?z)mHO{rnIRB_i0QgV1cqtUC+;wab1 zlwqZLxLBvF1Ss-v$!!D0PDR~Bz(O6rV^3KmrR-x3)Lpq~A>IEwP$@Y^qp!A%B22hy zS8*KV^e`B=AqbiaU9w_UQQvKhZPX=lvK(r9@fI$c9`)T9H-#19xiyy+U6aeq<^>95 z<+c@Zth`#kAml)3AgcdTP7=wkrs+y<{7>HR=3L{LDZP2W!_;(gNIAKW$&nN$(pN`L z+Gfsc#VBHg>zU{s66w-mxI`{8UDH`015qK7sZFXt^rj50%0QHqVa7o81sPhEfvB

lZ3*vQwb_U9&=W3hSJj&6AL~osc{ubB^QYD=ewH5Ibz&@KqdHk$5;m^g=?-- zUE?X*$8pb2lY!)TQFarJ7fBjbPiy&)XlEy*~$ZdB+m8E#m zhEKkIEB{(W(`L4lJG-6z@KvprMJ#8t!S=(6^ zok9IC63Mg7@SQ_5d`4f26QoG#V+E?$UeqDt&~jQ%gPTc4CxaVPMb#D?;muARC2A0; zT_;gniHPuE9x|$k@MTnKN)5dDh|pTF{DCBWOS|*#gn-~+BT`26uPn+zAKhkU8&e!9 z#_x^%Y?HUcb1cAjh45a~c@OvB;rg3y)vHxs&4!rng>DoOHegYD+27vr+x!iS;q>kA z1VZeDU$Qxn0cLUmEw*gW+qsA>Lx~0|C5N2!)pijOa=dx9kyI05^?`=b=29@(R;jS~ z5OXBiPNAGSMw&)S#&F@K8CsR$f>IE!;;cDCm zqm5BG$1cN8yFRJ?HKw&IJ*erK-KsV+qa=gh#LH#zT?YkJII`bEhfzmXpe()E;2-gwRCO7e zPC*R*ew=_$@>wl!<-+|)B6hJ7tyb*x+DIK^(t|XQb91J4T{D;0ED4S|6QE26Ka>zD zHSUOX^uR*ew-$ViYJdT#`~!_nYPCSFT2Rn@ zY%D8~d(*k-GuaNT-pzyx@?n<1-uSBOtc)$Vghqljt5?Zn3$Ao(IU^w67Eo8N$lJ1J z+k$PngH^m6UvY{RvSke>)CaH-_CWoq)el>QTQ!FHErOv=a0@l#T7>7~jB7(o>_ z!9$l(o1oBER$&$;lvPkrLyc}F1Tt8K-DZ9U$z2XZ1(}syuuq>>8?pGJ_XQs}7cnWh@TmdtA+I3vkY#T3*?TcT(NTcRlG4y-J;U>1woZspg zdIe8WGp^Nn3tldd)kzd!VSD~7I*!_&C<&AW`a_gc7D!(! zUhL9e9lk3Asm`vyfE&xPai)!P`dT=foDb@wbgEEq2wv2^$~tgax&}3qgD(&=lfmaC zPHM-5Cdh@Y}pBLV$5 z)@ZLBN_ysaEQ@r_h!#oro@|x={c5*L77oKKlYNJ;OMjWhTJ~(%#93natD%z3I&F4p zIU^g*qvgt%!;SKWHV!mg+o>e|}44XcwGI&A688|3bkm zkEKc#Xko3c07Ox1HAo6&v93f}Ww8_{(C+`L6?Dw?PgW*v?;btTE9}%Ee-W4Rh*>?h zOWVX>-NhD68J%YoHDEG>0R$Y!4wom$R7inmYHl{*BI>{DHL5FRC5b}UoD3>>v0 z%YLO*6I-J9XbBSWd+L2AaeMhkekJk}^daD^Ru6x~Mckd3~wK#!r6vOo%Y=;?qE zNs5{53kY}Wph?F_l)m#l>?$?l&>Lg+W@yj9 zmQ0S_9cme$o#$#hi&dCxFgma`FsQI5Ewy(Kr_$b1ZTNTk3w2E6U%sa(jwdw1-8E0& z|Bmq*(o764Aj)HRCH<2tqI;Tf7KXMcD`$-86t!SH6b;?cl*M@LK(E4hypW#Qc<@se z;~}6Q$9T}jOj>sP49tt!Jtv!z)jL}fg@2euv8NTHHCM*4yu=wA*$m4;r&cDzLS4B- zA<3}3Sa-0B=f$G@Vg+xlz}OW1Ea$}{FSVj#Ytz;^=a26Uy@GqF8Q0qE#mnWfHi-f( zjLy%Y|^g|m@!UDSdd_pM6?$}dRH$-!2iU%Vn({6H;X`{EO zIUURsHj_bFB9+}hokul(tX|WrVIO96?Hm0RomXm=;^WRx$YyST#i^Ca z+)`Jr=qH)mM|20_QNdxy&~AvZ5hm8W(8`F7>u+m3^BY$~qu`U&jB8wf884T|xW)>s zFsq+Or%|(NP*(=^3n-%us)8Jf^__%77K3{8uHtO7S@Ur$DGnB)6&`d52-bLr?OS?U z3R6^4XVD2DTXBXdt%1O)0gdAH^&l0>QkdK9bq{;(vYDS-vi1z_-w?Qe zZB*RJ)Ee9~nOY@4FttjoTz!+p)E+yfOzm;>#HN;?vY1-Ixp7SG&boJGEGv6?UMq_Z zlMU=UR;__$u!q^!%~n9m6;>vSJsXmfKZ?D;sg=pBQdh2+Cz;h}>kh)z&R55x9;cWG zWzEl8)36=gp%Ke(M-5SepP*)3J9;W!E{`2`g;SW&edsT0LK$IYJ#WKjWjz(V(3M*V z^d#$PnT~4*_dA2M)q1ZEqlz1z$v(_BkMOQC|490V=Ot-NGcq_pC`<--Nldbv%hRcb zFqd~~GB|@Lnp;&#QbjeG3`I+KEM+knb@VEtvj#n}$>66fCPP3!p2@(HoN*TE=QkPE z#!+c92<%)YgDR|y$#{d5TqX9|BC&^@T8`AmCqAhwSIm=4#?R;uRagOa^cgCgV5IpKCJkQJIX7;ZPXC~f0!w;@79b| zTp5${f23rqVUf+G{J^Q@jEZ=ZLS4DTAjzbBQ+E&^Eqr!N%keHVVPYchw1#3+bJ96N z(JDSfDVo0K+z=}`k(zN$&EJ8T%VTPigjblH^8rlM|Gq{95cX%Qtc>IH zcB$j@!UOyCPOlCo%4$u7Wk_~58?{CJh>V^)T~?e%4is1CVulS#(SkF}#yLQf@m#(R z40T>4sIy~K>QD~QP$yFkPze@Dti;V#OIfV*ivhB*&UergTW5aCVx0wV$Fa`0Vw+w` zZPQxT%>y#{FF=t&F?E1}fVp#FFAl+qmy_jJvxxd6cbYw+`6&v0Bxf51u5fCW9v= za@kFNnQ91A|EQ*nW$F*@8-y@!l}x4WV*`7;SZ~6>iy1xM=8eB-?$gKqcOUz^yG6b) z0qbj!6pYRcnhC!Z?bFck*98qfI4TXvAuu$|Y^2xICsjR$zqz-vUUY2Hc>o%=Q^5qs+E~9E$aSNS+Uk$uErTq)?xtrRO*V$|6N z6I%E68JqfYQrzX~dtJX==-^@_GQhMSUfxJj^ylY&+=O+nk6^vq=6!Bha)T?DQFx)| zi-}g;l4WLB{la1Dj#kFDaJ0)|+NF(%zuKU6Iu!WNp+2=iC)qGynz}zi7#aT02>x#y zmH*@@82)E+6qNYGQCLIWI>VXHmdWBMT#LHGQP@LI>?rV47Dquy?>LUau6vuc9t3ur z+2Gt4DTqFkorKkUk%EGJn2TVaPp|5%jDzq>l0{1eW^)kcom!b31nMfQgYYul!79E; zL2=4;4<^(H;T)(xwfbS#;3qYP`J;S8o#55fjH63}{dl=Nu0f2T3di8h=rZaUD72MZ z@DNHUw?IJ+HTqIQAWJlV{(jgdLD`~@Yjc|&+T#HWh}UXus(oucTo-D5cD-BR`h+p?GVgML#__Cfl;S`TCO5yuYlIuQt*urxC*#Nr{G}! z4UBa`uBzR-jWNF0tKs59zFWw44fsMsYI7E}!X<|Co_n~c&paT8+D{5 zXEMJ^%wT?%@VUAui~0R6fGo`K$LNX8FF$25zd}}EezQPa@q}8gJm?N@%T-u6SMxfZ ze$0hSzpnxP0}UuYr2$&mqy*nUI9<6ae4fnjZ&!Dc=!eV8+tt|HUT>CrH6Osp%b0Yv zlBzbN9xgA>sA|YJaW$_pjZo^MgMAuVg~}vmyVxvMwgdlT(@s(fCpyhudkz;AZ>PFz z!p(C%9B1F?VoMRf-xqFDZ)!M4s=qJX(DA!8s5<@dTfK=Iq!76dJBIXFS;qA?L+vpAph*OYxwJgZgZ|Q z>&+daujp=B^CsLj2>kLk_>*a3Bg3O-7VF8L=64C$A*Q9SJQv43s3fsR6(n* z*X?ywp>^T9vOn8fKzo}JBQ!uOl)!anb-dZgK?fn#e*^9AZ1uV&Xyhiv6#Z~xuRFi} z%I&!4uOCiEKbHJ|hNgD3JEP`A+`_M2C&j^8UcOGoM?QYZOGS}=Y%YD;Wgr(!H6bKTOt)lOB2=!vCr zwd2j!2oVY8dNm*_+rv#jrh_5Cm)xR(6J)W{d=+6Su1yO!xBONKI3e|m5R^LEL~mvh z&X!2F!)H~x-BxGE#TVmsXQqYIK%0%3W_#h{et0^Sq?V{;sq5E4-7cuP0q?h8aLMyQ zj?L})Ij4DgW14@iY93?FBZ)iYw`Zz;cb@yesJ*z-t=G`m#;!MuK~U~i`g=EoXG5gw zB|W+{0^-ui{sZ9_hO64ZRlanaG$fa(RE8)Y-}VmcKQZ)9CT)Qc(OAF$t5xgOZvWPk zE(}lVEH=8{krJJITEqV#ziVj%y&rDs)IAKedwO&#EE+tO{}z9iYRx%MlJ@L5>`?XQ zz&>sGsU)y6zzKq-b(NL6%tvrf5corJg6^u%af z%UTICQ=j$A7{ZGHq2l{pbaFa;DDP0ouY1*6$t#y}0|+UMH-sls%S4)vUz-oXfhNy}+r2JJmA^-Lp zQ4XU5TBJVPtZ7ZfMNe~H>e3q{EpAl3a-~xu?Zb|@yW}{W?zSbrAWVa4n!{!c=%0Rg z{9LnE6XPB3$@**M`-d?Yf3|-InE|g2jgR5kfnpIIY6}&4eY}3ny5M+qdwZ?9Kx&{+ NCL=2CjgZ-y{|D`$=v4p! literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.geometry3d.polyline.doctree b/docs/.doctrees/ladybug_geometry.geometry3d.polyline.doctree new file mode 100644 index 0000000000000000000000000000000000000000..30c196be73fd72f9f7dc977cbf5d44ed1109c905 GIT binary patch literal 79391 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

ScY^)O+%{71J>e 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.interop.obj.doctree b/docs/.doctrees/ladybug_geometry.interop.obj.doctree new file mode 100644 index 0000000000000000000000000000000000000000..dfcdfefb81afe4b472a75d6ba60201fcd3675629 GIT binary patch literal 49843 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 literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.intersection2d.doctree b/docs/.doctrees/ladybug_geometry.intersection2d.doctree new file mode 100644 index 0000000000000000000000000000000000000000..051209467d51263d0188a7e765c26b44389c8b7f GIT binary patch literal 56727 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 z z+ESr`_U#h(-^keySvZfyVciu?Sv!Ea6ak^Tjt0&aelel`q`BC<7 zNdJhr)qlj@}F3|q%4*%UoajgM1UIMkd`|JV%oZ5#l~vO&f){{{*-ij<`qd8bI7vQEBXmJGF!(?q8)V z`Y93j=Vf)L=raLj;*jVWRHZN}7i+&kV(k*(Js}r*l zSnKncW8^TavM9FTP^_Fz0;~nR>ZPTa6ZqW4sFV$>($jb@0UU4tQflR6>{G{K524~T zm28(M!`X{itF#s_Gv^Cg(L@?D0HuC6ygy$UE&40}YzJSHU_H866;1tD_HeEmIR}%o zFgj-#xg#Y)JySv}w=t%&P*ZxE(h5-1vt>;=970XcmKe02Ze_mKk20?H0up!$*SeOZ zL`@GApej-$>gnkOBt9ZjiYAss{}0?2Ji)k@M87trQoRraYWhP|DaSEIX?3_D-3;x6 zFlg?Wv7~N-p^TZo8os1WxnicoipFths0VmT~$8y{mv0mU;d_1bKh&S+s+JM zY!?8yZUZymKZE+>EyGR#RSa&)dPCYmnMmLg@K7UeY zs~K(Hhf3wzob5;BiScmNZB%0XkC5*0#5ii#Kw|t^R2q9?OfBJw@y(F5LqBC=d~s$c zi)F^r?lA`cA?g;&jBnRw#&;#>SUuxzb4{NrkBy$PSLeIQR%e`JdXtT9tHm>r*>k~@ zg0EWyUx-B4KY4zjj1Xn={3@z6E8QxE%!6OoJc+WD=Jlp0(sDU8mP*TI=o4vnq_q6}oiZ)IfUa0t&aT!uEmyn$ zv3!M3>yl03s@Yj?cCZge+0V=h@BWwl%25;b@5qc>BW_ZO*#>6p^U!Rz*U)F|hg99o zwNaB!hw0Q^?YO6_#24iiDXRPw8`!@~38;KB9|_laDUsQ@Wd3e#t1}Yg+kp3GBAcryPMx}u*lnIE=t zXTZg)Z_8u{zz}{lh>*WR2B!bE>^5yy&yx7XyAruQwnD^SC-_E54l=DZHG427YRWx0 zKSzAbIBys1$l+ZZX|UPwO|>bi&S}D7fx(}N^tJw}{fEj(Q>OMGph~mS$-;?k($xO5 zrI-_0;%87PJGG~$@xgzz)ILom+eymQ{!^?~T3eU%;!m@p`qcikN;rmXHlU*_Ihaubw0zSk(Kn*)08?-&6o>L|;fB5z zUM9_Xz6pMVNm4I#0Tgf()RL4y!N&_w73m_mor3A&FSDX!0}2**CCx-H1OXIWOOGhwB|x+okMuC$wSGqAVKb4H2T+#3J~0&Cp#xcw`XlHn%6_A z@o^>TF!UI~qpI%a*dswN>%rCSvw0ObHjr0L4UG&Jc(W2pd0Ze8sPj`qCXEYh^jAa=PrfCuUyI9#6S}^dnceH4K;8Fbh{q$ZmpBSa(A1&STY)34)q?Pf* z)w)rb!B2y$;g~_xt^v&8VN@DBWS9Q#0cn0pphZutmG0|@Nd_4fxA|}uTg&d5Xu5u;Le-dvq&c6#5 z<;b6OU>h9PWvL=wo#(|Evc+ zvxQTmqC>K1Rw|)G9J3}jsCPRkTC*y&OFarD_QR)ooSghdb~#B0ZJ**E=8uBL^yoe;|=x7F{>}j_iR@;zBQAZPxlPi3`oK zqIz8Dh|-g0>lrw1wW3S8h#Z|2*k$Iylz{b237x%)F_i`H(9@L8t{uLGd}kQ&-djd`410MuQ=eYS z!YD|}rHn#vl>&Q_1EGw-^pynmA|umC=_6q;e+Z5^_VPz`#jqE4wa(bf%apSm5pB6H z?~{r23r59Pa7?5Ch-6FExF8Qz^fq{R->*Vd*&afVsC-4$4f~tnJBhS(2QifM28(di zWblFVB@8F|mSVL$P7;a9`6iKhwsDf_icaMjI7tPSisB@btn51ABv+5p^2<0lmr;d$ zQ0_{Km&=tDiY^XjSLe-`oi!0ary&q^q>PCJ?Bv(hD()CFRpmIG2gqD!ocr}wJLFr5 z&Ff9U&b@*iq;xpkqk#h#unZD<7=8d6%3P2tlRgS9d<_Fpzz%-pXO8zw1e*<`SL3tdM=DDTUE>7?ZeL?8EvynEG}6J)Glg_f1ouZ7Q0OlT+XYHh+P% zeMR6n1RP25=zZ)wICXH9FE%lWW(5U!CwCO5@DB(E88f>)s+`g|NJV=1CEon^(VtQD zFp=!{5ywxS1TNXIyymJ&K7bDOL@xvuY1ZwgwF(@w2K3W&eL7FwsNonr!*Aj&XL+uu zS#wu$u-EtZSap2Qf&v6}Hkuwz$t8-b_zQmy!bh=-76j#4rstWR6(UGQ@$r2d?&65- ziJPfOkp@1(HSlYrYG7wl15@E%c?e{!<K}p!o>MMZ+!8cn1%ylH?>uB&!CoyJa9sriL>-)ixzLqOSTxY+ zRZ&T5`rDsTWj`zl`4o&NF9yevs;*bJaQx1$7CK4KT%iM!x@$NHx6==&ZOl@znF{Qy zx8nC7VF!49l8jQh#7e@_HULlmn=*Yu;^}|M+bp6;h~=Eb)Bmd!>jInoPgKgr)9Gp4 z$FB#To_>dR#AU$PGpx<}L0n+4-(W>IFP?to?xcYAObMMCsFbZUdYaN%Ib2-%iSl@Q z`rY}uyoB+hXFH)wElG)||4RW25zs01{esZxpL1gx*RDbjrWBC**c`=^0-94#b@Qf^nQqLjmfZ1FDaST4Z2jB8WkzT}GJ9i#P==PuK_vy_AjU zja0zv_rjdw9aUdR!0TTY@J3_zkN4nk9mw!K>#_1R zEa?5XV!k}`9tqC*7?DXM@5rI});-J`Si9oe@ut3@h!*#y9%S$A^Jm(|XF%uCk3n)qfjY4M)|Zb`7BF z??a`rqw3TWj;de02Uqk{qUsk7?^ZEvT^c(KmezX|2p88Ue02$2tvrT&= z>}m|8loN2S0RLd*GWt^nyV~@=5=_ksrl6hm|0eN%>jC3hFCdYNbFFJh%3S=` z0#rqwMgE~+o^>57I<~cK9|pO&O7%hzx%jVAr5tw_%f)|X3G*_3K(h~r<-D<)Or|Ao z#&WOK8oH{jBC0Z^+{CO$XQgGOXi)x6Qy6US2a_fN5L+CTZeYGcjq!ekEem%$b*pgF zf37t<>PP2V%Q1$3l1!>ZU^3%;0yT62K21}K?nU-ms(b1Cw}3HZ*imYgpo}D=?lTgM z@ilP7VT`ZS6@xL@)jGo%x2ql{VGOysM%j-ub9Y$IeAB34#tSnk&dEUPS^Fe&*Iu3Z z>w%3Os_y349yC+TgDVN2t0~Ku6lzK+f5#Ax=O`(ZM>rx0ItPhN8sXUJZ!qVzykr0# zhfqo94<$Z8fX7v=Yz**-zF!gFLPWU7z$4u^n7576@}<%(&-TxfOZo?jDegrguHUC~d8f?S;00b@Xj>=O{_R@9|oIu{6e1)&#;cw&Je zUtTyA0>R1_eDybE7Z-(!jGeCXBF+yGZ#aQTuqcOhZA8pIzuXkt+bdb82WI)@WiL|Q z`lBR2Rz`>tC3%u6%}N&xXLe4mMtEN-)>bC?_zD% zb`Ob_yn_|hViAdG+Am;C;kvN`cxpKS()Zo?-!M@IDqcpmOM=8^|jl^WGPmy*Ap zXQ9$RU_56*DfBd@(mOLh*v#G3X^>^**21xpg?~)9Jl{}$k4Zu=Gy$)0L(!6y@XF5? zpeph|ax4Y&zc;d?V}n;J(@8b!g&^RSom45u560k?ajhlb0r!}wMfNu)g0Q@c%LtP# zb+`yMbTM%ZBOsxdvJt(J3XDJwx-yv1R}xDdWRMlvz(^RuZQzJw1jp!#VFc`IoiT!! zE9aXIMMhima8v0u4-%7*Q|gn)gT6__)`C4_?@74bM^mS%+mFc`;X)oCa=_Z?RS;FE zGtNZPsp!Fi2UOkgJ)3eBL|S+0JyB%T_jk#8;C$ZQWQ86s$4xXLyP z8t8(n;m|K76S;R)kCna0tNC-xd6e5Y=A({ zD6(Y{L(u{qLNI>9%2POhOx)opD#4^2)sqf5HDAmif^){#g1g_3RJi^?!QYn=r34E8 zjw;PcXA3Vj7NFohIi72mQ;d*{(HDZL^t15Fi~lVpACY?gH!5YL3G_4`J|IOMYmTV8 z%XrDcGSe;3_Vp5@_!g6dwtq^D;y+kXJx1|tuID(kFR3;?Q^FFiLZxg=pr-QJu<4(R}xr_jCZ5% zGZI$w6gc8o&D-gUVKwY(ow1r@$soV#TPtm>ohpA|_DjlvG|`JjUCoi&1u7!Q7jwv+ zplTa<_SUaKb@Aae>3j9K%!gFn@Ttv*%S6&0T*HxCNFOiC5#{Ux%EK`T=693`%0n=b z_?%A?nKT45&0>3H6aycrO!DU(Jc=NV#O;Hj{F!X`m$dE9KwrLqN=4C^KVfBK=nHSh zz)Fq8ZQ0uNF4@nF(%P?=Zf&;njKeU))wWSE%%{QCa2O_P*8mLjepDJe3_~s9FpO~? zuIQ(PVV;-mq1Aa>(&UjMeE?Mp;V-vpKQ9SRyXpDYnu9$=uqDZ`&7a0YOm!<~4pIO! zSJR&oi$Z750pAI>t`KY?F^l$q`9yLLC9Gq{4lkXciM^W)9AT8Sg2UJm20LEcP=@ce zvGvQDz%z}yZPia<{j*y!SKVgAs9RoOsvWSg`g*zLVuu*o35q_BM!}YaJz(ni0^0X` zmcL-!fi*kWRfcxZrWexbX>@+lY1cc2DR=BDb64VoH3H~pyKFWwua@Y18*ma?;)}T@ zUSHM{oflCJzJ*kz?+y4q>5eijm4MnYsx&KyP7F+A0n{$zgX&N?jd_!yD>LLEE6&@J zKb1;Ytk5XMr2s=-gG$+82tAEE8^B`E8Ca1anyng>T?G9+O)+~+lyKEUtcA(AF$E_} zxa#{@Q9WFB4O4(-vux#6G@4MGW6SVDG0!ydrj*$9jTf4Dk~KbSZ45n4X~G!3F?Ou| zs#Vzd96pKG5hk5gtSoesen-A;-pJalXHS4iTsK;h5>&dc09BFoQYS5#_1?mYj%^jo zr$8>Q5WNrtsPr^d%JC_&@o!wqL^k~@mM@eMCPPL(&$Ou*y08%vdMO*x8>v7>a{KF~4FVYzn-!dF2(bQ1EbLzU?-8 zEvs+YS7K8~>?x{hRAr55RS2tLh#ICnhZ@&B#A0%6{$+3H9mbwV5U@>`>4lTN+oR3K z?0FgnwuaO7p64O-tBU)_!#(m2`SbqqaF=fd6vz4faDT(C_kw22Us(2B7P4WZvU|-I z!ntTSyM4#QDSQ%#U=N!BqW)Ba_5}^hb{9W(^ViT?om2Q(5kGrg3%~XTZoOJF>!;`` zUN?bud29Xfz_RNGaI;qI;hkH`s;9%4)r3`aYVBH;M}C#vW`0>3TQ=4jUo1`gd*%E4JBEKPSTd*Z?203e2GA%R&?3 zM8m4}RzPq1wCQyqE0n;Vhi%-MLabc3h^hYL;dHap?FChM?CltlnoKJz*8Ul8H(-dAn^u%Z?mvp-TBxB()AkEhSjBzzEf{9Nl(_@ z&NA49M4F9i*TWvPXMo0q(`GMltI@4+TeAVmjb6J8a7P#(1j}x){?ozP1R%4I-GOvF zKB+&t)%C4j!>yialS!Rto$0#1#gF+DHJO#vgX(J2Z*mjeS8X(Xv(~b}h-l?b*&*!t za1WUA(FNFsmms=V6ss;bTVM_yce-nJtKi8(SE@69_t2f1iC2?thZosF(DjcjF5o86_4bpuK#%CGpo6uV?Pkz_`FYO|&-2$hfqAB?HZkmnJ6bN8{vN+= zqN9C(5B=H)!SmT~{hOi$i?{82gdlhP*>UGh^X|NYG1IUm2fhS-+ zZXGMTU<1-I93WTW_iCwf%{KO0#LGy_s zcC_HGU`YGXNSImxFAJL31ei8_3=bskcCfE9yA5i~>c=E?sRp~hlWy{BXE%*wyYP=6 zPSNdPt;;I22giN;@<*^*fSzNi;*Hq(d6G9Ka_18WyeZ5vP|&^{7dJ9MDxN5W6HOl@ zQ~z-cDb5+9H5$U_8XkHZG$}CLI14oz&{VuKcAhzKcAz2=p=iG zPP%vKd^&cnoUR^XQFZjTPY=!d)OZmY#@9YgcZTg3f0!#*GD-%T#i^x*Aanm#%ey%5$k?XTfz zwYI!FXjKs;b2k%=?z^K7PmX+sQXl%!RN+g=sfYpL!=X zBHG3sL9X0v8!rkEs6kN5nW=lXy3O3p?q;Xn>NUvSt&pd|4&xZ7#gIlE_G%0N2Z$=8 AmH+?% literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.intersection3d.doctree b/docs/.doctrees/ladybug_geometry.intersection3d.doctree new file mode 100644 index 0000000000000000000000000000000000000000..054d0b3929abfd5d2e0df111230d2af12dcc13a9 GIT binary patch literal 44082 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 D55%b# literal 0 HcmV?d00001 diff --git a/docs/.doctrees/ladybug_geometry.triangulation.doctree b/docs/.doctrees/ladybug_geometry.triangulation.doctree new file mode 100644 index 0000000000000000000000000000000000000000..fb19048d38327494836ecb7191d4a8cca0b58de8 GIT binary patch literal 11138 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 + + + + + + Overview: module code — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + +

+ + +
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/boolean.html b/docs/_modules/ladybug_geometry/boolean.html new file mode 100644 index 00000000..91429277 --- /dev/null +++ b/docs/_modules/ladybug_geometry/boolean.html @@ -0,0 +1,2293 @@ + + + + + + + ladybug_geometry.boolean — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.boolean

+# coding=utf-8
+"""Module for boolean operations on 2D polygons (union, intersection, difference, xor).
+
+The functions here are derived from the pypolybool python library available at
+https://github.com/KaivnD/pypolybool
+
+The pypolybool library is, itself, a pure Python port of the polybooljs JavaScript
+library maintained by Sean Mconnelly available at https://github.com/velipso/polybooljs
+
+Full documentation of the method is available at
+https://sean.cm/a/polygon-clipping-pt2
+
+Based somewhat on the F. Martinez (2013) algorithm.
+
+Francisco Martínez, Carlos Ogayar, Juan R. Jiménez, Antonio J. Rueda (2013),
+"A simple algorithm for Boolean operations on polygons",
+Advances in Engineering Software, Volume 64, Pages 11-19, ISSN 0965-9978,
+https://doi.org/10.1016/j.advengsoft.2013.04.004.
+"""
+from __future__ import division
+
+
+"""____________OBJECTS FOR INPUT/OUTPUT FROM BOOLEAN OPERATIONS____________"""
+
+
+
[docs]class BooleanPoint: + """2D Point class used in polygon boolean operations. + + Args: + x: Float for X coordinate. + y: Float for Y coordinate + """ + + def __init__(self, x, y): + self.x = x + self.y = y + +
[docs] def is_equivalent(self, other_pt, tol): + """Check if this point is equivalent ot another within tolerance.""" + if not isinstance(other_pt, BooleanPoint): + return False + return abs(self.x - other_pt.x) < tol and abs(self.y - other_pt.y) < tol
+ +
[docs] @staticmethod + def collinear(pt1, pt2, pt3, tolerance): + """Get a boolean for whether 3 points are colinear.""" + dx1 = pt1.x - pt2.x + dy1 = pt1.y - pt2.y + dx2 = pt2.x - pt3.x + dy2 = pt2.y - pt3.y + return abs(dx1 * dy2 - dx2 * dy1) < tolerance
+ +
[docs] @staticmethod + def compare(pt1, pt2, tolerance): + """Get an integer for the relationship between two points. + + Zero indicates equal. Positive 1 is to the right. Negative 1 is to the left. + """ + if abs(pt1.x - pt2.x) < tolerance: + return 0 if abs(pt1.y - pt2.y) < tolerance else -1 if pt1.y < pt2.y else 1 + return -1 if pt1.x < pt2.x else 1
+ +
[docs] @staticmethod + def point_above_or_on_line(point, left, right, tolerance): + """Get a boolean for whether a point is above or on a line. + + Args: + point: BooleanPoint to be evaluated. + left: BooleanPoint for the left of the line segment. + right: BooleanPoint for the right of the line segment + """ + return (right.x - left.x) * (point.y - left.y) - (right.y - left.y) * ( + point.x - left.x + ) >= -tolerance
+ +
[docs] @staticmethod + def between(point, left, right, tolerance): + """Get a boolean for whether a point is between two points. + + Args: + point: BooleanPoint to be evaluated. + left: BooleanPoint for the left. + right: BooleanPoint for the right. + """ + dPyLy = point.y - left.y + dRxLx = right.x - left.x + dPxLx = point.x - left.x + dRyLy = right.y - left.y + + dot = dPxLx * dRxLx + dPyLy * dRyLy + if dot < tolerance: + return False + + sqlen = dRxLx * dRxLx + dRyLy * dRyLy + if dot - sqlen > -tolerance: + return False + + return True
+ + @staticmethod + def _lines_intersect(a0, a1, b0, b1, tolerance): + """Get an _IntersectionPoint object for the intersection of two line segments. + + Args: + a0: BooleanPoint for the first point of the first line segment. + a1: BooleanPoint for the second point of the first line segment. + b0: BooleanPoint for the first point of the second line segment. + b1: BooleanPoint for the second point of the second line segment. + """ + adx = a1.x - a0.x + ady = a1.y - a0.y + bdx = b1.x - b0.x + bdy = b1.y - b0.y + + axb = adx * bdy - ady * bdx + + if abs(axb) < tolerance: + return None + + dx = a0.x - b0.x + dy = a0.y - b0.y + + a = (bdx * dy - bdy * dx) / axb + b = (adx * dy - ady * dx) / axb + + return _IntersectionPoint( + BooleanPoint.__calc_along_using_value(a, tolerance), + BooleanPoint.__calc_along_using_value(b, tolerance), + BooleanPoint(a0.x + a * adx, a0.y + a * ady), + ) + + @staticmethod + def __calc_along_using_value(value, tolerance): + if value <= -tolerance: + return -2 + elif value < tolerance: + return -1 + elif value - 1 <= -tolerance: + return 0 + elif value - 1 < tolerance: + return 1 + else: + return 2 + + def __repr__(self): + return "{},{}".format(self.x, self.y) + + def __str__(self): + return "{},{}".format(self.x, self.y)
+ + +
[docs]class BooleanPolygon: + """Polygon class used in polygon boolean operations. + + Args: + regions: A list of lists of BooleanPoints representing the 2D points defining + the regions of the Polygon. The first sub-list is typically the + boundary of the polygon and each successive list represents a hole + within the boundary. It is also permissable for the holes + to lie outside the first polygon, in which case the shape is + interpreted as a MultiPolygon. As an alternative to BooleanPoints, + tuples of two float values are also permissable in which case the + values represent the X and Y coordinates of each vertex. + is_inverted: A boolean for whether the Polygon is inverted or not. For + polygons input to the boolean methods, this value should always be + False. (Default: False) + """ + + def __init__(self, regions, is_inverted=False): + _regions = [] + for region in regions: + tmp = [] + for pt in region: + if isinstance(pt, BooleanPoint): + tmp.append(pt) + elif isinstance(pt, tuple): + x, y = pt + tmp.append(BooleanPoint(x, y)) + _regions.append(tmp) + + self.regions = _regions + self.is_inverted = is_inverted
+ + +"""____________PRIMARY COMPUTATION CLASSES AND METHODS____________""" + + +class _Fill: + def __init__(self, below=None, above=None): + self.below = below + self.above = above + + def __repr__(self): + return "{},{}".format(self.above, self.below) + + def __str__(self): + return "{},{}".format(self.above, self.below) + + +class _Segment: + def __init__(self, start, end, myfill=None, otherfill=None): + self.start = start + self.end = end + self.myfill = myfill + self.otherfill = otherfill + + def __repr__(self): + return "S: {}, E: {}".format(self.start, self.end) + + def __str__(self): + return "S: {}, E: {}".format(self.start, self.end) + + +class _PolySegments: + def __init__(self, segments=None, is_inverted=False): + self.segments = segments + self.is_inverted = is_inverted + + +class _CombinedPolySegments: + def __init__(self, combined=None, is_inverted1=False, is_inverted2=False): + self.combined = combined + self.is_inverted1 = is_inverted1 + self.is_inverted2 = is_inverted2 + + +class _Matcher: + def __init__(self, index, matchesHead, matchesPt1): + self.index = index + self.matchesHead = matchesHead + self.matchesPt1 = matchesPt1 + + +class _IntersectionPoint: + def __init__(self, alongA, alongB, pt): + self.alongA = alongA + self.alongB = alongB + self.pt = pt + + +class _Node: + def __init__( + self, isRoot=False, isStart=False, pt=None, seg=None, primary=False, + next=None, previous=None, other=None, ev=None, status=None, remove=None, + ): + self.status = status + self.other = other + self.ev = ev + self.previous = previous + self.next = next + self.isRoot = isRoot + self.remove = remove + self.isStart = isStart + self.pt = pt + self.seg = seg + self.primary = primary + + +class _Transition: + def __init__(self, after, before, insert): + self.after = after + self.before = before + self.insert = insert + + +class _LinkedList: + def __init__(self): + self.__root = _Node(isRoot=True) + + def exists(self, node): + if node is None or node is self.__root: + return False + return True + + def isEmpty(self): + return self.__root.next is None + + def getHead(self): + return self.__root.next + + def insertBefore(self, node, check): + last = self.__root + here = self.__root.next + + while here is not None: + if check(here): + node.previous = here.previous + node.next = here + here.previous.next = node + here.previous = node + return + last = here + here = here.next + last.next = node + node.previous = last + node.next = None + + def findTransition(self, check): + previous = self.__root + here = self.__root.next + + while here is not None: + if check(here): + break + previous = here + here = here.next + + def insert_func(node): + node.previous = previous + node.next = here + previous.next = node + if here is not None: + here.previous = node + return node + + return _Transition( + before=(None if previous is self.__root else previous), + after=here, + insert=insert_func, + ) + + @staticmethod + def node(data): + data.previous = None + data.next = None + + def remove_func(): + data.previous.next = data.next + if data.next is not None: + data.next.previous = data.previous + data.previous = None + data.next = None + + data.remove = remove_func + return data + + +class _Intersecter: + """Primary intersection class.""" + + def __init__(self, selfIntersection, tol): + self.selfIntersection = selfIntersection + self.tol = tol + self.__eventRoot = _LinkedList() + + def newsegment(self, start, end): + return _Segment(start=start, end=end, myfill=_Fill()) + + def segmentCopy(self, start, end, seg): + return _Segment( + start=start, end=end, myfill=_Fill(seg.myfill.below, seg.myfill.above) + ) + + def __eventCompare(self, p1IsStart, p11, p12, p2IsStart, p21, p22): + comp = BooleanPoint.compare(p11, p21, self.tol) + if comp != 0: + return comp + + if p12.is_equivalent(p22, self.tol): + return 0 + + if p1IsStart != p2IsStart: + return 1 if p1IsStart else -1 + + return ( + 1 + if BooleanPoint.point_above_or_on_line( + p12, p21 if p2IsStart else p22, p22 if p2IsStart else p21, self.tol + ) + else -1 + ) + + def __eventAdd(self, ev, otherPt): + def check_func(here): + comp = self.__eventCompare( + ev.isStart, ev.pt, otherPt, here.isStart, here.pt, here.other.pt + ) + return comp < 0 + + self.__eventRoot.insertBefore(ev, check_func) + + def __eventAddSegmentStart(self, segment, primary): + evStart = _LinkedList.node( + _Node( + isStart=True, + pt=segment.start, + seg=segment, + primary=primary, + ) + ) + self.__eventAdd(evStart, segment.end) + return evStart + + def __eventAddSegmentEnd(self, evStart, segment, primary): + evEnd = _LinkedList.node( + _Node( + isStart=False, + pt=segment.end, + seg=segment, + primary=primary, + other=evStart, + ) + ) + evStart.other = evEnd + self.__eventAdd(evEnd, evStart.pt) + + def eventAddSegment(self, segment, primary): + evStart = self.__eventAddSegmentStart(segment, primary) + self.__eventAddSegmentEnd(evStart, segment, primary) + return evStart + + def __eventUpdateEnd(self, ev, end): + ev.other.remove() + ev.seg.end = end + ev.other.pt = end + self.__eventAdd(ev.other, ev.pt) + + def __eventDivide(self, ev, pt): + ns = self.segmentCopy(pt, ev.seg.end, ev.seg) + self.__eventUpdateEnd(ev, pt) + return self.eventAddSegment(ns, ev.primary) + + def __statusCompare(self, ev1, ev2): + a1 = ev1.seg.start + a2 = ev1.seg.end + b1 = ev2.seg.start + b2 = ev2.seg.end + + if BooleanPoint.collinear(a1, b1, b2, self.tol): + if BooleanPoint.collinear(a2, b1, b2, self.tol): + return 1 + return 1 if BooleanPoint.point_above_or_on_line(a2, b1, b2, self.tol) else -1 + return 1 if BooleanPoint.point_above_or_on_line(a1, b1, b2, self.tol) else -1 + + def __statusFindSurrounding(self, statusRoot, ev): + def check_func(here): + return self.__statusCompare(ev, here.ev) > 0 + + return statusRoot.findTransition(check_func) + + def __checkIntersection(self, ev1, ev2): + seg1 = ev1.seg + seg2 = ev2.seg + a1 = seg1.start + a2 = seg1.end + b1 = seg2.start + b2 = seg2.end + + i = BooleanPoint._lines_intersect(a1, a2, b1, b2, self.tol) + if i is None: + if not BooleanPoint.collinear(a1, a2, b1, self.tol): + return None + if a1.is_equivalent(b2, self.tol) or a2.is_equivalent(b1, self.tol): + return None + a1EquB1 = a1.is_equivalent(b1, self.tol) + a2EquB2 = a2.is_equivalent(b2, self.tol) + if a1EquB1 and a2EquB2: + return ev2 + + a1Between = not a1EquB1 and BooleanPoint.between(a1, b1, b2, self.tol) + a2Between = not a2EquB2 and BooleanPoint.between(a2, b1, b2, self.tol) + + if a1EquB1: + if a2Between: + self.__eventDivide(ev2, a2) + else: + self.__eventDivide(ev1, b2) + + return ev2 + elif a1Between: + if not a2EquB2: + if a2Between: + self.__eventDivide(ev2, a2) + else: + self.__eventDivide(ev1, b2) + self.__eventDivide(ev2, a1) + else: + if i.alongA == 0: + if i.alongB == -1: + self.__eventDivide(ev1, b1) + elif i.alongB == 0: + self.__eventDivide(ev1, i.pt) + elif i.alongB == 1: + self.__eventDivide(ev1, b2) + if i.alongB == 0: + if i.alongA == -1: + self.__eventDivide(ev2, a1) + elif i.alongA == 0: + self.__eventDivide(ev2, i.pt) + elif i.alongA == 1: + self.__eventDivide(ev2, a2) + return None + + def __checkBothIntersections(self, above, ev, below): + if above is not None: + eve = self.__checkIntersection(ev, above) + if eve is not None: + return eve + if below is not None: + return self.__checkIntersection(ev, below) + + return None + + def calculate(self, primaryPolyInverted, secondaryPolyInverted): + statusRoot = _LinkedList() + segments = [] + + cnt = 0 + + while not self.__eventRoot.isEmpty(): + cnt += 1 + ev = self.__eventRoot.getHead() + if ev.isStart: + surrounding = self.__statusFindSurrounding(statusRoot, ev) + above = ( + surrounding.before.ev if surrounding.before is not None else None + ) + below = surrounding.after.ev if surrounding.after is not None else None + + eve = self.__checkBothIntersections(above, ev, below) + if eve is not None: + if self.selfIntersection: + toggle = False + if ev.seg.myfill.below is None: + toggle = True + else: + toggle = ev.seg.myfill.above != ev.seg.myfill.below + + if toggle: + eve.seg.myfill.above = not eve.seg.myfill.above + else: + eve.seg.otherfill = ev.seg.myfill + ev.other.remove() + ev.remove() + + if self.__eventRoot.getHead() is not ev: + continue + + if self.selfIntersection: + toggle = False + if ev.seg.myfill.below is None: + toggle = True + else: + toggle = ev.seg.myfill.above != ev.seg.myfill.below + + if below is None: + ev.seg.myfill.below = primaryPolyInverted + else: + ev.seg.myfill.below = below.seg.myfill.above + + if toggle: + ev.seg.myfill.above = not ev.seg.myfill.below + else: + ev.seg.myfill.above = ev.seg.myfill.below + else: + if ev.seg.otherfill is None: + inside = False + if below is None: + inside = ( + secondaryPolyInverted + if ev.primary + else primaryPolyInverted + ) + else: + if ev.primary == below.primary: + inside = below.seg.otherfill.above + else: + inside = below.seg.myfill.above + ev.seg.otherfill = _Fill(inside, inside) + ev.other.status = surrounding.insert(_LinkedList.node(_Node(ev=ev))) + else: + st = ev.status + if st is None: + raise Exception( + 'PolyBool: Zero-length segment detected; ' + 'your tolerance is probably too small or too large' + ) + if statusRoot.exists(st.previous) and statusRoot.exists(st.next): + self.__checkIntersection(st.previous.ev, st.next.ev) + st.remove() + + if not ev.primary: + s = ev.seg.myfill + ev.seg.myfill = ev.seg.otherfill + ev.seg.otherfill = s + segments.append(ev.seg) + self.__eventRoot.getHead().remove() + return segments + + +class _RegionIntersecter(_Intersecter): + def __init__(self, tol): + _Intersecter.__init__(self, True, tol) + + def addRegion(self, region): + pt2 = region[-1] + for i in range(len(region)): + pt1 = pt2 + pt2 = region[i] + forward = BooleanPoint.compare(pt1, pt2, self.tol) + + if forward == 0: + continue + + seg = self.newsegment( + pt1 if forward < 0 else pt2, pt2 if forward < 0 else pt1 + ) + + self.eventAddSegment(seg, True) + + def calculate(self, inverted): + return _Intersecter.calculate(self, inverted, False) + + +class _SegmentIntersecter(_Intersecter): + def __init__(self, tol): + _Intersecter.__init__(self, False, tol) + + def calculate( + self, segments1, is_inverted1, segments2, is_inverted2 + ): + for seg in segments1: + self.eventAddSegment(self.segmentCopy(seg.start, seg.end, seg), True) + + for seg in segments2: + self.eventAddSegment(self.segmentCopy(seg.start, seg.end, seg), False) + + return _Intersecter.calculate(self, is_inverted1, is_inverted2) + + +class _SegmentChainerMatcher: + def __init__(self): + self.firstMatch = _Matcher(0, False, False) + self.secondMatch = _Matcher(0, False, False) + + self.nextMatch = self.firstMatch + + def setMatch(self, index, matchesHead, matchesPt1): + self.nextMatch.index = index + self.nextMatch.matchesHead = matchesHead + self.nextMatch.matchesPt1 = matchesPt1 + if self.nextMatch is self.firstMatch: + self.nextMatch = self.secondMatch + return False + self.nextMatch = None + return True + + +def _list_shift(list): + list.pop(0) + + +def _list_pop(list): + list.pop() + + +def _list_splice(list, index, count): + del list[index: index + count] + + +def _list_unshift(list, element): + list.insert(0, element) + + +def _segmentChainer(segments, tol): + regions = [] + chains = [] + + for seg in segments: + pt1 = seg.start + pt2 = seg.end + if pt1.is_equivalent(pt2, tol): + continue + + scm = _SegmentChainerMatcher() + + for i in range(len(chains)): + chain = chains[i] + head = chain[0] + tail = chain[-1] + + if head.is_equivalent(pt1, tol): + if scm.setMatch(i, True, True): + break + elif head.is_equivalent(pt2, tol): + if scm.setMatch(i, True, False): + break + elif tail.is_equivalent(pt1, tol): + if scm.setMatch(i, False, True): + break + elif tail.is_equivalent(pt2, tol): + if scm.setMatch(i, False, False): + break + + if scm.nextMatch is scm.firstMatch: + chains.append([pt1, pt2]) + continue + + if scm.nextMatch is scm.secondMatch: + index = scm.firstMatch.index + pt = pt2 if scm.firstMatch.matchesPt1 else pt1 + addToHead = scm.firstMatch.matchesHead + + chain = chains[index] + grow = chain[0] if addToHead else chain[-1] + grow2 = chain[1] if addToHead else chain[-2] + oppo = chain[-1] if addToHead else chain[0] + oppo2 = chain[-2] if addToHead else chain[1] + + if BooleanPoint.collinear(grow2, grow, pt, tol): + if addToHead: + _list_shift(chain) + else: + _list_pop(chain) + grow = grow2 + if oppo.is_equivalent(pt, tol): + _list_splice(chains, index, 1) + if BooleanPoint.collinear(oppo2, oppo, grow, tol): + if addToHead: + _list_pop(chain) + else: + _list_shift(chain) + regions.append(chain) + continue + if addToHead: + _list_unshift(chain, pt) + else: + chain.append(pt) + continue + + def reverseChain(index): + chains[index].reverse() + + def appendChain(index1, index2): + chain1 = chains[index1] + chain2 = chains[index2] + tail = chain1[-1] + tail2 = chain1[-2] + head = chain2[0] + head2 = chain2[1] + + if BooleanPoint.collinear(tail2, tail, head, tol): + _list_pop(chain1) + tail = tail2 + if BooleanPoint.collinear(tail, head, head2, tol): + _list_shift(chain2) + + chains[index1] = chain1 + chain2 + _list_splice(chains, index2, 1) + + f = scm.firstMatch.index + s = scm.secondMatch.index + + reverseF = len(chains[f]) < len(chains[s]) + if scm.firstMatch.matchesHead: + if scm.secondMatch.matchesHead: + if reverseF: + reverseChain(f) + appendChain(f, s) + else: + reverseChain(s) + appendChain(s, f) + else: + appendChain(s, f) + else: + if scm.secondMatch.matchesHead: + appendChain(f, s) + else: + if reverseF: + reverseChain(f) + appendChain(s, f) + else: + reverseChain(s) + appendChain(f, s) + + return regions + + +def __select(segments, selection): + result = [] + for seg in segments: + index = ( + (8 if seg.myfill.above else 0) + + (4 if seg.myfill.below else 0) + + (2 if seg.otherfill is not None and seg.otherfill.above else 0) + + (1 if seg.otherfill is not None and seg.otherfill.below else 0) + ) + + if selection[index] != 0: + result.append( + _Segment( + start=seg.start, + end=seg.end, + myfill=_Fill(selection[index] == 2, above=selection[index] == 1), + ) + ) + return result + + +"""____________CORE INTERFACE FOR MANAGING INTERSECTIONS____________""" + + +def _segments(poly, tol): + """Get the intersected PolySegments of a BooleanPolygon. + + Args: + poly: A BooleanPolygon for which PolySegments will be computed. + tol: The intersection tolerance. + """ + i = _RegionIntersecter(tol) + for region in poly.regions: + i.addRegion(region) + return _PolySegments(i.calculate(poly.is_inverted), poly.is_inverted) + + +def _combine(segments1, segments2, tol): + """Combine intersected PolySegments into a CombinedPolySegments object. + + Args: + segments1: The first PolySegments object to be combined. + segments2: The second PolySegments to be combined. + tol: The intersection tolerance. + """ + i = _SegmentIntersecter(tol) + return _CombinedPolySegments( + i.calculate( + segments1.segments, + segments1.is_inverted, + segments2.segments, + segments2.is_inverted, + ), + segments1.is_inverted, + segments2.is_inverted, + ) + + +def _select_union(polyseg): + """Select the union from the PolySegments. + + above1 below1 above2 below2 Keep? Value + 0 0 0 0 => no 0 + 0 0 0 1 => yes filled below 2 + 0 0 1 0 => yes filled above 1 + 0 0 1 1 => no 0 + 0 1 0 0 => yes filled below 2 + 0 1 0 1 => yes filled below 2 + 0 1 1 0 => no 0 + 0 1 1 1 => no 0 + 1 0 0 0 => yes filled above 1 + 1 0 0 1 => no 0 + 1 0 1 0 => yes filled above 1 + 1 0 1 1 => no 0 + 1 1 0 0 => no 0 + 1 1 0 1 => no 0 + 1 1 1 0 => no 0 + 1 1 1 1 => no 0 + """ + return _PolySegments( + segments=__select( + # fmt:off + polyseg.combined, [ + 0, 2, 1, 0, + 2, 2, 0, 0, + 1, 0, 1, 0, + 0, 0, 0, 0, + ] + # fmt:on + ), + is_inverted=(polyseg.is_inverted1 or polyseg.is_inverted2), + ) + + +def _select_intersect(polyseg): + """Select the intersection from the PolySegments. + + above1 below1 above2 below2 Keep? Value + 0 0 0 0 => no 0 + 0 0 0 1 => no 0 + 0 0 1 0 => no 0 + 0 0 1 1 => no 0 + 0 1 0 0 => no 0 + 0 1 0 1 => yes filled below 2 + 0 1 1 0 => no 0 + 0 1 1 1 => yes filled below 2 + 1 0 0 0 => no 0 + 1 0 0 1 => no 0 + 1 0 1 0 => yes filled above 1 + 1 0 1 1 => yes filled above 1 + 1 1 0 0 => no 0 + 1 1 0 1 => yes filled below 2 + 1 1 1 0 => yes filled above 1 + 1 1 1 1 => no 0 + """ + return _PolySegments( + segments=__select( + # fmt:off + polyseg.combined, [ + 0, 0, 0, 0, + 0, 2, 0, 2, + 0, 0, 1, 1, + 0, 2, 1, 0 + ] + # fmt:on + ), + is_inverted=(polyseg.is_inverted1 and polyseg.is_inverted2), + ) + + +def _select_difference(polyseg): + """Select the difference from the PolySegments. + + above1 below1 above2 below2 Keep? Value + 0 0 0 0 => no 0 + 0 0 0 1 => no 0 + 0 0 1 0 => no 0 + 0 0 1 1 => no 0 + 0 1 0 0 => yes filled below 2 + 0 1 0 1 => no 0 + 0 1 1 0 => yes filled below 2 + 0 1 1 1 => no 0 + 1 0 0 0 => yes filled above 1 + 1 0 0 1 => yes filled above 1 + 1 0 1 0 => no 0 + 1 0 1 1 => no 0 + 1 1 0 0 => no 0 + 1 1 0 1 => yes filled above 1 + 1 1 1 0 => yes filled below 2 + 1 1 1 1 => no 0 + """ + return _PolySegments( + segments=__select( + # fmt:off + polyseg.combined, [ + 0, 0, 0, 0, + 2, 0, 2, 0, + 1, 1, 0, 0, + 0, 1, 2, 0 + ] + # fmt:on + ), + is_inverted=(polyseg.is_inverted1 and not polyseg.is_inverted2), + ) + + +def _select_difference_rev(polyseg): + """Select the reversed difference from the PolySegments. + + above1 below1 above2 below2 Keep? Value + 0 0 0 0 => no 0 + 0 0 0 1 => yes filled below 2 + 0 0 1 0 => yes filled above 1 + 0 0 1 1 => no 0 + 0 1 0 0 => no 0 + 0 1 0 1 => no 0 + 0 1 1 0 => yes filled above 1 + 0 1 1 1 => yes filled above 1 + 1 0 0 0 => no 0 + 1 0 0 1 => yes filled below 2 + 1 0 1 0 => no 0 + 1 0 1 1 => yes filled below 2 + 1 1 0 0 => no 0 + 1 1 0 1 => no 0 + 1 1 1 0 => no 0 + 1 1 1 1 => no 0 + """ + return _PolySegments( + segments=__select( + # fmt:off + polyseg.combined, [ + 0, 2, 1, 0, + 0, 0, 1, 1, + 0, 2, 0, 2, + 0, 0, 0, 0 + ] + # fmt:on + ), + is_inverted=(not polyseg.is_inverted1 and polyseg.is_inverted2), + ) + + +def _select_xor(polyseg): + """Select the exclusive disjunction from the PolySegments. + + above1 below1 above2 below2 Keep? Value + 0 0 0 0 => no 0 + 0 0 0 1 => yes filled below 2 + 0 0 1 0 => yes filled above 1 + 0 0 1 1 => no 0 + 0 1 0 0 => yes filled below 2 + 0 1 0 1 => no 0 + 0 1 1 0 => no 0 + 0 1 1 1 => yes filled above 1 + 1 0 0 0 => yes filled above 1 + 1 0 0 1 => no 0 + 1 0 1 0 => no 0 + 1 0 1 1 => yes filled below 2 + 1 1 0 0 => no 0 + 1 1 0 1 => yes filled above 1 + 1 1 1 0 => yes filled below 2 + 1 1 1 1 => no 0 + """ + return _PolySegments( + segments=__select( + # fmt:off + polyseg.combined, [ + 0, 2, 1, 0, + 2, 0, 0, 1, + 1, 0, 0, 2, + 0, 1, 2, 0 + ] + # fmt:on + ), + is_inverted=(polyseg.is_inverted1 != polyseg.is_inverted2), + ) + + +def _polygon(segments, tol): + return BooleanPolygon(_segmentChainer(segments.segments, tol), segments.is_inverted) + + +def __operate(poly1, poly2, selector, tol): + firstPolygonRegions = _segments(poly1, tol) + secondPolygonRegions = _segments(poly2, tol) + combinedSegments = _combine(firstPolygonRegions, secondPolygonRegions, tol) + seg = selector(combinedSegments) + return _polygon(seg, tol) + + +"""____________PUBLIC FUNCTIONS FOR BOOLEAN OPERATIONS____________""" + + +
[docs]def union_all(polygons, tolerance): + """Get a BooleanPolygon for the union of multiple polygons. + + Using this method is more computationally efficient than calling the union() + method multiple times as this method will only compute the intersection of + the segments once. + + Args: + polygons: An array of BooleanPolygons for which the union will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A BooleanPolygon for the union across all of the input polygons. + """ + seg1 = _segments(polygons[0], tolerance) + for i in range(1, len(polygons)): + seg2 = _segments(polygons[i], tolerance) + comb = _combine(seg1, seg2, tolerance) + seg1 = _select_union(comb) + return _polygon(seg1, tolerance)
+ + +
[docs]def intersect_all(polygons, tolerance): + """Get a BooleanPolygon for the intersection of multiple polygons. + + Using this method is more computationally efficient than calling the intersect() + method multiple times as this method will only compute the intersection of + the segments once. + + Args: + polygons: An array of BooleanPolygons for which the intersection will + be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A BooleanPolygon for the intersection across all of the input polygons. + """ + seg1 = _segments(polygons[0], tolerance) + for i in range(1, len(polygons)): + seg2 = _segments(polygons[i], tolerance) + comb = _combine(seg1, seg2, tolerance) + seg1 = _select_intersect(comb) + return _polygon(seg1, tolerance)
+ + +
[docs]def split(poly1, poly2, tolerance): + """Split two BooleanPolygons with one another to get the intersection and difference. + + Using this method is more computationally efficient than calling the intersect() + and difference() methods individually as this method will only compute the + intersection of the segments once. + + Args: + poly1: A BooleanPolygon for the first polygon that will be split with + the second polygon. + poly2: A BooleanPolygon for the second polygon that will be split with + the first polygon. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A tuple with three elements + + - intersection: A BooleanPolygon for the intersection of the two + input polygons. + + - poly1_difference: A BooleanPolygon for the portion of poly1 that does + not overlap with poly2. When combined with the intersection, this + makes a split version of poly1. + + - poly2_difference: A BooleanPolygon for the portion of poly2 that does + not overlap with poly1. When combined with the intersection, this + makes a split version of poly2. + """ + first_regions = _segments(poly1, tolerance) + second_regions = _segments(poly2, tolerance) + comb = _combine(first_regions, second_regions, tolerance) + intersection = _polygon(_select_intersect(comb), tolerance) + poly1_difference = _polygon(_select_difference(comb), tolerance) + poly2_difference = _polygon(_select_difference_rev(comb), tolerance) + return intersection, poly1_difference, poly2_difference
+ + +
[docs]def union(poly1, poly2, tolerance): + """Get a BooleanPolygon for the union of two polygons. + + Note that the result will not differentiate hole polygons from boundary polygons. + + Args: + poly1: A BooleanPolygon for the first polygon for which the union will + be computed. + poly2: A BooleanPolygon for the second polygon for which the union will + be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A BooleanPolygon for the union of the two polygons. + """ + return __operate(poly1, poly2, _select_union, tolerance)
+ + +
[docs]def intersect(poly1, poly2, tolerance): + """Get a BooleanPolygon for the intersection of two polygons. + + Args: + poly1: A BooleanPolygon for the first polygon for which the intersection + will be computed. + poly2: A BooleanPolygon for the second polygon for which the intersection + will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A BooleanPolygon for the intersection of the two polygons. + """ + return __operate(poly1, poly2, _select_intersect, tolerance)
+ + +
[docs]def difference(poly1, poly2, tolerance): + """Get a BooleanPolygon for the subtraction of poly2 from poly1. + + Args: + poly1: A BooleanPolygon for the the polygon that will be subtracted from. + poly2: A BooleanPolygon for the polygon to subtract with. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A BooleanPolygon for the difference of poly1 - poly2. + """ + return __operate(poly1, poly2, _select_difference, tolerance)
+ + +
[docs]def difference_reversed(poly1, poly2, tolerance): + """Get a BooleanPolygon for the subtraction of poly1 from poly2. + + Args: + poly1: A BooleanPolygon for the polygon to subtract with. + poly2: A BooleanPolygon for the the polygon that will be subtracted from. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A BooleanPolygon for the difference of poly2 - poly1. + """ + return __operate(poly1, poly2, _select_difference_rev, tolerance)
+ + +
[docs]def xor(poly1, poly2, tolerance): + """Get a BooleanPolygon for the exclusive disjunction of two Polygons. + + Note that this method is prone to merging holes that may exist in the + result into the boundary to create a single list of joined vertices, + which may not always be desirable. In this case, it may be desirable + to do two separate difference calculations instead or use the split method. + + Also note that, when the result includes separate polygons for holes, + it will not differentiate hole polygons from boundary polygons. + + Args: + poly1: A BooleanPolygon for the first polygon for which the exclusive + disjunction will be computed. + poly2: A BooleanPolygon for the second polygon for which the exclusive + disjunction will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A BooleanPolygon for the exclusive disjunction of the two polygons. + """ + return __operate(poly1, poly2, _select_xor, tolerance)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/bounding.html b/docs/_modules/ladybug_geometry/bounding.html new file mode 100644 index 00000000..d1a4acdc --- /dev/null +++ b/docs/_modules/ladybug_geometry/bounding.html @@ -0,0 +1,1294 @@ + + + + + + + ladybug_geometry.bounding — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.bounding

+# coding=utf-8
+"""Utility functions for computing bounding boxes and extents around geometry."""
+from __future__ import division
+
+from ladybug_geometry.geometry2d.pointvector import Point2D
+from ladybug_geometry.geometry3d.pointvector import Point3D
+
+
+
[docs]def bounding_domain_x(geometries): + """Get minimum and maximum X coordinates of multiple geometries. + + Args: + geometries: An array of any ladybug_geometry objects for which the extents + of the X domain will be computed. Note that all objects must have + a min and max property. + + Returns: + A tuple with the min and the max X coordinates around the geometry. + """ + min_x, max_x = geometries[0].min.x, geometries[0].max.x + for geom in geometries[1:]: + if geom.min.x < min_x: + min_x = geom.min.x + if geom.max.x > max_x: + max_x = geom.max.x + return min_x, max_x
+ + +
[docs]def bounding_domain_y(geometries): + """Get minimum and maximum Y coordinates of multiple geometries. + + Args: + geometries: An array of any ladybug_geometry objects for which the extents + of the Y domain will be computed. Note that all objects must have + a min and max property. + + Returns: + A tuple with the min and the max Y coordinates around the geometry. + """ + min_y, max_y = geometries[0].min.y, geometries[0].max.y + for geom in geometries[1:]: + if geom.min.y < min_y: + min_y = geom.min.y + if geom.max.y > max_y: + max_y = geom.max.y + return min_y, max_y
+ + +
[docs]def bounding_domain_z(geometries): + """Get minimum and maximum Z coordinates of multiple geometries. + + Args: + geometries: An array of any 3D ladybug_geometry objects for which the extents + of the Z domain will be computed. Note that all objects must have + a min and max property and they cannot be 2D objects. + + Returns: + A tuple with the min and the max Z coordinates around the geometry. + """ + min_z, max_z = geometries[0].min.z, geometries[0].max.z + for geom in geometries: + if geom.max.z > max_z: + max_z = geom.max.z + if geom.min.z < min_z: + min_z = geom.min.z + return min_z, max_z
+ + +
[docs]def bounding_domain_z_2d_safe(geometries): + """Get minimum and maximum Z coordinates in a manner that is safe for 2D geometries. + + Args: + geometries: An array of any ladybug_geometry objects for which the extents + of the Z domain will be computed. Any 2D objects within this list will + be assumed to have a Z-value of zero. + + Returns: + A tuple with the min and the max Z coordinates around the geometry. + """ + try: + min_z, max_z = geometries[0].min.z, geometries[0].max.z + except AttributeError: + min_z, max_z = 0, 0 + for geom in geometries: + try: + if geom.max.z > max_z: + max_z = geom.max.z + if geom.min.z < min_z: + min_z = geom.min.z + except AttributeError: + if 0 > max_z: + max_z = 0 + if 0 < min_z: + min_z = 0 + return min_z, max_z
+ + +def _orient_geometry(geometries, axis_angle, center): + """Orient both 2D and 3D geometry to a given axis angle and center point. + + This is used by the methods that compute bounding rectangles. + """ + new_geometries = [] + for geom in geometries: + try: # assume that it is a 2D geometry object + new_geometries.append(geom.rotate(-axis_angle, center)) + except TypeError: # it's a 3D geometry object + new_geometries.append(geom.rotate_xy(-axis_angle, center)) + return new_geometries + + +
[docs]def bounding_rectangle(geometries, axis_angle=0): + """Get the min and max of an oriented bounding rectangle around 2D or 3D geometry. + + Args: + geometries: An array of 2D or 3D geometry objects. Note that all objects + must have a min and max property. + axis_angle: The counter-clockwise rotation angle in radians in the XY plane + to represent the orientation of the bounding rectangle extents. (Default: 0). + + Returns: + A tuple with two Point2D objects representing the min point and max point + of the bounding rectangle respectively. + """ + if axis_angle != 0: # rotate geometry to the bounding box + cpt = geometries[0].vertices[0] + geometries = _orient_geometry(geometries, axis_angle, cpt) + xx = bounding_domain_x(geometries) + yy = bounding_domain_y(geometries) + min_pt = Point2D(xx[0], yy[0]) + max_pt = Point2D(xx[1], yy[1]) + if axis_angle != 0: # rotate the points back + cpt = Point2D(cpt.x, cpt.y) # cast Point3D to Point2D + min_pt = min_pt.rotate(axis_angle, cpt) + max_pt = max_pt.rotate(axis_angle, cpt) + return min_pt, max_pt
+ + +
[docs]def bounding_rectangle_extents(geometries, axis_angle=0): + """Get the width and length of an oriented bounding rectangle around 2D or 3D geometry. + + Args: + geometries: An array of 2D or 3D geometry objects. Note that all objects + must have a min and max property. + axis_angle: The counter-clockwise rotation angle in radians in the XY plane + to represent the orientation of the bounding rectangle extents. (Default: 0). + + Returns: + A tuple with 2 values corresponding to the width and length of the bounding + rectangle. + """ + if axis_angle != 0: + cpt = geometries[0].vertices[0] + geometries = _orient_geometry(geometries, axis_angle, cpt) + xx = bounding_domain_x(geometries) + yy = bounding_domain_y(geometries) + return xx[1] - xx[0], yy[1] - yy[0]
+ + +
[docs]def bounding_box(geometries, axis_angle=0): + """Get the min and max of an oriented bounding box around 3D geometry. + + Args: + geometries: An array of 3D geometry objects. Note that all objects must + have a min and max property. + axis_angle: The counter-clockwise rotation angle in radians in the XY plane + to represent the orientation of the bounding box extents. (Default: 0). + + Returns: + A tuple with two Point3D objects representing the min point and max point + of the bounding box respectively. + """ + if axis_angle != 0: # rotate geometry to the bounding box + cpt = geometries[0].vertices[0] + geometries = [geom.rotate_xy(-axis_angle, cpt) for geom in geometries] + xx = bounding_domain_x(geometries) + yy = bounding_domain_y(geometries) + zz = bounding_domain_z_2d_safe(geometries) + min_pt = Point3D(xx[0], yy[0], zz[0]) + max_pt = Point3D(xx[1], yy[1], zz[1]) + if axis_angle != 0: # rotate the points back + min_pt = min_pt.rotate_xy(axis_angle, cpt) + max_pt = max_pt.rotate_xy(axis_angle, cpt) + return min_pt, max_pt
+ + +
[docs]def bounding_box_extents(geometries, axis_angle=0): + """Get the width, length and height of an oriented bounding box around 3D geometry. + + Args: + geometries: An array of 3D geometry objects. Note that all objects must + have a min and max property. + axis_angle: The counter-clockwise rotation angle in radians in the XY plane + to represent the orientation of the bounding box extents. (Default: 0). + + Returns: + A tuple with 3 values corresponding to the width, length and height of + the bounding box. + """ + if axis_angle != 0: + cpt = geometries[0].vertices[0] + geometries = [geom.rotate_xy(-axis_angle, cpt) for geom in geometries] + xx = bounding_domain_x(geometries) + yy = bounding_domain_y(geometries) + zz = bounding_domain_z_2d_safe(geometries) + return xx[1] - xx[0], yy[1] - yy[0], zz[1] - zz[0]
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/dictutil.html b/docs/_modules/ladybug_geometry/dictutil.html new file mode 100644 index 00000000..ab86caf3 --- /dev/null +++ b/docs/_modules/ladybug_geometry/dictutil.html @@ -0,0 +1,1154 @@ + + + + + + + ladybug_geometry.dictutil — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.dictutil

+# coding=utf-8
+"""Utilities to convert any Ladybug Geometry dictionary to Python objects.
+
+Note that importing this module will import almost all modules within the
+Ladybug_geometry library in order to be able to re-serialize almost any
+dictionary produced from the library.
+"""
+
+from ladybug_geometry.geometry2d import Vector2D, Point2D, Ray2D, \
+    LineSegment2D, Arc2D, Polyline2D, Polygon2D, Mesh2D
+from ladybug_geometry.geometry3d import Vector3D, Point3D, Ray3D, LineSegment3D, \
+    Arc3D, Polyline3D, Polyface3D, Mesh3D, Plane, Face3D, Sphere, Cone, Cylinder
+
+
+
[docs]def geometry_dict_to_object(ladybug_geom_dict, raise_exception=True): + """ + Args: + ladybug_geom_dict (dict): A dictionary of any Ladybug Geometry object. + raise_exception (bool): Boolean to note whether an exception should be raised + if the object is not identified as a part of ladybug_geometry. + Default: True. + + Returns: + A Python object derived from the input ladybug_geom_dict. + """ + + lbt_types = { + 'Vector2D': Vector2D, + 'Point2D': Point2D, + 'Ray2D': Ray2D, + 'LineSegment2D': LineSegment2D, + 'Arc2D': Arc2D, + 'Polyline2D': Polyline2D, + 'Polygon2D': Polygon2D, + 'Mesh2D': Mesh2D, + 'Vector3D': Vector3D, + 'Point3D': Point3D, + 'Ray3D': Ray3D, + 'LineSegment3D': LineSegment3D, + 'Arc3D': Arc3D, + 'Polyline3D': Polyline3D, + 'Mesh3D': Mesh3D, + 'Plane': Plane, + 'Polyface3D': Polyface3D, + 'Face3D': Face3D, + 'Sphere': Sphere, + 'Cone': Cone, + 'Cylinder': Cylinder + } + + # Get the ladybug_geometry object 'Type' + try: + obj_type = ladybug_geom_dict['type'] + except KeyError: + raise ValueError('Ladybug dictionary lacks required "type" key.') + + # Build a new Ladybug Python Object based on the "Type" + try: + lbt_class = lbt_types[obj_type] + return lbt_class.from_dict(ladybug_geom_dict) + except KeyError: + if raise_exception: + raise ValueError( + '{} is not a recognized ladybug geometry type'.format(obj_type)) + else: + return None
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry2d/arc.html b/docs/_modules/ladybug_geometry/geometry2d/arc.html new file mode 100644 index 00000000..d7305841 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry2d/arc.html @@ -0,0 +1,1581 @@ + + + + + + + ladybug_geometry.geometry2d.arc — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry2d.arc

+# coding=utf-8
+"""2D Arc"""
+from __future__ import division
+import math
+
+from .pointvector import Point2D, Vector2D
+from .polyline import Polyline2D
+from ..intersection2d import closest_point2d_on_arc2d, intersect_line2d_arc2d, \
+    intersect_line2d_infinite_arc2d
+
+
+
[docs]class Arc2D(object): + """2D arc object. + + Args: + c: A Point2D representing the center of the arc. + r: A number representing the radius of the arc. + a1: A number between 0 and 2 * pi for the start angle of the arc. + Note that the direction of the arc is always counterclockwise. + a2: A number between 0 and 2 * pi for the end angle of the arc. + Note that the direction of the arc is always counterclockwise. + + Properties: + * c + * r + * a1 + * a2 + * p1 + * p2 + * midpoint + * min + * max + * length + * angle + * is_circle + * is_inverted + """ + __slots__ = ( + '_c', '_r', '_a1', '_a2', '_cos_a1', '_sin_a1', '_cos_a2', '_sin_a2', + '_min', '_max') + + def __init__(self, c, r, a1=0, a2=2 * math.pi): + """Initialize Arc2D.""" + assert isinstance(c, Point2D), "Expected Point2D. Got {}.".format(type(c)) + assert r > 0, 'Arc radius must be greater than 0. Got {}.'.format(r) + assert 0 <= a1 <= 2 * math.pi, 'Arc start angle must be between 0 and 2*pi. ' \ + 'Got {}.'.format(a1) + assert 0 <= a2 <= 2 * math.pi, 'Arc start angle must be between 0 and 2*pi. ' \ + 'Got {}.'.format(a2) + self._c = c + self._r = r + self._a1 = a1 + self._a2 = a2 + self._cos_a1 = math.cos(a1) + self._sin_a1 = math.sin(a1) + self._cos_a2 = math.cos(a2) + self._sin_a2 = math.sin(a2) + self._min = None + self._max = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Arc2D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Arc2D" + "c": (10, 0), + "r": 5, + "a1": 0, + "a2": 3.14159 + } + """ + return cls(Point2D.from_array(data['c']), + data['r'], data['a1'], data['a2'])
+ +
[docs] @classmethod + def from_start_mid_end(cls, p1, m, p2, circle=False): + """Initialize a new arc from start, middle, and end points. + + Note that input points will be assumed to be in counterclockwise order. + + Args: + p1: The start point of the arc. + m: Any point along the length of the arc that is not the start or end. + p2: The end point of the arc. + circle: Set to True if you would like the output to be a full circle + defined by the three points instead of an arc with a start and end. + Default is False. + """ + for pt in (p1, m, p2): + assert isinstance(pt, Point2D), "Expected Point2D. Got {}.".format(type(pt)) + e1 = (p1.x ** 2 + p1.y ** 2) + e2 = (m.x ** 2 + m.y ** 2) + e3 = (p2.x ** 2 + p2.y ** 2) + den = 2 * (p1.x * (m.y - p2.y) - p1.y * (m.x - p2.x) + m.x * p2.y - p2.x * m.y) + try: + x = -(e1 * (p2.y - m.y) + e2 * (p1.y - p2.y) + e3 * (m.y - p1.y)) / den + y = -(e1 * (m.x - p2.x) + e2 * (p2.x - p1.x) + e3 * (p1.x - m.x)) / den + except ZeroDivisionError: + raise ValueError('Input points {}, {}, {} are colinear and ' + 'cannot define an arc.'.format(p1, m, p2)) + r = math.sqrt((x - p1.x) ** 2 + (y - p1.y) ** 2) + if circle is True: + return cls(Point2D(x, y), r) + else: + a1 = Vector2D(1, 0).angle_counterclockwise(Vector2D(p1.x - x, p1.y - y)) + a2 = Vector2D(1, 0).angle_counterclockwise(Vector2D(p2.x - x, p2.y - y)) + return cls(Point2D(x, y), r, a1, a2)
+ + @property + def c(self): + """Center point of the circle on which the arc lies.""" + return self._c + + @property + def r(self): + """Radius of arc.""" + return self._r + + @property + def a1(self): + """Start angle of the arc in radians.""" + return self._a1 + + @property + def a2(self): + """End angle of the arc in radians.""" + return self._a2 + + @property + def p1(self): + """Start point.""" + return Point2D( + self.c.x + self._cos_a1 * self.r, self.c.y + self._sin_a1 * self.r) + + @property + def p2(self): + """End point.""" + return Point2D( + self.c.x + self._cos_a2 * self.r, self.c.y + self._sin_a2 * self.r) + + @property + def midpoint(self): + """Midpoint.""" + return self.point_at(0.5) + + @property + def min(self): + """A Point2D for the minimum bounding rectangle vertex around this geometry.""" + if self._min is None: + self._calculate_min_max() + return self._min + + @property + def max(self): + """A Point2D for the maximum bounding rectangle vertex around this geometry.""" + if self._max is None: + self._calculate_min_max() + return self._max + + @property + def length(self): + """The length of the arc.""" + return self.angle * self.r + + @property + def angle(self): + """The total angle over the domain of the arc in radians.""" + _diff = self._a2 - self._a1 + return _diff if not self.is_inverted else 2 * math.pi + _diff + + @property + def area(self): + """Area of the circle to which the arc belongs.""" + assert self.is_circle, 'Arc must be a closed circle to access "area" property.' + return math.pi * self.r ** 2 + + @property + def is_circle(self): + """Boolean for whether the arc is a full circle (True) or not (False).""" + return self.a1 == 0 and self.a2 == 2 * math.pi + + @property + def is_inverted(self): + """Boolean noting whether the end angle a2 is smaller than the start angle a1.""" + return self._a2 < self._a1 + +
[docs] def move(self, moving_vec): + """Get an arc that has been moved along a vector. + + Args: + moving_vec: A Vector2D with the direction and distance to move the arc. + """ + return Arc2D(self.c.move(moving_vec), self.r, self.a1, self.a2)
+ +
[docs] def rotate(self, angle, origin): + """Get a arc that is rotated counterclockwise by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point2D for the origin around which the arc will + be rotated. + """ + _a1 = self.a1 + angle + _a2 = self.a2 + angle + _a1 = _a1 - 2 * math.pi if _a1 > 2 * math.pi else _a1 + _a2 = _a2 - 2 * math.pi if _a2 > 2 * math.pi else _a2 + return Arc2D(self.c.rotate(angle, origin), self.r, _a1, _a2)
+ +
[docs] def reflect(self, normal, origin): + """Get a arc reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector2D representing the normal vector for the plane across + which the arc will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point2D representing the origin from which to reflect. + """ + return Arc2D.from_start_mid_end(self.p2.reflect(normal, origin), + self.midpoint.reflect(normal, origin), + self.p1.reflect(normal, origin))
+ +
[docs] def scale(self, factor, origin=None): + """Scale a arc by a factor from an origin point. + + Args: + factor: A number representing how much the arc should be scaled. + origin: A Point2D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0). + """ + return Arc2D(self.c.scale(factor, origin), self.r * factor, self.a1, self.a2)
+ +
[docs] def subdivide(self, distances): + """Get Point2D values along the arc that subdivide it based on input distances. + + Args: + distances: A list of distances along the arc at which to subdivide it. + This can also be a single number that will be repeated until the + end of the arc. + """ + if isinstance(distances, (float, int)): + distances = [distances] + arc_length = self.length + dist = distances[0] + index = 0 + sub_pts = [self.p1] + while dist < arc_length: + sub_pts.append(self.point_at_length(dist)) + if index < len(distances) - 1: + index += 1 + dist += distances[index] + sub_pts.append(self.p2) + return sub_pts
+ +
[docs] def subdivide_evenly(self, number): + """Get Point2D values along the arc that divide it into evenly-spaced segments. + + Args: + number: The number of segments into which the arc will be divided. + """ + interval = 1 / number + parameter = interval + sub_pts = [self.p1] + while parameter <= 1.000000001: + sub_pts.append(self.point_at(parameter)) + parameter += interval + return sub_pts
+ +
[docs] def point_at(self, parameter): + """Get a point at a given fraction along the arc. + + Args: + parameter: The fraction between the start and end point where the + desired point lies. For example, 0.5 will yield the midpoint. + """ + _ang = self._a1 + self.angle * parameter + _ang = _ang if _ang <= math.pi * 2 else _ang - math.pi * 2 + return Point2D( + self.c.x + math.cos(_ang) * self.r, self.c.y + math.sin(_ang) * self.r)
+ +
[docs] def point_at_angle(self, angle): + """Get a point at a given angle along the arc. + + Args: + angle: The angle in radians from the start point along the arc + to get the Point2D. + """ + _ang = self._a1 + angle + _ang = _ang if _ang <= math.pi * 2 else _ang - math.pi * 2 + return Point2D( + self.c.x + math.cos(_ang) * self.r, self.c.y + math.sin(_ang) * self.r)
+ +
[docs] def point_at_length(self, length): + """Get a point at a given distance along the arc segment. + + Args: + length: The distance along the arc from the start point where the + desired point lies. + """ + return self.point_at(length / self.length)
+ +
[docs] def closest_point(self, point): + """Get the closest Point2D on this object to another Point2D. + + Args: + point: A Point2D object to which the closest point on this object + will be computed. + + Returns: + Point2D for the closest point on this line to the input point. + """ + return closest_point2d_on_arc2d(point, self)
+ +
[docs] def distance_to_point(self, point): + """Get the minimum distance between this object and the input point. + + Args: + point: A Point2D object to which the minimum distance will be computed. + + Returns: + The distance to the input point. + """ + close_pt = self.closest_point(point) + return point.distance_to_point(close_pt)
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersection between this Arc2D and another Ray2 or LineSegment2D. + + Args: + line_ray: Another LineSegment2D or Ray2D or to intersect. + + Returns: + A list of 2 Point2D objects if a full intersection exists. + A list with a single Point2D object if the line is tangent or intersects + only once. None if no intersection exists. + """ + return intersect_line2d_arc2d(line_ray, self)
+ +
[docs] def intersect_line_infinite(self, line_ray): + """Get the intersection between this Arc2D and an infinitely extending Ray2D. + + Args: + line_ray: Another LineSegment2D or Ray2D or to intersect. + + Returns: + A list of 2 Point2D objects if a full intersection exists. + A list with a single Point2D object if the line is tangent or intersects + only once. None if no intersection exists. + """ + return intersect_line2d_infinite_arc2d(line_ray, self)
+ +
[docs] def split_line_infinite(self, line_ray): + """Split this Arc2D in 2-3 using an infinitely extending Ray2D or LineSegment2D. + + Args: + line_ray: A LineSegment2D or Ray2D that will be extended infinitely for + intersection. + + Returns: + A list with 2 or 3 Arc2D objects if the split was successful. + Will be a list with 1 Arc2D if no intersection exists. + """ + inters = intersect_line2d_infinite_arc2d(line_ray, self) + if inters is None: + return [self] + elif self.is_circle: + if len(inters) != 2: + return [self] + a1 = self._a_from_pt(inters[0]) + a2 = self._a_from_pt(inters[1]) + return [Arc2D(self.c, self.r, a1, a2), Arc2D(self.c, self.r, a2, a1)] + elif len(inters) == 1: + am = self._a_from_pt(inters[0]) + return [Arc2D(self.c, self.r, self.a1, am), + Arc2D(self.c, self.r, am, self.a2)] + elif len(inters) == 2: + am1 = self._a_from_pt(inters[0]) + am2 = self._a_from_pt(inters[1]) + if self._cc_difference(am1) < self._cc_difference(am2): + return [Arc2D(self.c, self.r, self.a1, am1), + Arc2D(self.c, self.r, am1, am2), + Arc2D(self.c, self.r, am2, self.a2)] + else: + return [Arc2D(self.c, self.r, self.a1, am2), + Arc2D(self.c, self.r, am2, am1), + Arc2D(self.c, self.r, am1, self.a2)]
+ +
[docs] def to_polyline(self, divisions, interpolated=True): + """Get this Arc2D as an approximated Polyline2D. + + Args: + divisions: The number of segments into which the arc will be divided. + interpolated: Boolean to note whether the polyline should be interpolated + between the input vertices when it is translated to other interfaces. + This property has no effect on the geometric calculations performed + by this library and is only present in order to assist with + display/translation. (Default: True) + """ + pts = self.subdivide_evenly(divisions) + return Polyline2D(pts, interpolated)
+ +
[docs] def to_dict(self): + """Get Arc2D as a dictionary.""" + return {'type': 'Arc2D', 'c': self.c.to_array(), + 'r': self.r, 'a1': self.a1, 'a2': self.a2}
+ +
[docs] def duplicate(self): + """Get a copy of this object.""" + return self.__copy__()
+ + def _pt_in(self, point): + if self.is_circle: + return True + else: + v = Vector2D(point.x - self.c.x, point.y - self.c.y) + a = Vector2D(1, 0).angle_counterclockwise(v) + return (not self.is_inverted and self.a1 < a < self.a2) or \ + (self.is_inverted and self.a1 > a > self.a2) + + def _a_from_pt(self, point): + """Get the angle along the arc given a point along the arc.""" + v = Vector2D(point.x - self.c.x, point.y - self.c.y) + return Vector2D(1, 0).angle_counterclockwise(v) + + def _cc_difference(self, angle): + """Get counterclockwise different between an angle and the start of this arc.""" + _diff = angle - self.a1 + return _diff if not angle < self.a1 else 2 * math.pi + _diff + + def _calculate_min_max(self): + """Calculate maximum and minimum Point2D for this object.""" + # get the quadrants of the start and end of the arc + start_quad = self._angle_quadrant(self._a1) + end_quad = self._angle_quadrant(self._a2) + # get the min and max of the start and end points + x_cor = (self._cos_a1 * self.r, self._cos_a2 * self.r) + y_cor = (self._sin_a1 * self.r, self._sin_a2 * self.r) + mnx, mny = min(x_cor), min(y_cor) + mxx, mxy = max(x_cor), max(y_cor) + # build extremum matrices + r = self.r + x_max = ((mxx, r, r, r), (mxx, mxx, r, r), + (mxx, mxx, mxx, r), (mxx, mxx, mxx, mxx)) + y_max = ((mxy, mxy, mxy, mxy), (r, mxy, r, r), + (r, mxy, mxy, r), (r, mxy, mxy, mxy)) + x_min = ((mnx, -r, mnx, mnx), (mnx, mnx, mnx, mnx), + (-r, -r, mnx, -r), (-r, -r, mnx, mnx)) + y_min = ((mny, -r, -r, mny), (mny, mny, -r, mny), + (mny, mny, mny, mny), (-r, -r, -r, mny)) + # select the desired values from the extremum matrices + min_pt = (x_min[end_quad][start_quad], y_min[end_quad][start_quad]) + max_pt = (x_max[end_quad][start_quad], y_max[end_quad][start_quad]) + self._min = Point2D(min_pt[0] + self.c.x, min_pt[1] + self.c.y) + self._max = Point2D(max_pt[0] + self.c.x, max_pt[1] + self.c.y) + + @staticmethod + def _angle_quadrant(angle): + """Get the quadrant of a given angle in radians.""" + if angle < math.pi / 2: + return 0 + elif angle < math.pi: + return 1 + elif angle < math.pi * (3 / 2): + return 2 + return 3 + + def __copy__(self): + return Arc2D(self.c, self.r, self.a1, self.a2) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self.c, self.r, self.a1, self.a2) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Arc2D) and self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + return 'Arc2D (center <%.2f, %.2f>) (radius <%.2f>) (length <%.2f>)' % \ + (self.c.x, self.c.y, self.r, self.length)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry2d/line.html b/docs/_modules/ladybug_geometry/geometry2d/line.html new file mode 100644 index 00000000..8bf015f0 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry2d/line.html @@ -0,0 +1,1377 @@ + + + + + + + ladybug_geometry.geometry2d.line — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry2d.line

+# coding=utf-8
+"""2D Line Segment"""
+from __future__ import division
+
+from .pointvector import Vector2D, Point2D
+from ._1d import Base1DIn2D
+from ..intersection2d import closest_point2d_between_line2d, intersect_line2d, \
+    intersect_line_segment2d
+
+
+
[docs]class LineSegment2D(Base1DIn2D): + """2D line segment object. + + Args: + p: A Point2D representing the first point of the line segment. + v: A Vector2D representing the vector to the second point. + + Properties: + * p + * v + * p1 + * p2 + * min + * max + * midpoint + * endpoints + * length + * vertices + """ + __slots__ = () + + def __init__(self, p, v): + """Initialize LineSegment2D.""" + Base1DIn2D.__init__(self, p, v) + +
[docs] @classmethod + def from_end_points(cls, p1, p2): + """Initialize a line segment from a start point and and end point. + + Args: + p1: A Point2D representing the first point of the line segment. + p2: A Point2D representing the second point of the line segment. + """ + v = p2 - p1 + return cls(p1, Vector2D(v.x, v.y))
+ +
[docs] @classmethod + def from_sdl(cls, s, d, length): + """Initialize a line segment from a start point, direction, and length. + + Args: + s: A Point2D representing the start point of the line segment. + d: A Vector2D representing the direction of the line segment. + length: A number representing the length of the line segment. + """ + return cls(s, d * length / d.magnitude)
+ +
[docs] @classmethod + def from_array(cls, line_array): + """ Create a LineSegment2D from a nested array of two endpoint coordinates. + + Args: + line_array: Nested tuples ((pt1.x, pt1.y), (pt2.x, pt2.y)), where + pt1 and pt2 represent the endpoints of the line segment. + """ + return LineSegment2D.from_end_points(*tuple(Point2D(*pt) for pt in line_array))
+ + @property + def p1(self): + """First point (same as p).""" + return self.p + + @property + def p2(self): + """Second point.""" + return Point2D(self.p.x + self.v.x, self.p.y + self.v.y) + + @property + def midpoint(self): + """Midpoint.""" + return self.point_at(0.5) + + @property + def endpoints(self): + """Tuple of endpoints """ + return (self.p1, self.p2) + + @property + def length(self): + """The length of the line segment.""" + return self.v.magnitude + + @property + def vertices(self): + """Tuple of both vertices in this object.""" + return (self.p1, self.p2) + +
[docs] def is_equivalent(self, other, tolerance): + """Boolean noting equivalence (within tolerance) between this line and another. + + The order of the line points do not matter for equivalence to be true. + + Args: + other: LineSegment2D for comparison. + tolerance: float representing point equivalence. + + Returns: + True if equivalent else False + """ + tol = tolerance + return ( + self.p1.is_equivalent(other.p1, tol) and self.p2.is_equivalent(other.p2, tol) + ) or ( + self.p1.is_equivalent(other.p2, tol) and self.p2.is_equivalent(other.p1, tol) + )
+ +
[docs] def flip(self): + """Get a copy of this line segment that is flipped.""" + return LineSegment2D(self.p2, self.v.reverse())
+ +
[docs] def move(self, moving_vec): + """Get a line segment that has been moved along a vector. + + Args: + moving_vec: A Vector2D with the direction and distance to move the ray. + """ + return LineSegment2D(self.p.move(moving_vec), self.v)
+ +
[docs] def rotate(self, angle, origin): + """Get a line segment that is rotated counterclockwise by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point2D for the origin around which the line segment will + be rotated. + """ + return LineSegment2D(self.p.rotate(angle, origin), self.v.rotate(angle))
+ +
[docs] def reflect(self, normal, origin): + """Get a line segment reflected across a plane with the input normal and origin. + + Args: + normal: A Vector2D representing the normal vector for the plane across + which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point2D representing the origin from which to reflect. + """ + return LineSegment2D(self.p.reflect(normal, origin), self.v.reflect(normal))
+ +
[docs] def scale(self, factor, origin=None): + """Scale a line segment by a factor from an origin point. + + Args: + factor: A number representing how much the line segment should be scaled. + origin: A Point2D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0). + """ + return LineSegment2D(self.p.scale(factor, origin), self.v * factor)
+ +
[docs] def subdivide(self, distances): + """Get Point2D values along the line that subdivide it based on input distances. + + Args: + distances: A list of distances along the line at which to subdivide it. + This can also be a single number that will be repeated until the + end of the line. + """ + if isinstance(distances, (float, int)): + distances = [distances] + line_length = self.length + dist = distances[0] + index = 0 + sub_pts = [self.p] + while dist < line_length: + sub_pts.append(self.point_at_length(dist)) + if index < len(distances) - 1: + index += 1 + dist += distances[index] + sub_pts.append(self.p2) + return sub_pts
+ +
[docs] def subdivide_evenly(self, number): + """Get Point2D values along the line that divide it into evenly-spaced segments. + + Args: + number: The number of segments into which the line will be divided. + """ + interval = 1 / number + parameter = interval + sub_pts = [self.p] + while parameter <= 1: + sub_pts.append(self.point_at(parameter)) + parameter += interval + return sub_pts
+ +
[docs] def point_at(self, parameter): + """Get a point at a given fraction along the line segment. + + Args: + parameter: The fraction between the start and end point where the + desired point lies. For example, 0.5 will yield the midpoint. + """ + return self.p + self.v * parameter
+ +
[docs] def point_at_length(self, length): + """Get a point at a given distance along the line segment. + + Args: + length: The distance along the line from the start point where the + desired point lies. + """ + return self.p + self.v * (length / self.length)
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersection between this object and another Ray2 or LineSegment2D. + + Args: + line_ray: Another LineSegment2D or Ray2D or to intersect. + + Returns: + Point2D of intersection if it exists. None if no intersection exists. + """ + if isinstance(line_ray, LineSegment2D): + return intersect_line_segment2d(self, line_ray) + return intersect_line2d(self, line_ray)
+ +
[docs] def closest_points_between_line(self, line): + """Get the two closest Point2D between this object to another LineSegment2D. + + Note that the line segments should not intersect for the result to be valid. + + Args: + line: A LineSegment2D object to which the closest points + will be computed. + + Returns: + Two Point2D objects representing + + 1) The closest point on this object to the input line. + 2) The closest point on the input line to this object. + """ + dist, pts = closest_point2d_between_line2d(self, line) + return pts
+ +
[docs] def distance_to_line(self, line): + """Get the minimum distance between this object and the input LineSegment2D. + + Note that the line segments should not intersect for the result to be valid. + + Args: + line: A LineSegment2D object to which the minimum distance will be computed. + + Returns: + The minimum distance to the input line. + """ + dist, pts = closest_point2d_between_line2d(self, line) + return dist
+ +
[docs] def to_dict(self): + """Get LineSegment2D as a dictionary.""" + base = Base1DIn2D.to_dict(self) + base['type'] = 'LineSegment2D' + return base
+ +
[docs] def to_array(self): + """ A nested list representing the two line endpoint coordinates.""" + return (self.p1.to_array(), self.p2.to_array())
+ + def _u_in(self, u): + return u >= 0.0 and u <= 1.0 + + def __abs__(self): + return abs(self.v) + + def __copy__(self): + return LineSegment2D(self.p, self.v) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (hash(self.p), hash(self.v)) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, LineSegment2D) and self.__key() == other.__key() + + def __repr__(self): + return 'LineSegment2D (<%.2f, %.2f> to <%.2f, %.2f>)' % \ + (self.p.x, self.p.y, self.p.x + self.v.x, self.p.y + self.v.y)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry2d/mesh.html b/docs/_modules/ladybug_geometry/geometry2d/mesh.html new file mode 100644 index 00000000..7bf910ed --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry2d/mesh.html @@ -0,0 +1,1827 @@ + + + + + + + ladybug_geometry.geometry2d.mesh — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry2d.mesh

+# coding=utf-8
+"""2D Mesh"""
+from __future__ import division
+try:
+    from itertools import izip as zip  # python 2
+except ImportError:
+    xrange = range  # python 3
+
+from .._mesh import MeshBase
+from ..triangulation import earcut
+
+from .pointvector import Point2D, Vector2D
+from .line import LineSegment2D
+from .polyline import Polyline2D
+from .polygon import Polygon2D
+
+
+
[docs]class Mesh2D(MeshBase): + """2D Mesh object. + + Args: + vertices: A list or tuple of Point2D objects for vertices. + faces: A list of tuples with each tuple having either 3 or 4 integers. + These integers correspond to indices within the list of vertices. + colors: An optional list of colors that correspond to either the faces + of the mesh or the vertices of the mesh. Default is None. + + Properties: + * vertices + * faces + * colors + * is_color_by_face + * min + * max + * center + * area + * centroid + * face_areas + * face_centroids + * face_area_centroids + * face_vertices + * vertex_connected_faces + * edges + * naked_edges + * internal_edges + * non_manifold_edges + """ + __slots__ = ('_min', '_max', '_center', '_centroid') + + def __init__(self, vertices, faces, colors=None): + """Initialize Mesh2D.""" + self._vertices = self._check_vertices_input(vertices) + self._faces = self._check_faces_input(faces) + self._is_color_by_face = False # default if colors is None + self.colors = colors + + self._min = None + self._max = None + self._center = None + self._area = None + self._centroid = None + self._face_areas = None + self._face_centroids = None + self._face_area_centroids = None + self._vertex_connected_faces = None + self._edge_indices = None + self._edge_types = None + self._edges = None + self._naked_edges = None + self._internal_edges = None + self._non_manifold_edges = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Mesh2D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Mesh2D", + "vertices": [(0, 0), (10, 0), (0, 10)], + "faces": [(0, 1, 2)], + "colors": [{"r": 255, "g": 0, "b": 0}] + } + """ + colors = None + if 'colors' in data and data['colors'] is not None and len(data['colors']) != 0: + try: + from ladybug.color import Color + except ImportError: + raise ImportError('Colors are specified in input Mesh2D dictionary ' + 'but failed to import ladybug.color') + colors = tuple(Color.from_dict(col) for col in data['colors']) + fcs = tuple(tuple(f) for f in data['faces']) # cast to immutable type + return cls(tuple(Point2D.from_array(pt) for pt in data['vertices']), fcs, colors)
+ +
[docs] @classmethod + def from_face_vertices(cls, faces, purge=True): + """Create a mesh from a list of faces with each face defined by Point2Ds. + + Args: + faces: A list of faces with each face defined as a list of 3 or 4 Point2D. + purge: A boolean to indicate if duplicate vertices should be shared between + faces. Default is True to purge duplicate vertices, which can be slow + for large lists of faces but results in a higher-quality mesh with + a smaller size in memory. Default is True. + """ + vertices, face_collector = cls._interpret_input_from_face_vertices(faces, purge) + return cls(tuple(vertices), tuple(face_collector))
+ +
[docs] @classmethod + def from_polygon_triangulated(cls, boundary_polygon, hole_polygons=None): + """Initialize a triangulated Mesh2D from a Polygon2D. + + The triangles of the mesh faces will always completely fill the shape + defines by the input boundary_polygon with holes subtracted from it. + + Args: + boundary_polygon: A Polygon2D object representing the boundary of the shape. + hole_polygons: Optional list of Polygon2D objects representing holes + within the boundary_polygon. + """ + assert isinstance(boundary_polygon, Polygon2D), 'boundary_polygon must be a ' \ + 'Polygon2D to use from_polygon_triangulated. Got {}.'.format( + type(boundary_polygon)) + + if hole_polygons is None and boundary_polygon.is_convex: # fan triangulation! + _faces = [] + for i in xrange(1, len(boundary_polygon) - 1): + _faces.append((0, i, i + 1)) + _new_mesh = cls(boundary_polygon.vertices, _faces) + else: # slower ear-clipping method + if hole_polygons is not None: + for hole in hole_polygons: + assert isinstance(hole, Polygon2D), 'Hole must be a Polygon2D ' \ + 'to use from_polygon_triangulated. Got {}.'.format(type(hole)) + _vertices, _faces = Mesh2D._ear_clipping_triangulation( + boundary_polygon, hole_polygons) + _new_mesh = cls(_vertices, _faces) + + return _new_mesh
+ +
[docs] @classmethod + def from_polygon_grid(cls, polygon, x_dim, y_dim, generate_centroids=True): + """Initialize a gridded Mesh2D from a Polygon2D. + + Note that this gridded mesh will usually not completely fill the polygon. + Essentially, this method generates a grid over the domain of the polygon + and then removes any points that do not lie within the polygon. + + Args: + polygon: A Polygon2D object. + x_dim: The x dimension of the grid cells as a number. + y_dim: The y dimension of the grid cells as a number. + generate_centroids: Set to True to have the face centroids generated + alongside the grid of vertices, which is much faster than having + them generated upon request as they typically are. However, if you + have no need for the face centroids, you would save memory by setting + this to False. Default is True. + """ + assert isinstance(polygon, Polygon2D), 'Expected Polygon2D for' \ + ' Mesh2D.from_polygon_grid. Got {}'.format(type(polygon)) + # figure out how many x and y cells to make + _x_dim, _num_x = Mesh2D._domain_dimensions(polygon.max.x - polygon.min.x, x_dim) + _y_dim, _num_y = Mesh2D._domain_dimensions(polygon.max.y - polygon.min.y, y_dim) + _poly_min = polygon.min + + # generate the gid of points and faces + _verts = Mesh2D._grid_vertices(_poly_min, _num_x, _num_y, _x_dim, _y_dim) + _faces = Mesh2D._grid_faces(_num_x, _num_y) + _centroids = None + if generate_centroids is True: # calculate centroids if requested + _centroids = Mesh2D._grid_centroids( + _poly_min, _num_x, _num_y, _x_dim, _y_dim) + + # figure out which vertices lie inside the polygon + # for tolerance reasons, we scale the polygon by a very small amount + # this avoids the fringe cases noted in the Polygon2d.is_point_inside description + tol_pt = Vector2D(0.0000001, 0.0000001) + scaled_poly = Polygon2D( + tuple(pt.scale(1.000001, _poly_min) - tol_pt for pt in polygon.vertices)) + _pattern = [scaled_poly.is_point_inside(_v) for _v in _verts] + + # build the mesh + _mesh_init = cls(_verts, _faces) + _mesh_init._face_centroids = _centroids + _mesh_init._face_area_centroids = _centroids + _new_mesh, _face_pattern = _mesh_init.remove_vertices(_pattern) + _new_mesh._face_areas = x_dim * y_dim + return _new_mesh
+ +
[docs] @classmethod + def from_grid(cls, base_point=Point2D(), num_x=1, num_y=1, x_dim=1, y_dim=1, + generate_centroids=True): + """Initialize a Mesh2D from parameters that define a grid. + + Args: + base_point: The base point from which the mesh grid will be generated. + Default is (0, 0). + num_x: An integer for the number of mesh cells to generate in the + x direction. Default is 1. + num_y: An integer for the number of mesh cells to generate in the + y direction. Default is 1. + x_dim: The x dimension of the grid cells as a number. Default is 1. + y_dim: The y dimension of the grid cells as a number. Default is 1. + generate_centroids: Set to True to have the face centroids generated + alongside the grid of vertices, which is much faster than having + them generated upon request as they typically are. However, if you + have no need for the face centroids, you would save memory by setting + this to False. Default is True. + """ + _verts = Mesh2D._grid_vertices(base_point, num_x, num_y, x_dim, y_dim) + _faces = Mesh2D._grid_faces(num_x, num_y) + _centroids = None + if generate_centroids is True: + _centroids = Mesh2D._grid_centroids(base_point, num_x, num_y, x_dim, y_dim) + + _new_mesh = cls(tuple(_verts), tuple(_faces)) + _new_mesh._face_areas = x_dim * y_dim + _new_mesh._face_centroids = _centroids + _new_mesh._face_area_centroids = _centroids + return _new_mesh
+ + @property + def min(self): + """A Point2D for the minimum bounding rectangle vertex around this geometry.""" + if self._min is None: + self._calculate_min_max() + return self._min + + @property + def max(self): + """A Point2D for the maximum bounding rectangle vertex around this geometry.""" + if self._max is None: + self._calculate_min_max() + return self._max + + @property + def center(self): + """A Point2D for the center of the bounding rectangle around this geometry.""" + if self._center is None: + min, max = self.min, self.max + self._center = Point2D((min.x + max.x) / 2, (min.y + max.y) / 2) + return self._center + + @property + def face_areas(self): + """A tuple of face areas that parallels the faces property.""" + if self._face_areas is None: + self._face_areas = tuple(self._face_area(face) for face in self.faces) + elif isinstance(self._face_areas, (float, int)): # grid of faces with same area + self._face_areas = tuple(self._face_areas for face in self.faces) + return self._face_areas + + @property + def centroid(self): + """The centroid of the mesh as a Point2D (aka. center of mass). + + Note that the centroid is more time consuming to compute than the center + (or the middle point of the bounding rectangle). So the center might be + preferred over the centroid if you just need a rough point for the middle + of the mesh. + """ + if self._centroid is None: + _weight_x = 0 + _weight_y = 0 + for _c, _a in zip(self.face_area_centroids, self.face_areas): + _weight_x += _c.x * _a + _weight_y += _c.y * _a + self._centroid = Point2D(_weight_x / self.area, _weight_y / self.area) + return self._centroid + + @property + def face_edges(self): + """List of polylines with one Polyline2D for each face. + + This is faster to compute compared to the edges and results in effectively + the same type of wireframe visualization. + """ + _all_verts = self._vertices + f_edges = [] + for face in self._faces: + verts = tuple(_all_verts[v] for v in face) + (_all_verts[face[0]],) + f_edges.append(Polyline2D(verts)) + return f_edges + + @property + def edges(self): + """"Tuple of all edges in this Mesh3D as LineSegment3D objects.""" + if self._edges is None: + if self._edge_indices is None: + self._compute_edge_info() + self._edges = tuple(LineSegment2D.from_end_points( + self.vertices[seg[0]], self.vertices[seg[1]]) + for seg in self._edge_indices) + return self._edges + + @property + def naked_edges(self): + """"Tuple of all naked edges in this Mesh3D as LineSegment3D objects. + + Naked edges belong to only one face in the mesh (they are not + shared between faces). + """ + if self._naked_edges is None: + self._naked_edges = self._get_edge_type(0) + return self._naked_edges + + @property + def internal_edges(self): + """"Tuple of all internal edges in this Mesh3D as LineSegment3D objects. + + Internal edges are shared between two faces in the mesh. + """ + if self._internal_edges is None: + self._internal_edges = self._get_edge_type(1) + return self._internal_edges + + @property + def non_manifold_edges(self): + """"Tuple of all non-manifold edges in this mesh as LineSegment3D objects. + + Non-manifold edges are shared between three or more faces. + """ + if self._non_manifold_edges is None: + if self._edges is None: + self.edges + nm_edges = [] + for i, type in enumerate(self._edge_types): + if type > 1: + nm_edges.append(self._edges[i]) + self._non_manifold_edges = tuple(nm_edges) + return self._non_manifold_edges + +
[docs] def triangulated(self): + """Get a version of this Mesh2D where all quads have been triangulated.""" + _new_faces = [] + for face in self.faces: + if len(face) == 3: + _new_faces.append(face) + else: + _triangles = Mesh2D._quad_to_triangles([self._vertices[i] for i in face]) + _triangles = [tuple(face[vertex_idx] for vertex_idx in new_face) + for new_face in _triangles] + _new_faces.extend(_triangles) + _new_faces = tuple(_new_faces) + + _new_colors = self.colors + if self.is_color_by_face is True: + _new_colors = [] + for i, face in enumerate(self.faces): + if len(face) == 3: + _new_colors.append(self.colors[i]) + else: + _new_colors.extend([self.colors[i]] * 2) + _new_colors = tuple(_new_colors) + + _new_mesh = Mesh2D(self.vertices, _new_faces, _new_colors) + return _new_mesh
+ +
[docs] def remove_vertices(self, pattern): + """Get a version of this mesh where vertices are removed according to a pattern. + + Args: + pattern: A list of boolean values denoting whether a vertex should + remain in the mesh (True) or be removed from the mesh (False). + The length of this list must match the number of this mesh's vertices. + + Returns: + A tuple with two elements + + - new_mesh: + A mesh where the vertices have been removed according + to the input pattern. + + - face_pattern: + A list of boolean values that corresponds to the + original mesh faces noting whether the face is in the new mesh (True) + or has been removed from the new mesh (False). + """ + _new_verts, _new_faces, _new_colors, _new_f_cent, _new_f_area, face_pattern = \ + self._remove_vertices(pattern) + + new_mesh = Mesh2D(_new_verts, _new_faces, _new_colors) + new_mesh._face_centroids = _new_f_cent + new_mesh._face_areas = _new_f_area + return new_mesh, face_pattern
+ +
[docs] def remove_faces(self, pattern): + """Get a version of this mesh where faces are removed according to a pattern. + + Args: + pattern: A list of boolean values denoting whether a face should + remain in the mesh (True) or be removed from the mesh (False). + The length of this list must match the number of this mesh's faces. + + Returns: + A tuple with two elements + + - new_mesh: + A mesh where the faces have been removed according + to the input pattern. + + - vertex_pattern: + A list of boolean values that corresponds to the + original mesh vertices noting whether the vertex is in the new mesh + (True) or has been removed from the new mesh (False). + """ + vertex_pattern = self._vertex_pattern_from_remove_faces(pattern) + _new_verts, _new_faces, _new_colors, _new_f_cent, _new_f_area, face_pattern = \ + self._remove_vertices(vertex_pattern, pattern) + + new_mesh = Mesh2D(_new_verts, _new_faces, _new_colors) + new_mesh._face_centroids = _new_f_cent + new_mesh._face_areas = _new_f_area + return new_mesh, vertex_pattern
+ +
[docs] def remove_faces_only(self, pattern): + """Get a version of this mesh where faces are removed and vertices are unaltered. + + This is faster than the Mesh2D.remove_faces method but will likely result + a lower-quality mesh where several vertices exist in the mesh that are not + referenced by any face. This may be preferred if pure speed of removing + faces is a priority over smallest size of the mesh in memory. + + Args: + pattern: A list of boolean values denoting whether a face should + remain in the mesh (True) or be removed from the mesh (False). + The length of this list must match the number of this mesh's faces. + + Returns: + new_mesh -- A mesh where the faces have been removed according + to the input pattern. + """ + _new_faces, _new_colors, _new_f_cent, _new_f_area = \ + self._remove_faces_only(pattern) + + new_mesh = Mesh2D(self.vertices, _new_faces, _new_colors) + new_mesh._face_centroids = _new_f_cent + new_mesh._face_areas = _new_f_area + return new_mesh
+ +
[docs] def rotate(self, angle, origin): + """Get a mesh that is rotated counterclockwise by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point2D for the origin around which the point will be rotated. + """ + _verts = tuple([pt.rotate(angle, origin) for pt in self.vertices]) + return self._mesh_transform(_verts)
+ +
[docs] def scale(self, factor, origin=None): + """Scale a mesh by a factor from an origin point. + + Args: + factor: A number representing how much the mesh should be scaled. + origin: A Point representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0). + """ + if origin is None: + _verts = tuple( + Point2D(pt.x * factor, pt.y * factor) for pt in self.vertices) + else: + _verts = tuple(pt.scale(factor, origin) for pt in self.vertices) + return self._mesh_scale(_verts, factor)
+ +
[docs] def to_dict(self): + """Get Mesh2D as a dictionary.""" + colors = None + if self.colors is not None: + colors = [col.to_dict() for col in self.colors] + return {'type': 'Mesh2D', + 'vertices': [pt.to_array() for pt in self.vertices], + 'faces': self.faces, 'colors': colors}
+ +
[docs] @staticmethod + def join_meshes(meshes): + """Join an array of Mesh2Ds into a single Mesh2D. + + Args: + meshes: An array of meshes to be joined into one. + + Returns: + A single Mesh2D object derived from the input meshes. + """ + # set up empty lists of objects to be filled + verts = [] + faces = [] + colors = [] + + # loop through all of the meshes and get new faces + total_v_i = 0 + for mesh in meshes: + verts.extend(mesh._vertices) + for fc in mesh._faces: + faces.append(tuple(v_i + total_v_i for v_i in fc)) + total_v_i += len(mesh._vertices) + if mesh._colors: + colors.extend(mesh._colors) + + # create the new mesh + if len(colors) != 0: + new_mesh = Mesh2D(verts, faces, colors) + else: + new_mesh = Mesh2D(verts, faces) + + # attempt to transfer the centroids and normals + if all(msh._face_centroids is not None for msh in meshes): + new_mesh._face_centroids = tuple(pt for msh in meshes for pt in msh) + if all(msh._face_areas is not None for msh in meshes): + new_mesh._face_areas = tuple(a for msh in meshes for a in msh.face_areas) + return new_mesh
+ + def _calculate_min_max(self): + """Calculate maximum and minimum Point2D for this object.""" + min_pt = [self.vertices[0].x, self.vertices[0].y] + max_pt = [self.vertices[0].x, self.vertices[0].y] + + for v in self.vertices[1:]: + if v.x < min_pt[0]: + min_pt[0] = v.x + elif v.x > max_pt[0]: + max_pt[0] = v.x + if v.y < min_pt[1]: + min_pt[1] = v.y + elif v.y > max_pt[1]: + max_pt[1] = v.y + + self._min = Point2D(min_pt[0], min_pt[1]) + self._max = Point2D(max_pt[0], max_pt[1]) + + def _get_edge_type(self, edge_type): + """Get all of the edges of a certain type in this mesh.""" + if self._edges is None: + self.edges + sel_edges = [] + for i, type in enumerate(self._edge_types): + if type == edge_type: + sel_edges.append(self._edges[i]) + return tuple(sel_edges) + + def _face_area(self, face): + """Return the area of a face.""" + return Mesh2D._get_area(tuple(self._vertices[i] for i in face)) + + def _tri_face_centroid(self, face): + """Compute the centroid of a triangular face.""" + return Mesh2D._tri_centroid(tuple(self._vertices[i] for i in face)) + + def _quad_face_centroid(self, face): + """Compute the centroid of a quadrilateral face.""" + return Mesh2D._quad_centroid(tuple(self._vertices[i] for i in face)) + + def _mesh_transform(self, verts): + """Transform mesh in a way that transfers properties and avoids extra checks.""" + _new_mesh = Mesh2D(verts, self.faces) + self._transfer_properties(_new_mesh) + return _new_mesh + + def _mesh_scale(self, verts, factor): + """Scale mesh in a way that transfers properties and avoids extra checks.""" + _new_mesh = Mesh2D(verts, self.faces) + self._transfer_properties_scale(_new_mesh, factor) + return _new_mesh + + def _check_vertices_input(self, vertices): + if not isinstance(vertices, tuple): + vertices = tuple(vertices) + for vert in vertices: + assert isinstance(vert, Point2D), \ + 'Expected Point2D for {} vertex. Got {}.'.format( + self.__class__.__name__, type(vert)) + return vertices + + @staticmethod + def _ear_clipping_triangulation(polygon, holes=None): + """Triangulate a polygon and holes using the ear clipping method.""" + # flatten the list of vertices and holes into a single list for earcut + vert_coords, hole_indices = [], None + for pt in polygon: + vert_coords.extend((pt.x, pt.y)) + if holes is not None: + hole_indices = [] + for hole in holes: + hole_indices.append(int(len(vert_coords) / 2)) + for pt in hole: + vert_coords.extend((pt.x, pt.y)) + + # run the ear clipping triangulation + result_tri = earcut(vert_coords, hole_indices) + vertices = tuple(Point2D(*vert_coords[st:st + 2]) + for st in range(0, len(vert_coords), 2)) + faces = tuple(tuple(result_tri[st:st + 3]) + for st in range(0, len(result_tri), 3)) + return vertices, faces + + @staticmethod + def _quad_to_triangles(verts): + """Return two triangles that represent any quadrilateral.""" + # check if the quad is convex + convex = True + pt1, pt2, pt3 = verts[1], verts[2], verts[3] + start_val = True if (pt2.x - pt1.x) * (pt3.y - pt2.y) - \ + (pt2.y - pt1.y) * (pt3.x - pt2.x) > 0 else False + for i, pt3 in enumerate(verts[:3]): + pt1 = verts[i - 2] + pt2 = verts[i - 1] + val = True if (pt2.x - pt1.x) * (pt3.y - pt2.y) - \ + (pt2.y - pt1.y) * (pt3.x - pt2.x) > 0 else False + if val is not start_val: + convex = False + break + if convex is True: + # if the quad is convex, either diagonal splits it into triangles + return [(0, 1, 2), (2, 3, 0)] + else: + # if it is concave, we need to select the right diagonal of the two + return Mesh2D._concave_quad_to_triangles(verts) + + @staticmethod + def _concave_quad_to_triangles(verts): + """Return two triangles that represent a concave quadrilateral.""" + quad_poly = Polygon2D(verts) + diagonal = LineSegment2D.from_end_points(quad_poly[0], quad_poly[2]) + if quad_poly.is_point_inside(diagonal.midpoint, Vector2D(1, 0.00001)): + # if the diagonal midpoint is inside the quad, it splits it into two ears + return [(0, 1, 2), (2, 3, 0)] + else: + # if not, then the other diagonal splits it into two ears + return [(1, 2, 3), (3, 0, 1)] + + @staticmethod + def _face_center(verts): + """Get the center of a list of Point3D vertices.""" + _cent_x = sum([v.x for v in verts]) + _cent_y = sum([v.y for v in verts]) + v_count = len(verts) + return Point2D(_cent_x / v_count, _cent_y / v_count) + + @staticmethod + def _quad_centroid(verts): + """Get the centroid of a list of 4 Point2D vertices.""" + _tri_i = Mesh2D._quad_to_triangles(verts) + _tri_verts = ([verts[i] for i in _tri_i[0]], [verts[i] for i in _tri_i[1]]) + _tri_c = [Mesh2D._tri_centroid(tri) for tri in _tri_verts] + _tri_a = [Mesh2D._get_area(tri) for tri in _tri_verts] + _tot_a = sum(_tri_a) + _cent_x = (_tri_c[0].x * _tri_a[0] + _tri_c[1].x * _tri_a[1]) / _tot_a + _cent_y = (_tri_c[0].y * _tri_a[0] + _tri_c[1].y * _tri_a[1]) / _tot_a + return Point2D(_cent_x, _cent_y) + + @staticmethod + def _tri_centroid(verts): + """Get the centroid of a list of 3 Point2D vertices.""" + _cent_x = sum([v.x for v in verts]) + _cent_y = sum([v.y for v in verts]) + return Point2D(_cent_x / 3, _cent_y / 3) + + @staticmethod + def _get_area(verts): + """Return the area of a list of Point2D vertices.""" + _a = 0 + for i, pt in enumerate(verts): + _a += verts[i - 1].x * pt.y - verts[i - 1].y * pt.x + return abs(_a / 2) + + @staticmethod + def _domain_dimensions(_dom, _dim): + """Get corrected dimensions and number of cells over a domain.""" + _num = int(_dom / _dim) + _num = 1 if _num == 0 else _num + _dim = _dom / _num + return _dim, _num + + @staticmethod + def _grid_vertices(base_point, num_x, num_y, x_dim, y_dim): + """Generate Point2D vertices for a grid.""" + _verts = [] + _x = base_point.x + for i in xrange(num_x + 1): + _y = base_point.y + for j in xrange(num_y + 1): + _verts.append(Point2D(_x, _y)) + _y += y_dim + _x += x_dim + return _verts + + @staticmethod + def _grid_faces(num_x, num_y): + """Generate face tuples for a grid.""" + _faces = [] + _c = 0 + for i in xrange(num_x): + for j in xrange(num_y): + _faces.append((_c, _c + num_y + 1, _c + num_y + 2, _c + 1)) + _c += 1 + _c += 1 + return _faces + + @staticmethod + def _grid_centroids(base_point, num_x, num_y, x_dim, y_dim): + """Generate Point2D centroids for a grid.""" + _centroids = [] + _x_half = x_dim / 2 + _y_half = y_dim / 2 + _x = base_point.x + for i in xrange(num_x): + _y = base_point.y + for j in xrange(num_y): + _centroids.append(Point2D(_x + _x_half, _y + _y_half)) + _y += y_dim + _x += x_dim + return tuple(_centroids) + + def __copy__(self): + _new_mesh = Mesh2D(self.vertices, self.faces) + self._transfer_properties(_new_mesh) + _new_mesh._face_centroids = self._face_centroids + _new_mesh._centroid = self._centroid + return _new_mesh + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return tuple(hash(pt) for pt in self._vertices) + \ + tuple(hash(face) for face in self._faces) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Mesh2D) and self.__key() == other.__key() + + def __repr__(self): + return 'Ladybug Mesh2D ({} faces) ({} vertices)'.format( + len(self.faces), len(self))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry2d/pointvector.html b/docs/_modules/ladybug_geometry/geometry2d/pointvector.html new file mode 100644 index 00000000..f1ce0e42 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry2d/pointvector.html @@ -0,0 +1,1563 @@ + + + + + + + ladybug_geometry.geometry2d.pointvector — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry2d.pointvector

+# coding=utf-8
+"""2D Vector and 2D Point"""
+from __future__ import division
+
+import math
+import operator
+
+
+
[docs]class Vector2D(object): + """2D Vector object. + + Args: + x: Number for the X coordinate. + y: Number for the Y coordinate. + + Properties: + * x + * y + * magnitude + * magnitude_squared + * is_zero + """ + __slots__ = ('_x', '_y') + + def __init__(self, x=0, y=0): + """Initialize 2D Vector.""" + self._x = self._cast_to_float(x) + self._y = self._cast_to_float(y) + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Vector2D/Point2D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "x": 10, + "y": 0 + } + """ + return cls(data['x'], data['y'])
+ +
[docs] @classmethod + def from_array(cls, array): + """Initialize a Vector2D/Point2D from an array. + + Args: + array: A tuple or list with two numbers representing the x and y + values of the point. + """ + return cls(array[0], array[1])
+ +
[docs] def to_array(self): + """Get Vector2D/Point2D as a tuple of two numbers""" + return (self.x, self.y)
+ + @property + def x(self): + """Get the X coordinate.""" + return self._x + + @property + def y(self): + """Get the Y coordinate.""" + return self._y + + @property + def magnitude(self): + """Get the magnitude of the vector.""" + return self.__abs__() + + @property + def magnitude_squared(self): + """Get the magnitude squared of the vector.""" + return self.x ** 2 + self.y ** 2 + + @property + def min(self): + """Always equal to (0, 0). + + This property exists to help with bounding box calculations. + """ + return Point2D(0, 0) + + @property + def max(self): + """Always equal to (0, 0). + + This property exists to help with bounding box calculations. + """ + return Point2D(0, 0) + +
[docs] def is_zero(self, tolerance): + """Boolean to note whether the vector is within a given zero tolerance. + + Args: + tolerance: The tolerance below which the vector is considered to + be a zero vector. + """ + return abs(self.x) <= tolerance and abs(self.y) <= tolerance
+ +
[docs] def is_equivalent(self, other, tolerance): + """Test whether this object is equivalent to another within a certain tolerance. + + Note that if you want to test whether the coordinate values are perfectly + equal to one another, the == operator can be used. + + Args: + other: Another Point2D for which geometric equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + objects at which they can be considered geometrically equivalent. + Returns: + True if equivalent. False if not equivalent. + """ + return abs(self.x - other.x) <= tolerance and \ + abs(self.y - other.y) <= tolerance
+ +
[docs] def normalize(self): + """Get a copy of the vector that is a unit vector (magnitude=1).""" + d = self.magnitude + try: + return Vector2D(self.x / d, self.y / d) + except ZeroDivisionError: + return self.duplicate()
+ +
[docs] def reverse(self): + """Get a copy of this vector that is reversed.""" + return self.__neg__()
+ +
[docs] def dot(self, other): + """Get the dot product of this vector with another.""" + return self.x * other.x + self.y * other.y
+ +
[docs] def determinant(self, other): + """Get the determinant between this vector and another 2D vector.""" + return self.x * other.y - self.y * other.x
+ +
[docs] def cross(self): + """Get the cross product of this vector.""" + return Vector2D(self.y, -self.x)
+ +
[docs] def angle(self, other): + """Get the smallest angle between this vector and another.""" + try: + return math.acos(self.dot(other) / (self.magnitude * other.magnitude)) + except ValueError: # python floating tolerance can cause math domain error + if self.dot(other) < 0: + return math.acos(-1) + return math.acos(1)
+ +
[docs] def angle_counterclockwise(self, other): + """Get the counterclockwise angle between this vector and another.""" + inner = self.angle(other) + det = self.determinant(other) + if det >= 0: + return inner # if the det > 0 then self is immediately clockwise of other + else: + return 2 * math.pi - inner # if the det < 0 then other is clockwise of self
+ +
[docs] def angle_clockwise(self, other): + """Get the clockwise angle between this vector and another.""" + inner = self.angle(other) + det = self.determinant(other) + if det <= 0: + return inner # if the det > 0 then self is immediately clockwise of other + else: + return 2 * math.pi - inner # if the det < 0 then other is clockwise of self
+ +
[docs] def rotate(self, angle): + """Get a vector that is rotated counterclockwise by a certain angle. + + Args: + angle: An angle for rotation in radians. + """ + return Vector2D._rotate(self, angle)
+ +
[docs] def reflect(self, normal): + """Get a vector that is reflected across a plane with the input normal vector. + + Args: + normal: A Vector2D representing the normal vector for the plane across + which the vector will be reflected. THIS VECTOR MUST BE NORMALIZED. + """ + return Vector2D._reflect(self, normal)
+ +
[docs] def duplicate(self): + """Get a copy of this vector.""" + return self.__copy__()
+ +
[docs] def to_dict(self): + """Get Vector2D as a dictionary.""" + return {'type': 'Vector2D', + 'x': self.x, + 'y': self.y}
+ +
[docs] @staticmethod + def circular_mean(angles): + """Compute the circular mean across a list of angles in radians. + + If no circular mean exists, the normal mean will be returned. + + Args: + angles: A list of angles in radians. + """ + avg_x = sum(math.cos(ang) for ang in angles) / len(angles) + avg_y = sum(math.sin(ang) for ang in angles) / len(angles) + if (avg_x, avg_y) == (0, 0): # just return the normal mean + return sum(angles) / len(angles) + return math.atan2(avg_y, avg_x)
+ + def _cast_to_float(self, value): + """Ensure that an input coordinate value is a float.""" + try: + number = float(value) + except Exception: + raise TypeError( + 'Coordinates must be numbers. Got {}: {}.'.format(type(value), value)) + return number + + @staticmethod + def _rotate(vec, angle): + """Hidden rotation method used by both Point2D and Vector2D.""" + cos_a = math.cos(angle) + sin_a = math.sin(angle) + qx = cos_a * vec.x - sin_a * vec.y + qy = sin_a * vec.x + cos_a * vec.y + return Vector2D(qx, qy) + + @staticmethod + def _reflect(vec, normal): + """Hidden reflection method used by both Point2D and Vector2D.""" + d = 2 * (vec.x * normal.x + vec.y * normal.y) + return Vector2D(vec.x - d * normal.x, vec.y - d * normal.y) + + def __copy__(self): + return self.__class__(self.x, self.y) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self.x, self.y) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, (Vector2D, Point2D)) and \ + self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + + def __nonzero__(self): + return self.x != 0 or self.y != 0 + + def __len__(self): + return 2 + + def __getitem__(self, key): + return (self.x, self.y)[key] + + def __iter__(self): + return iter((self.x, self.y)) + + def __add__(self, other): + # Vector + Point -> Point + # Vector + Vector -> Vector + if isinstance(other, Point2D): + return Point2D(self.x + other.x, self.y + other.y) + elif isinstance(other, Vector2D): + return Vector2D(self.x + other.x, self.y + other.y) + else: + raise TypeError('Cannot add {} and {}'.format( + self.__class__.__name__, type(other))) + + __radd__ = __add__ + + def __sub__(self, other): + # Vector - Point -> Point + # Vector - Vector -> Vector + if isinstance(other, Point2D): + return Point2D(self.x - other.x, self.y - other.y) + elif isinstance(other, Vector2D): + return Vector2D(self.x - other.x, self.y - other.y) + else: + raise TypeError('Cannot subtract {} and {}'.format( + self.__class__.__name__, type(other))) + + def __rsub__(self, other): + if isinstance(other, (Vector2D, Point2D)): + return Vector2D(other.x - self.x, other.y - self.y) + else: + assert hasattr(other, '__len__') and len(other) == 2, \ + 'Cannot subtract {} and {}'.format( + self.__class__.__name__, type(other)) + return Vector2D(other.x - self[0], other.y - self[1]) + + def __mul__(self, other): + assert type(other) in (int, float), \ + 'Cannot multiply types {} and {}'.format( + self.__class__.__name__, type(other)) + return Vector2D(self.x * other, self.y * other) + + __rmul__ = __mul__ + + def __div__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector2D(self.x / other, self.y / other) + + def __rdiv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector2D(other / self.x, other / self.y) + + def __floordiv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector2D(operator.floordiv(self.x, other), + operator.floordiv(self.y, other)) + + def __rfloordiv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector2D(operator.floordiv(other, self.x), + operator.floordiv(other, self.y)) + + def __truediv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector2D(operator.truediv(self.x, other), + operator.truediv(self.y, other)) + + def __rtruediv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector2D(operator.truediv(other, self.x), + operator.truediv(other, self.y)) + + def __neg__(self): + return Vector2D(-self.x, -self.y) + + __pos__ = __copy__ + + def __abs__(self): + return math.sqrt(self.x ** 2 + self.y ** 2) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + """Vector2D representation.""" + return 'Vector2D (%.2f, %.2f)' % (self.x, self.y)
+ + +
[docs]class Point2D(Vector2D): + """2D Point object. + + Args: + x: Number for the X coordinate. + y: Number for the Y coordinate. + + Properties: + * x + * y + """ + __slots__ = () + + @property + def min(self): + """Always equal to the point itself. + + This property exists to help with bounding box calculations. + """ + return self + + @property + def max(self): + """Always equal to the point itself. + + This property exists to help with bounding box calculations. + """ + return self + +
[docs] def move(self, moving_vec): + """Get a point that has been moved along a vector. + + Args: + moving_vec: A Vector2D with the direction and distance to move the point. + """ + return Point2D(self.x + moving_vec.x, self.y + moving_vec.y)
+ +
[docs] def rotate(self, angle, origin): + """Rotate a point counterclockwise by a certain angle around an origin. + + Args: + angle: An angle for rotation in radians. + origin: A Point2D for the origin around which the point will be rotated. + """ + return Vector2D._rotate(self - origin, angle) + origin
+ +
[docs] def reflect(self, normal, origin): + """Get a point reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector2D representing the normal vector for the plane across + which the point will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point2D representing the origin from which to reflect. + """ + return Vector2D._reflect(self - origin, normal) + origin
+ +
[docs] def scale(self, factor, origin=None): + """Scale a point by a factor from an origin point. + + Args: + factor: A number representing how much the point should be scaled. + origin: A Point2D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0). + """ + if origin is None: + return Point2D(self.x * factor, self.y * factor) + else: + return (factor * (self - origin)) + origin
+ +
[docs] def distance_to_point(self, point): + """Get the distance from this point to another Point2D.""" + vec = (self.x - point.x, self.y - point.y) + return math.sqrt(vec[0] ** 2 + vec[1] ** 2)
+ +
[docs] def to_dict(self): + """Get Point2D as a dictionary.""" + return {'type': 'Point2D', + 'x': self.x, + 'y': self.y}
+ + def __add__(self, other): + # Point + Vector -> Point + # Point + Point -> Vector + if isinstance(other, Point2D): + return Vector2D(self.x + other.x, self.y + other.y) + elif isinstance(other, Vector2D): + return Point2D(self.x + other.x, self.y + other.y) + else: + raise TypeError('Cannot add Point2D and {}'.format(type(other))) + + def __sub__(self, other): + # Point - Vector -> Point + # Point - Point -> Vector + if isinstance(other, Point2D): + return Vector2D(self.x - other.x, self.y - other.y) + elif isinstance(other, Vector2D): + return Point2D(self.x - other.x, self.y - other.y) + else: + raise TypeError('Cannot subtract Point2D and {}'.format(type(other))) + + def __repr__(self): + """Point2D representation.""" + return 'Point2D (%.2f, %.2f)' % (self.x, self.y) + + def __lt__(self, other): + """ Lesser then inequality method. This is used by certain external + data structure libraries to efficiently store and retrieve point data. + """ + if isinstance(other, Vector2D): + return self.x < other.x + + def __gt__(self, other): + """ Greater then inequality method. This is used by certain external + data structure libraries to efficiently store and retrieve point data. + """ + if isinstance(other, Vector2D): + return self.x > other.x
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry2d/polygon.html b/docs/_modules/ladybug_geometry/geometry2d/polygon.html new file mode 100644 index 00000000..b02c6d2e --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry2d/polygon.html @@ -0,0 +1,3576 @@ + + + + + + + ladybug_geometry.geometry2d.polygon — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry2d.polygon

+# coding=utf-8
+"""2D Polygon"""
+from __future__ import division
+import math
+import time
+from collections import deque
+
+try:  # Python3
+    from queue import PriorityQueue
+except ImportError:  # Python2
+    from Queue import PriorityQueue
+
+from .pointvector import Point2D, Vector2D
+from .line import LineSegment2D
+from .ray import Ray2D
+from .polyline import Polyline2D
+from ..triangulation import _linked_list, _eliminate_holes
+from ..intersection2d import intersect_line2d, intersect_line2d_infinite, \
+    does_intersection_exist_line2d, closest_point2d_on_line2d, \
+    closest_end_point2d_between_line2d, closest_point2d_on_line2d_infinite
+from ._2d import Base2DIn2D
+import ladybug_geometry.boolean as pb
+
+inf = float("inf")
+
+
+
[docs]class Polygon2D(Base2DIn2D): + """2D polygon object. + + Args: + vertices: A list of Point2D objects representing the vertices of the polygon. + + Properties: + * vertices + * segments + * inside_angles + * outside_angles + * min + * max + * center + * perimeter + * area + * is_clockwise + * is_convex + * is_self_intersecting + * self_intersection_points + * is_valid + """ + __slots__ = ('_segments', '_inside_angles', '_outside_angles', '_perimeter', '_area', + '_is_clockwise', '_is_convex', '_is_self_intersecting') + + def __init__(self, vertices): + """Initialize Polygon2D.""" + Base2DIn2D.__init__(self, vertices) + self._segments = None + self._perimeter = None + self._inside_angles = None + self._outside_angles = None + self._area = None + self._is_clockwise = None + self._is_convex = None + self._is_self_intersecting = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Polygon2D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Polygon2D", + "vertices": [(0, 0), (10, 0), (0, 10)] + } + """ + return cls(tuple(Point2D.from_array(pt) for pt in data['vertices']))
+ +
[docs] @classmethod + def from_array(cls, point_array): + """Create a Polygon2D from a nested array of vertex coordinates. + + Args: + point_array: nested array of point arrays. + """ + return Polygon2D(Point2D(*point) for point in point_array)
+ +
[docs] @classmethod + def from_rectangle(cls, base_point, height_vector, base, height): + """Initialize Polygon2D from rectangle parameters. + + Initializing a polygon this way has the added benefit of having its properties + quickly computed. + + Args: + base_point: A Point2D for the lower left vertex of the polygon. + height_vector: A vector denoting the direction of the rectangle height. + base: A number indicating the length of the base of the rectangle. + height: A number indicating the length of the height of the rectangle. + """ + assert isinstance(base_point, Point2D), \ + 'base_point must be Point2D. Got {}.'.format(type(base_point)) + assert isinstance(height_vector, Vector2D), \ + 'height_vector must be Vector2D. Got {}.'.format(type(height_vector)) + assert isinstance(base, (float, int)), 'base must be a number.' + assert isinstance(height, (float, int)), 'height must be a number.' + _hv_norm = height_vector.normalize() + _bv = Vector2D(_hv_norm.y, -_hv_norm.x) * base + _hv = _hv_norm * height + _verts = (base_point, base_point + _bv, base_point + _hv + _bv, base_point + _hv) + polygon = cls(_verts) + polygon._perimeter = base * 2 + height * 2 + polygon._area = base * height + polygon._is_clockwise = False + polygon._is_convex = True + polygon._is_self_intersecting = False + return polygon
+ +
[docs] @classmethod + def from_regular_polygon(cls, number_of_sides, radius=1, base_point=Point2D()): + """Initialize Polygon2D from regular polygon parameters. + + Args: + number_of_sides: An integer for the number of sides on the regular + polygon. This number must be greater than 2. + radius: A number indicating the distance from the polygon's center + where the vertices of the polygon will lie. + The default is set to 1. + base_point: A Point2D for the center of the regular polygon. + The default is the Origin at (0, 0). + """ + assert isinstance(number_of_sides, int), 'number_of_sides must be an ' \ + 'integer. Got {}.'.format(type(number_of_sides)) + assert number_of_sides > 2, 'number_of_sides must be greater than 2. ' \ + 'Got {}.'.format(number_of_sides) + assert isinstance(base_point, Point2D), \ + 'base_point must be Point2D. Got {}.'.format(type(base_point)) + assert isinstance(radius, (float, int)), 'height must be a number.' + + # calculate angle at which each vertex is rotated from the previous one + angle = (math.pi * 2) / number_of_sides + cos_a = math.cos(angle) + sin_a = math.sin(angle) + + # pick a starting vertex that makes sense for the number of sides + if number_of_sides % 2 == 0: + start_vert = Point2D(base_point.x - radius, base_point.y) + start_vert = start_vert.rotate(angle / 2, base_point) + else: + start_vert = Point2D(base_point.x, base_point.y + radius) + _vertices = [start_vert] + + # generate the vertices + for i in range(number_of_sides - 1): + last_pt = _vertices[-1] + qx = cos_a * (last_pt.x - base_point.x) - sin_a * (last_pt.y - base_point.y) + qy = sin_a * (last_pt.x - base_point.x) + cos_a * (last_pt.y - base_point.y) + _vertices.append(Point2D(qx + base_point.x, qy + base_point.y)) + + # build the new polygon and set the properties that we know. + _new_poly = cls(_vertices) + _new_poly._is_clockwise = False + _new_poly._is_convex = True + _new_poly._is_self_intersecting = False + return _new_poly
+ +
[docs] @classmethod + def from_shape_with_hole(cls, boundary, hole): + """Initialize a Polygon2D from a boundary shape with a hole inside of it. + + This method will convert the shape into a single concave polygon by drawing + a line from the hole to the outer boundary. + + Args: + boundary: A list of Point2D objects for the outer boundary of the polygon + inside of which the hole is contained. + hole: A list of Point2D objects for the hole. + """ + # check that the inputs are in the correct format + assert isinstance(boundary, list), \ + 'boundary should be a list. Got {}'.format(type(boundary)) + assert isinstance(hole, list), \ + 'hole should be a list. Got {}'.format(type(hole)) + + # check that the direction of vertices for the hole is opposite the boundary + bound_direction = Polygon2D._are_clockwise(boundary) + if cls._are_clockwise(hole) is bound_direction: + hole.reverse() + + # join the hole with the boundary at the closest point + dist_dict = {} + for i, b_pt in enumerate(boundary): + for j, h_pt in enumerate(hole): + dist_dict[b_pt.distance_to_point(h_pt)] = (i, j) + boundary = cls._merge_boundary_and_hole(boundary, hole, dist_dict) + + # return the polygon with some properties set based on what we know + _new_poly = cls(boundary) + _new_poly._is_clockwise = bound_direction + _new_poly._is_convex = False + return _new_poly
+ +
[docs] @classmethod + def from_shape_with_holes(cls, boundary, holes): + """Initialize a Polygon2D from a boundary shape with holes inside of it. + + This method will convert the shape into a single concave polygon by drawing + lines from the holes to the outer boundary. + + Args: + boundary: A list of Point2D objects for the outer boundary of the polygon + inside of which all of the holes are contained. + holes: A list of lists with one list for each hole in the shape. Each hole + should be a list of at least 3 Point2D objects. + """ + # check that the inputs are in the correct format. + assert isinstance(boundary, list), \ + 'boundary should be a list. Got {}'.format(type(boundary)) + assert isinstance(holes, list), \ + 'holes should be a list. Got {}'.format(type(holes)) + for hole in holes: + assert isinstance(hole, list), \ + 'hole should be a list. Got {}'.format(type(hole)) + assert len(hole) >= 3, \ + 'hole should have at least 3 vertices. Got {}'.format(len(hole)) + + # check that the direction of vertices for the hole is opposite the boundary + bound_direction = cls._are_clockwise(boundary) + for hole in holes: + if cls._are_clockwise(hole) is bound_direction: + hole.reverse() + + # recursively add the nearest hole to the boundary until there are none left. + boundary = cls._merge_boundary_and_holes(boundary, holes) + + # return the polygon with some properties set based on what we know + _new_poly = cls(boundary) + _new_poly._is_clockwise = bound_direction + _new_poly._is_convex = False + return _new_poly
+ +
[docs] @classmethod + def from_shape_with_holes_fast(cls, boundary, holes): + """Initialize a Polygon2D from a boundary shape with holes using a fast method. + + This method is similar in principle to the from_shape_with_holes method + but it uses David Eberly's algorithm for finding a bridge between the holes + and outer polygon. This is extremely fast in comparison to the methods used + by from_shape_with_holes but is not always the prettiest or the shortest + pathway through the holes. Granted, it is very practical for shapes with + lots of holes (eg. 100 holes) and will run in a fraction of the time for + this case. + + Args: + boundary: A list of Point2D objects for the outer boundary of the polygon + inside of which all of the holes are contained. + holes: A list of lists with one list for each hole in the shape. Each hole + should be a list of at least 3 Point2D objects. + """ + # check the initial direction of the boundary vertices + bound_direction = cls._are_clockwise(boundary) + # format the coordinates for input to the earcut methods + vert_coords, hole_indices = [], None + for pt in boundary: + vert_coords.append(pt.x) + vert_coords.append(pt.y) + hole_indices = [] + for hole in holes: + hole_indices.append(int(len(vert_coords) / 2)) + for pt in hole: + vert_coords.append(pt.x) + vert_coords.append(pt.y) + + # eliminate the holes within the list + outer_len = hole_indices[0] * 2 + outer_node = _linked_list(vert_coords, 0, outer_len, 2, True) + outer_node = _eliminate_holes(vert_coords, hole_indices, outer_node, 2) + + # loop through the chain of nodes and translate them to Point2D + start_i = outer_node.i + vertices = [Point2D(outer_node.x, outer_node.y)] + node = outer_node.next + node_counter, orig_start_i = 0, 0 + while node.i != start_i: + vertices.append(Point2D(node.x, node.y)) + node_counter += 1 + if node.i == 0: + orig_start_i = node_counter + node = node.next + + # ensure that the starting vertex is the same as the input boundary + vertices = vertices[orig_start_i:] + vertices[:orig_start_i] + vertices[0] = boundary[0] # this avoids issues of floating point tolerance + + # return the polygon with some properties set based on what we know + _new_poly = cls(vertices) + _new_poly._is_clockwise = bound_direction + _new_poly._is_convex = False + _new_poly._is_self_intersecting = False + return _new_poly
+ + @property + def vertices(self): + """Tuple of all vertices in this geometry.""" + return self._vertices + + @property + def segments(self): + """Tuple of all line segments in the polygon.""" + if self._segments is None: + _segs = self._segments_from_vertices(self.vertices) + self._segments = tuple(_segs) + return self._segments + + @property + def inside_angles(self): + """Tuple of angles in radians for the interior angles of the polygon. + + These are aligned with the vertices such that the first angle corresponds + to the inside angle at the first vertex, the second at the second vertex, + and so on. + """ + if self._inside_angles is None: + angles, is_clock = [], self.is_clockwise + for i, pt in enumerate(self.vertices): + v1 = self.vertices[i - 2] - self.vertices[i - 1] + v2 = pt - self.vertices[i - 1] + v_angle = v1.angle_counterclockwise(v2) if is_clock \ + else v1.angle_clockwise(v2) + angles.append(v_angle) + angles.append(angles.pop(0)) # move the start angle to the end + self._inside_angles = tuple(angles) + return self._inside_angles + + @property + def outside_angles(self): + """Tuple of angles in radians for the exterior angles of the polygon. + + These are aligned with the vertices such that the first angle corresponds + to the inside angle at the first vertex, the second at the second vertex, + and so on. + """ + if self._outside_angles is None: + pi2 = math.pi * 2 + self._outside_angles = tuple(pi2 - a for a in self.inside_angles) + return self._outside_angles + + @property + def perimeter(self): + """The perimeter of the polygon.""" + if self._perimeter is None: + self._perimeter = sum([seg.length for seg in self.segments]) + return self._perimeter + + @property + def area(self): + """The area of the polygon.""" + if self._area is None: + _a = 0 + for i, pt in enumerate(self.vertices): + _a += self.vertices[i - 1].x * pt.y - self.vertices[i - 1].y * pt.x + self._area = _a / 2 + return abs(self._area) + + @property + def is_clockwise(self): + """Boolean for whether the polygon vertices are in clockwise order.""" + if self._is_clockwise is None: + if self._area is None: + self.area + self._is_clockwise = self._area < 0 + return self._is_clockwise + + @property + def is_convex(self): + """Boolean noting whether the polygon is convex (True) or non-convex (False).""" + if self._is_convex is None: + self._is_convex = True + if len(self.vertices) == 3: + pass + else: + _segs = self.segments + if self.is_clockwise: + for i, _s in enumerate(_segs): + if _segs[i - 1].v.determinant(_s.v) > 0: # counterclockwise turn + self._is_convex = False + break + else: + for i, _s in enumerate(_segs): + if _segs[i - 1].v.determinant(_s.v) < 0: # clockwise turn + self._is_convex = False + break + return self._is_convex + + @property + def is_self_intersecting(self): + """Boolean noting whether the polygon has self-intersecting edges. + + Note that this property is relatively computationally intense to obtain compared + to properties like area and is_convex. Also, most CAD programs forbid geometry + with self-intersecting edges. So it is recommended that this property only + be used in quality control scripts where the origin of the geometry is unknown. + """ + if self._is_self_intersecting is None: + self._is_self_intersecting = False + if self.is_convex is False: + _segs = self.segments + for i, _s in enumerate(_segs[1: len(_segs) - 1]): + _skip = (i, i + 1, i + 2) + _other_segs = [x for j, x in enumerate(_segs) if j not in _skip] + for _oth_s in _other_segs: + if _s.intersect_line_ray(_oth_s) is not None: # intersection! + self._is_self_intersecting = True + break + if self._is_self_intersecting is True: + break + return self._is_self_intersecting + + @property + def self_intersection_points(self): + """A tuple of Point2Ds for the locations where the polygon intersects itself. + + This will be an empty tuple if the polygon is not self-intersecting and it + is generally recommended that the Polygon2D.is_self_intersecting property + be checked before using this property. + """ + if self.is_self_intersecting: + int_pts = [] + _segs = self.segments + for i, _s in enumerate(_segs[1: len(_segs) - 1]): + _skip = (i, i + 1, i + 2) + _other_segs = [x for j, x in enumerate(_segs) if j not in _skip] + for _oth_s in _other_segs: + int_pt = _s.intersect_line_ray(_oth_s) + if int_pt is not None: # intersection! + int_pts.append(int_pt) + return tuple(int_pts) + return () + + @property + def is_valid(self): + """Boolean noting whether the polygon is valid (having a non-zero area). + + Note that polygons are still considered valid if they have self-intersecting + edges, or duplicate/colinear vertices. The s_self_intersecting property + identifies self-intersecting edges, and the remove_colinear_vertices method + will remove duplicate/colinear vertices. + """ + return not self.area == 0 + +
[docs] def is_equivalent(self, other, tolerance): + """Boolean for equivalence between this polygon and another (within tolerance). + + The order of the polygon vertices do not have to start from the + same vertex for equivalence to be true, but must be in the same counterclockwise + or clockwise order. + + Args: + other: Polygon2D for comparison. + tolerance: float representing point equivalence. + + Returns: + True if equivalent else False + """ + + # Check number of points + if len(self.vertices) != len(other.vertices): + return False + + vertices = self.vertices + + # Check order + if not vertices[0].is_equivalent(other.vertices[0], tolerance): + self_idx = None + other_pt = other.vertices[0] + for i, pt in enumerate(self.vertices): + if pt.is_equivalent(other_pt, tolerance): + self_idx = i + break + + if self_idx is None: + return False + + # Re-order polygon vertices to match other + vertices = vertices[self_idx:] + vertices[:self_idx] + + is_equivalent = True + for pt, other_pt in zip(vertices[1:], other.vertices[1:]): + is_equivalent = is_equivalent and pt.is_equivalent(other_pt, tolerance) + return is_equivalent
+ +
[docs] def is_rectangle(self, angle_tolerance): + """Test whether this Polygon2D is a rectangle given an angle tolerance. + + Note that this method will return False if the Polygon2D does not have + four vertices, even if they are duplicated or colinear. + + Args: + angle_tolerance: The max angle in radians that the corners of the + rectangle can differ from a right angle before it is not + considered a rectangle. + """ + if len(self.vertices) != 4: + return False + min_ang = (math.pi / 2) - angle_tolerance + max_ang = (math.pi / 2) + angle_tolerance + for i, pt in enumerate(self.vertices): + v1 = self.vertices[i - 2] - self.vertices[i - 1] + v2 = pt - self.vertices[i - 1] + v_angle = v1.angle(v2) + if v_angle < min_ang or v_angle > max_ang: + return False + return True
+ +
[docs] def rectangular_approximation(self): + """Get a rectangular Polygon2D with the same area and aspect ratio as this one. + + This is useful when an interface requires a rectangular input but the + user-defined geometry can be any shape. The resulting rectangle will share + the same center as this one. + """ + b_rect_len = self.max.x - self.min.x + b_rect_hgt = self.max.y - self.min.y + aspect_ratio = b_rect_len / b_rect_hgt + final_hgt = math.sqrt(self.area / aspect_ratio) + final_len = self.area / final_hgt + b_pt = Point2D(self.center.x - (final_len / 2), self.center.y - (final_hgt / 2)) + return Polygon2D.from_rectangle(b_pt, Vector2D(0, 1), final_len, final_hgt)
+ +
[docs] def pole_of_inaccessibility(self, tolerance): + """Get the pole of inaccessibility for the polygon. + + The pole of inaccessibility is the most distant internal point from the + polygon outline. It is not to be confused with the centroid, which + represents the "center of mass" of the polygon and may be outside of + the polygon if the shape is concave. The poly of inaccessibility is + useful for optimal placement of a text label on a polygon. + + The algorithm here is a port of the polylabel library from MapBox + assembled by Michal Hatak. (https://github.com/Twista/python-polylabel). + + Args: + tolerance: The precision to which the pole of inaccessibility + will be computed. + """ + # compute the cell size from the bounding rectangle + min_x, min_y = self.min.x, self.min.y + max_x, max_y = self.max.x, self.max.y + width = max_x - min_x + height = max_y - min_y + cell_size = min(width, height) + h = cell_size / 2.0 + max_dim = max(width, height) + if cell_size == 0 or self.area < max_dim * tolerance: + # degenerate polygon; just return the center + return self.center + + # get an array representation of the polygon and set up the priority queue + _polygon = tuple(pt.to_array() for pt in self.vertices) + cell_queue = PriorityQueue() + + # cover polygon with initial cells + x = min_x + while x < max_x: + y = min_y + while y < max_y: + c = _Cell(x + h, y + h, h, _polygon) + y += cell_size + cell_queue.put((-c.max, time.time(), c)) + x += cell_size + + best_cell = self._get_centroid_cell(_polygon) + + bbox_cell = _Cell(min_x + width / 2, min_y + height / 2, 0, _polygon) + if bbox_cell.d > best_cell.d: + best_cell = bbox_cell + + # recursively iterate until we find the pole + num_of_probes = cell_queue.qsize() + while not cell_queue.empty(): + _, __, cell = cell_queue.get() + + if cell.d > best_cell.d: + best_cell = cell + + if cell.max - best_cell.d <= tolerance: + continue + + h = cell.h / 2 + c = _Cell(cell.x - h, cell.y - h, h, _polygon) + cell_queue.put((-c.max, time.time(), c)) + c = _Cell(cell.x + h, cell.y - h, h, _polygon) + cell_queue.put((-c.max, time.time(), c)) + c = _Cell(cell.x - h, cell.y + h, h, _polygon) + cell_queue.put((-c.max, time.time(), c)) + c = _Cell(cell.x + h, cell.y + h, h, _polygon) + cell_queue.put((-c.max, time.time(), c)) + num_of_probes += 4 + return Point2D(best_cell.x, best_cell.y)
+ +
[docs] def remove_colinear_vertices(self, tolerance): + """Get a version of this polygon without colinear or duplicate vertices. + + Args: + tolerance: The minimum distance that a vertex can be from a line + before it is considered colinear. + """ + if len(self.vertices) == 3: + return self # Polygon2D cannot have fewer than 3 vertices + new_vertices = [] # list to hold the new vertices + skip = 0 # track the number of vertices being skipped/removed + # loop through vertices and remove all cases of colinear verts + for i, _v in enumerate(self.vertices): + _a = self[i - 2 - skip].determinant(self[i - 1]) + self[i - 1].determinant(_v) + \ + _v.determinant(self[i - 2 - skip]) + if abs(_a) >= tolerance: + new_vertices.append(self[i - 1]) + skip = 0 + else: + skip += 1 + # catch case of last two vertices being equal but distinct from first point + if skip != 0 and self.vertices[-2].is_equivalent(self.vertices[-1], tolerance): + pts_2d = self.vertices + _a = pts_2d[-3].determinant(pts_2d[-1]) + \ + pts_2d[-1].determinant(pts_2d[0]) + pts_2d[0].determinant(pts_2d[-3]) + if abs(_a) >= tolerance: + new_vertices.append(pts_2d[-1]) + return Polygon2D(new_vertices)
+ +
[docs] def reverse(self): + """Get a copy of this polygon where the vertices are reversed.""" + _new_poly = Polygon2D(tuple(pt for pt in reversed(self.vertices))) + self._transfer_properties(_new_poly) + if self._is_clockwise is not None: + _new_poly._is_clockwise = not self._is_clockwise + return _new_poly
+ +
[docs] def move(self, moving_vec): + """Get a polygon that has been moved along a vector. + + Args: + moving_vec: A Vector2D with the direction and distance to move the polygon. + """ + _new_poly = Polygon2D(tuple(pt.move(moving_vec) for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def rotate(self, angle, origin): + """Get a polygon that is rotated counterclockwise by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point2D for the origin around which the point will be rotated. + """ + _new_poly = Polygon2D(tuple(pt.rotate(angle, origin) for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def reflect(self, normal, origin): + """Get a polygon reflected across a plane with the input normal and origin. + + Args: + normal: A Vector2D representing the normal vector for the plane across + which the polygon will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point2D representing the origin from which to reflect. + """ + _new_poly = Polygon2D(tuple(pt.reflect(normal, origin) for pt in self.vertices)) + self._transfer_properties(_new_poly) + if self._is_clockwise is not None: + _new_poly._is_clockwise = not self._is_clockwise + return _new_poly
+ +
[docs] def scale(self, factor, origin=None): + """Scale a polygon by a factor from an origin point. + + Args: + factor: A number representing how much the polygon should be scaled. + origin: A Point2D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0). + """ + if origin is None: + return Polygon2D(tuple( + Point2D(pt.x * factor, pt.y * factor) for pt in self.vertices)) + else: + return Polygon2D(tuple(pt.scale(factor, origin) for pt in self.vertices))
+ +
[docs] def offset(self, distance, check_intersection=False): + """Offset the polygon by a given distance inwards or outwards. + + Note that the resulting shape may be self-intersecting if the distance + is large enough and the is_self_intersecting property may be used to identify + these shapes. + + Args: + distance: The distance inwards that the polygon will be offset. + Positive values will always be offset inwards while negative ones + will be offset outwards. + check_intersection: A boolean to note whether the resulting operation + should be checked for self intersection and, if so, None will be + returned instead of the mis-shaped polygon. + """ + # make sure the offset is not zero + if distance == 0: + return self + + # loop through the vertices and get the new offset vectors + init_verts = self._vertices if not self.is_clockwise \ + else list(reversed(self._vertices)) + init_verts = [pt for i, pt in enumerate(init_verts) if pt != init_verts[i - 1]] + move_vecs, max_i = [], len(init_verts) - 1 + for i, pt in enumerate(init_verts): + v1 = init_verts[i - 1] - pt + end_i = i + 1 if i != max_i else 0 + v2 = init_verts[end_i] - pt + if not self.is_clockwise: + ang = v1.angle_clockwise(v2) / 2 + if ang == 0: + ang = math.pi / 2 + m_vec = v1.rotate(-ang).normalize() + m_dist = distance / math.sin(ang) + else: + ang = v1.angle_counterclockwise(v2) / 2 + if ang == 0: + ang = math.pi / 2 + m_vec = v1.rotate(ang).normalize() + m_dist = -distance / math.sin(ang) + m_vec = m_vec * m_dist + move_vecs.append(m_vec) + + # move the vertices by the offset to create the new Polygon2D + new_pts = tuple(pt.move(m_vec) for pt, m_vec in zip(init_verts, move_vecs)) + if self.is_clockwise: + new_pts = tuple(reversed(new_pts)) + new_poly = Polygon2D(new_pts) + + # check for self intersection between the moving vectors if requested + if check_intersection: + poly_segs = new_poly.segments + _segs = [LineSegment2D(p, v) for p, v in zip(init_verts, move_vecs)] + _skip = (0, len(_segs) - 1) + _other_segs = [x for j, x in enumerate(poly_segs) if j not in _skip] + for _oth_s in _other_segs: + if _segs[0].intersect_line_ray(_oth_s) is not None: # intersection! + return None + for i, _s in enumerate(_segs[1: len(_segs)]): + _skip = (i, i + 1) + _other_segs = [x for j, x in enumerate(poly_segs) if j not in _skip] + for _oth_s in _other_segs: + if _s.intersect_line_ray(_oth_s) is not None: # intersection! + return None + return new_poly
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersections between this polygon and a Ray2D or LineSegment2D. + + Args: + line_ray: A LineSegment2D or Ray2D or to intersect. + + Returns: + A list with Point2D objects for the intersections. + List will be empty if no intersection exists. + """ + intersections = [] + for _s in self.segments: + inters = intersect_line2d(_s, line_ray) + if inters is not None: + intersections.append(inters) + return intersections
+ +
[docs] def intersect_line_infinite(self, ray): + """Get the intersections between this polygon and a Ray2D extended infinitely. + + Args: + ray: A Ray2D or to intersect. This will be extended in both + directions infinitely for the intersection. + + Returns: + A list with Point2D objects for the intersections. + List will be empty if no intersection exists. + """ + intersections = [] + for _s in self.segments: + inters = intersect_line2d_infinite(_s, ray) + if inters is not None: + intersections.append(inters) + return intersections
+ +
[docs] def point_relationship(self, point, tolerance): + """Test whether a Point2D lies inside, outside or on the boundary of the polygon. + + Compared to other methods like is_point_inside this method is slow. However, + it covers all edge cases, including the literal edge of the polygon. + + Args: + point: A Point2D for which the relationship to the polygon will be tested. + tolerance: The minimum distance from the edge at which a point is + considered to lie on the edge. + + Returns: + An integer denoting the relationship of the point. + + This will be one of the following: + + * -1 = Outside polygon + * 0 = On the edge of the polygon + * +1 = Inside polygon + """ + if self.is_point_on_edge(point, tolerance): + return 0 + if self.is_point_inside_bound_rect(point): + return 1 + return -1
+ +
[docs] def is_point_on_edge(self, point, tolerance): + """Test whether a Point2D lies on the boundary edges of the polygon. + + Args: + point: A Point2D for which the edge relationship will be tested. + tolerance: The minimum distance from the edge at which a point is + considered to lie on the edge. + + Returns: + A boolean denoting whether the point lies on the polygon edges (True) + or not on the edges (False). + """ + for _s in self.segments: + close_pt = closest_point2d_on_line2d(point, _s) + if point.distance_to_point(close_pt) <= tolerance: + return True + return False
+ +
[docs] def is_point_inside_check(self, point): + """Test whether a Point2D lies inside the polygon with checks for fringe cases. + + This method uses the same calculation as the the `is_point_inside` method + but it includes additional checks for the fringe cases noted in the + `is_point_inside` description. Using this method means that it will always + yield the right result for all convex polygons and concave polygons with + one concave turn (provided that they do not have colinear vertices). + This is suitable for nearly all practical purposes and the only cases + that could yield an incorrect result are when a point is co-linear with + two or more polygon edges along the X vector like so: + + .. code-block:: shell + + _____ _____ _____ + | . |___| |___| | + |_________________________| + + While this method covers most fringe cases, it will not test for whether + a point lies perfectly on the edge of the polygon so it assesses whether + a point lies inside the polygon up to Python floating point tolerance + (16 digits). If distinguishing edge conditions from inside/ outside is + important, the `point_relationship` method should be used. + + Args: + point: A Point2D for which the inside/ outside relationship will be tested. + Returns: + A boolean denoting whether the point lies inside (True) or outside (False). + """ + def non_duplicate_intersect(test_ray): + inters = [] + n_int = 0 + for _s in self.segments: + inter = intersect_line2d(_s, test_ray) + if inter is not None: + try: + if inter != inters[-1]: # ensure intersection is not duplicated + n_int += 1 + inters.append(inter) + except IndexError: + n_int += 1 + inters.append(inter) + return n_int, inters + + n_int, inters = non_duplicate_intersect(Ray2D(point, Vector2D(1, 0))) + # check that intersections do not form a polygon segment co-linear with test_ray + if self.is_convex is False and n_int == 2: + for _s in self.segments: + if _s.p1 == inters[0] and _s.p2 == inters[1]: + return self.is_point_inside(point, Vector2D(0, 1)) + if n_int % 2 == 0: + return False + + n_int, inters = non_duplicate_intersect(Ray2D(point, Vector2D(0, 1))) + # check that intersections do not form a polygon segment co-linear with test_ray + if self.is_convex is False and n_int == 2: + for _s in self.segments: + if _s.p1 == inters[0] and _s.p2 == inters[1]: + return self.is_point_inside(point, Vector2D(1, 0)) + if n_int % 2 == 0: + return False + return True
+ +
[docs] def is_point_inside(self, point, test_vector=Vector2D(1, 0.00001)): + """Test whether a Point2D lies inside or outside the polygon. + + This method is the fastest way to tell if a point is inside a polygon when + the given point lies inside the boundary rectangle of the polygon. + However, while this method gives the correct result in 99.9% of cases, + there are a few fringe cases where it will not give the correct result. + Specifically these are: + + .. code-block:: shell + + 1 - When the test_ray intersects perfectly with a polygon vertex. + For example, this case with an X-unit (1, 0) test_vector: + _____________ + | . | + | / + |___________/ + 2 - When there are two polygon vertices that are colinear with the point + along the test_ray. For example, this case with an X-unit test_vector: + _____ + | . |____ + |__________| + + Use the `is_point_inside_check` method if a result that covers these fringe + cases is needed. Oftentimes, it is more practical to use a test_vector + with a low probability of encountering the fringe cases than to use the + (much longer) `is_point_inside_check` method. + + Args: + point: A Point2D for which the inside/outside relationship will be tested. + test_vector: Optional vector to set the direction in which intersections + with the polygon edges will be evaluated to determine if the + point is inside. Default is a slight variation of the X-unit + vector with a low probability of encountering the unsupported + fringe cases. + + Returns: + A boolean denoting whether the point lies inside (True) or outside (False). + """ + test_ray = Ray2D(point, test_vector) + n_int = 0 + for _s in self.segments: + if does_intersection_exist_line2d(_s, test_ray): + n_int += 1 + if n_int % 2 == 0: + return False + return True
+ +
[docs] def is_point_inside_bound_rect(self, point, test_vector=Vector2D(1, 0.00001)): + """Test whether a Point2D lies roughly inside or outside the polygon. + + This function is virtually identical to the `is_point_inside` + method but will first do a check to see if the point lies inside the + polygon bounding rectangle. As such, it is a faster approach when one + expects many of tested points to lie far away from the polygon. + + Args: + point: A Point2D for which the inside/ outside relationship will be tested. + test_vector: Optional vector to set the direction in which intersections + with the polygon edges will be evaluated to determine if the + point is inside. Default is the X unit vector. + + Returns: + A boolean denoting whether the point lies inside (True) or outside (False). + """ + min = self.min + max = self.max + if point.x < min.x or point.y < min.y or point.x > max.x or point.y > max.y: + return False + return self.is_point_inside(point, test_vector)
+ +
[docs] def is_polygon_inside(self, polygon): + """Test whether another Polygon2D lies completely inside this polygon. + + Args: + polygon: A Polygon2D to test whether it is completely inside this one. + + Returns: + A boolean denoting whether the polygon lies inside (True) or not (False). + """ + # if the first polygon vertex lies outside, we know it is not inside. + if not self.is_point_inside_bound_rect(polygon[0]): + return False + # if one of the edges intersects, we know it has crossed outside. + for seg in self.segments: + for _s in polygon.segments: + if does_intersection_exist_line2d(seg, _s): + return False + return True
+ +
[docs] def is_polygon_outside(self, polygon): + """Test whether another Polygon2D lies completely outside this polygon. + + Args: + polygon: A Polygon2D to test whether it is completely outside this one. + + Returns: + A boolean denoting whether the polygon lies outside (True) or not (False). + """ + # if the first polygon vertex lies inside, we know it is not outside. + if self.is_point_inside_bound_rect(polygon[0]): + return False + # if one of the edges intersects, we know it has crossed inside. + for seg in self.segments: + for _s in polygon.segments: + if does_intersection_exist_line2d(seg, _s): + return False + return True
+ +
[docs] def does_polygon_touch(self, polygon, tolerance): + """Test whether another Polygon2D touches, overlaps or is inside this polygon. + + Args: + polygon: A Polygon2D to test whether it touches this polygon. + tolerance: The minimum distance from an edge at which a point is + considered to touch the edge. + + Returns: + A boolean denoting whether the polygon touches (True) or not (False). + """ + # perform a bounding rectangle check to see if the polygons cannot overlap + if not Polygon2D.overlapping_bounding_rect(self, polygon, tolerance): + return False + + # first evaluate the point relationships + pt_rels1 = [self.point_relationship(pt, tolerance) for pt in polygon] + if 0 in pt_rels1 or 1 in pt_rels1: + return True # definitely touching polygons + pt_rels2 = [polygon.point_relationship(pt, tolerance) for pt in self] + if 0 in pt_rels2 or 1 in pt_rels2: + return True # definitely touching polygons + + # if any of the segments intersect the other polygon, there is overlap + for seg in self.segments: + for _s in polygon.segments: + if does_intersection_exist_line2d(seg, _s): + return True + + # we can reliably say that the polygons do not touch + return False
+ +
[docs] def polygon_relationship(self, polygon, tolerance): + """Test whether another Polygon2D lies inside, outside or overlaps this one. + + This method is not usually the fastest for understanding the relationship + between polygons but it accurately accounts for tolerance such that the + case of the two polygons sharing edges will not determine the outcome. + Only when the polygon has vertices that are truly outside this polygon + within the tolerance will the relationship become outside (or intersecting + if one of the vertices is already inside within the tolerance). + + In the case of the input polygon being identical to the current polygon, + the relationship will be Inside. + + Args: + polygon: A Polygon2D for which the relationship to the current polygon + will be tested. + tolerance: The minimum distance from the edge at which a point is + considered to lie on the edge. + + Returns: + An integer denoting the relationship of the polygon. + + This will be one of the following: + + * -1 = Outside this polygon + * 0 = Overlaps (intersects) this polygon + * +1 = Inside this polygon + """ + # perform a bounding rectangle check to see if the polygons cannot overlap + if not Polygon2D.overlapping_bounding_rect(self, polygon, tolerance): + return -1 + + # first evaluate the point relationships to rule out the inside case + pt_rels1 = [self.point_relationship(pt, tolerance) for pt in polygon] + pt_rels2 = [polygon.point_relationship(pt, tolerance) for pt in self] + if all(r1 >= 0 for r1 in pt_rels1) and all(r2 <= 0 for r2 in pt_rels2): + poi = polygon._point_in_polygon(tolerance) + if self.is_point_inside(poi) == 1: + return 1 # definitely inside the polygon + if 1 in pt_rels1 or 1 in pt_rels2: + return 0 # definitely overlap in the polygons + if all(r2 == 0 for r2 in pt_rels2): + poi = self._point_in_polygon(tolerance) + if polygon.is_point_inside(poi) == 1: + return 0 + + # offset one of the polygons inward by the tolerance + off_poly = polygon.offset(tolerance) + # if any of the offset segments intersect the other polygon, there is overlap + for seg in self.segments: + for _s in off_poly.segments: + if does_intersection_exist_line2d(seg, _s): + return 0 + + # we can reliably say that the polygons have nothing to do with one another + return -1
+ +
[docs] def distance_to_point(self, point): + """Get the minimum distance between this shape and a point. + + Points that are inside the Polygon2D will return a distance of zero. + If the distance of an interior point to an edge is needed, the + distance_from_edge_to_point method should be used. + + Args: + point: A Point2D object to which the minimum distance will be computed. + + Returns: + The distance to the input point. Will be zero if the point is + inside the Polygon2D. + """ + if self.is_point_inside_bound_rect(point): + return 0 + return min(seg.distance_to_point(point) for seg in self.segments)
+ +
[docs] def distance_from_edge_to_point(self, point): + """Get the minimum distance between the edge of this shape and the input point. + + Args: + point: A Point2D object to which the minimum distance will be computed. + + Returns: + The distance to the input point. Will be zero if the point is + inside the Polygon2D. + """ + return min(seg.distance_to_point(point) for seg in self.segments)
+ +
[docs] def snap_to_polygon(self, polygon, tolerance): + """Snap another Polygon2D to this one for differences smaller than the tolerance. + + This is useful to run before performing operations where small tolerance + differences are likely to cause issues, such as in boolean operations. + + Args: + polygon: A Polygon2D which will be snapped to the current polygon. + tolerance: The minimum distance at which points will be snapped. + + Returns: + A version of the polygon that is snapped to this Polygon2D. + """ + new_verts = [] + for pt in polygon.vertices: + # first check if the point can be snapped to a vertex + for s_pt in self.vertices: + if pt.is_equivalent(s_pt, tolerance): + new_verts.append(s_pt) + break + else: + # check if the point can be snapped to a segment + for seg in self.segments: + s_pt = seg.closest_point(pt) + if s_pt.distance_to_point(pt) <= tolerance: + new_verts.append(s_pt) + break + else: # point could not be snapped + new_verts.append(pt) + return Polygon2D(new_verts)
+ +
[docs] def snap_to_grid(self, grid_increment): + """Snap this polygon's vertices to the nearest grid node defined by an increment. + + Args: + grid_increment: A positive number for dimension of each grid cell. This + typically should be equal to the tolerance or larger but should + not be larger than the smallest detail of the polygon that you + wish to resolve. + + Returns: + A version of this polygon that is snapped to the grid. + """ + new_verts = [] + for pt in self.vertices: + new_x = grid_increment * round(pt.x / grid_increment) + new_y = grid_increment * round(pt.y / grid_increment) + new_verts.append(Point2D(new_x, new_y)) + return Polygon2D(new_verts)
+ +
[docs] def to_dict(self): + """Get Polygon2D as a dictionary.""" + return {'type': 'Polygon2D', + 'vertices': [pt.to_array() for pt in self.vertices]}
+ +
[docs] def to_array(self): + """Get a list of lists where each sub-list represents a Point2D vertex.""" + return tuple(pt.to_array() for pt in self.vertices)
+ + def _to_bool_poly(self): + """Translate the Polygon2D to a BooleanPolygon.""" + b_pts = (pb.BooleanPoint(pt.x, pt.y) for pt in self.vertices) + return pb.BooleanPolygon([b_pts]) + + def _to_snapped_bool_poly(self, snap_ref_polygon, tolerance): + """Snap a Polygon2D to this one and translate it to a BooleanPolygon. + + This is necessary to ensure that boolean operations will succeed between + two polygons. + """ + new_poly = snap_ref_polygon.snap_to_polygon(self, tolerance) + return new_poly._to_bool_poly() + + @staticmethod + def _from_bool_poly(bool_polygon): + """Get a list of Polygon2D from a BooleanPolygon object.""" + return [Polygon2D(tuple(Point2D(pt.x, pt.y) for pt in new_poly)) + for new_poly in bool_polygon.regions if len(new_poly) > 2] + +
[docs] def boolean_union(self, polygon, tolerance): + """Get a list of Polygon2D for the union of this Polygon and another. + + Note that the result will not differentiate hole polygons from boundary + polygons and so it may be desirable to use the Polygon2D.is_polygon_inside + method to distinguish whether a given polygon in the result represents + a hole in another polygon in the result. + + Also note that this method will return the original polygons when there + is no overlap in the two. + + Args: + polygon: A Polygon2D for which the union with the current polygon + will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A list of Polygon2D representing the union of the two polygons. + """ + result = pb.union( + self._to_bool_poly(), + polygon._to_snapped_bool_poly(self, tolerance), + tolerance / 100) + return Polygon2D._from_bool_poly(result)
+ +
[docs] def boolean_intersect(self, polygon, tolerance): + """Get a list of Polygon2D for the intersection of this Polygon and another. + + Args: + polygon: A Polygon2D for which the intersection with the current polygon + will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A list of Polygon2D representing the intersection of the two polygons. + Will be an empty list if no overlap exists between the polygons. + """ + result = pb.intersect( + self._to_bool_poly(), + polygon._to_snapped_bool_poly(self, tolerance), + tolerance / 100) + return Polygon2D._from_bool_poly(result)
+ +
[docs] def boolean_difference(self, polygon, tolerance): + """Get a list of Polygon2D for the subtraction of another polygon from this one. + + Args: + polygon: A Polygon2D for which the difference with the current polygon + will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A list of Polygon2D representing the difference of the two polygons. + Will be an empty list if subtracting the polygons results in the complete + elimination of this polygon. Will be the original polygon when there + is no overlap between the polygons. + """ + result = pb.difference( + self._to_bool_poly(), + polygon._to_snapped_bool_poly(self, tolerance), + tolerance / 100) + return Polygon2D._from_bool_poly(result)
+ +
[docs] def boolean_xor(self, polygon, tolerance): + """Get Polygon2D list for the exclusive disjunction of this polygon and another. + + Note that this method is prone to merging holes that may exist in the + result into the boundary to create a single list of joined vertices, + which may not always be desirable. In this case, it may be desirable + to do two separate boolean_difference calculations instead. + + Also note that, when the result includes separate polygons for holes, + it will not differentiate hole polygons from boundary polygons + and so it may be desirable to use the Polygon2D.is_polygon_inside + method to distinguish whether a given polygon in the result represents + a hole within another polygon in the result. + + Args: + polygon: A Polygon2D for which the exclusive disjunction with the + current polygon will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A list of Polygon2D representing the exclusive disjunction of the + two polygons. Will be the original polygons when there is no overlap + in the two. + """ + result = pb.xor( + self._to_bool_poly(), + polygon._to_snapped_bool_poly(self, tolerance), + tolerance / 100) + return Polygon2D._from_bool_poly(result)
+ +
[docs] @staticmethod + def snap_polygons(polygons, tolerance): + """Snap several Polygon2D to each other if they differ less than the tolerance. + + This is useful to run before performing operations where small tolerance + differences are likely to cause issues, such as in boolean operations. + + Args: + polygons: A list of Polygon2D, which will be snapped to each other. + tolerance: The minimum distance at which points will be snapped. + + Returns: + A list of the input polygon2D that have been snapped to one another. + """ + new_polygons = list(polygons) + for i, poly_1 in enumerate(new_polygons): + try: + for j, poly_2 in enumerate(new_polygons[i + 1:]): + new_polygons[i + j + 1] = poly_1.snap_to_polygon(poly_2, tolerance) + except IndexError: + pass # we have reached the end of the list of polygons + return new_polygons
+ +
[docs] @staticmethod + def boolean_union_all(polygons, tolerance): + """Get a list of Polygon2D for the union of several Polygon2D. + + Using this method is more computationally efficient than calling the + Polygon2D.boolean_union() method multiple times as this method will + only compute the intersection of the segments once. + + Note that the result will not differentiate hole polygons from boundary + polygons and so it may be desirable to use the Polygon2D.is_polygon_inside + method to distinguish whether a given polygon in the result represents + a hole in another polygon in the result. + + Also note that this method will return the original polygons when there + is no overlap in the two. + + Args: + polygons: An array of Polygon2D for which the union will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A list of Polygon2D representing the union of all the polygons. + """ + polygons = Polygon2D.snap_polygons(polygons, tolerance) + bool_polys = [poly._to_bool_poly() for poly in polygons] + result = pb.union_all(bool_polys, tolerance / 100) + return Polygon2D._from_bool_poly(result)
+ +
[docs] @staticmethod + def boolean_intersect_all(polygons, tolerance): + """Get a list of Polygon2D for the intersection of several Polygon2D. + + Using this method is more computationally efficient than calling the + Polygon2D.boolean_intersect() method multiple times as this method will + only compute the intersection of the segments once. + + Note that the result will not differentiate hole polygons from boundary + polygons and so it may be desirable to use the Polygon2D.is_polygon_inside + method to distinguish whether a given polygon in the result represents + a hole in another polygon in the result. + + Also note that this method will return the original polygons when there + is no overlap in the two. + + Args: + polygons: An array of Polygon2D for which the intersection will be computed. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A list of Polygon2D representing the intersection of all the polygons. + Will be an empty list if no overlap exists between the polygons. + """ + polygons = Polygon2D.snap_polygons(polygons, tolerance) + bool_polys = [poly._to_bool_poly() for poly in polygons] + result = pb.intersect_all(bool_polys, tolerance / 100) + return Polygon2D._from_bool_poly(result)
+ +
[docs] @staticmethod + def boolean_split(polygon1, polygon2, tolerance): + """Split two Polygon2D with one another to get the intersection and difference. + + Using this method is more computationally efficient than calling the + Polygon2D.intersect() and Polygon2D.difference() methods individually as + this method will only compute the intersection of the segments once. + + Note that the result will not differentiate hole polygons from boundary + polygons and so it may be desirable to use the Polygon2D.is_polygon_inside + method to distinguish whether a given polygon in the result represents + a hole in another polygon in the result. + + Args: + polygon1: A Polygon2D for the first polygon that will be split with + the second polygon. + polygon2: A Polygon2D for the second polygon that will be split with + the first polygon. + tolerance: The minimum distance between points before they are + considered distinct from one another. + + Returns: + A tuple with three elements + + - intersection: A list of Polygon2D for the intersection of the two + input polygons. + + - poly1_difference: A list of Polygon2D for the portion of polygon1 that does + not overlap with polygon2. When combined with the intersection, this + makes a split version of polygon1. + + - poly2_difference: A list of Polygon2D for the portion of polygon2 that does + not overlap with polygon1. When combined with the intersection, this + makes a split version of polygon2. + """ + int_result, poly1_result, poly2_result = pb.split( + polygon1._to_bool_poly(), + polygon2._to_snapped_bool_poly(polygon1, tolerance), + tolerance / 100) + intersection = Polygon2D._from_bool_poly(int_result) + poly1_difference = Polygon2D._from_bool_poly(poly1_result) + poly2_difference = Polygon2D._from_bool_poly(poly2_result) + return intersection, poly1_difference, poly2_difference
+ +
[docs] @staticmethod + def intersect_polygon_segments(polygon_list, tolerance): + """Intersect the line segments of a Polygon2D array to ensure matching segments. + + Specifically, this method checks a list of polygons in a pairwise manner to + see if one contains a vertex along an edge segment of the other within the + given tolerance. If so, the method creates a co-located vertex at that point, + partitioning the edge segment into two edge segments. Point ordering is + reserved within each Polygon2D and the order of Polygon2Ds within the input + polygon_list is also preserved. + + Args: + polygon_list: List of Polygon2Ds which will have their segments + intersected with one another. + tolerance: Distance within which two points are considered to be + co-located. + + Returns: + The input list of Polygon2D objects with extra vertices inserted + where necessary. + """ + for i in range(len(polygon_list) - 1): + # No need for j to start at 0 since two polygons are passed + # and they are compared against one other within intersect_segments. + for j in range(i + 1, len(polygon_list)): + polygon_list[i], polygon_list[j] = \ + Polygon2D.intersect_segments(polygon_list[i], polygon_list[j], + tolerance) + return polygon_list
+ +
[docs] @staticmethod + def intersect_segments(polygon1, polygon2, tolerance): + """Intersect the line segments of two Polygon2Ds to ensure matching segments. + + Specifically, this method checks two adjacent polygons to see if one contains + a vertex along an edge segment of the other within the given tolerance. If so, + it creates a co-located vertex at that point, partitioning the edge segment + into two edge segments. Point ordering is preserved. + + Args: + polygon1: First polygon to check. + polygon2: Second polygon to check. + tolerance: Distance within which two points are considered to be co-located. + + Returns: + Two polygon objects with extra vertices inserted if necessary. + """ + polygon1_updates = [] + polygon2_updates = [] + + # bounding rectangle check + if not Polygon2D.overlapping_bounding_rect(polygon1, polygon2, tolerance): + return polygon1, polygon2 # no overlap + + # test if each point of polygon2 is within the tolerance distance of any segment + # of polygon1. If so, add the closest point on the segment to the polygon1 + # update list. And vice versa (testing polygon2 against polygon1). + for i1, seg1 in enumerate(polygon1.segments): + for i2, seg2 in enumerate(polygon2.segments): + # Test polygon1 against polygon2 + x = closest_point2d_on_line2d(seg2.p1, seg1) + if all(p.distance_to_point(x) > tolerance for p in polygon1.vertices) \ + and x.distance_to_point(seg2.p1) <= tolerance: + polygon1_updates.append((i1, x)) + # Test polygon2 against polygon1 + y = closest_point2d_on_line2d(seg1.p1, seg2) + if all(p.distance_to_point(y) > tolerance for p in polygon2.vertices) \ + and y.distance_to_point(seg1.p1) <= tolerance: + polygon2_updates.append((i2, y)) + + # apply any updates to polygon1 + polygon1 = Polygon2D._insert_updates_in_order(polygon1, polygon1_updates) + + # Apply any updates to polygon2 + polygon2 = Polygon2D._insert_updates_in_order(polygon2, polygon2_updates) + + return polygon1, polygon2
+ +
[docs] @staticmethod + def overlapping_bounding_rect(polygon1, polygon2, tolerance): + """Check if the bounding rectangles of two polygons overlap within a tolerance. + + This is particularly useful as a check before performing computationally intense + processes between two polygons like intersection or boolean operations. + Checking the overlap of the bounding boxes is extremely quick with this + method's use of the the Separating Axis Theorem. + + Args: + polygon1: The first polygon to check. + polygon2: The second polygon to check. + tolerance: Distance within which two points are considered to be co-located. + """ + # Bounding rectangle check using the Separating Axis Theorem + polygon1_width = polygon1.max.x - polygon1.min.x + polygon2_width = polygon2.max.x - polygon2.min.x + dist_btwn_x = abs(polygon1.center.x - polygon2.center.x) + x_gap_btwn_rect = dist_btwn_x - (0.5 * polygon1_width) - (0.5 * polygon2_width) + + polygon1_height = polygon1.max.y - polygon1.min.y + polygon2_height = polygon2.max.y - polygon2.min.y + dist_btwn_y = abs(polygon1.center.y - polygon2.center.y) + y_gap_btwn_rect = dist_btwn_y - (0.5 * polygon1_height) - (0.5 * polygon2_height) + + if x_gap_btwn_rect > tolerance or y_gap_btwn_rect > tolerance: + return False # no overlap + return True # overlap exists
+ +
[docs] @staticmethod + def group_by_overlap(polygons, tolerance): + """Group Polygon2Ds that overlap one another within the tolerance. + + This is useful as a pre-step before running Polygon2D.boolean_union_all() + in order to assess whether unionizing is necessary and to ensure that + it is only performed among the necessary groups of polygons. + + This method will return the minimal number of overlapping polygon groups + thanks to a recursive check of whether groups can be merged. + + Args: + polygons: A list of Polygon2D to be grouped by their overlapping. + tolerance: The minimum distance from the edge of a neighboring polygon + at which a point is considered to overlap with that polygon. + + Returns: + A list of lists where each sub-list represents a group of polygons + that all overlap with one another. + """ + # sort the polygons by area to help larger ones grab smaller ones + polygons = list(sorted(polygons, key=lambda x: x.area, reverse=True)) + + # loop through the polygons and check to see if it overlaps with the others + grouped_polys = [[polygons[0]]] + for poly in polygons[1:]: + group_found = False + for poly_group in grouped_polys: + for oth_poly in poly_group: + if poly.polygon_relationship(oth_poly, tolerance) >= 0: + poly_group.append(poly) + 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 + + # if some groups were found, recursively merge groups together + old_group_len = len(polygons) + while len(grouped_polys) != old_group_len: + new_groups, g_to_remove = grouped_polys[:], [] + for i, group_1 in enumerate(grouped_polys): + try: + for j, group_2 in enumerate(grouped_polys[i + 1:]): + if Polygon2D._groups_overlap(group_1, group_2, tolerance): + new_groups[i] = new_groups[i] + group_2 + g_to_remove.append(i + j + 1) + except IndexError: + pass # we have reached the end of the list of polygons + if len(g_to_remove) != 0: + g_to_remove = list(set(g_to_remove)) + g_to_remove.sort() + for ri in reversed(g_to_remove): + new_groups.pop(ri) + old_group_len = len(grouped_polys) + grouped_polys = new_groups + return grouped_polys
+ +
[docs] @staticmethod + def group_by_touching(polygons, tolerance): + """Group Polygon2Ds that touch or overlap one another within the tolerance. + + This is useful to group geometries together before extracting a bounding + rectangle or convex hull around multiple polygons. + + This method will return the minimal number of polygon groups + thanks to a recursive check of whether groups can be merged. + + Args: + polygons: A list of Polygon2D to be grouped by their touching. + tolerance: The minimum distance from the edge of a neighboring polygon + at which a point is considered to touch that polygon. + + Returns: + A list of lists where each sub-list represents a group of polygons + that all touch or overlap with one another. + """ + # sort the polygons by area to help larger ones grab smaller ones + polygons = list(sorted(polygons, key=lambda x: x.area, reverse=True)) + + # loop through the polygons and check to see if it overlaps with the others + grouped_polys = [[polygons[0]]] + for poly in polygons[1:]: + group_found = False + for poly_group in grouped_polys: + for oth_poly in poly_group: + if poly.does_polygon_touch(oth_poly, tolerance): + poly_group.append(poly) + group_found = True + break + if group_found: + break + if not group_found: # the polygon does not touch any of the others + grouped_polys.append([poly]) # make a new group for the polygon + + # if some groups were found, recursively merge groups together + old_group_len = len(polygons) + while len(grouped_polys) != old_group_len: + new_groups, g_to_remove = grouped_polys[:], [] + for i, group_1 in enumerate(grouped_polys): + try: + for j, group_2 in enumerate(grouped_polys[i + 1:]): + if Polygon2D._groups_touch(group_1, group_2, tolerance): + new_groups[i] = new_groups[i] + group_2 + g_to_remove.append(i + j + 1) + except IndexError: + pass # we have reached the end of the list of polygons + if len(g_to_remove) != 0: + g_to_remove = list(set(g_to_remove)) + g_to_remove.sort() + for ri in reversed(g_to_remove): + new_groups.pop(ri) + old_group_len = len(grouped_polys) + grouped_polys = new_groups + return grouped_polys
+ + @staticmethod + def _groups_overlap(group_1, group_2, tolerance): + """Evaluate whether two groups of Polygons overlap with one another.""" + for poly_1 in group_1: + for poly_2 in group_2: + if poly_1.polygon_relationship(poly_2, tolerance) >= 0: + return True + return False + + @staticmethod + def _groups_touch(group_1, group_2, tolerance): + """Evaluate whether two groups of Polygons touch with one another.""" + for poly_1 in group_1: + for poly_2 in group_2: + if poly_1.does_polygon_touch(poly_2, tolerance): + return True + return False + +
[docs] @staticmethod + def joined_intersected_boundary(polygons, tolerance): + """Get the boundary around several Polygon2D that are touching one another. + + This method is faster and more reliable than the gap_crossing_boundary + but requires that the Polygon2D be touching one another within the tolerance. + + Args: + polygons: The polygons to be joined into a boundary. These polygons + should have colinear vertices removed and they should not contain + degenerate polygons at the tolerance. The remove_colinear_vertices + method can be used to pre-process the input polygons to ensure they + meet these criteria. + tolerance: The tolerance at which the polygons are to be intersected + and then joined to give a resulting boundary. + + Returns: + A list of Polygon2D that represent the boundary around the input polygons. + Note that some of these Polygon2D may represent 'holes' within others + and it may be necessary to assess this when interpreting the result. + """ + # intersect the polygons with one another + int_poly = Polygon2D.intersect_polygon_segments(polygons, tolerance) + + # get indices of all unique vertices across the polygons + vertices = [] # collection of vertices as point objects + poly_indices = [] # collection of polygon indices + for loop in int_poly: + ind = [] + for v in loop: + found = False + for i, vert in enumerate(vertices): + if v.is_equivalent(vert, tolerance): + found = True + ind.append(i) + break + if not found: # add new point + vertices.append(v) + ind.append(len(vertices) - 1) + poly_indices.append(ind) + + # use the unique vertices to extract naked edges + edge_i = [] + edge_t = [] + for poly_i in poly_indices: + for i, vi in enumerate(poly_i): + try: # this can get slow for large number of vertices + ind = edge_i.index((vi, poly_i[i - 1])) + edge_t[ind] += 1 + except ValueError: # make sure reversed edge isn't there + try: + ind = edge_i.index((poly_i[i - 1], vi)) + edge_t[ind] += 1 + except ValueError: # add a new edge + if poly_i[i - 1] != vi: # avoid cases of same start and end + edge_i.append((poly_i[i - 1], vi)) + edge_t.append(0) + ext_edges = [] + for i, et in enumerate(edge_t): + if et == 0: + edg_ind = edge_i[i] + pts_2d = (vertices[edg_ind[0]], vertices[edg_ind[1]]) + ext_edges.append(LineSegment2D.from_end_points(*pts_2d)) + + # join the naked edges into closed polygons + outlines = Polyline2D.join_segments(ext_edges, tolerance) + closed_polys = [] + for bnd in outlines: + if isinstance(bnd, Polyline2D) and bnd.is_closed(tolerance): + closed_polys.append(bnd.to_polygon(tolerance)) + return closed_polys
+ +
[docs] @staticmethod + def gap_crossing_boundary(polygons, min_separation, tolerance): + """Get the boundary around several Polygon2D, crossing gaps of min_separation. + + This method is less reliable than the joined_intersected_boundary because + higher values of min_separation that are greater than the lengths of polygon + segments can cause important details of the polygons to disappear. However, + when used appropriately, it can provide a boundary that jumps across gaps + to give resulting polygons that effectively bound all of the input polygons. + + Args: + polygons: The polygons to be joined into a boundary. These polygons + should have colinear vertices removed and they should not contain + degenerate polygons at the tolerance. The remove_colinear_vertices + method can be used to pre-process the input polygons to ensure they + meet these criteria. + min_separation: A number for the minimum distance between Polygon2D that + is considered a meaningful separation. In other words, this is + the maximum distance of the gap across + tolerance: The maximum difference between coordinate values of two + vertices at which they can be considered equivalent. + + Returns: + A list of Polygon2D that represent the boundary around the input polygons. + Note that some of these Polygon2D may represent 'holes' within others + and it may be necessary to assess this when interpreting the result. + """ + # ensure that all of the input polygons are counterclockwise + cclock_poly = [] + for poly in polygons: + if poly.is_clockwise: + cclock_poly.append(poly.reverse()) + else: + cclock_poly.append(poly) + + # determine which Polygon2D segments are 'exterior' using the min_separation + right_ang = -math.pi / 2 + ext_segs = [] + for i, poly in enumerate(cclock_poly): + # remove any short segments + rel_segs = [s for s in poly.segments if s.length > min_separation] + + # create min_separation line segments to be used to test intersection + test_segs = [] + for _s in rel_segs: + d_vec = _s.v.rotate(right_ang).normalize() + seg_pts = _s.subdivide(min_separation) + if len(seg_pts) <= 3: + seg_pts = [_s.midpoint] + else: + seg_pts = seg_pts[1:-1] + spec_test_segs = [] + for spt in seg_pts: + m_pt = spt.move(d_vec * -tolerance) + spec_test_segs.append(LineSegment2D(m_pt, d_vec * min_separation)) + test_segs.append(spec_test_segs) + + # intersect the test line segments to asses which parts are exterior + non_int_segs = [] + other_poly = [p for j, p in enumerate(cclock_poly) if j != i] + for j, (_s, int_lins) in enumerate(zip(rel_segs, test_segs)): + int_vals = [0] * len(int_lins) + for m, int_lin in enumerate(int_lins): + for _oth_p in other_poly: + if _oth_p.intersect_line_ray(int_lin): # intersection! + int_vals[m] = 1 + break + if sum(int_vals) == len(int_lins): # fully internal line + continue + else: # if the polygon is concave, also check for self intersection + if not poly.is_convex: + _other_segs = [x for k, x in enumerate(rel_segs) if k != j] + for m, int_lin in enumerate(int_lins): + for _oth_s in _other_segs: + if int_lin.intersect_line_ray(_oth_s) is not None: + int_vals[m] = 1 + break + + # determine the exterior segments using the intersections + check_sum = sum(int_vals) + if check_sum == 0: # fully external line + non_int_segs.append(_s) + elif len(int_vals) == 1 or check_sum == len(int_vals): + continue # fully internal line + else: # line that extends from inside to outside + # first see if the exterior part is meaningful + count_in_a_rows, repeat_count = [], 0 + for v in int_vals: + if v == 0: + repeat_count += 1 + count_in_a_rows.append(repeat_count) + else: + repeat_count = 0 + max_repeat = max(count_in_a_rows) + # if the exterior part is meaningful, split it + if max_repeat != 1: + last_pt = _s.p1 if int_vals[0] == 0 else None + for v, ts in zip(int_vals, int_lins): + if v == 0 and last_pt is None: + last_pt = ts.p1 + elif v == 1 and last_pt is not None: + lin_seg = LineSegment2D.from_end_points(last_pt, ts.p1) + last_pt = None + non_int_segs.append(lin_seg) + if last_pt is not None: + lin_seg = LineSegment2D.from_end_points(last_pt, _s.p2) + non_int_segs.append(lin_seg) + + ext_segs.extend(non_int_segs) + + # loop through exterior segments and add segments across the min_separation + joining_segs = [] + for i, e_seg in enumerate(ext_segs): + try: + for o_seg in ext_segs[i + 1:]: + dist, pts = closest_end_point2d_between_line2d(e_seg, o_seg) + if tolerance < dist <= min_separation: + joining_segs.append(LineSegment2D.from_end_points(*pts)) + except IndexError: + pass # we have reached the end of the list + + # join all of the segments together into polylines + all_segs = ext_segs + joining_segs + ext_bounds = Polyline2D.join_segments(all_segs, tolerance) + + # separate valid closed boundaries from open ones + closed_polys, open_bounds = [], [] + for bnd in ext_bounds: + if isinstance(bnd, Polyline2D) and bnd.is_closed(tolerance): + try: + closed_polys.append(bnd.to_polygon(tolerance)) + except AssertionError: # not a valid polygon + pass + else: + open_bounds.append(bnd) + + # if the resulting polylines are not closed, join the nearest end points + if len(closed_polys) != len(ext_bounds): + extra_segs = [] + for i, s_bnd in enumerate(open_bounds): + self_seg = LineSegment2D.from_end_points(s_bnd.p1, s_bnd.p2) + poss_segs = [self_seg] + try: + for o_bnd in open_bounds[i + 1:]: + pts = [ + (s_bnd.p1, o_bnd.p1), (s_bnd.p1, o_bnd.p2), + (s_bnd.p2, o_bnd.p1), (s_bnd.p2, o_bnd.p2)] + for comb in pts: + poss_segs.append(LineSegment2D.from_end_points(*comb)) + except IndexError: + continue # we have reached the end of the list + # sort the possible segments by their length + poss_segs.sort(key=lambda x: x.length, reverse=False) + if poss_segs[0] is self_seg: + extra_segs.append(poss_segs[0]) + else: # two possible connecting segments + extra_segs.append(poss_segs[0]) + extra_segs.append(poss_segs[1]) + # remove any duplicates from the extra segment list + non_dup_segs = [] + for e_seg in extra_segs: + for f_seg in non_dup_segs: + if e_seg.is_equivalent(f_seg, tolerance): + break + else: + non_dup_segs.append(e_seg) + extra_segs = non_dup_segs + # take the best available segments that fit the criteria + extra_segs.sort(key=lambda x: x.length, reverse=False) + extra_segs = extra_segs[:len(open_bounds)] + + # join all segments, hopefully into a final closed polyline + all_segs = ext_segs + joining_segs + extra_segs + ext_bounds = Polyline2D.join_segments(all_segs, tolerance) + closed_polys = [] + for bnd in ext_bounds: + if isinstance(bnd, Polyline2D) and bnd.is_closed(tolerance): + try: + closed_polys.append(bnd.to_polygon(tolerance)) + except AssertionError: # not a valid polygon + pass + + return closed_polys
+ +
[docs] @staticmethod + def common_axes(polygons, direction, min_distance, merge_distance, angle_tolerance): + """Get LineSegment2Ds for the most common axes across a set of Polygon2Ds. + + This is often useful as a step before aligning a set of polygons to these + common axes. + + Args: + polygons: A list or tuple of Polygon2D objects for which common axes + will be evaluated. + direction: A Vector2D object to represent the direction in which the + common axes will be evaluated and generated + min_distance: The minimum distance at which common axes will be evaluated. + This value should typically be a little larger than the model + tolerance (eg. 5 to 20 times the tolerance) in order to ensure that + possible common axes across the input polygons are not missed. + merge_distance: The distance at which common axes next to one another + will be merged into a single axis. This should typically be 2-3 + times the min_distance in order to avoid generating several axes + that are immediately adjacent to one another. When using this + method to generate axes for alignment, this merge_distance should + be in the range of the alignment distance. + angle_tolerance: The max angle difference in radians that the polygon + segments direction can differ from the input direction before the + segments are not factored into this calculation of common axes. + + Returns: + A tuple with two elements. + + - common_axes: A list of LineSegment2D objects for the common + axes across the input polygons. + + - axis_values: A list of integers that aligns with the common_axes + and denotes how many segments of the input polygons each axis + relates to. Higher numbers indicate that that the axis is more + common among all of the possible axes. + """ + # gather the relevant segments of the input polygons + min_ang, max_ang = angle_tolerance, math.pi - angle_tolerance + rel_segs = [] + for p_gon in polygons: + for seg in p_gon.segments: + try: + s_ang = direction.angle(seg.v) + if s_ang < min_ang or s_ang > max_ang: + rel_segs.append(seg) + except ZeroDivisionError: # zero length segment to ignore + continue + if len(rel_segs) == 0: + return [], [] # none of the polygon segments are relevant in the direction + + # determine the extents around the polygons and the input direction + gen_vec = direction.rotate(math.pi / 2) + axis_angle = Vector2D(0, 1).angle_counterclockwise(gen_vec) + orient_poly = polygons + if axis_angle != 0: # rotate geometry to the bounding box + cpt = polygons[0].vertices[0] + orient_poly = [pl.rotate(-axis_angle, cpt) for pl in polygons] + xx = Polygon2D._bounding_domain_x(orient_poly) + yy = Polygon2D._bounding_domain_y(orient_poly) + min_pt = Point2D(xx[0], yy[0]) + max_pt = Point2D(xx[1], yy[1]) + if axis_angle != 0: # rotate the points back + min_pt = min_pt.rotate(axis_angle, cpt) + max_pt = max_pt.rotate(axis_angle, cpt) + + # generate all possible axes from the extents and min_distance + axis_vec = direction.normalize() * (xx[1] - xx[0]) + incr_vec = gen_vec.normalize() * (min_distance) + current_pt = min_pt + current_dist, max_dist = 0, yy[1] - yy[0] + all_axes = [] + while current_dist < max_dist: + axis = LineSegment2D(current_pt, axis_vec) + all_axes.append(axis) + current_pt = current_pt.move(incr_vec) + current_dist += min_distance + + # evaluate the axes based on how many relevant segments they are next to + mid_pts = [seg.midpoint for seg in rel_segs] + rel_axes, axes_value = [], [] + for axis in all_axes: + axis_val = 0 + for pt in mid_pts: + close_pt = closest_point2d_on_line2d_infinite(pt, axis) + if close_pt.distance_to_point(pt) <= min_distance: + axis_val += 1 + if axis_val != 0: + rel_axes.append(axis) + axes_value.append(axis_val) + if len(rel_axes) == 0: + return [], [] # none of the generated axes are relevant + + # group the axes by proximity + last_ax = rel_axes[0] + axes_groups = [[last_ax]] + group_values = [[axes_value[0]]] + for axis, val in zip(rel_axes[1:], axes_value[1:]): + if axis.p.distance_to_point(last_ax.p) <= merge_distance: + axes_groups[-1].append(axis) + group_values[-1].append(val) + else: # start a new group + axes_groups.append([axis]) + group_values.append([val]) + last_ax = axis + + # average the line segments that are within the merge_distance of one another + axis_values = [max(val) for val in group_values] + common_axes = [] + for ax_group, grp_vals in zip(axes_groups, group_values): + if len(ax_group) == 1: + common_axes.append(ax_group[0]) + else: + index_max = max(range(len(grp_vals)), key=grp_vals.__getitem__) + common_axes.append(ax_group[index_max]) + return common_axes, axis_values
+ + @staticmethod + def _bounding_domain_x(geometries): + """Get minimum and maximum X coordinates of multiple polygons.""" + min_x, max_x = geometries[0].min.x, geometries[0].max.x + for geom in geometries[1:]: + if geom.min.x < min_x: + min_x = geom.min.x + if geom.max.x > max_x: + max_x = geom.max.x + return min_x, max_x + + @staticmethod + def _bounding_domain_y(geometries): + """Get minimum and maximum Y coordinates of multiple polygons.""" + min_y, max_y = geometries[0].min.y, geometries[0].max.y + for geom in geometries[1:]: + if geom.min.y < min_y: + min_y = geom.min.y + if geom.max.y > max_y: + max_y = geom.max.y + return min_y, max_y + + def _point_in_polygon(self, tolerance): + """Get a Point2D that is always reliably inside this Polygon2D. + + The point will be close to the edge of the Polygon but it will always + be inside it for all concave geometries. Furthermore, it is relatively + fast compared with computing the pole_of_inaccessibility. + """ + try: + poly = self.remove_colinear_vertices(tolerance) + move_vec, v_angle = self._inward_pointing_vec(poly) + except (AssertionError, ZeroDivisionError): # zero area Polygon2D; use center + return self.center + + move_vec = move_vec * ((tolerance / math.sin(v_angle / 2)) + 0.00001) + point_in_poly = poly.vertices[0] + move_vec + if not self.is_point_inside(point_in_poly): + point_in_poly = poly.vertices[0] - move_vec + return point_in_poly + + @staticmethod + def _inward_pointing_vec(polygon): + """Get a unit vector pointing inward/outward from the first vertex of the Polygon + """ + v1 = polygon.vertices[-1] - polygon.vertices[0] + v2 = polygon.vertices[1] - polygon.vertices[0] + v_angle = v1.angle(v2) + if v_angle == math.pi: # colinear vertices; prevent averaging to zero + rgt_ang = math.pi / 2 + return v1.rotate(rgt_ang).normalize(), rgt_ang + else: # average the two edge vectors together + avg_coords = ((v1.x + v2.x) / 2), ((v1.y + v2.y) / 2) + return Vector2D(*avg_coords).normalize(), v_angle + + def _transfer_properties(self, new_polygon): + """Transfer properties from this polygon to a new polygon. + + This is used by the transform methods that don't alter the relationship of + face vertices to one another (move, rotate, reflect). + """ + new_polygon._perimeter = self._perimeter + new_polygon._area = self._area + new_polygon._is_convex = self._is_convex + new_polygon._is_self_intersecting = self._is_self_intersecting + new_polygon._is_clockwise = self._is_clockwise + + @staticmethod + def _get_centroid_cell(polygon): + """Get a Cell object at the centroid of the Polygon2D.""" + area = 0 + x = 0 + y = 0 + b = polygon[-1] # prev + for a in polygon: + f = a[0] * b[1] - b[0] * a[1] + x += (a[0] + b[0]) * f + y += (a[1] + b[1]) * f + area += f * 3 + b = a + if area == 0: + return _Cell(polygon[0][0], polygon[0][1], 0, polygon) + return _Cell(x / area, y / area, 0, polygon) + + @staticmethod + def _insert_updates_in_order(polygon, polygon_updates): + """Insert updates from the intersect_segments method into a polygon. + + This method ensures that multiple updates to a single segment are inserted + in the correct order over the polygon. + + Args: + polygon: A Polygon2D to be updated with intersection points + polygon_updates: A list of tuples where each tuple has two values. The + first is the index of the segment to be updated and the second is + the point to insert. + """ + polygon_updates.sort(key=lambda x: x[0]) # sort updates by order of insertion + poly_points = list(polygon.vertices) # convert tuple to mutable list + last_i = -1 + colinear_count = 0 + for update in polygon_updates[::-1]: # traverse backwards to preserve order + new_i = update[0] + 1 + if new_i == last_i: # check order of new intersections on the same segment + colinear_count += 1 + p1 = poly_points[update[0]] + for i, pt in enumerate(poly_points[new_i:new_i + colinear_count]): + if p1.distance_to_point(pt) > p1.distance_to_point(update[1]): + poly_points.insert(new_i + i, update[1]) + break + else: + poly_points.insert(new_i + colinear_count, update[1]) + else: + colinear_count = 0 + poly_points.insert(new_i, update[1]) + last_i = new_i + return Polygon2D(poly_points) + + @staticmethod + def _segments_from_vertices(vertices): + _segs = [] + for i, vert in enumerate(vertices): + _seg = LineSegment2D.from_end_points(vertices[i - 1], vert) + _segs.append(_seg) + _segs.append(_segs.pop(0)) # segments will start from the first point + return _segs + + @staticmethod + def _merge_boundary_and_holes(boundary, holes, split=False): + """Return a list of points for a boundary merged with all holes. + + The holes are merged one-by-one using the shortest distance from all of + the holes to the boundary to ensure one hole's seam does not cross another. + The run time of this method scales linearly with the total number of + vertices, which makes it significantly better for shapes with many holes + compared to recursively calling the Polygon2D._merge_boundary_and_closest_hole + method. However, it is not nearly as efficient as the method used in + Polygon2D.from_shape_with_holes_fast, which is typically not as beautiful + of a result as this method. + + Args: + boundary: A list of Point2D objects for the outer boundary inside of + which the hole is contained. + hole: A list of lists where each sub-list represents a hole and contains + several Point2D objects that represent the hole. + split: A boolean to note whether the last hole should be merged into + the boundary, effectively splitting the shape into two lists of + vertices instead of a single list. This is useful when trying to + translate a shape with holes to a platform that does not support + holes or struggles with single lists of vertices that wind inward + to cut out the holes since this option returns two "normal" concave + polygons. However, it is possible that the shape cannot be + reliably split this way and, in this case, this method will + return None. (Default: False). + + Returns: + A single list of vertices with the holes merged into the boundary. When + split is True and the splitting is successful, this will be two lists + of vertices for the split shapes. If splitting was not successful, + this method will return None and, if a hole-less split shape is + still required, it is recommended that triangulation be used to get + the hole-less shapes. + """ + # compute the initial distances between the holes and the boundary + original_boundary = boundary[:] + hole_dicts, min_dists = [], [] + for hole in holes: + dist_dict = {} + for i, b_pt in enumerate(boundary): + for j, h_pt in enumerate(hole): + dist_dict[b_pt.distance_to_point(h_pt)] = [i, j] + hole_dicts.append(dist_dict) + min_dists.append(min(dist_dict.keys())) + # merge each hole into the boundary, moving progressively by minimum distance + final_hole_count = 0 if not split else 1 + while len(holes) > final_hole_count: + # merge the hole into the boundary + hole_index = min_dists.index(min(min_dists)) + boundary, old_hole, b_ind = Polygon2D._merge_boundary_and_hole_detailed( + boundary, holes[hole_index], hole_dicts[hole_index]) + # remove the hole from the older lists + hole_dicts.pop(hole_index) + holes.pop(hole_index) + # update the hole_dicts based on the new boundary + add_ind = len(old_hole) + for hd in hole_dicts: + for ind_list in hd.values(): + if ind_list[0] > b_ind: + ind_list[0] += add_ind + # add the distances from the old hole to the remaining holes to hole_dicts + old_hole_ind = [b_ind + i for i in range(add_ind)] + for hole, dist_dict in zip(holes, hole_dicts): + for bi, b_pt in zip(old_hole_ind, old_hole): + for j, h_pt in enumerate(hole): + dist_dict[b_pt.distance_to_point(h_pt)] = [bi, j] + # generated an updated min_dists list + min_dists = [min(dist_dict.keys()) for dist_dict in hole_dicts] + if not split: + return boundary + + # sort the distances to find the closest point + last_hd = hole_dicts[0] + sort_dist = sorted(last_hd.keys()) + # find the closest connection between the hole and the original boundary polygon + p1_indices = dist_dict[sort_dist[0]] + p2_index = 1 + p2_indices = dist_dict[sort_dist[p2_index]] + p2_bound_pt = boundary[p2_indices[0]] + while p2_bound_pt not in original_boundary: + p2_index += 1 + p2_indices = dist_dict[sort_dist[p2_index]] + p2_bound_pt = boundary[p2_indices[0]] + p2_hole_pt = hole[p2_indices[1]] + # merge the hole into the boundary + hole_deque = deque(hole) + hole_deque.rotate(-p1_indices[1]) + hole_insert = [boundary[p1_indices[0]]] + list(hole_deque) + \ + [hole[p1_indices[1]]] + boundary[p1_indices[0]:p1_indices[0]] = hole_insert + # use the second most distant points to split the shape + p2_bound_i = boundary.index(p2_bound_pt) + p2_hole_i = boundary.index(p2_hole_pt) + if p2_hole_i < p2_bound_i: + boundary_1 = boundary[p2_hole_i:p2_bound_i + 1] + boundary_2 = boundary[:p2_hole_i + 1] + boundary[p2_bound_i:] + else: + boundary_1 = boundary[p2_bound_i:p2_hole_i + 1] + boundary_2 = boundary[:p2_bound_i + 1] + boundary[p2_hole_i:] + poly_1, poly_2 = Polygon2D(boundary_1), Polygon2D(boundary_2) + + # if the split polygons are self-intersecting, try to find a solution + p2_index = 0 + while poly_1.is_self_intersecting or poly_2.is_self_intersecting: + p2_index += 1 + try: + p2_indices = dist_dict[sort_dist[p2_index]] + except IndexError: # no solution was found; just return None + return None + p2_bound_pt = boundary[p2_indices[0]] + p2_hole_pt = hole[p2_indices[1]] + p2_bound_i = boundary.index(p2_bound_pt) + p2_hole_i = boundary.index(p2_hole_pt) + if p2_hole_i < p2_bound_i: + boundary_1 = boundary[p2_hole_i:p2_bound_i + 1] + boundary_2 = boundary[:p2_hole_i + 1] + boundary[p2_bound_i:] + else: + boundary_1 = boundary[p2_bound_i:p2_hole_i + 1] + boundary_2 = boundary[:p2_bound_i + 1] + boundary[p2_hole_i:] + try: + poly_1, poly_2 = Polygon2D(boundary_1), Polygon2D(boundary_2) + except AssertionError: + pass # the polygons are not valid; keep searching + return boundary_1, boundary_2 + + @staticmethod + def _merge_boundary_and_hole_detailed(boundary, hole, dist_dict): + """Create a single list of points with a hole and boundary. + + This method will also return the newly-added vertices of the hole as + well as the index of where the hole was inserted in the larger boundary. + + Args: + boundary: A list of Point2D objects for the outer boundary inside of + which the hole is contained. + hole: A list of Point2D objects for the hole. + dist_dict: A dictionary with keys of distances between each of the points + in the boundary and hole lists and values as tuples with two values: + (the index of the boundary point, the index of the hole point) + + Returns: + A tuple with three values + + - boundary: A single list of vertices with the input hole merged + into the boundary. + + - hole_insert: A list of vertices representing the hole that has + been inserted into the boundary. + + - insert_index: An integer for where the hole was inserted in the + boundary. + """ + min_dist = min(dist_dict.keys()) + min_indexes = dist_dict[min_dist] + hole_deque = deque(hole) + hole_deque.rotate(-min_indexes[1]) + hole_insert = [boundary[min_indexes[0]]] + list(hole_deque) + \ + [hole[min_indexes[1]]] + boundary[min_indexes[0]:min_indexes[0]] = hole_insert + insert_index = min_indexes[0] + return boundary, hole_insert, insert_index + + @staticmethod + def _merge_boundary_and_closest_hole(boundary, holes): + """Return a list of points for a boundary merged with the closest hole.""" + hole_dicts = [] + min_dists = [] + for hole in holes: + dist_dict = {} + for i, b_pt in enumerate(boundary): + for j, h_pt in enumerate(hole): + dist_dict[b_pt.distance_to_point(h_pt)] = (i, j) + hole_dicts.append(dist_dict) + min_dists.append(min(dist_dict.keys())) + hole_index = min_dists.index(min(min_dists)) + new_boundary = Polygon2D._merge_boundary_and_hole( + boundary, holes[hole_index], hole_dicts[hole_index]) + holes.pop(hole_index) + return new_boundary, holes + + @staticmethod + def _merge_boundary_and_hole(boundary, hole, dist_dict): + """Create a single list of points describing a boundary shape with a hole. + + Args: + boundary: A list of Point2D objects for the outer boundary inside of + which the hole is contained. + hole: A list of Point2D objects for the hole. + dist_dict: A dictionary with keys of distances between each of the points + in the boundary and hole lists and values as tuples with two values: + (the index of the boundary point, the index of the hole point) + """ + min_dist = min(dist_dict.keys()) + min_indexes = dist_dict[min_dist] + hole_deque = deque(hole) + hole_deque.rotate(-min_indexes[1]) + hole_insert = [boundary[min_indexes[0]]] + list(hole_deque) + \ + [hole[min_indexes[1]]] + boundary[min_indexes[0]:min_indexes[0]] = hole_insert + return boundary + + @staticmethod + def _are_clockwise(vertices): + """Check if a list of vertices are clockwise. + + This is a quicker calculation when all you need is the direction and not area. + """ + _a = 0 + for i, pt in enumerate(vertices): + _a += vertices[i - 1].x * pt.y - vertices[i - 1].y * pt.x + return _a < 0 + + def __copy__(self): + _new_poly = Polygon2D(self._vertices) + _new_poly._segments = self._segments + _new_poly._perimeter = self._perimeter + _new_poly._area = self._area + _new_poly._is_clockwise = self._is_clockwise + _new_poly._is_convex = self._is_convex + _new_poly._is_self_intersecting = self._is_self_intersecting + return _new_poly + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return tuple(hash(pt) for pt in self._vertices) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Polygon2D) and self.__key() == other.__key() + + def __repr__(self): + return 'Polygon2D ({} vertices)'.format(len(self))
+ + +class _Cell(object): + """2D cell object used in certain Polygon computations (eg. pole_of_inaccessibility). + + Args: + x: The X coordinate of the cell origin. + y: The Y coordinate of the cell origin. + h: The dimension of the cell. + polygon: An array representation of a Polygon2D. + + Properties: + * x + * y + * h + * d + * max + """ + __slots__ = ('x', 'y', 'h', 'd', 'max') + + def __init__(self, x, y, h, polygon): + self.h = h + self.y = y + self.x = x + self.d = self._point_to_polygon_distance(x, y, polygon) + self.max = self.d + self.h * math.sqrt(2) + + def _point_to_polygon_distance(self, x, y, polygon): + """Get the distance from an X,Y point to the edge of a Polygon.""" + inside = False + min_dist_sq = inf + + b = polygon[-1] + for a in polygon: + + if (a[1] > y) != (b[1] > y) and \ + (x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0]): + inside = not inside + + min_dist_sq = min(min_dist_sq, self._get_seg_dist_sq(x, y, a, b)) + b = a + + result = math.sqrt(min_dist_sq) + if not inside: + return -result + return result + + @staticmethod + def _get_seg_dist_sq(px, py, a, b): + """Get the squared distance from a point to a segment.""" + x = a[0] + y = a[1] + dx = b[0] - x + dy = b[1] - y + + if dx != 0 or dy != 0: + t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy) + + if t > 1: + x = b[0] + y = b[1] + + elif t > 0: + x += dx * t + y += dy * t + + dx = px - x + dy = py - y + + return dx * dx + dy * dy + + def __lt__(self, other): + return self.max < other.max + + def __lte__(self, other): + return self.max <= other.max + + def __gt__(self, other): + return self.max > other.max + + def __gte__(self, other): + return self.max >= other.max + + def __eq__(self, other): + return self.max == other.max +
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry2d/polyline.html b/docs/_modules/ladybug_geometry/geometry2d/polyline.html new file mode 100644 index 00000000..be6d2849 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry2d/polyline.html @@ -0,0 +1,1415 @@ + + + + + + + ladybug_geometry.geometry2d.polyline — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry2d.polyline

+# coding=utf-8
+"""2D Polyline"""
+from __future__ import division
+
+from ._2d import Base2DIn2D
+from .pointvector import Point2D
+from .line import LineSegment2D
+from ..intersection2d import intersect_line2d, intersect_line2d_infinite
+from .._polyline import _group_vertices
+
+
+
[docs]class Polyline2D(Base2DIn2D): + """2D polyline object. + + Args: + vertices: A list of Point2D objects representing the vertices of the polyline. + interpolated: Boolean to note whether the polyline should be interpolated + between the input vertices when it is translated to other interfaces. + Note that this property has no bearing on the geometric calculations + performed by this library and is only present in order to assist with + display/translation. + + Properties: + * vertices + * segments + * min + * max + * center + * p1 + * p2 + * length + * is_self_intersecting + * interpolated + """ + __slots__ = ('_interpolated', '_segments', '_length', '_is_self_intersecting') + + def __init__(self, vertices, interpolated=False): + """Initialize Polyline2D.""" + Base2DIn2D.__init__(self, vertices) + self._interpolated = interpolated + self._segments = None + self._length = None + self._is_self_intersecting = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Polyline2D from a dictionary. + + Args: + data: A python dictionary in the following format. + + .. code-block:: python + + { + "type": "Polyline2D", + "vertices": [(0, 0), (10, 0), (0, 10)] + } + """ + interp = data['interpolated'] if 'interpolated' in data else False + return cls(tuple(Point2D.from_array(pt) for pt in data['vertices']), interp)
+ +
[docs] @classmethod + def from_array(cls, point_array): + """Create a Polyline2D from a nested array of vertex coordinates. + + Args: + point_array: Nested array of point arrays. + """ + return Polyline2D(Point2D(*point) for point in point_array)
+ +
[docs] @classmethod + def from_polygon(cls, polygon): + """Create a closed Polyline2D from a Polygon2D. + + Args: + polygon: A Polygon2D object to be converted to a Polyline2D. + """ + return Polyline2D(polygon.vertices + (polygon.vertices[0],))
+ + @property + def segments(self): + """Tuple of all line segments in the polyline.""" + if self._segments is None: + self._segments = \ + tuple(LineSegment2D.from_end_points(vert, self._vertices[i + 1]) + for i, vert in enumerate(self._vertices[:-1])) + return self._segments + + @property + def length(self): + """The length of the polyline.""" + if self._length is None: + self._length = sum([seg.length for seg in self.segments]) + return self._length + + @property + def p1(self): + """Starting point of the Polyline2D.""" + return self._vertices[0] + + @property + def p2(self): + """End point of the Polyline2D.""" + return self._vertices[-1] + + @property + def is_self_intersecting(self): + """Boolean noting whether the polyline has self-intersecting segments.""" + if self._is_self_intersecting is None: + self._is_self_intersecting = False + _segs = self.segments + for i, _s in enumerate(_segs[1: len(_segs) - 1]): + _skip = (i, i + 1, i + 2) + _other_segs = [x for j, x in enumerate(_segs) if j not in _skip] + for _oth_s in _other_segs: + if _s.intersect_line_ray(_oth_s) is not None: # intersection! + self._is_self_intersecting = True + break + if self._is_self_intersecting is True: + break + return self._is_self_intersecting + + @property + def interpolated(self): + """Boolean noting whether the polyline should be interpolated upon translation. + + Note that this property has no bearing on the geometric calculations + performed by this library and is only present in order to assist with + display/translation. + """ + return self._interpolated + +
[docs] def is_closed(self, tolerance): + """Test whether this polyline is closed to within the tolerance. + + Args: + tolerance: The minimum difference between vertices below which vertices + are considered the same. + """ + return self._vertices[0].is_equivalent(self._vertices[-1], tolerance)
+ +
[docs] def remove_colinear_vertices(self, tolerance): + """Get a version of this polyline without colinear or duplicate vertices. + + Args: + tolerance: The minimum distance that a vertex can be from a line + before it is considered colinear. + """ + if len(self.vertices) == 3: + return self # Polyline2D cannot have fewer than 3 vertices + new_vertices = [self.vertices[0]] # first vertex is always ok + for i, _v in enumerate(self.vertices[1:-1]): + _a = self[i].determinant(_v) + _v.determinant(self[i + 2]) + \ + self[i + 2].determinant(self[i]) + if abs(_a) >= tolerance: + new_vertices.append(_v) + new_vertices.append(self[-1]) # last vertex is always ok + _new_poly = Polyline2D(new_vertices) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def reverse(self): + """Get a copy of this polyline where the vertices are reversed.""" + _new_poly = Polyline2D(tuple(pt for pt in reversed(self.vertices))) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def move(self, moving_vec): + """Get a polyline that has been moved along a vector. + + Args: + moving_vec: A Vector2D with the direction and distance to move the polyline. + """ + _new_poly = Polyline2D(tuple(pt.move(moving_vec) for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def rotate(self, angle, origin): + """Get a polyline that is rotated counterclockwise by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point2D for the origin around which the point will be rotated. + """ + _new_poly = Polyline2D(tuple(pt.rotate(angle, origin) for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def reflect(self, normal, origin): + """Get a polyline reflected across a plane with the input normal and origin. + + Args: + normal: A Vector2D representing the normal vector for the plane across + which the polyline will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point2D representing the origin from which to reflect. + """ + _new_poly = Polyline2D(tuple(pt.reflect(normal, origin) for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def scale(self, factor, origin=None): + """Scale a polyline by a factor from an origin point. + + Args: + factor: A number representing how much the polyline should be scaled. + origin: A Point2D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0). + """ + if origin is None: + _new_poly = Polyline2D(tuple( + Point2D(pt.x * factor, pt.y * factor) for pt in self.vertices)) + else: + _new_poly = Polyline2D(tuple( + pt.scale(factor, origin) for pt in self.vertices)) + _new_poly._interpolated = self._interpolated + return _new_poly
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersections between this polyline and a Ray2D or LineSegment2D. + + Args: + line_ray: A LineSegment2D or Ray2D or to intersect. + + Returns: + A list with Point2D objects for the intersections. + List will be empty if no intersection exists. + """ + intersections = [] + for _s in self.segments: + inters = intersect_line2d(_s, line_ray) + if inters is not None: + intersections.append(inters) + return intersections
+ +
[docs] def intersect_line_infinite(self, ray): + """Get the intersections between this polyline and a Ray2D extended infinitely. + + Args: + ray: A Ray2D or to intersect. This will be extended in both + directions infinitely for the intersection. + + Returns: + A list with Point2D objects for the intersections. + List will be empty if no intersection exists. + """ + intersections = [] + for _s in self.segments: + inters = intersect_line2d_infinite(_s, ray) + if inters is not None: + intersections.append(inters) + return intersections
+ +
[docs] def to_dict(self): + """Get Polyline2D as a dictionary.""" + base = {'type': 'Polyline2D', + 'vertices': [pt.to_array() for pt in self.vertices]} + if self.interpolated: + base['interpolated'] = self.interpolated + return base
+ +
[docs] def to_array(self): + """Get a list of lists where each sub-list represents a Point2D vertex.""" + return tuple(pt.to_array() for pt in self.vertices)
+ +
[docs] def to_polygon(self, tolerance): + """Get a Polygon2D derived from this object. + + If the polyline is closed to within the tolerance, the segments of this + polyline and the resulting polygon will match. Otherwise, an extra + LineSegment2D will be added to connect the start and end of the polyline. + + Args: + tolerance: The minimum difference between vertices below which vertices + are considered the same. + """ + from .polygon import Polygon2D # must be imported here to avoid circular import + if self.is_closed(tolerance): + return Polygon2D(self._vertices[:-1]) + return Polygon2D(self._vertices)
+ +
[docs] @staticmethod + def join_segments(segments, tolerance): + """Get an array of Polyline2Ds from a list of LineSegment2Ds. + + Args: + segments: An array of LineSegment2D objects. + tolerance: The minimum difference in X, Y, and Z values at which Point2Ds + are considered equivalent. Segments with points that match within the + tolerance will be joined. + + Returns: + An array of Polyline2D and LineSegment2D objects assembled from the + joined segments. + """ + # group the vertices that make up polylines + grouped_verts = _group_vertices(segments, tolerance) + + # create the Polyline2D and LineSegment2D objects + joined_lines = [] + for v_list in grouped_verts: + if len(v_list) == 2: + joined_lines.append(LineSegment2D.from_end_points(v_list[0], v_list[1])) + else: + joined_lines.append(Polyline2D(v_list)) + return joined_lines
+ + def _transfer_properties(self, new_polyline): + """Transfer properties from this polyline to a new polyline.""" + new_polyline._interpolated = self._interpolated + new_polyline._length = self._length + new_polyline._is_self_intersecting = self._is_self_intersecting + + def __copy__(self): + return Polyline2D(self._vertices, self._interpolated) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return tuple(hash(pt) for pt in self._vertices) + (self._interpolated,) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Polyline2D) and self.__key() == other.__key() + + def __repr__(self): + return 'Polyline2D ({} vertices)'.format(len(self))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry2d/ray.html b/docs/_modules/ladybug_geometry/geometry2d/ray.html new file mode 100644 index 00000000..bc8de1c3 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry2d/ray.html @@ -0,0 +1,1191 @@ + + + + + + + ladybug_geometry.geometry2d.ray — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry2d.ray

+# coding=utf-8
+"""2D Ray"""
+from __future__ import division
+
+from .pointvector import Vector2D, Point2D
+from ._1d import Base1DIn2D
+
+
+
[docs]class Ray2D(Base1DIn2D): + """2D Ray object. + + Args: + p: A Point2D representing the base of the ray. + v: A Vector2D representing the direction of the ray. + + Properties: + * p + * v + * min + * max + """ + __slots__ = () + + def __init__(self, p, v): + """Initialize Ray2D.""" + Base1DIn2D.__init__(self, p, v) + +
[docs] @classmethod + def from_array(cls, ray_array): + """ Create a Ray2D from a nested array with a point and a vector. + + Args: + ray_array: Nested tuples ((p.x, p.y), (v.x, v.y)). + """ + return Ray2D(Point2D(*ray_array[0]), Vector2D(*ray_array[1]))
+ +
[docs] def reverse(self): + """Get a copy of this ray that is reversed.""" + return Ray2D(self.p, self.v.reverse())
+ +
[docs] def move(self, moving_vec): + """Get a ray that has been moved along a vector. + + Args: + moving_vec: A Vector2D with the direction and distance to move the ray. + """ + return Ray2D(self.p.move(moving_vec), self.v)
+ +
[docs] def rotate(self, angle, origin): + """Get a ray that is rotated counterclockwise by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point2D for the origin around which the ray will be rotated. + """ + return Ray2D(self.p.rotate(angle, origin), self.v.rotate(angle))
+ +
[docs] def reflect(self, normal, origin): + """Get a ray reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector2D representing the normal vector for the plane across + which the ray will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point2D representing the origin from which to reflect. + """ + return Ray2D(self.p.reflect(normal, origin), self.v.reflect(normal))
+ +
[docs] def scale(self, factor, origin=None): + """Scale a ray by a factor from an origin point. + + Args: + factor: A number representing how much the ray should be scaled. + origin: A Point2D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0). + """ + return Ray2D(self.p.scale(factor, origin), self.v * factor)
+ +
[docs] def to_dict(self): + """Get Ray2D as a dictionary.""" + base = Base1DIn2D.to_dict(self) + base['type'] = 'Ray2D' + return base
+ +
[docs] def to_array(self): + """A nested array representing the start point and vector.""" + return (self.p.to_array(), self.v.to_array())
+ + def _u_in(self, u): + return u >= 0.0 + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (hash(self.p), hash(self.v)) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Ray2D) and self.__key() == other.__key() + + def __repr__(self): + return 'Ray2D (point <%.2f, %.2f>) (vector <%.2f, %.2f>)' % \ + (self.p.x, self.p.y, self.v.x, self.v.y)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/arc.html b/docs/_modules/ladybug_geometry/geometry3d/arc.html new file mode 100644 index 00000000..9cfb40b4 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/arc.html @@ -0,0 +1,1523 @@ + + + + + + + ladybug_geometry.geometry3d.arc — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.arc

+# coding=utf-8
+"""3D Arc"""
+from __future__ import division
+
+from .plane import Plane
+
+from ..geometry2d.pointvector import Point2D, Vector2D
+from ..geometry2d.ray import Ray2D
+from ..geometry2d.arc import Arc2D
+from .pointvector import Vector3D, Point3D
+from .polyline import Polyline3D
+
+import math
+
+
+
[docs]class Arc3D(object): + """3D arc object. + + Args: + plane: A Plane in which the arc lies with an origin representing the + center of the circle for the arc. + radius: A number representing the radius of the arc. + a1: A number between 0 and 2 * pi for the start angle of the arc. + a2: A number between 0 and 2 * pi for the end angle of the arc. + + Properties: + * plane + * radius + * a1 + * a2 + * p1 + * p2 + * midpoint + * c + * min + * max + * length + * angle + * is_circle + * is_inverted + * arc2d + """ + __slots__ = ('_plane', '_arc2d', '_min', '_max') + + def __init__(self, plane, radius, a1=0, a2=2 * math.pi): + """Initialize Arc3D.""" + assert isinstance(plane, Plane), "Expected Plane. Got {}.".format(type(plane)) + self._plane = plane + self._arc2d = Arc2D(Point2D(0, 0), radius, a1, a2) + self._min = None + self._max = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Arc3D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Arc3D" + "plane": {"n": (0, 0, 1), "o": (0, 10, 0), "x": (1, 0, 0)}, + "radius": 5, + "a1": 0, + "a2": 3.14159 + } + """ + return cls(Plane.from_dict(data['plane']), data['radius'], + data['a1'], data['a2'])
+ +
[docs] @classmethod + def from_start_mid_end(cls, p1, m, p2, circle=False): + """Initialize a new arc from start, middle, and end points. + + Note that input points will be assumed to be in counterclockwise order. + + Args: + p1: The start point of the arc. + m: Any point along the length of the arc that is not the start or end. + p2: The end point of the arc. + circle: Set to True if you would like the output to be a full circle + defined by the three points instead of an arc with a start and end. + Default is False. + """ + plane = cls._plane_from_vertices(p1, m, p2) + p1_2d, m_2d, p2_2d = plane.xyz_to_xy(p1), plane.xyz_to_xy(m), plane.xyz_to_xy(p2) + arc_2d = Arc2D.from_start_mid_end(p1_2d, m_2d, p2_2d, circle) + return cls(Plane(plane.n, plane.xy_to_xyz(arc_2d.c), plane.x), + arc_2d.r, arc_2d.a1, arc_2d.a2)
+ +
[docs] @classmethod + def from_arc2d(cls, arc2d, z=0): + """Initialize a new Arc3D from an Arc2D and a z value. + + Args: + arc2d: An Arc2D to be used to generate the Arc3D. + z: A number for the Z coordinate value of the arc. + """ + plane = Plane(o=Point3D(arc2d.c.x, arc2d.c.y, z)) + return cls(plane, arc2d.r, arc2d.a1, arc2d.a2)
+ + @property + def plane(self): + """A Plane in which the arc lies with an origin for the center of the arc.""" + return self._plane + + @property + def radius(self): + """Radius of arc.""" + return self._arc2d.r + + @property + def a1(self): + """Start angle of the arc in radians.""" + return self._arc2d.a1 + + @property + def a2(self): + """End angle of the arc in radians.""" + return self._arc2d.a2 + + @property + def p1(self): + """Start point.""" + return self.plane.xy_to_xyz(self.arc2d.p1) + + @property + def p2(self): + """End point.""" + return self.plane.xy_to_xyz(self.arc2d.p2) + + @property + def midpoint(self): + """Midpoint.""" + return self.point_at(0.5) + + @property + def c(self): + """Center point of the circle on which the arc lies.""" + return self.plane.o + + @property + def min(self): + """A Point3D for the minimum bounding box vertex around this geometry.""" + if self._min is None: + self._calculate_min_max() + return self._min + + @property + def max(self): + """A Point3D for the maximum bounding box vertex around this geometry.""" + if self._max is None: + self._calculate_min_max() + return self._max + + @property + def length(self): + """The length of the arc.""" + return self.angle * self.radius + + @property + def angle(self): + """The total angle over the domain of the arc in radians.""" + _diff = self.a2 - self.a1 + return _diff if not self.is_inverted else 2 * math.pi + _diff + + @property + def area(self): + """Area of the circle to which the arc belongs.""" + assert self.is_circle, 'Arc must be a closed circle to access "area" property.' + return math.pi * self.radius ** 2 + + @property + def is_circle(self): + """Boolean for whether the arc is a full circle (True) or not (False).""" + return self.a1 == 0 and self.a2 == 2 * math.pi + + @property + def is_inverted(self): + """Boolean noting whether the end angle a2 is smaller than the start angle a1.""" + return self.a2 < self.a1 + + @property + def arc2d(self): + """An Arc2D within the plane of the Arc3D.""" + return self._arc2d + +
[docs] def move(self, moving_vec): + """Get an arc that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the arc. + """ + return Arc3D(self.plane.move(moving_vec), self.radius, self.a1, self.a2)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this arc by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + return Arc3D(self.plane.rotate(axis, angle, origin), + self.radius, self.a1, self.a2)
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a arc that is rotated counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the arc will be rotated. + """ + return Arc3D(self.plane.rotate_xy(angle, origin), self.radius, self.a1, self.a2)
+ +
[docs] def reflect(self, normal, origin): + """Get a arc reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the arc will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + arc2d = self.arc2d.reflect(Vector2D(0, 1), Point2D(0, 0)) + return Arc3D(self.plane.reflect(normal, origin), self.radius, arc2d.a1, arc2d.a2)
+ +
[docs] def scale(self, factor, origin=None): + """Scale a arc by a factor from an origin point. + + Args: + factor: A number representing how much the arc should be scaled. + origin: A Point2D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + return Arc3D(self.plane.scale(factor, origin), self.radius * factor, + self.a1, self.a2)
+ +
[docs] def subdivide(self, distances): + """Get Point3D values along the arc that subdivide it based on input distances. + + Args: + distances: A list of distances along the arc at which to subdivide it. + This can also be a single number that will be repeated until the + end of the arc. + """ + return [self.plane.xy_to_xyz(pt) for pt in self.arc2d.subdivide(distances)]
+ +
[docs] def subdivide_evenly(self, number): + """Get Point3D values along the arc that divide it into evenly-spaced segments. + + Args: + number: The number of segments into which the arc will be divided. + """ + return [self.plane.xy_to_xyz(pt) for pt in self.arc2d.subdivide_evenly(number)]
+ +
[docs] def point_at(self, parameter): + """Get a point at a given fraction along the arc. + + Args: + parameter: The fraction between the start and end point where the + desired point lies. For example, 0.5 will yield the midpoint. + """ + return self.plane.xy_to_xyz(self.arc2d.point_at(parameter))
+ +
[docs] def point_at_angle(self, angle): + """Get a point at a given angle along the arc. + + Args: + angle: The angle in radians from the start point along the arc + to get the Point3D. + """ + return self.plane.xy_to_xyz(self.arc2d.point_at_angle(angle))
+ +
[docs] def point_at_length(self, length): + """Get a point at a given distance along the arc segment. + + Args: + length: The distance along the arc from the start point where the + desired point lies. + """ + return self.point_at(length / self.length)
+ +
[docs] def closest_point(self, point): + """Get the closest Point3D on this object to another Point3D. + + Args: + point: A Point3D object to which the closest point on this object + will be computed. + + Returns: + Point3D for the closest point on this line to the input point. + """ + plane_pt = self.plane.closest_point(point) + return self.plane.xy_to_xyz( + self.arc2d.closest_point(self.plane.xyz_to_xy(plane_pt)))
+ +
[docs] def distance_to_point(self, point): + """Get the minimum distance between this object and the input point. + + Args: + point: A Point3D object to which the minimum distance will be computed. + + Returns: + The distance to the input point. + """ + close_pt = self.closest_point(point) + return point.distance_to_point(close_pt)
+ +
[docs] def intersect_plane(self, plane): + """Get the intersection between this Arc3D and a Plane. + + Args: + plane: A Plane that will be intersected with this arc. + + Returns: + A list of Point3D objects if the intersection was successful. + None if no intersection exists. + """ + _plane_int_ray = plane.intersect_plane(self.plane) + if _plane_int_ray is not None: + _p12d = self.plane.xyz_to_xy(_plane_int_ray.p) + _p22d = self.plane.xyz_to_xy(_plane_int_ray.p + _plane_int_ray.v) + _v2d = _p22d - _p12d + _int_ray2d = Ray2D(_p12d, _v2d) + _int_pt2d = self.arc2d.intersect_line_infinite(_int_ray2d) + if _int_pt2d is not None: + return [self.plane.xy_to_xyz(pt) for pt in _int_pt2d] + return None
+ +
[docs] def split_with_plane(self, plane): + """Split this Arc3D in 2 or 3 smaller arcs using a Plane. + + Args: + plane: A Plane that will be used to split this arc. + + Returns: + A list with two or three Arc3D objects if the split was successful. + Will be a list with 1 Arc3D if no intersection exists. + """ + _plane_int_ray = plane.intersect_plane(self.plane) + if _plane_int_ray is not None: + _p12d = self.plane.xyz_to_xy(_plane_int_ray.p) + _p22d = self.plane.xyz_to_xy(_plane_int_ray.p + _plane_int_ray.v) + _v2d = _p22d - _p12d + _int_ray2d = Ray2D(_p12d, _v2d) + _int_pt2d = self.arc2d.split_line_infinite(_int_ray2d) + if len(_int_pt2d) != 1: + return [Arc3D(self.plane, self.radius, arc.a1, arc.a2) + for arc in _int_pt2d] + return [self]
+ +
[docs] def to_polyline(self, divisions, interpolated=True): + """Get this Arc3D as an approximated Polyline3D. + + Args: + divisions: The number of segments into which the arc will be divided. + interpolated: Boolean to note whether the polyline should be interpolated + between the input vertices when it is translated to other interfaces. + This property has no effect on the geometric calculations performed + by this library and is only present in order to assist with + display/translation. (Default: True) + """ + pts = self.subdivide_evenly(divisions) + return Polyline3D(pts, interpolated)
+ +
[docs] def to_dict(self): + """Get Arc3D as a dictionary.""" + return {'type': 'Arc3D', 'plane': self.plane.to_dict(), + 'radius': self.radius, 'a1': self.a1, 'a2': self.a2}
+ +
[docs] def duplicate(self): + """Get a copy of this object.""" + return self.__copy__()
+ + def _calculate_min_max(self): + """Calculate maximum and minimum Point3D for this object. + + At this point in time, this method only calculates the bounding box + around the circle of the arc using the formula here. + https://stackoverflow.com/questions/2592011/\ + bounding-boxes-for-circle-and-arcs-in-3d + """ + if self.is_circle: + o, r = self._plane.o, self.radius + ax = self._plane.n.angle(Vector3D(1, 0, 0)) + ay = self._plane.n.angle(Vector3D(0, 1, 0)) + az = self._plane.n.angle(Vector3D(0, 0, 1)) + r_vec = (math.sin(ax) * r, math.sin(ay) * r, math.sin(az) * r) + self._min = Point3D(o.x - r_vec[0], o.y - r_vec[1], o.z - r_vec[2]) + self._max = Point3D(o.x + r_vec[0], o.y + r_vec[1], o.z + r_vec[2]) + else: # get the min and max of the Arc2D + min_pt2d, max_pt2d = self._arc2d.min, self._arc2d.max + self._min = self.plane.xy_to_xyz(min_pt2d) + self._max = self.plane.xy_to_xyz(max_pt2d) + + @staticmethod + def _plane_from_vertices(pt1, pt2, pt3): + """Get a plane from three vertices.""" + try: + v1 = pt2 - pt1 + v2 = pt3 - pt1 + n = v1.cross(v2) + except Exception as e: + raise ValueError('Incorrect Point3D input for Arc3D:\n\t{}'.format(e)) + return Plane(n, pt1) + + def __copy__(self): + return Arc3D(self.plane, self.radius, self.a1, self.a2) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (hash(self.plane), self.radius, self.a1, self.a2) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Arc3D) and self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + return 'Arc3D (center {}) (radius {}) (length {})'.format( + self.plane.o, self.radius, self.length)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/cone.html b/docs/_modules/ladybug_geometry/geometry3d/cone.html new file mode 100644 index 00000000..3eb42bf2 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/cone.html @@ -0,0 +1,1322 @@ + + + + + + + ladybug_geometry.geometry3d.cone — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.cone

+# coding=utf-8
+"""Cone"""
+from __future__ import division
+
+from .pointvector import Point3D, Vector3D
+from .plane import Plane
+from .arc import Arc3D
+
+import math
+
+
+
[docs]class Cone(object): + """Cone object. + + Args: + vertex: A Point3D at the tip of the cone. + axis: A Vector3D representing the direction and height of the cone. + The vector extends from the vertex to the center of the base. + angle: An angle in radians representing the half angle between + the axis and the surface. + + Properties: + * vertex + * axis + * angle + * height + * slant_height + * radius + * area + * volume + * min + * max + * base + """ + __slots__ = ('_vertex', '_axis', '_angle', '_base', '_min', '_max') + + def __init__(self, vertex, axis, angle): + """Initialize Cone.""" + assert isinstance(vertex, Point3D), \ + "Expected Point3D. Got {}.".format(type(vertex)) + assert isinstance(axis, Vector3D), \ + "Expected Vector3D. Got {}.".format(type(axis)) + assert angle > 0, 'Cone angle must be greater than 0. Got {}.'.format(angle) + self._vertex = vertex + self._axis = axis + self._angle = angle + self._base = None + self._min = None + self._max = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Cone from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Cone" + "vertex": (10, 0, 0), + "axis": (0, 0, 1), + "angle": 1.0 + } + """ + return cls(Point3D.from_array(data['vertex']), + Vector3D.from_array(data['axis']), + data['angle'])
+ + @property + def vertex(self): + """Vertex of cone.""" + return self._vertex + + @property + def axis(self): + """Axis of cone.""" + return self._axis + + @property + def angle(self): + """Angle of cone""" + return self._angle + + @property + def height(self): + """Height of cone""" + return self.axis.magnitude + + @property + def radius(self): + """Radius of a cone""" + return self.height * math.tan(self.angle) + + @property + def slant_height(self): + """Slant height of a cone""" + return math.sqrt(self.radius ** 2 + self.height ** 2) + + @property + def area(self): + """Surface area of a cone""" + return math.pi * self.radius ** 2 + math.pi * self.radius * self.slant_height + + @property + def volume(self): + """Volume of a cone""" + return 1 / 3 * math.pi * self.radius ** 2 * self.height + + @property + def base(self): + """Get an Arc3D representing the circular base of the cone.""" + if self._base is None: + plane = Plane(self.axis.reverse(), self.vertex + self.axis) + self._base = Arc3D(plane, self.radius) + return self._base + + @property + def min(self): + """A Point3D for the minimum bounding box vertex around this geometry.""" + if self._min is None: + self._calculate_min_max() + return self._min + + @property + def max(self): + """A Point3D for the maximum bounding box vertex around this geometry.""" + if self._max is None: + self._calculate_min_max() + return self._max + +
[docs] def move(self, moving_vec): + """Get a cone that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the cone. + """ + return Cone(self.vertex.move(moving_vec), self.axis, self.angle)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this cone by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the cone will be rotated. + """ + return Cone(self.vertex.rotate(axis, angle, origin), + self.axis.rotate(axis, angle), + self.angle)
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a cone that is rotated counterclockwise in the world XY plane by an angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the cone will be rotated. + """ + return Cone(self.vertex.rotate_xy(angle, origin), + self.axis.rotate_xy(angle), + self.angle)
+ +
[docs] def reflect(self, normal, origin): + """Get a cone reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the arc will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + return Cone(self.vertex.reflect(normal, origin), + self.axis.reflect(normal), + self.angle)
+ +
[docs] def scale(self, factor, origin=None): + """Scale a cone by a factor from an origin point. + + Args: + factor: A number representing how much the cone should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + return Cone(self.vertex.scale(factor, origin), + self.axis * factor, + self.angle)
+ +
[docs] def duplicate(self): + """Get a copy of this object.""" + return self.__copy__()
+ +
[docs] def to_dict(self): + """Get Cone as a dictionary.""" + return { + 'type': 'Cone', + 'vertex': self.vertex.to_array(), + 'axis': self.axis.to_array(), + 'angle': self.angle + }
+ + def _calculate_min_max(self): + """Calculate maximum and minimum Point3D for this object.""" + base = self.base + bmn, bmx, ver = base.min, base.max, self.vertex + self._min = Point3D(min(bmn.x, ver.x), min(bmn.y, ver.y), min(bmn.z, ver.z)) + self._max = Point3D(max(bmx.x, ver.x), max(bmx.y, ver.y), max(bmx.z, ver.z)) + + def __copy__(self): + return Cone(self.vertex, self.axis, self.angle) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self._vertex, self._axis, self._angle) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Cone) and self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + return 'Cone (vertex {}) (axis {}) (angle {}) (height {})'.\ + format(self.vertex, self.axis, self.angle, self.height)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/cylinder.html b/docs/_modules/ladybug_geometry/geometry3d/cylinder.html new file mode 100644 index 00000000..cfdd45d0 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/cylinder.html @@ -0,0 +1,1348 @@ + + + + + + + ladybug_geometry.geometry3d.cylinder — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.cylinder

+# coding=utf-8
+"""Cylinder"""
+from __future__ import division
+
+from .pointvector import Point3D, Vector3D
+from .plane import Plane
+from .arc import Arc3D
+
+import math
+
+
+
[docs]class Cylinder(object): + """Cylinder object. + + Args: + center: A Point3D at the center of the bottom base of the cylinder. + axis: A Vector3D representing the direction and height of the cylinder. + The vector extends from the bottom base center to the top base center. + radius: A number representing the radius of the cylinder. + + Properties: + * center + * axis + * radius + * center_end + * diameter + * height + * area + * volume + * min + * max + * base_bottom + * base_top + """ + __slots__ = ('_center', '_axis', '_radius', + '_base_bottom', '_base_top', '_min', '_max') + + def __init__(self, center, axis, radius): + """Initialize Cylinder.""" + assert isinstance(center, Point3D), \ + "Expected Point3D. Got {}.".format(type(center)) + assert isinstance(axis, Vector3D), \ + "Expected Vector3D. Got {}.".format(type(axis)) + assert radius > 0, \ + 'Cylinder radius must be greater than 0. Got {}.'.format(radius) + self._center = center + self._axis = axis + self._radius = radius + self._base_bottom = None + self._base_top = None + self._min = None + self._max = None + +
[docs] @classmethod + def from_start_end(cls, p1, p2, radius): + """Initialize a new cylinder from start and end points. + + Args: + p1: The start point of the cylinder, represents the center of the + bottom base of the cylinder. + p2: The end point of the cylinder, represents the center of the top + base of the cylinder + radius: A number representing the radius of the cylinder. + """ + axis = p2 - p1 + return cls(p1, axis, radius)
+ +
[docs] @classmethod + def from_dict(cls, data): + """Create a Cylinder from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Cylinder" + "center": (10, 0, 0), + "axis": (0, 0, 1), + "radius": 1.0 + } + """ + return cls(Point3D.from_array(data['center']), + Vector3D.from_array(data['axis']), + data['radius'])
+ + @property + def center(self): + """Center of Cylinder.""" + return self._center + + @property + def axis(self): + """Axis of Cylinder.""" + return self._axis + + @property + def radius(self): + """Radius of Cylinder""" + return self._radius + + @property + def center_end(self): + """Center of the opposite end of Cylinder.""" + return self.center + self.axis + + @property + def diameter(self): + """Diameter of Cylinder""" + return self.radius * 2 + + @property + def height(self): + """Height of Cylinder""" + return self.axis.magnitude + + @property + def area(self): + """Surface area of a Cylinder""" + return 2 * math.pi * self.radius * self.height + 2 * math.pi * self.radius ** 2 + + @property + def volume(self): + """Volume of a Cylinder""" + return math.pi * self.radius ** 2 * self.height + + @property + def base_bottom(self): + """Get an Arc3D representing the bottom circular base of the cylinder.""" + if self._base_bottom is None: + self._base_bottom = Arc3D(Plane(self.axis, self.center), self.radius) + return self._base_bottom + + @property + def base_top(self): + """Get an Arc3D representing the top circular base of the cylinder.""" + if self._base_top is None: + plane = Plane(self.axis, self.center + self.axis) + self._base_top = Arc3D(plane, self.radius) + return self._base_top + + @property + def min(self): + """A Point3D for the minimum bounding box vertex around this geometry.""" + if self._min is None: + self._calculate_min_max() + return self._min + + @property + def max(self): + """A Point3D for the maximum bounding box vertex around this geometry.""" + if self._max is None: + self._calculate_min_max() + return self._max + +
[docs] def move(self, moving_vec): + """Get a Cylinder that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the Cylinder. + """ + return Cylinder(self.center.move(moving_vec), self.axis, self.radius)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this Cylinder by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the cylinder will be rotated. + """ + return Cylinder(self.center.rotate(axis, angle, origin), + self.axis.rotate(axis, angle), + self.radius)
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a Cylinder that is rotated counterclockwise in the world XY plane by an angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the cylinder will be rotated. + """ + return Cylinder(self.center.rotate_xy(angle, origin), + self.axis.rotate_xy(angle), + self.radius)
+ +
[docs] def reflect(self, normal, origin): + """Get a Cylinder reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the arc will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + return Cylinder(self.center.reflect(normal, origin), + self.axis.reflect(normal), + self.radius)
+ +
[docs] def scale(self, factor, origin=None): + """Scale a Cylinder by a factor from an origin point. + + Args: + factor: A number representing how much the Cylinder should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + return Cylinder(self.center.scale(factor, origin), + self.axis * factor, + self.radius * factor)
+ +
[docs] def duplicate(self): + """Get a copy of this object.""" + return self.__copy__()
+ +
[docs] def to_dict(self): + """Get Cylinder as a dictionary.""" + return { + 'type': 'Cylinder', + 'center': self.center.to_array(), + 'axis': self.axis.to_array(), + 'radius': self.radius + }
+ + def _calculate_min_max(self): + """Calculate maximum and minimum Point3D for this object.""" + base1, base2 = self.base_bottom, self.base_top + b1mn, b1mx, b2mn, b2mx = base1.min, base1.max, base2.min, base2.max + self._min = Point3D( + min(b1mn.x, b2mn.x), min(b1mn.y, b2mn.y), min(b1mn.z, b2mn.z)) + self._max = Point3D( + max(b1mx.x, b2mx.x), max(b1mx.y, b2mx.y), max(b1mx.z, b2mx.z)) + + def __copy__(self): + return Cylinder(self.center, self.axis, self.radius) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self._center, self._axis, self._radius) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Cylinder) and self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + return 'Cylinder (center {}) (axis {}) (radius {})'.\ + format(self.center, self.axis, self.radius)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/face.html b/docs/_modules/ladybug_geometry/geometry3d/face.html new file mode 100644 index 00000000..75743ec8 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/face.html @@ -0,0 +1,3987 @@ + + + + + + + ladybug_geometry.geometry3d.face — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.face

+# coding=utf-8
+"""Planar Face in 3D Space"""
+from __future__ import division
+import math
+import sys
+if (sys.version_info > (3, 0)):  # python 3
+    xrange = range
+
+from .pointvector import Point3D, Vector3D
+from .ray import Ray3D
+from .line import LineSegment3D
+from .polyline import Polyline3D
+from .plane import Plane
+from .mesh import Mesh3D
+from ._2d import Base2DIn3D
+
+from ..intersection3d import closest_point3d_on_line3d
+
+from ..geometry2d.pointvector import Point2D, Vector2D
+from ..geometry2d.ray import Ray2D
+from ..geometry2d.polygon import Polygon2D
+from ..geometry2d.mesh import Mesh2D
+
+import ladybug_geometry.boolean as pb
+
+
+
[docs]class Face3D(Base2DIn3D): + """Planar Face in 3D space. + + Args: + boundary: A list or tuple of Point3D objects representing the outer + boundary vertices of the face. + plane: A Plane object indicating the plane in which the face exists. + If None, the Plane normal will automatically be calculated by + analyzing the input vertices and the origin of the plane will be + the first vertex of the input vertices. Default: None. + holes: Optional list of lists with one list for each hole in the face. + Each hole should be a list of at least 3 Point3D objects. + If None, it will be assumed that there are no holes in the face. + The boundary and holes are stored as separate lists of Point3Ds on the + `boundary` and `holes` properties of this object. However, the + `vertices` property will always contain all vertices across the shape. + For a Face3D that has holes, it will trace out a single shape that + turns inwards from the boundary to cut out the holes. + enforce_right_hand: Boolean to note whether a check should be run to + ensure that input vertices are counterclockwise within the input plane, + thereby enforcing the right-hand rule. By default, this is True + and ensures that all Face3D objects adhere to the right-hand rule. + It is recommended that this only be set to False in cases where you + are certain that the input vertices are counter-clockwise + within the input plane and you would like to avoid the extra + unnecessary check. + + Properties: + * vertices + * plane + * boundary + * holes + * polygon2d + * boundary_polygon2d + * hole_polygon2d + * triangulated_mesh2d + * triangulated_mesh3d + * boundary_segments + * hole_segments + * normal + * min + * max + * center + * perimeter + * area + * centroid + * altitude + * azimuth + * is_clockwise + * is_convex + * is_self_intersecting + * self_intersection_points + * is_valid + * has_holes + * upper_left_corner + * lower_left_corner + * upper_right_corner + * lower_right_corner + * upper_left_counter_clockwise_vertices + * lower_left_counter_clockwise_vertices + * lower_right_counter_clockwise_vertices + * upper_right_counter_clockwise_vertices + """ + __slots__ = ('_plane', '_polygon2d', '_mesh2d', '_mesh3d', + '_boundary', '_holes', '_boundary_segments', '_hole_segments', + '_boundary_polygon2d', '_hole_polygon2d', + '_perimeter', '_area', '_centroid', + '_is_convex', '_is_self_intersecting') + HOLE_VERTEX_THRESHOLD = 400 # threshold at which faster hole merging method is used + + def __init__(self, boundary, plane=None, holes=None, enforce_right_hand=True): + """Initialize Face3D.""" + # process the boundary and plane inputs + self._boundary = self._check_vertices_input(boundary) + if plane is not None: + assert isinstance(plane, Plane), 'Expected Plane for Face3D.' \ + ' Got {}.'.format(type(plane)) + else: + plane = self._plane_from_vertices(boundary) + self._plane = plane + + # process boundary and holes input + if holes: + assert isinstance(holes, (tuple, list)), \ + 'holes should be a tuple or list. Got {}'.format(type(holes)) + self._holes = tuple( + self._check_vertices_input(hole, 'hole') for hole in holes) + # create a Polygon2D from the vertices + _boundary2d = [self._plane.xyz_to_xy(_v) for _v in boundary] + _holes2d = [[self._plane.xyz_to_xy(_v) for _v in hole] for hole in holes] + v_count = len(_boundary2d) # count the vertices for hole merging method + for h in _holes2d: + v_count += len(h) + _polygon2d = Polygon2D.from_shape_with_holes_fast(_boundary2d, _holes2d) \ + if v_count > self.HOLE_VERTEX_THRESHOLD else \ + Polygon2D.from_shape_with_holes(_boundary2d, _holes2d) + # convert Polygon2D vertices to 3D to become the vertices of the face. + self._vertices = tuple(self._plane.xy_to_xyz(_v) + for _v in _polygon2d.vertices) + self._polygon2d = _polygon2d + else: + self._holes = None + self._vertices = self._boundary + self._polygon2d = None + + # perform a check of vertex orientation and enforce counter clockwise vertices + if enforce_right_hand is True: + if self.is_clockwise is True: + self._boundary = tuple(reversed(self._boundary)) + self._vertices = tuple(reversed(self._vertices)) + if self._polygon2d is not None: + self._polygon2d = self._polygon2d.reverse() + + # set other properties to None for now + self._mesh2d = None + self._mesh3d = None + self._boundary_polygon2d = None + self._hole_polygon2d = None + self._boundary_segments = None + self._hole_segments = None + self._min = None + self._max = None + self._center = None + self._perimeter = None + self._area = None + self._centroid = None + self._is_convex = None + self._is_self_intersecting = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Face3D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Face3D", + "boundary": [(0, 0, 0), (10, 0, 0), (0, 10, 0)], + "plane": {"n": (0, 0, 1), "o": (0, 0, 0), "x": (1, 0, 0)}, + "holes": [[(2, 2, 0), (5, 2, 0), (2, 5, 0)]] + } + """ + holes = None + if 'holes' in data and data['holes'] is not None: + holes = tuple(tuple( + Point3D.from_array(pt) for pt in hole) for hole in data['holes']) + plane = None + if 'plane' in data and data['plane'] is not None: + plane = Plane.from_dict(data['plane']) + return cls(tuple(Point3D.from_array(pt) for pt in data['boundary']), + plane, holes)
+ +
[docs] @classmethod + def from_extrusion(cls, line_segment, extrusion_vector): + """Initialize Face3D by extruding a line segment. + + Initializing a face this way has the added benefit of having its + properties quickly computed. + + Args: + line_segment: A LineSegment3D to be extruded. + extrusion_vector: A vector denoting the direction and distance to + extrude the line segment. + """ + assert isinstance(line_segment, LineSegment3D), \ + 'line_segment must be LineSegment3D. Got {}.'.format(type(line_segment)) + assert isinstance(extrusion_vector, Vector3D), \ + 'extrusion_vector must be Vector3D. Got {}.'.format(type(extrusion_vector)) + _p1 = line_segment.p1 + _p2 = line_segment.p2 + _verts = (_p1, _p2, _p2 + extrusion_vector, _p1 + extrusion_vector) + _plane = Plane(line_segment.v.cross(extrusion_vector), _p1) + face = cls(_verts, _plane, enforce_right_hand=False) + _base = line_segment.length + _dist = extrusion_vector.magnitude + _height = _dist * math.sin(extrusion_vector.angle(line_segment.v)) + face._perimeter = _base * 2 + _dist * 2 + face._area = _base * _height + face._centroid = _p1 + (line_segment.v * 0.5) + (extrusion_vector * 0.5) + face._is_convex = True + face._is_self_intersecting = False + return face
+ +
[docs] @classmethod + def from_rectangle(cls, base, height, base_plane=None): + """Initialize Face3D from rectangle parameters (base + height) and a base plane. + + Initializing a face this way has the added benefit of having its + properties quickly computed. + + Args: + base: A number indicating the length of the base of the rectangle. + height: A number indicating the length of the height of the rectangle. + base_plane: A Plane object in which the rectangle will be created. + The origin of this plane will be the lower left corner of the + rectangle and the X and Y axes will form the sides. + Default is the world XY plane. + """ + assert isinstance(base, (float, int)), 'Rectangle base must be a number.' + assert isinstance(height, (float, int)), 'Rectangle height must be a number.' + if base_plane is not None: + assert isinstance(base_plane, Plane), \ + 'base_plane must be Plane. Got {}.'.format(type(base_plane)) + else: + base_plane = Plane(Vector3D(0, 0, 1), Point3D(0, 0, 0)) + _o = base_plane.o + _b_vec = base_plane.x * base + _h_vec = base_plane.y * height + _verts = (_o, _o + _b_vec, _o + _h_vec + _b_vec, _o + _h_vec) + face = cls(_verts, base_plane, enforce_right_hand=False) + face._perimeter = base * 2 + height * 2 + face._area = base * height + face._centroid = _o + (_b_vec * 0.5) + (_h_vec * 0.5) + face._is_convex = True + face._is_self_intersecting = False + return face
+ +
[docs] @classmethod + def from_regular_polygon(cls, side_count, radius=1, base_plane=None): + """Initialize Face3D from regular polygon parameters and a base_plane. + + Args: + side_count: An integer for the number of sides on the regular + polygon. This number must be greater than 2. + radius: A number indicating the distance from the polygon's center + where the vertices of the polygon will lie. + The default is set to 1. + base_plane: A Plane object for the plane in which the face exists. + The origin of this plane will be used as the center of the polygon. + If None, the default will be the WorldXY plane. + """ + # set the default base_plane + if base_plane is not None: + assert isinstance(base_plane, Plane), 'Expected Plane. Got {}'.format( + type(base_plane)) + else: + base_plane = Plane(Vector3D(0, 0, 1), Point3D(0, 0, 0)) + + # create the regular polygon face + _polygon2d = Polygon2D.from_regular_polygon(side_count, radius) + _vert3d = tuple(base_plane.xy_to_xyz(_v) for _v in _polygon2d.vertices) + _face = cls(_vert3d, base_plane, enforce_right_hand=False) + + # assign extra properties that we know to the face + _face._polygon2d = _polygon2d + _face._center = base_plane.o + _face._centroid = base_plane.o + _face._is_convex = True + _face._is_self_intersecting = False + return _face
+ +
[docs] @classmethod + def from_punched_geometry(cls, base_face, sub_faces): + """Create a face with holes punched in it from sub-faces. + + Args: + base_face: A Face3D that acts as a parent to the sub_faces, completely + encircling them. + sub_faces: A list of Face3D objects that will be punched into the + base_face. These faces must lie completely within the base_face + for the result to be valid. The is_sub_face() method can be + used to check sub_faces before they are input here. + """ + assert isinstance(base_face, Face3D), \ + 'base_face should be a Face3D. Got {}'.format(type(base_face)) + for hole in sub_faces: + assert isinstance(hole, Face3D), \ + 'sub_face should be a list. Got {}'.format(type(hole)) + hole_verts = [list(sf.boundary) for sf in sub_faces] + if base_face.has_holes: + hole_verts.extend([list(h) for h in base_face.holes]) + return cls(base_face.boundary, base_face.plane, hole_verts, + enforce_right_hand=False)
+ + @property + def vertices(self): + """Tuple of all vertices in this face. + + Note that, in the case of a face with holes, some vertices will be repeated + since this property effectively traces out a single boundary around the + whole shape, winding inward to cut out the holes. + """ + return self._vertices + + @property + def plane(self): + """Tuple of all vertices in this face.""" + return self._plane + + @property + def polygon2d(self): + """A Polygon2D of this face in the 2D space of the face's plane. + + Note that this is a single polygon object even when there are holes in the + face since such a polygon can be made by drawing a line from the holes to + the outer boundary. + """ + if self._polygon2d is None: + _vert2d = tuple(self._plane.xyz_to_xy(_v) for _v in self.vertices) + self._polygon2d = Polygon2D(_vert2d) + return self._polygon2d + + @property + def triangulated_mesh2d(self): + """A triangulated Mesh2D in the 2D space of the face's plane.""" + if self._mesh2d is None: + self._mesh2d = Mesh2D.from_polygon_triangulated( + self.boundary_polygon2d, self.hole_polygon2d) + return self._mesh2d + + @property + def triangulated_mesh3d(self): + """A triangulated Mesh3D of this face.""" + if self._mesh3d is None: + _vert3d = tuple(self._plane.xy_to_xyz(_v) for _v in + self.triangulated_mesh2d.vertices) + self._mesh3d = Mesh3D(_vert3d, self.triangulated_mesh2d.faces) + return self._mesh3d + + @property + def boundary(self): + """Tuple of vertices on the boundary of this face. + + For most Face3D objects, this will be identical to the vertices property. + However, when the Face3D has holes within it, this property stores + the outer boundary of the shape. + """ + return self._boundary + + @property + def holes(self): + """Tuple with one tuple of vertices for each hole within this face. + + This property will be None when the face has no holes in it. + """ + return self._holes + + @property + def boundary_segments(self): + """Tuple of all line segments bordering the face. + + Note that this does not include segments for any holes in the face. + Just the outer boundary. + """ + if self._boundary_segments is None: + _segs = [] + for i, vert in enumerate(self.boundary): + _seg = LineSegment3D.from_end_points(self.boundary[i - 1], vert) + _segs.append(_seg) + _segs.append(_segs.pop(0)) # segments will start from the first vertex + self._boundary_segments = tuple(_segs) + return self._boundary_segments + + @property + def hole_segments(self): + """Tuple with a tuple of line segments for each hole in the face. + + This will be None if there are no holes in the face. + """ + if self._holes is not None and self._hole_segments is None: + _all_segs = [] + for hole in self.holes: + _segs = [] + for i, vert in enumerate(hole): + _seg = LineSegment3D.from_end_points(hole[i - 1], vert) + _segs.append(_seg) + _segs.append(_segs.pop(0)) # segments will start from the first vertex + _all_segs.append(_segs) + self._hole_segments = tuple(tuple(_s) for _s in _all_segs) + return self._hole_segments + + @property + def boundary_polygon2d(self): + """A Polygon2D of the face boundary in the 2D space of the face's plane. + + Note that this does not include any holes in the face. Just the outer boundary. + """ + if self._boundary_polygon2d is None: + _vert2d = tuple(self._plane.xyz_to_xy(_v) for _v in self.boundary) + self._boundary_polygon2d = Polygon2D(_vert2d) + return self._boundary_polygon2d + + @property + def hole_polygon2d(self): + """A list of Polygon2D for the face holes in the 2D space of the face's plane. + """ + if self._holes is not None and self._hole_polygon2d is None: + self._hole_polygon2d = [] + for hole in self.holes: + _vert2d = tuple(self._plane.xyz_to_xy(_v) for _v in hole) + self._hole_polygon2d.append(Polygon2D(_vert2d)) + return self._hole_polygon2d + + @property + def normal(self): + """Normal vector for the plane in which the face exists.""" + return self._plane.n + + @property + def perimeter(self): + """The perimeter of the face. This includes the length of holes in the face.""" + if self._perimeter is None: + self._perimeter = sum([seg.length for seg in self.boundary_segments]) + if self._holes is not None: + for hole in self.hole_segments: + self._perimeter += sum([seg.length for seg in hole]) + return self._perimeter + + @property + def area(self): + """The area of the face.""" + if self._area is None: + self._area = self.polygon2d.area + return self._area + + @property + def centroid(self): + """The centroid of the face as a Point3D (aka. center of mass). + + Note that the centroid is more time consuming to compute than the center + (or the middle point of the face bounding box). So the center might be + preferred over the centroid if you just need a rough point for the middle + of the face. + """ + if self._centroid is None: + _cent2d = self.triangulated_mesh2d.centroid + self._centroid = self._plane.xy_to_xyz(_cent2d) + return self._centroid + + @property + def azimuth(self): + """Get the azimuth of the Face3D (between 0 and 2 * Pi). + + This will be zero if the Face3D is perfectly horizontal. + """ + return self.plane.azimuth + + @property + def altitude(self): + """Get the altitude of the Face3D (between Pi/2 and -Pi/2).""" + return self.plane.altitude + + @property + def is_clockwise(self): + """Boolean for whether the face vertices and boundary are in clockwise order. + + Note that all Face3D objects should have counterclockwise vertices (meaning + that this property should always be False). This property exists largely + for testing / debugging purposes. + """ + return self.polygon2d.is_clockwise + + @property + def is_convex(self): + """Boolean noting whether the face is convex (True) or non-convex (False). + + Note that any face with holes will be automatically considered non-convex + since the underlying polygon_2d is always non-convex in this case. + """ + if self._is_convex is None: + self._is_convex = self.polygon2d.is_convex + return self._is_convex + + @property + def is_self_intersecting(self): + """Boolean noting whether the face has self-intersecting edges. + + Note that this property is relatively computationally intense to obtain compared + to properties like area and is_convex. Also, most CAD programs forbid geometry + with self-intersecting edges. So it is recommended that this property only + be used in quality control scripts where the origin of the geometry is unknown. + """ + if self._is_self_intersecting is None: + self._is_self_intersecting = False + if self.boundary_polygon2d.is_self_intersecting: + self._is_self_intersecting = True + if self.has_holes: + for hp in self.hole_polygon2d: + if hp.is_self_intersecting: + self._is_self_intersecting = True + break + return self._is_self_intersecting + + @property + def self_intersection_points(self): + """A tuple of Point3Ds for the locations where the Face3D intersects itself. + + This will be an empty tuple if the Face3D is not self-intersecting and it + is generally recommended that the Face3D.is_self_intersecting property + be checked before using this property. + """ + if self.is_self_intersecting: + int_pts = [] + for pt2 in self.boundary_polygon2d.self_intersection_points: + int_pts.append(self.plane.xy_to_xyz(pt2)) + if self.has_holes: + for hp in self.hole_polygon2d: + for pt2 in hp.self_intersection_points: + int_pts.append(self.plane.xy_to_xyz(pt2)) + return tuple(int_pts) + return () + + @property + def is_valid(self): + """Boolean noting whether the face is valid (having a non-zero area). + + Note that faces are still considered valid if they have out-of-plane vertices, + self-intersecting edges, or duplicate/colinear vertices. The check_planar + method can be used to detect if there are out-of-plane vertices. The + is_self_intersecting property identifies self-intersecting edges, and the + remove_colinear_vertices method will remove duplicate/colinear vertices.""" + return not self.area == 0 + + @property + def has_holes(self): + """Boolean noting whether the face has holes within it.""" + return self._holes is not None + + @property + def upper_left_corner(self): + """Get the vertex in the upper-left corner of the face's bounding box.""" + return self._corner_point('min', 'max') + + @property + def lower_left_corner(self): + """Get the vertex in the lower-left corner of the face's bounding box.""" + return self._corner_point('min', 'min') + + @property + def upper_right_corner(self): + """Get the vertex in the upper-right corner of the face's bounding box.""" + return self._corner_point('max', 'max') + + @property + def lower_right_corner(self): + """Get the vertex in the lower-right corner of the face's bounding box.""" + return self._corner_point('max', 'min') + + @property + def upper_left_counter_clockwise_vertices(self): + """Get face vertices starting from the upper left and moving counterclockwise. + + Horizontal faces will treat the positive Y axis as up. All other faces + treat the positive Z axis as up. + """ + corner_pt, polygon = self._corner_point_and_polygon(self._vertices, 'min', 'max') + verts3d, verts2d = self._counter_clockwise_verts(polygon) + return self._corner_pt_verts(corner_pt, verts3d, verts2d) + + @property + def lower_left_counter_clockwise_vertices(self): + """Get face vertices starting from the lower left and moving counterclockwise. + + Horizontal faces will treat the positive Y axis as up. All other faces + treat the positive Z axis as up. + """ + corner_pt, polygon = self._corner_point_and_polygon(self._vertices, 'min', 'min') + verts3d, verts2d = self._counter_clockwise_verts(polygon) + return self._corner_pt_verts(corner_pt, verts3d, verts2d) + + @property + def lower_right_counter_clockwise_vertices(self): + """Get face vertices starting from the lower left and moving counterclockwise. + + Horizontal faces will treat the positive Y axis as up. All other faces + treat the positive Z axis as up. + """ + corner_pt, polygon = self._corner_point_and_polygon(self._vertices, 'max', 'min') + verts3d, verts2d = self._counter_clockwise_verts(polygon) + return self._corner_pt_verts(corner_pt, verts3d, verts2d) + + @property + def upper_right_counter_clockwise_vertices(self): + """Get face vertices starting from the lower left and moving counterclockwise. + + Horizontal faces will treat the positive Y axis as up. All other faces + treat the positive Z axis as up. + """ + corner_pt, polygon = self._corner_point_and_polygon(self._vertices, 'max', 'max') + verts3d, verts2d = self._counter_clockwise_verts(polygon) + return self._corner_pt_verts(corner_pt, verts3d, verts2d) + +
[docs] def pole_of_inaccessibility(self, tolerance): + """Get the pole of inaccessibility for the Face3D. + + The pole of inaccessibility is the most distant internal point from the + Face3D outline. It is not to be confused with the centroid, which + represents the "center of mass" of the shape and may be outside of + the Face3D if the shape is concave. The poly of inaccessibility is + useful for optimal placement of a text label on the Face3D. + + Args: + tolerance: The precision to which the pole of inaccessibility + will be computed. + """ + return self.plane.xy_to_xyz(self.polygon2d.pole_of_inaccessibility(tolerance))
+ +
[docs] def is_horizontal(self, tolerance): + """Check whether a this face is horizontal within a given tolerance. + + Args: + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. + + Returns: + True if the face is horizontal. False if it is not. + """ + return self.max.z - self.min.z <= tolerance
+ +
[docs] def is_geometrically_equivalent(self, face, tolerance): + """Check whether a given face is geometrically equivalent to this Face. + + Geometrical equivalence is defined as being coplanar with this face, + having the same number of vertices, and having each vertex map-able between + the faces. Clockwise relationships do not have to match nor does the normal + direction of the face. However, all other properties must be matching to + within the input tolerance. + + This is useful for identifying matching surfaces when solving for adjacency + and you need to ensure that two faces match perfectly in their area and vertices. + Note that you may also want to use the remove_colinear_vertices() method + on input faces before using this method in order to count faces with the + same non-colinear vertices as geometrically equivalent. + + 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. + """ + # rule out surfaces if they don't fit key criteria + if not self.center.is_equivalent(face.center, tolerance): + return False + if len(self.vertices) != len(face.vertices): + return False + + # see if we can find a matching vertex + match_i = None + for i, pt in enumerate(self.vertices): + if pt.is_equivalent(face[0], tolerance): + match_i = i + break + + # check equivalency of each vertex + if match_i is None: + return False + elif self[match_i - 1].is_equivalent(face[1], tolerance): + for i in xrange(len(self.vertices)): + if self[match_i - i].is_equivalent(face[i], tolerance) is False: + return False + elif self[match_i + 1].is_equivalent(face[1], tolerance): + for i in xrange(0, -len(self.vertices), -1): + if self[match_i + i].is_equivalent(face[i], tolerance) is False: + return False + else: + return False + return True
+ +
[docs] def is_centered_adjacent(self, face, tolerance): + """Check whether a given face is centered adjacent with this Face. + + Centered adjacency is defined as sharing the same center point as this face + and being next to one another to within the tolerance. + + This is useful for identifying matching faces when you want to quickly + solve for adjacency and you are not concerned about false positives in cases + where one face does not perfectly match the other in terms of vertex ordering. + + Args: + face: Another face for which centered adjacency will be tested. + tolerance: The minimum difference between the coordinate values of two + centers at which they can be considered centered adjacent. + Returns: + True if centered adjacent. False if not centered adjacent. + """ + if not self.center.is_equivalent(face.center, tolerance): # center check + return False + # construct a ray using this face's normal and a point just behind this face + point_on_face = self._point_on_face(tolerance) + point_on_face = point_on_face - (self.normal * tolerance) # move below + test_ray = Ray3D(point_on_face, self.normal) + # shoot ray from this face to the other to verify adjacency + if face.intersect_line_ray(test_ray): + return True + return False
+ +
[docs] def is_sub_face(self, face, tolerance, angle_tolerance): + """Check whether a given face is a sub-face of this face. + + Sub-faces will lie in the same plane as this one and have all of their + vertices completely within the boundary of this face. + + This is useful for identifying whether a given sub-face (ie. a window or door) + can be assigned as a child to this face. + + Args: + face: Another face for which sub-face equivalency will be tested. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. + angle_tolerance: The max angle in radians that the plane normals can + differ from one another in order for them to be considered coplanar. + + Returns: + True if it can be a valid sub-face. False if it is not a valid sub-face. + """ + # test whether the surface is coplanar + if not self.plane.is_coplanar_tolerance(face.plane, tolerance, angle_tolerance): + return False + + # if it is, convert sub-face to a polygon in this face's plane + return self._is_sub_face(face)
+ +
[docs] def polygon_in_face(self, sub_face, origin=None, flip=False): + """Get a Polygon2D for a sub_face within the plane of this Face3D. + + Note that there is no check within this method to determine whether the + the sub_face is coplanar with this Face3D or is fully bounded by it. + So the is_sub_face method should be used to evaluate this before using + this method. + + Args: + sub_face: A Face3D for which a Polygon2D in the plane of this + Face3D will be returned. + origin: An optional Point3D to set the origin of the plane in which + the sub_face will be evaluated. Plugging in values like the + Face's lower_left_corner can standardize the geometry rules + for the resulting polygon. If None, this face's own + plane will be used. (Default: None). + flip: Boolean to note whether the x-axis of the plane should be flipped + when translating this the sub_face vertices. + """ + # set the process the origin into a plane + if origin is None: + plane = self.plane if not flip else self.plane.flip() + else: + if self._plane.n.z in (1, -1): + plane = Plane(self._plane.n, origin, Vector3D(1, 0, 0)) if not flip \ + else Plane(self._plane.n, origin, Vector3D(-1, 0, 0)) + else: + proj_y = Vector3D(0, 0, 1).project(self._plane.n) + proj_x = proj_y.rotate(self._plane.n, math.pi / -2) + plane = Plane(self._plane.n, origin, proj_x) + pts_2d = tuple(plane.xyz_to_xy(pt) for pt in sub_face.boundary) + return Polygon2D(pts_2d)
+ +
[docs] def is_point_on_face(self, point, tolerance): + """Check whether a given point is on this face. + + This includes both a check to be sure that the point is in the plane of this + face and a check to ensure that point lies in the boundary of the face. + + Args: + point: A Point3D to evaluate whether it lies on the face. + tolerance: The minimum difference between the coordinate values of two + vertices at which they can be considered equivalent. + Returns: + True if the point is on the face. False if it is not. + """ + # test whether the point is in the plane of the face + if self.plane.distance_to_point(point) > tolerance: + return False + # if it is, convert the point into this face's plane + vert2d = self.plane.xyz_to_xy(point) + return self.polygon2d.is_point_inside(vert2d)
+ +
[docs] def check_planar(self, tolerance, raise_exception=True): + """Check that all of the face's vertices lie within the face's plane. + + This check is not done by default when creating the face since + it is assumed that there is likely a check for planarity before the face + is created (ie. in CAD software where the face likely originates from). + This method is intended for quality control checking when the origin of + face geometry is unknown or is known to come from a place where no + planarity check was performed. + + Args: + tolerance: The minimum distance between a given vertex and a the + face's plane at which the vertex is said to lie in the plane. + raise_exception: Boolean to note whether an exception should be raised + if a vertex does not lie within the face's plane. If True, an + exception message will be given in such cases, which notes the non-planar + vertex and its distance from the plane. If False, this method will + simply return a False boolean if a vertex is found that is out + of plane. Default is True to raise an exception. + + Returns: + True if planar within the tolerance. False if not planar. + """ + for _v in self.vertices: + if self._plane.distance_to_point(_v) >= tolerance: + if raise_exception is True: + raise ValueError( + 'Vertex {} is out of plane with its parent face.\nDistance ' + 'to plane is {}'.format(_v, self._plane.distance_to_point(_v))) + else: + return False + return True
+ +
[docs] def non_planar_vertices(self, tolerance): + """Get a tuple of Point3D for any vertices that lie outside the face's plane. + + This will be an empty tuple when the Face3D is planar and it is recommended + that the Face3D.check_planar method be used before calling this one. + + Args: + tolerance: The minimum distance between a given vertex and a the + face's plane at which the vertex is said to lie in the plane. + """ + np_verts = [] + for _v in self.vertices: + if self._plane.distance_to_point(_v) >= tolerance: + np_verts.append(_v) + return tuple(np_verts)
+ +
[docs] def remove_duplicate_vertices(self, tolerance): + """Get a version of this face without duplicate vertices. + + Args: + tolerance: The minimum distance between a two vertices at which + they are considered co-located or duplicated. + """ + if not self.has_holes: # we only need to evaluate one list of vertices + new_vertices = tuple( + pt for i, pt in enumerate(self._vertices) + if not pt.is_equivalent(self._vertices[i - 1], tolerance)) + _new_face = Face3D(new_vertices, self.plane, enforce_right_hand=False) + return _new_face + # the face has holes + _boundary = tuple( + pt for i, pt in enumerate(self._boundary) + if not pt.is_equivalent(self._boundary[i - 1], tolerance)) + _holes = tuple( + tuple(p for i, p in enumerate(h) if not p.is_equivalent(h[i - 1], tolerance)) + for j, h in enumerate(self._holes)) + _new_face = Face3D(_boundary, self.plane, _holes, enforce_right_hand=False) + return _new_face
+ +
[docs] def remove_colinear_vertices(self, tolerance): + """Get a version of this face without colinear or duplicate vertices. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. + """ + if not self.has_holes: # we only need to evaluate one list of vertices + new_vertices = self._remove_colinear( + self._vertices, self.polygon2d, tolerance) + _new_face = Face3D(new_vertices, self.plane, enforce_right_hand=False) + return _new_face + # the face has holes + _boundary = self._remove_colinear( + self._boundary, self.boundary_polygon2d, tolerance) + _holes = tuple(self._remove_colinear(hole, self.hole_polygon2d[i], tolerance) + for i, hole in enumerate(self._holes)) + _new_face = Face3D(_boundary, self.plane, _holes, enforce_right_hand=False) + return _new_face
+ +
[docs] def flip(self): + """Get a face with a flipped direction from this one.""" + _new_face = Face3D(reversed(self.vertices), self.plane.flip(), + enforce_right_hand=False) + self._transfer_properties(_new_face) + if self._holes is not None: + _new_face._boundary = tuple(reversed(self._boundary)) + _new_face._holes = self._holes + return _new_face
+ +
[docs] def move(self, moving_vec): + """Get a face that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the face. + """ + _verts = self._move(self.vertices, moving_vec) + _new_face = self._face_transform(_verts, self.plane.move(moving_vec)) + if self._holes is not None: + _new_face._boundary = self._move(self._boundary, moving_vec) + _new_face._holes = tuple(self._move(hole, moving_vec) + for hole in self._holes) + return _new_face
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a face by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + _verts = self._rotate(self.vertices, axis, angle, origin) + _new_face = self._face_transform(_verts, self.plane.rotate(axis, angle, origin)) + if self._holes is not None: + _new_face._boundary = self._rotate(self._boundary, axis, angle, origin) + _new_face._holes = tuple(self._rotate(hole, axis, angle, origin) + for hole in self._holes) + return _new_face
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a face rotated counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + _verts = self._rotate_xy(self.vertices, angle, origin) + _new_face = self._face_transform(_verts, self.plane.rotate_xy(angle, origin)) + if self._holes is not None: + _new_face._boundary = self._rotate_xy(self._boundary, angle, origin) + _new_face._holes = tuple(self._rotate_xy(hole, angle, origin) + for hole in self._holes) + return _new_face
+ +
[docs] def reflect(self, normal, origin): + """Get a face reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the face will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + _verts = self._reflect(self.vertices, normal, origin) + _new_face = self._face_transform_reflect( + _verts, self.plane.reflect(normal, origin)) + if self._holes is not None: + _new_face._boundary = self._reflect(self._boundary, normal, origin) + _new_face._holes = tuple(self._reflect(hole, normal, origin) + for hole in self._holes) + return _new_face
+ +
[docs] def scale(self, factor, origin=None): + """Scale a face by a factor from an origin point. + + Args: + factor: A number representing how much the face should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + _verts = self._scale(self.vertices, factor, origin) + _new_face = self._face_transform_scale(_verts, None, factor) + if self._holes is not None: + _new_face._boundary = self._scale(self._boundary, factor, origin) + _new_face._holes = tuple(self._scale(hole, factor, origin) + for hole in self._holes) + return _new_face
+ +
[docs] def split_through_holes(self): + """Get this Face3D split through its holes to get Face3D without holes. + + This method attempts to return the minimum number of non-holed shapes that + are needed to represent the original Face3D. If this fails, the result + will be derived from a triangulated shape. If getting a minimum number + of constituent Face3D is not important, it is more efficient to just + use all of the triangles in Face3D.triangulated_mesh3d instead of the + result of this method. + + Returns: + A list of Face3D without holes that together form a geometric + representation of this Face3D. If this Face3D has no holes a list + with a single Face3D is returned. + """ + def _shared_vertex_count(vert_set, verts): + """Get the number of shared vertices.""" + in_set = tuple(v for v in verts if v in vert_set) + return len(in_set) + + def _shared_edge_count(edge_set, verts): + """Get the number of shared edges.""" + edges = tuple((verts[i], verts[i - 1]) for i in range(3)) + in_set = tuple(e for e in edges if e in edge_set) + return len(in_set) + + if not self.has_holes: + return (self,) + # check that the direction of vertices for the hole is opposite the boundary + boundary = list(self.boundary_polygon2d.vertices) + holes = [list(hole.vertices) for hole in self.hole_polygon2d] + bound_direction = Polygon2D._are_clockwise(boundary) + for hole in holes: + if Polygon2D._are_clockwise(hole) is bound_direction: + hole.reverse() + # try to split the polygon neatly in two + s_result = Polygon2D._merge_boundary_and_holes(boundary, holes, split=True) + if s_result is not None: + poly_1, poly_2 = s_result + vert_1 = tuple(self.plane.xy_to_xyz(pt) for pt in poly_1) + vert_2 = tuple(self.plane.xy_to_xyz(pt) for pt in poly_2) + face_1 = Face3D(vert_1, plane=self.plane) + face_2 = Face3D(vert_2, plane=self.plane) + return face_1, face_2 + # if splitting in two did not work, then triangulate it and merge them together + tri_mesh = self.triangulated_mesh3d + tri_verts = tri_mesh.vertices + rel_f = tri_mesh.faces[0] + tri_faces = [[tuple(tri_verts[pt] for pt in rel_f)]] + tri_face_sets = [set(rel_f)] + tri_edge_sets = [set((rel_f[i - 1], rel_f[i]) for i in range(3))] + faces_to_test = list(tri_mesh.faces[1:]) + # group the faces along matched edges + for f in faces_to_test: + connected = False + for tfs, fs, es in zip(tri_faces, tri_face_sets, tri_edge_sets): + svc = _shared_vertex_count(fs, f) + sec = _shared_edge_count(es, f) + if svc == 2 and sec == 1: # matched edge + tfs.append(tuple(tri_verts[pt] for pt in f)) + for i, v in enumerate(f): + fs.add(v) + es.add((f[i - 1], f[i])) + break + elif svc == 3: # definitely a new shape + connected = True + else: # not ready to be merged; put it to the back + if connected: + tri_faces.append([tuple(tri_verts[pt] for pt in f)]) + tri_face_sets.append(set(f)) + tri_edge_sets.append(set((f[i - 1], f[i]) for i in range(3))) + else: + faces_to_test.append(f) + # create Face3Ds from the triangle groups + final_faces = [] + for tf in tri_faces: + t_mesh = Mesh3D.from_face_vertices(tf) + ed_len = (seg.length for seg in t_mesh.naked_edges) + tol = min(ed_len) / 10 + f_bound = Polyline3D.join_segments(t_mesh.naked_edges, tol) + final_faces.append(Face3D(f_bound[0].vertices, plane=self.plane)) + return final_faces
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersection between this face and the input Line3D or Ray3D. + + Args: + line_ray: A Line3D or Ray3D object for which intersection will be computed. + + Returns: + Point3D for the intersection. Will be None if no intersection exists. + """ + _plane_int = self._plane.intersect_line_ray(line_ray) + if _plane_int is not None: + _int2d = self._plane.xyz_to_xy(_plane_int) + if self.polygon2d.is_point_inside_bound_rect(_int2d): + return _plane_int + return None
+ +
[docs] def intersect_plane(self, plane): + """Get the intersection between this face and the input plane. + + Args: + plane: A Plane object for which intersection will be computed. + + Returns: + List of LineSegment3D objects for the intersection. + Will be None if no intersection exists. + """ + _plane_int_ray = self._plane.intersect_plane(plane) + if _plane_int_ray is not None: + _p12d = self._plane.xyz_to_xy(_plane_int_ray.p) + _p22d = self._plane.xyz_to_xy(_plane_int_ray.p + _plane_int_ray.v) + _v2d = _p22d - _p12d + _int_ray2d = Ray2D(_p12d, _v2d) + _int_pt2d = self.polygon2d.intersect_line_infinite(_int_ray2d) + if len(_int_pt2d) != 0: + if len(_int_pt2d) > 2: # sort the points along the intersection line + _int_pt2d.sort(key=lambda pt: pt.x) + _int_pt3d = [self._plane.xy_to_xyz(pt) for pt in _int_pt2d] + _int_seg3d = [] + for i in xrange(0, len(_int_pt3d) - 1, 2): + _int_seg3d.append(LineSegment3D.from_end_points( + _int_pt3d[i], _int_pt3d[i + 1])) + return _int_seg3d + return None
+ +
[docs] def project_point(self, point): + """Project a Point3D onto this face. + + Note that this method does a check to see if the point can be projected to + within this face's boundary. If all that is needed is a point projected + into the plane of this face, the Plane.project_point() method should be + used with this face's plane property. + + Args: + point: A Point3D object to project. + + Returns: + Point3D for the point projected onto this face. Will be None if the + point cannot be projected to within the boundary of the face. + """ + _plane_int = point.project(self._plane.n, self._plane.o) + _plane_int2d = self._plane.xyz_to_xy(_plane_int) + if self.polygon2d.is_point_inside_bound_rect(_plane_int2d): + return _plane_int + return None
+ +
[docs] def mesh_grid(self, x_dim, y_dim=None, offset=None, flip=False, + generate_centroids=True): + """Get a gridded Mesh3D over this face. + + This method generates a mesh grid over the domain of the face + and then removes any vertices that do not lie within it. + + Note that the x_dim and y_dim refer to dimensions within the X and Y + coordinate system of this faces's plane. So rotating this plane will + result in rotated grid cells. + + 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 None, which will not offset the grid at all. + flip: Set to True to have the mesh normals reversed from the direction + of this face and to have the offset input move the mesh in the + opposite direction from this face's normal. + generate_centroids: Set to True to have the face centroids generated + alongside the grid of vertices, which is much faster than having + them generated upon request as they typically are. However, if you + have no need for the face centroids, you would save time and memory + by setting this to False. Default is True. + """ + # check the inputs and set defaults + self._check_number_mesh_grid(x_dim, 'x_dim') + if y_dim is not None: + self._check_number_mesh_grid(y_dim, 'y_dim') + else: + y_dim = x_dim + if offset is not None: + self._check_number_mesh_grid(offset, 'offset') + + # generate the mesh grid and convert it to a 3D mesh + grid_mesh2d = Mesh2D.from_polygon_grid( + self.polygon2d, x_dim, y_dim, generate_centroids) + if offset is None or offset == 0: + vert_3d = tuple(self._plane.xy_to_xyz(pt) + for pt in grid_mesh2d.vertices) + else: + _off_num = -1 * offset if flip is True else offset + _off_plane = self.plane.move(self.plane.n * _off_num) + vert_3d = tuple(_off_plane.xy_to_xyz(pt) + for pt in grid_mesh2d.vertices) + grid_mesh3d = Mesh3D(vert_3d, grid_mesh2d.faces) + grid_mesh3d._face_areas = grid_mesh2d._face_areas + + # assign the face plane normal to the mesh normals + if flip is True: + grid_mesh3d._face_normals = self._plane.n.reverse() + grid_mesh3d._vertex_normals = self._plane.n.reverse() + grid_mesh3d._faces = tuple( + tuple(reversed(face)) for face in grid_mesh3d._faces) # right-hand rule + else: + grid_mesh3d._face_normals = self._plane.n + grid_mesh3d._vertex_normals = self._plane.n + + # transform the centroids to 3D space if they were generated + if generate_centroids is True: + _conv_plane = self._plane if offset is None or offset == 0 else _off_plane + grid_mesh3d._face_centroids = tuple(_conv_plane.xy_to_xyz(pt) + for pt in grid_mesh2d.face_centroids) + + return grid_mesh3d
+ +
[docs] def contour_by_number(self, contour_count, direction_vector, flip_side, tolerance): + """Generate a list of LineSegment3D objects contouring the face. + + Args: + contour_count: A positive integer for the number of contours + to generate over the face. + direction_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. Recommended + value is Vector2D(0, 1). + flip_side: Boolean to note whether the side the contours start from + should be flipped. Recommended value is False to have contours + on top or right. + Setting to True will start contours on the bottom or left. + tolerance: An optional value to remove any contours with a length less + than the tolerance. + """ + # interpret the 2D direction_vector into one that exists in 3D space + ref_plane = Plane(self._plane.n, Point3D(0, 0, 0), self._plane.x) + if ref_plane.y.z < 0: + ref_plane = ref_plane.rotate(ref_plane.n, math.pi, ref_plane.o) + plane_normal = ref_plane.xy_to_xyz(direction_vector).normalize() + + # get a diagonal going across the face + diagonal = self._diagonal_along_self(direction_vector, tolerance) + if not flip_side: + diagonal = diagonal.flip() # flip diagonal if user has requested it + + # generate the contours + contours = [] + for pt in diagonal.subdivide_evenly(contour_count)[:-1]: + result = self.intersect_plane(Plane(plane_normal, pt)) + if result is not None: + contours.extend(result) + + # remove any contours that are smaller than the tolerance. + if tolerance != 0: + contours = [l_seg for l_seg in contours if l_seg.length >= tolerance] + return contours
+ +
[docs] def contour_by_distance_between(self, distance, direction_vector, flip_side, + tolerance): + """Generate a list of LineSegment3D objects contouring the face. + + Args: + distance: A number for the distance between each contour. + direction_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. Recommended + value is Vector2D(0, 1). + flip_side: Boolean to note whether the side the contours start from + should be flipped. Recommended value is is False to have contours + start on top or right. Setting to True will start contours on + the bottom or left. + tolerance: An optional value to remove any contours with a length less + than the tolerance. + """ + # interpret the 2D direction_vector into one that exists in 3D space + ref_plane = Plane(self._plane.n, Point3D(0, 0, 0), self._plane.x) + if ref_plane.y.z < 0: + ref_plane = ref_plane.rotate(ref_plane.n, math.pi, ref_plane.o) + plane_normal = ref_plane.xy_to_xyz(direction_vector).normalize() + + # get a diagonal going across the face + diagonal = self._diagonal_along_self(direction_vector, tolerance) + if not flip_side: + diagonal = diagonal.flip() # flip diagonal if user has requested it + + # compute the diagonal subdivision distance using the plane_normal + angle = plane_normal.angle(diagonal.v) + angle = abs(angle - math.pi) if angle > math.pi / 2 else angle + proj_dist = distance / math.cos(angle) + + # generate the contours + contours = [] + for pt in diagonal.subdivide(proj_dist)[:-1]: + pass + result = self.intersect_plane(Plane(plane_normal, pt)) + if result is not None: + contours.extend(result) + + # remove any contours that are smaller than the tolerance. + if tolerance != 0: + contours = [l_seg for l_seg in contours if l_seg.length >= tolerance] + return contours
+ +
[docs] def contour_fins_by_number(self, fin_count, depth, offset, angle, + contour_vector, flip_side, tolerance): + """Generate a list of Fac3D objects over this face (like louvers or fins). + + Args: + fin_count: A positive integer for the number of fins to generate. + depth: A number for the depth to extrude the fins. + offset: A number for the distance to offset fins from this face. + Recommended value is 0 for no offset. + angle: A number for the for an angle to rotate the fins in radians. + Recommended value 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. Recommended + value is Vector2D(0, 1). + flip_side: Boolean to note whether the side the fins start from + should be flipped. Recommended value is False to have contours + start on top or right. Setting to True will start contours on + the bottom or left. + tolerance: An optional value to remove any contours with a length less + than the tolerance. + """ + extru_vec = self._get_fin_extrusion_vector(depth, angle, contour_vector) + contours = self.contour_by_number( + fin_count, contour_vector, flip_side, tolerance) + return self._get_extrusion_fins(contours, extru_vec, offset)
+ +
[docs] def contour_fins_by_distance_between(self, distance, depth, offset, angle, + contour_vector, flip_side, tolerance): + """Generate a list of Fac3D objects over this face (like louvers or fins). + + Args: + distance: A number for the approximate distance between each contour. + depth: A number for the depth to extrude the fins. + offset: A number for the distance to offset fins from this face. + Recommended value is 0 for no offset. + angle: A number for the for an angle to rotate the fins in radians. + Recommended value 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. Recommended + value is Vector2D(0, 1). + flip_side: Boolean to note whether the side the fins start from + should be flipped. Recommended value is False to have contours + start on top or right. Setting to True will start contours on + the bottom or left. + tolerance: An optional value to remove any contours with a length less + than the tolerance. + """ + extru_vec = self._get_fin_extrusion_vector(depth, angle, contour_vector) + contours = self.contour_by_distance_between( + distance, contour_vector, flip_side, tolerance) + return self._get_extrusion_fins(contours, extru_vec, offset)
+ +
[docs] def sub_faces_by_ratio(self, ratio): + """Get a list of faces with a combined area equal to ratio times this face area. + + All sub faces will lie inside the boundaries of this face and will have + the same normal as this face. + + Args: + ratio: A number between 0 and 1 for the ratio between the area of + the sub faces and the area of this face. + + Returns: + A list of Face3D objects for sub faces. + """ + scale_factor = ratio ** .5 + if self.is_convex: + return [self.scale(scale_factor, self.centroid)] + else: + _tri_mesh = self.triangulated_mesh3d + _tri_faces = [[_tri_mesh[i] for i in face] for face in _tri_mesh.faces] + _scaled_verts = [] + for i, _tri in enumerate(_tri_faces): + _scaled_verts.append( + [pt.scale(scale_factor, _tri_mesh.face_centroids[i]) for pt in _tri]) + return [Face3D(_t, self.plane) for _t in _scaled_verts]
+ +
[docs] def sub_faces_by_ratio_gridded(self, ratio, x_dim, y_dim=None): + """Get a list of faces with a combined area equal to ratio times this face area. + + All sub faces will lie inside the boundaries of this face and have the same + normal as this face. + + Sub faces will be arranged in a grid derived from this face's plane property. + 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. + + If the x_dim and/or y_dim are too large for this face, this method will + return essentially the same result as the sub_faces_by_ratio method. + + Args: + ratio: A number between 0 and 1 for the ratio between the area of + the sub faces 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. + + Returns: + A list of Face3D objects for sub faces. + """ + try: # get the gridded mesh derived from this face + grid_mesh = self.mesh_grid(x_dim, y_dim) + except AssertionError: # there are no faces; just return sub_faces_by_ratio + return self.sub_faces_by_ratio(ratio) + + # compute the area that each of the mesh faces need to be scaled to + _verts, _faces = grid_mesh.vertices, grid_mesh.faces + _x_dim = _verts[_faces[0][0]].distance_to_point(_verts[_faces[0][1]]) + _y_dim = _verts[_faces[0][1]].distance_to_point(_verts[_faces[0][2]]) + fac = (self.area * ratio) / (_x_dim * _y_dim * len(_faces)) + + # if the factor is greater than 1, sub-faces will be overlapping + if fac >= 1: + return self.sub_faces_by_ratio(ratio) + s_fac = fac ** 0.5 + + # generate the Face3D objects while scaling them to the correct size + sub_faces = [] + for face, centr in zip(_faces, grid_mesh.face_centroids): + _f = Face3D(tuple(_verts[i].scale(s_fac, centr) for i in face), self.plane) + if self._is_sub_face(_f): # catch edge cases + sub_faces.append(_f) + return sub_faces
+ +
[docs] def sub_faces_by_ratio_rectangle(self, ratio, tolerance): + """Get a list of faces with a combined area equal to ratio times this face area. + + This function is virtually equivalent to the sub_faces_by_ratio method + but a check will be performed to see if any rectangles can be pulled out + of this face's geometry. This tends to make the result a bit cleaner, + especially for concave faces that have rectangles (like L-shaped faces). + + Args: + ratio: A number between 0 and 1 for the ratio between the area of + the sub faces and the area of this face. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. + + Returns: + A list of Face3D objects for sub faces. If there is a rectangle in this + shape, the scaled rectangle will be the first item in this list. + """ + rect_res = self.extract_rectangle(tolerance) + if rect_res is None: + return self.sub_faces_by_ratio(ratio) + bottom_seg, top_seg, other_faces = rect_res + rect_face = Face3D((bottom_seg.p1, bottom_seg.p2, top_seg.p2, top_seg.p1), + self.plane) + scale_factor = ratio ** .5 + sub_faces = [rect_face.scale(scale_factor, rect_face.center)] + for face in other_faces: + sfs = face.sub_faces_by_ratio(ratio) + for sf in sfs: + if sf.area > tolerance: + sub_faces.append(sf) + return sub_faces
+ +
[docs] def sub_faces_by_ratio_sub_rectangle(self, ratio, sub_rect_height, sill_height, + horizontal_separation, vertical_separation, + tolerance): + """Get a list of faces with a combined area equal to ratio times this face area. + + This function is virtually equivalent to the sub_faces_by_ratio_rectangle + method but any rectangles that are found will be broken down into sub-rectangles + using the other inputs (sub_rect_height, sill_height, horizontal_separation, + vertical_separation). This allows for the creation of a wide array of + rectangular sub-face geometries. + + Args: + ratio: A number between 0 and 1 for the ratio between the area of + the sub faces and the area of this face. + sub_rect_height: A number for the target height of the output sub- + rectangles. Note that, if the ratio is too large for the height, + the ratio will take precedence and the sub-rectangle 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 sub-rectangles. Note that, if the + ratio is too large for the height, the ratio will take precedence + and the sub-rectangle height will be smaller than this value. + horizontal_separation: A number for the target separation between + individual sub-rectangle centerlines. If this number is larger than + the parent rectangle base, only one sub-rectangle will be produced. + vertical_separation: An optional number to create a single vertical + separation between top and bottom sub-rectangles. The default is + 0 for no separation. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. + + Returns: + A list of Face3D objects for sub faces. If there is a rectangle in this + shape, the scaled rectangle will be the first item in this list. + """ + rect_res = self.extract_rectangle(tolerance) + if rect_res is None: + return self.sub_faces_by_ratio(ratio) + bottom_seg, top_seg, other_faces = rect_res + height_seg = LineSegment3D.from_end_points(bottom_seg.p, top_seg.p) + norm_tup = self._normal_from_3pts(bottom_seg.p, bottom_seg.p2, top_seg.p) + norm = Vector3D(*norm_tup).normalize() + base_plane = Plane(norm, bottom_seg.p, bottom_seg.v) + sub_faces = Face3D.sub_rects_from_rect_ratio( + base_plane, bottom_seg.length, height_seg.length, ratio, + sub_rect_height, sill_height, horizontal_separation, vertical_separation) + for face in other_faces: + sfs = face.sub_faces_by_ratio(ratio) + for sf in sfs: + if sf.area > tolerance: + sub_faces.append(sf) + return sub_faces
+ +
[docs] def sub_faces_by_dimension_rectangle(self, sub_rect_height, sub_rect_width, + sill_height, horizontal_separation, tolerance): + """Get a list of rectangular faces within this Face3D. + + Note that this method will only yield results if there is a rectangle to + be extracted from this Face3D's geometry. + + Args: + sub_rect_height: A number for the target height of the output rectangles. + sub_rect_width: A number for the target width of the output rectangles. + sill_height: A number for the target height above the bottom edge of + the rectangle to start the sub-rectangles. If the sub_rect_height + is too large for the sill_height to fit within the rectangle, + the sub_rect_height will take precedence. + horizontal_separation: A number for the target separation between + individual sub-rectangle centerlines. If this number is larger than + the parent rectangle base, only one sub-rectangle will be produced. + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. + + Returns: + A list of Face3D objects for sub faces. + """ + rect_res = self.extract_rectangle(tolerance) + if rect_res is None: + return [] + bottom_seg, top_seg, _ = rect_res + height_seg = LineSegment3D.from_end_points(bottom_seg.p, top_seg.p) + norm_tup = self._normal_from_3pts(bottom_seg.p, bottom_seg.p2, top_seg.p) + norm = Vector3D(*norm_tup).normalize() + base_plane = Plane(norm, bottom_seg.p, bottom_seg.v) + sub_faces = Face3D.sub_rects_from_rect_dimensions( + base_plane, bottom_seg.length, height_seg.length, sub_rect_height, + sub_rect_width, sill_height, horizontal_separation) + return sub_faces
+ +
[docs] def get_top_bottom_horizontal_edges(self, tolerance): + """Get top and bottom horizontal edges of this Face if they exist. + + Args: + tolerance: The maximum difference between the z values of the start and + end coordinates at which an edge is considered horizontal. + + Returns: + (bottom_edge, top_edge) with each as LineSegment3D if they exist. + None if they do not exist. + """ + # test if each of the edges are vertical. + horizontal_edges = [] + for edge in self.boundary_segments: + if edge.is_horizontal(tolerance): + horizontal_edges.append(edge) + + if len(horizontal_edges) < 2: + return None + else: + sorted_edges = sorted(horizontal_edges, key=lambda edge: edge.p.z) + return sorted_edges[0], sorted_edges[1]
+ +
[docs] def get_left_right_vertical_edges(self, tolerance): + """Get left and right vertical edges of this Face if they exist. + + Args: + tolerance: The maximum difference between the x any y values of the start + and end coordinates at which an edge is considered vertical. + + Returns: + (left_edge, right_edge) with each as LineSegment3D if they exist. Left in + this case is defined as the edge with the lower X coordinates. + Result will be None if vertical edges do not exist. + """ + # test if each of the edges are vertical. + vertical_edges = [] + for edge in self.boundary_segments: + if edge.is_vertical(tolerance): + vertical_edges.append(edge) + + if len(vertical_edges) < 2: + return None + else: + if abs(self.normal.x) != 1: + sorted_edges = sorted(vertical_edges, key=lambda edge: edge.p.x) + else: + sorted_edges = sorted(vertical_edges, key=lambda edge: edge.p.y) + return sorted_edges[0], sorted_edges[-1]
+ +
[docs] def extract_rectangle(self, tolerance): + """Extract top and bottom line segments of a rectangle within this Face. + + This method will only return geometry if: + + 1) There are no holes in the face. + + 2) The face is not parallel to the World XY plane. + + 3) There are two parallel edges to this face, which are either + oriented horizontally or vertically. + + 4) There must be enough overlap between these edges for a rectangle + to be drawn between them. + + If this Face does not satisfy this criteria, None will be returned. + + Args: + tolerance: The maximum difference between point values for them to be + considered a part of a rectangle. + + Returns: + A tuple with three elements + + - bottom_edge: A LineSegment3D representing the bottom of the rectangle. + + - top_edge: A LineSegment3D representing the top of the rectangle. + + - other_faces: + A list of Face3D objects for the parts of this face not + included in the rectangle. The length of this list will be between + 0 (if this face is already rectangular) and 2 (if there are non- + rectangular geometries on either side of the rectangle.) + """ + # perform checks on the face to see if a rectangle is extractable + if self.has_holes: + return None + if abs(self.normal.x) <= tolerance and abs(self.normal.y) <= tolerance: + # face lies within a horizontal plane; we cannot distinguish top and bottom + return None + clean_face = self.remove_colinear_vertices(tolerance) + + # try to extract a rectangle from horizontal curves + horiz_result = clean_face.get_top_bottom_horizontal_edges(tolerance) + if horiz_result is not None: + bottom_seg, top_seg = horiz_result + split_res = clean_face._split_with_rectangle(bottom_seg, top_seg, tolerance) + if split_res is not None: + return LineSegment3D.from_end_points(split_res[0][1], split_res[0][3]), \ + LineSegment3D.from_end_points(split_res[0][0], split_res[0][2]), \ + split_res[1] + + # try to extract a rectangle from vertical curves + vert_result = clean_face.get_left_right_vertical_edges(tolerance) + if vert_result is not None: + left_seg, right_seg = vert_result + split_res = clean_face._split_with_rectangle(left_seg, right_seg, tolerance) + if split_res is not None: + seg_1 = LineSegment3D.from_end_points(split_res[0][0], split_res[0][1]) + seg_2 = LineSegment3D.from_end_points(split_res[0][2], split_res[0][3]) + sorted_edges = sorted([seg_1, seg_2], key=lambda edge: edge.p.z) + return sorted_edges[0], sorted_edges[1], split_res[1] + return None
+ +
[docs] @staticmethod + def sub_rects_from_rect_ratio( + base_plane, parent_base, parent_height, ratio, sub_rect_height, sill_height, + horizontal_separation, vertical_separation=0): + """Get a list of rectangular Face3D objects using an area ratio and parameters. + + All of the resulting Face3D objects lie within a parent rectangle defined + by the parent_base, parent_height, and base_plane. The combined area of the + resulting rectangles is equal to the area of the larger rectangle multiplied + by the input ratio. This method is particularly useful for generating + rectangular window surfaces. + + Args: + base_plane: A Plane object in which the rectangle exists. + The origin of this plane will be the lower left corner of the + rectangle and the X and Y axes will form the sides. + parent_base: A number indicating the length of the base of the + parent rectangle. + parent_height: A number indicating the length of the height of the + parent rectangle. + ratio: A number between 0 and 1 for the ratio between the area of + the sub rectangle faces and the area of this face. + sub_rect_height: A number for the target height of the output sub- + rectangles. Note that, if the ratio is too large for the height, + the ratio will take precedence and the sub-rectangle 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 sub-rectangles. Note that, if the + ratio is too large for the height, the ratio will take precedence + and the sub-rectangle height will be smaller than this value. + horizontal_separation: A number for the target separation between + individual sub-rectangle center lines. If this number is larger than + the parent rectangle base, only one sub-rectangle will be produced. + vertical_separation: An optional number to create a single vertical + separation between top and bottom sub-rectangles. The default is + 0 for no separation. + + Returns: + A list of Face3D objects for sub faces. + """ + # calculate the target area to make the combined sub-rectangles + target_area = parent_base * parent_height * ratio + # find the maximum area for subdivision into smaller, taller sub-rectangles + max_area_subdiv = parent_base * 0.98 * sub_rect_height + # if sub_rect_height > parent_height, set it to just under parent_height + max_subh = 0.98 * parent_height + sub_rect_height = max_subh if sub_rect_height > max_subh else sub_rect_height + # if sill_height is close to 0, set it to just above 0. + min_sill = 0.01 * parent_height + sill_height = min_sill if sill_height < min_sill else sill_height + # properties used throughout the computation of sub-rectangles + bottom_seg = LineSegment3D.from_sdl(base_plane.o, base_plane.x, parent_base) + + if target_area < max_area_subdiv: + # divide up the rectangle into points on the bottom. + if parent_base > (horizontal_separation / 2): + num_div = round(parent_base / horizontal_separation, 0) + else: + num_div = 1 + btm_div_pts = bottom_seg.subdivide_evenly(num_div) + btm_div_segs = tuple(LineSegment3D.from_end_points(pt, btm_div_pts[i + 1]) + for i, pt in enumerate(btm_div_pts[:-1])) + # move the segments to the sill height + max_sill_h = parent_height * 0.99 - sub_rect_height + sill_vec = base_plane.y * sill_height if sill_height < max_sill_h \ + else base_plane.y * max_sill_h + div_segs = tuple(seg.move(sill_vec) for seg in btm_div_segs) + # scale the segments along their center points + seg_width = div_segs[0].length + subrect_width = (target_area / sub_rect_height) / num_div + scale_fac = subrect_width / seg_width + scaled_segs = [seg.scale(scale_fac, seg.midpoint) for seg in div_segs] + # find the maximum acceptable area for splitting the glazing vertically. + if vertical_separation != 0: + max_split_vert = parent_height - sill_height - sub_rect_height \ + - (0.02 * parent_height) + if vertical_separation < 0 or max_split_vert < 0: + vertical_separation = 0 + elif vertical_separation > max_split_vert: + vertical_separation = max_split_vert + # generate the vertices by 'extruding' along a window height vector. + final_faces = [] + if vertical_separation != 0: + sub_rect_height = sub_rect_height / 2 + h_vec = base_plane.y * sub_rect_height + vert_move_vec = base_plane.y * (sub_rect_height + vertical_separation) + vert_segs = [seg.move(vert_move_vec) for seg in scaled_segs] + for seg in scaled_segs + vert_segs: + final_faces.append(Face3D( + (seg.p1, seg.p2, seg.p2 + h_vec, seg.p1 + h_vec), base_plane)) + else: + h_vec = base_plane.y * sub_rect_height + for seg in scaled_segs: + final_faces.append(Face3D( + (seg.p1, seg.p2, seg.p2 + h_vec, seg.p1 + h_vec), base_plane)) + else: + # make a single sub-rectangle at an appropriate sill height + max_sill_h = parent_height * 0.99 - (target_area / (parent_base * 0.98)) + sill_vec = base_plane.y * sill_height if sill_height < max_sill_h \ + else base_plane.y * max_sill_h + seg_init = bottom_seg.move(sill_vec) + seg = seg_init.scale(0.98, seg_init.midpoint) + # find the maximum acceptable area for splitting the glazing vertically. + if vertical_separation != 0: + max_split_vert = parent_height - sill_height - \ + (target_area / (parent_base * 0.98)) - (0.02 * parent_height) + if vertical_separation < 0 or max_split_vert < 0: + vertical_separation = 0 + elif vertical_separation > max_split_vert: + vertical_separation = max_split_vert + # generate the vertices by 'extruding' along a window height vector. + if vertical_separation != 0: + sub_rect_height = (target_area / (parent_base * 0.98)) / 2 + h_vec = base_plane.y * sub_rect_height + vert_move_vec = base_plane.y * (sub_rect_height + vertical_separation) + vert_seg = seg.move(vert_move_vec) + final_faces = [] + for seg in [seg, vert_seg]: + final_faces.append(Face3D( + (seg.p1, seg.p2, seg.p2 + h_vec, seg.p1 + h_vec), base_plane)) + else: + h_vec = base_plane.y * (target_area / (parent_base * 0.98)) + final_faces = [Face3D((seg.p1, seg.p2, seg.p2 + h_vec, seg.p1 + h_vec), + base_plane)] + return final_faces
+ +
[docs] @staticmethod + def sub_rects_from_rect_dimensions( + base_plane, parent_base, parent_height, sub_rect_height, sub_rect_width, + sill_height, horizontal_separation): + """Get a list of rectangular Face3D objects from dimensions and parameters. + + All of the resulting Face3D objects lie within a parent rectangle defined + by the parent_base, parent_height, and base_plane. + + Args: + base_plane: A Plane object in which the rectangle exists. + The origin of this plane will be the lower left corner of the + rectangle and the X and Y axes will form the sides. + parent_base: A number indicating the length of the base of the + parent rectangle. + parent_height: A number indicating the length of the height of the + parent rectangle. + sub_rect_height: A number for the target height of the output rectangles. + sub_rect_width: A number for the target width of the output rectangles. + sill_height: A number for the target height above the bottom edge of + the rectangle to start the sub-rectangles. If the sub_rect_height + is too large for the sill_height to fit within the rectangle, + the sub_rect_height will take precedence. + horizontal_separation: A number for the target separation between + individual sub-rectangle center lines. If this number is larger than + the parent rectangle base, only one sub-rectangle will be produced. + + Returns: + A list of Face3D objects for sub faces. + """ + # if sub_rect_height > parent_height, set it to just under parent_height + sub_rect_height = parent_height - 0.02 * parent_height if \ + sub_rect_height >= parent_height else sub_rect_height + # if sill_height is close to 0, set it to just above 0 + sill_hgt = 0.01 * parent_height if sill_height < 0.01 * parent_height \ + else sill_height + # adjust sill_hgt if sum of it and sub_rect_height > parent_height + if sub_rect_height + sill_hgt >= parent_height: + sill_hgt = parent_height - sub_rect_height - (parent_height * 0.01) + + # ensure that the horizontal_separation is always greater than sub_rect_width + if sub_rect_width >= horizontal_separation: + horizontal_separation = sub_rect_width * 1.02 + + # determine if the parameters should yield multiple sub-windows or just one + max_width_break_up = parent_base / 2 + num_div = round(parent_base / horizontal_separation) if \ + parent_base > horizontal_separation / 2 else 1 + # properties used throughout the computation of sub-rectangles + sill_vec = base_plane.y * sill_hgt + bottom_seg = LineSegment3D.from_sdl(base_plane.o, base_plane.x, parent_base) + + if sub_rect_width < max_width_break_up: + # determine the number of times that the rectangle should be subdivided + div_dist = parent_base / 2 if num_div == 1 else horizontal_separation + if num_div * sub_rect_width + (num_div - 1) * \ + (horizontal_separation - sub_rect_width) > parent_base: + num_div = math.floor(parent_base / horizontal_separation) + + # Get a segment in the center of the bottom + scale_fac = (div_dist * num_div) / parent_base + rect_seg = bottom_seg.scale(scale_fac, bottom_seg.point_at(0.5)) + rect_seg = rect_seg.move(sill_vec) + btm_div_pts = rect_seg.subdivide_evenly(num_div) + if len(btm_div_pts) == num_div: + btm_div_pts.append(rect_seg.p2) + + # divide up the rectangle into points on the bottom + btm_div_segs = tuple(LineSegment3D.from_end_points(pt, btm_div_pts[i + 1]) + for i, pt in enumerate(btm_div_pts[:-1])) + # scale the line segments along their center points + line_cent_pt = tuple(line.point_at(0.5) for line in btm_div_segs) + scale_factor = sub_rect_width / div_dist + btm_div_segs = tuple(line.scale(scale_factor, mid_pt) + for line, mid_pt in zip(btm_div_segs, line_cent_pt)) + # generate the vertices by 'extruding' along window height vector + h_vec = base_plane.y * sub_rect_height + final_faces = [Face3D((line.p2, line.p1, line.p1 + h_vec, line.p2 + h_vec), + base_plane) for line in btm_div_segs] + else: # make a single sub-rectangle at an appropriate sill height + if sub_rect_width >= parent_base: + sub_rect_width = parent_base * 0.98 + scale_fac = sub_rect_width / parent_base + rect_seg = bottom_seg.scale(scale_fac, bottom_seg.point_at(0.5)) + seg = rect_seg.move(sill_vec) + # generate the vertices by 'extruding' along window height vector + h_vec = base_plane.y * sub_rect_height + final_faces = [Face3D((seg.p2, seg.p1, seg.p1 + h_vec, seg.p2 + h_vec), + base_plane)] + return final_faces
+ +
[docs] def coplanar_difference(self, faces, tolerance, angle_tolerance): + """Subtract one or more coplanar Face3D from this Face3D. + + Note that, when the faces are not coplanar or they do not overlap, the + original face will be returned. + + Args: + faces: A list of Face3D for which will be subtracted from this Face3D. + tolerance: The minimum difference between X, Y and Z values at which + vertices are considered distinct from one another. + angle_tolerance: The max angle in radians that the plane normals can + differ from one another in order for them to be considered coplanar. + + Returns: + A List of Face3D representing the original Face3D with the input faces + subtracted from it. + """ + # define the primary boolean polygon + prim_pl = self.plane + f1_poly = self.boundary_polygon2d + try: + f1_poly = f1_poly.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate face input + return self + f1_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in f1_poly.vertices)] + if self.has_holes: + for hole in self.hole_polygon2d: + f1_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in hole.vertices)) + b_poly1 = pb.BooleanPolygon(f1_polys) + + # pre-process the Face3Ds to be intersected + relevant_b_polys = [] + for face2 in faces: + # test whether the faces are coplanar + if not prim_pl.is_coplanar_tolerance(face2.plane, tolerance, angle_tolerance): + continue + # test whether the two polygons have any overlap in 2D space + f2_poly = Polygon2D(tuple(prim_pl.xyz_to_xy(pt) for pt in face2.boundary)) + if f1_poly.polygon_relationship(f2_poly, tolerance) == -1: + continue + # snap the polygons to one another to avoid tolerance issues + try: + f2_poly = f2_poly.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate faces input + continue + s2_poly = f1_poly.snap_to_polygon(f2_poly, tolerance) + # get BooleanPolygons of the two faces + f2_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in s2_poly.vertices)] + if face2.has_holes: + for hole in face2.holes: + h_pt2d = (prim_pl.xyz_to_xy(pt) for pt in hole) + f2_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in h_pt2d)) + b_poly2 = pb.BooleanPolygon(f2_polys) + relevant_b_polys.append(b_poly2) + + # if no relevant polygons were found, return self + if len(relevant_b_polys) == 0: + return self + + # loop through the boolean polygons and subtract them + int_tol = tolerance / 100 + for b_poly2 in relevant_b_polys: + # subtract the boolean polygons + try: + b_poly1 = pb.difference(b_poly1, b_poly2, int_tol) + except Exception: + return self # typically a tolerance issue causing failure + + # rebuild the Face3D from the result of the subtraction + return Face3D._from_bool_poly(b_poly1, prim_pl)
+ +
[docs] @staticmethod + def coplanar_union(face1, face2, tolerance, angle_tolerance): + """Boolean Union two coplanar Face3D with one another. + + Args: + face1: A Face3D for the first face that will be unioned with the second face. + face2: A Face3D for the second face that will be unioned with the first face. + tolerance: The minimum difference between X, Y and Z values at which + vertices are considered distinct from one another. + angle_tolerance: The max angle in radians that the plane normals can + differ from one another in order for them to be considered coplanar. + + Returns: + A single Face3D for the Union of the two input Face3D. When the faces + are not coplanar or they do not overlap, None will be returned. + """ + # test whether the faces are coplanar + prim_pl = face1.plane + if not prim_pl.is_coplanar_tolerance(face2.plane, tolerance, angle_tolerance): + return None + # test whether the two polygons have any overlap in 2D space + f1_poly = face1.boundary_polygon2d + f2_poly = Polygon2D(tuple(prim_pl.xyz_to_xy(pt) for pt in face2.boundary)) + if f1_poly.polygon_relationship(f2_poly, tolerance) == -1: + return None + # snap the polygons to one another to avoid tolerance issues + try: + f1_poly = f1_poly.remove_colinear_vertices(tolerance) + f2_poly = f2_poly.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate faces input + return None + s2_poly = f1_poly.snap_to_polygon(f2_poly, tolerance) + # get BooleanPolygons of the two faces + f1_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in f1_poly.vertices)] + f2_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in s2_poly.vertices)] + if face1.has_holes: + for hole in face1.hole_polygon2d: + f1_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in hole.vertices)) + if face2.has_holes: + for hole in face2.holes: + h_pt2d = (prim_pl.xyz_to_xy(pt) for pt in hole) + f2_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in h_pt2d)) + b_poly1 = pb.BooleanPolygon(f1_polys) + b_poly2 = pb.BooleanPolygon(f2_polys) + # union the two boolean polygons with one another + int_tol = tolerance / 100 + try: + poly_result = pb.union(b_poly1, b_poly2, int_tol) + except Exception: + return None # typically a tolerance issue causing failure + # rebuild the Face3D from the results and return them + union_faces = Face3D._from_bool_poly(poly_result, prim_pl) + return union_faces[0]
+ +
[docs] @staticmethod + def coplanar_intersection(face1, face2, tolerance, angle_tolerance): + """Boolean Intersection two coplanar Face3D with one another. + + Args: + face1: A Face3D for the first face that will be intersected with + the second face. + face2: A Face3D for the second face that will be intersected with + the first face. + tolerance: The minimum difference between X, Y and Z values at which + vertices are considered distinct from one another. + angle_tolerance: The max angle in radians that the plane normals can + differ from one another in order for them to be considered coplanar. + + Returns: + A list of Face3D for the Intersection of the two input Face3D. + When the faces are not coplanar or they do not overlap, None will + be returned. + """ + # test whether the faces are coplanar + prim_pl = face1.plane + if not prim_pl.is_coplanar_tolerance(face2.plane, tolerance, angle_tolerance): + return None + # test whether the two polygons have any overlap in 2D space + f1_poly = face1.boundary_polygon2d + f2_poly = Polygon2D(tuple(prim_pl.xyz_to_xy(pt) for pt in face2.boundary)) + if f1_poly.polygon_relationship(f2_poly, tolerance) == -1: + return None + # snap the polygons to one another to avoid tolerance issues + try: + f1_poly = f1_poly.remove_colinear_vertices(tolerance) + f2_poly = f2_poly.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate faces input + return None + s2_poly = f1_poly.snap_to_polygon(f2_poly, tolerance) + # get BooleanPolygons of the two faces + f1_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in f1_poly.vertices)] + f2_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in s2_poly.vertices)] + if face1.has_holes: + for hole in face1.hole_polygon2d: + f1_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in hole.vertices)) + if face2.has_holes: + for hole in face2.holes: + h_pt2d = (prim_pl.xyz_to_xy(pt) for pt in hole) + f2_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in h_pt2d)) + b_poly1 = pb.BooleanPolygon(f1_polys) + b_poly2 = pb.BooleanPolygon(f2_polys) + # intersect the two boolean polygons with one another + int_tol = tolerance / 100 + try: + poly_result = pb.intersect(b_poly1, b_poly2, int_tol) + except Exception: + return None # typically a tolerance issue causing failure + # rebuild the Face3D from the results and return them + int_faces = Face3D._from_bool_poly(poly_result, prim_pl) + return int_faces
+ +
[docs] @staticmethod + def coplanar_split(face1, face2, tolerance, angle_tolerance): + """Split two coplanar Face3D with one another (ensuring matching overlapped area) + + When the faces are not coplanar or they do not overlap, the original + faces will be returned. + + Args: + face1: A Face3D for the first face that will be split with the second face. + face2: A Face3D for the second face that will be split with the first face. + tolerance: The minimum difference between X, Y and Z values at which + vertices are considered distinct from one another. + angle_tolerance: The max angle in radians that the plane normals can + differ from one another in order for them to be considered coplanar. + + Returns: + A tuple with two elements + + - face1_split: A list of Face3D for the split version of the input face1. + + - face2_split: A list of Face3D for the split version of the input face2. + """ + # test whether the faces are coplanar + prim_pl = face1.plane + if not prim_pl.is_coplanar_tolerance(face2.plane, tolerance, angle_tolerance): + return [face1], [face2] + # test whether the two polygons have any overlap in 2D space + f1_poly = face1.boundary_polygon2d + f2_poly = Polygon2D(tuple(prim_pl.xyz_to_xy(pt) for pt in face2.boundary)) + if f1_poly.polygon_relationship(f2_poly, tolerance) == -1: + return [face1], [face2] + # snap the polygons to one another to avoid tolerance issues + try: + f1_poly = f1_poly.remove_colinear_vertices(tolerance) + f2_poly = f2_poly.remove_colinear_vertices(tolerance) + except AssertionError: # degenerate faces input + return [face1], [face2] + s2_poly = f1_poly.snap_to_polygon(f2_poly, tolerance) + # get BooleanPolygons of the two faces + f1_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in f1_poly.vertices)] + f2_polys = [(pb.BooleanPoint(pt.x, pt.y) for pt in s2_poly.vertices)] + if face1.has_holes and face2.has_holes: # snap corresponding holes together + f1h_polys = face1.hole_polygon2d + f2h_polys = [Polygon2D(tuple(prim_pl.xyz_to_xy(pt) for pt in h_pts)) + for h_pts in face2.holes] + for f1hp in f1h_polys: + for hi, f2hp in enumerate(f2h_polys): + if f1hp.center.distance_to_point(f2hp.center) < tolerance: + f2h_polys[hi] = f1hp.snap_to_polygon(f2hp, tolerance) + for hole in f1h_polys: + f1_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in hole.vertices)) + for hole in f2h_polys: + f2_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in hole.vertices)) + elif face1.has_holes: + for hole in face1.hole_polygon2d: + f1_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in hole.vertices)) + elif face2.has_holes: + for hole in face2.holes: + h_pt2d = (prim_pl.xyz_to_xy(pt) for pt in hole) + f2_polys.append((pb.BooleanPoint(pt.x, pt.y) for pt in h_pt2d)) + b_poly1 = pb.BooleanPolygon(f1_polys) + b_poly2 = pb.BooleanPolygon(f2_polys) + # split the two boolean polygons with one another + int_tol = tolerance / 100 + try: + int_result, poly1_result, poly2_result = pb.split(b_poly1, b_poly2, int_tol) + except Exception: + return [face1], [face2] # typically a tolerance issue causing failure + # rebuild the Face3D from the results and return them + int_faces = Face3D._from_bool_poly(int_result, prim_pl) + poly1_faces = Face3D._from_bool_poly(poly1_result, prim_pl) + poly2_faces = Face3D._from_bool_poly(poly2_result, prim_pl) + face1_split = poly1_faces + int_faces + face2_split = poly2_faces + int_faces + return face1_split, face2_split
+ +
[docs] @staticmethod + def coplanar_union_all(faces, tolerance, angle_tolerance): + """Boolean Union several coplanar Face3D together. + + Note that this method does not perform any check for whether the input + faces overlap before it performs the unioning operation. So it is + recommended that the Face3D.group_by_coplanar_overlap method be run + before using this method to union each group together. + + Args: + faces: A list of Face3D that will be unioned together. + tolerance: The minimum difference between X, Y and Z values at which + vertices are considered distinct from one another. + angle_tolerance: The max angle in radians that the plane normals can + differ from one another in order for them to be considered coplanar. + + Returns: + A list of Face3D for the Union of all the input Face3D. When the faces + are not coplanar, None will be returned. + """ + # test whether the faces are coplanar + prim_pl = faces[0].plane + for of in faces[1:]: + if not prim_pl.is_coplanar_tolerance(of.plane, tolerance, angle_tolerance): + return None + # convert all boundaries and holes to 2D space + hole_decoder = [False] + all_poly = [faces[0].boundary_polygon2d] + if faces[0].has_holes: + for hole in faces[0].hole_polygon2d: + all_poly.extend(hole) + hole_decoder.append(True) + for of in faces[1:]: + of_poly = Polygon2D(tuple(prim_pl.xyz_to_xy(pt) for pt in of.boundary)) + all_poly.append(of_poly) + hole_decoder.append(False) + if of.has_holes: + for hole in of.holes: + h_poly = Polygon2D(tuple(prim_pl.xyz_to_xy(pt) for pt in hole)) + all_poly.extend(h_poly) + hole_decoder.append(True) + # snap the polygons to one another to avoid tolerance issues + try: + all_poly = [ply.remove_colinear_vertices(tolerance) for ply in all_poly] + except AssertionError: # degenerate faces input + return None + all_poly = Polygon2D.snap_polygons(all_poly, tolerance) + # create BooleanPolygons of the faces + bool_polys = [] + prev_poly = None + for ply, is_hole in zip(all_poly, hole_decoder): + bool_pts = (pb.BooleanPoint(pt.x, pt.y) for pt in ply.vertices) + if not is_hole: + if prev_poly is not None: + bool_polys.append(pb.BooleanPolygon(prev_poly)) + prev_poly = [bool_pts] + else: + prev_poly.append(bool_pts) + bool_polys.append(pb.BooleanPolygon(prev_poly)) + # union the boolean polygons with one another + int_tol = tolerance / 100 + try: + poly_result = pb.union_all(bool_polys, int_tol) + except Exception: + return None # typically a tolerance issue causing failure + # rebuild the Face3D from the results and return them + union_faces = Face3D._from_bool_poly(poly_result, prim_pl) + return union_faces
+ + @staticmethod + def _from_bool_poly(bool_polygon, plane): + """Get a list of Face3D from a BooleanPolygon. + + This method will automatically check whether any of the regions is meant + to be a hole within the others when it creates the Face3D. + + Args: + bool_polygon: A BooleanPolygon to be interpreted to Face3D. + plane: The Plane in which the resulting Face3Ds exist. + """ + # serialize the BooleanPolygon into Polygon2D + polys = [Polygon2D(tuple(Point2D(pt.x, pt.y) for pt in new_poly)) + for new_poly in bool_polygon.regions if len(new_poly) > 2] + if len(polys) == 0: + return [] + if len(polys) == 1: + verts_3d = tuple(plane.xy_to_xyz(pt) for pt in polys[0].vertices) + return [Face3D(verts_3d, plane)] + # sort the polygons by area and check if any are inside the others + polys.sort(key=lambda x: x.area, reverse=True) + poly_groups = [[polys[0]]] + for sub_poly in polys[1:]: + for i, pg in enumerate(poly_groups): + if pg[0].is_polygon_inside(sub_poly): # it's a hole + poly_groups[i].append(sub_poly) + break + else: # it's a separate Face3D + poly_groups.append([sub_poly]) + # convert all vertices to 3D and return the Face3D + face_3d = [] + for pg in poly_groups: + pg_3d = [] + for shp in pg: + pg_3d.append(tuple(plane.xy_to_xyz(pt) for pt in shp.vertices)) + face_3d.append(Face3D(pg_3d[0], plane, holes=pg_3d[1:])) + return face_3d + +
[docs] @staticmethod + def group_by_coplanar_overlap(faces, tolerance): + """Group coplanar Face3Ds depending on whether they overlap one another. + + This is useful as a pre-step before running Face3D.coplanar_union() + in order to assess whether union-ing is necessary and to ensure that + it is only performed among the necessary groups of faces. + + This method will return the minimal number of overlapping polygon groups + thanks to a recursive check of whether groups can be merged. + + Args: + faces: A list of Face3D to be grouped by their 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 Face3Ds + that all overlap with one another. + """ + # sort the faces by area to ensure larger ones grab smaller ones + faces = list(sorted(faces, key=lambda x: x.area, reverse=True)) + # create polygons for all of the faces + r_plane = faces[0].plane + polygons = [Polygon2D([r_plane.xyz_to_xy(pt) for pt in face.vertices]) + for face in faces] + + # loop through the polygons and check to see if it overlaps with the others + grouped_polys, grouped_faces = [[polygons[0]]], [[faces[0]]] + for poly, face in zip(polygons[1:], faces[1:]): + group_found = False + for poly_group, face_group in zip(grouped_polys, grouped_faces): + 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_faces.append([face]) # make a new group for the face + + # if some groups were found, recursively merge groups together + old_group_len = len(polygons) + while len(grouped_polys) != old_group_len: + new_poly_groups, new_face_groups = grouped_polys[:], grouped_faces[:] + g_to_remove = [] + for i, group_1 in enumerate(grouped_polys): + try: + zip_obj = zip(grouped_polys[i + 1:], grouped_faces[i + 1:]) + for j, (group_2, f2) in enumerate(zip_obj): + if Polygon2D._groups_overlap(group_1, group_2, tolerance): + new_poly_groups[i] = new_poly_groups[i] + group_2 + new_face_groups[i] = new_face_groups[i] + f2 + g_to_remove.append(i + j + 1) + except IndexError: + pass # we have reached the end of the list of polygons + if len(g_to_remove) != 0: + g_to_remove = list(set(g_to_remove)) + g_to_remove.sort() + for ri in reversed(g_to_remove): + new_poly_groups.pop(ri) + new_face_groups.pop(ri) + old_group_len = len(grouped_polys) + grouped_polys = new_poly_groups + grouped_faces = new_face_groups + return grouped_faces
+ +
[docs] @staticmethod + def join_coplanar_faces(faces, tolerance): + """Join a list of coplanar Face3Ds together to get as few as possible. + + Note that this method does not perform any boolean union operations on + the input faces. It will only join the objects together along shared edges. + + Args: + faces: A list of Face3D objects to be joined together. These should + all be coplanar but they do not need to have their colinear + vertices removed or be intersected for matching segments along + which they are joined. + tolerance: The maximum difference between values at which point vertices + are considered to be the same. + + Returns: + A list of Face3Ds for the minimum number joined together. + """ + # get polygons for the faces that all lie within the same plane + face_polys, base_plane = [], faces[0].plane + for fg in faces: + verts2d = tuple(base_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(base_plane.xyz_to_xy(_v) for _v in hole) + face_polys.append(Polygon2D(verts2d)) + + # remove colinear vertices + clean_face_polys = [] + for geo in face_polys: + try: + clean_face_polys.append(geo.remove_colinear_vertices(tolerance)) + except AssertionError: # degenerate geometry to ignore + pass + + # get the joined boundaries around the Polygon2D + joined_bounds = Polygon2D.joined_intersected_boundary( + clean_face_polys, tolerance) + + # convert the boundary polygons back to Face3D + if len(joined_bounds) == 1: # can be represented with a single Face3D + verts3d = tuple(base_plane.xy_to_xyz(_v) for _v in joined_bounds[0]) + return [Face3D(verts3d, plane=base_plane)] + else: # need to separate holes from distinct Face3Ds + bound_faces = [] + for poly in joined_bounds: + verts3d = tuple(base_plane.xy_to_xyz(_v) for _v in poly) + bound_faces.append(Face3D(verts3d, plane=base_plane)) + return Face3D.merge_faces_to_holes(bound_faces, tolerance)
+ +
[docs] @staticmethod + def merge_faces_to_holes(faces, tolerance): + """Take of list of Face3Ds and merge any sub-faces into the others as holes. + + This is particularly useful when translating 2D Polygons back into a 3D + space and it is unknown whether certain polygons represent holes in the + others. + + Args: + faces: A list of Face3D which will be merged into fewer faces with + any sub-faces represented as holes. + tolerance: The tolerance to be used for evaluating sub-faces. + """ + # sort the faces by area and separate base face from the remaining + faces = sorted(faces, key=lambda x: x.area, reverse=True) + base_face = faces[0] + remain_faces = list(faces[1:]) + + # merge the smaller faces into the larger faces + merged_face3ds = [] + while len(remain_faces) > 0: + merged_face3ds.append( + Face3D._match_holes_to_face(base_face, remain_faces, tolerance)) + if len(remain_faces) > 1: + base_face = remain_faces[0] + del remain_faces[0] + elif len(remain_faces) == 1: # lone last Face3D + merged_face3ds.append(remain_faces[0]) + del remain_faces[0] + return merged_face3ds
+ + @staticmethod + def _match_holes_to_face(base_face, other_faces, tol): + """Attempt to merge other faces into a base face as holes. + + Args: + base_face: A Face3D to serve as the base. + other_faces: A list of other Face3D objects to attempt to merge into + the base_face as a hole. This method will delete any faces + that are successfully merged into the output from this list. + tol: The tolerance to be used for evaluating sub-faces. + + Returns: + A Face3D which has holes in it if any of the other_faces is a valid + sub face. + """ + holes = [] + more_to_check = True + while more_to_check: + for i, r_face in enumerate(other_faces): + if base_face.is_sub_face(r_face, tol, 1): + holes.append(r_face) + del other_faces[i] + break + else: + more_to_check = False + if len(holes) == 0: + return base_face + else: + hole_verts = [hole.vertices for hole in holes] + return Face3D(base_face.vertices, Plane(n=Vector3D(0, 0, 1)), hole_verts) + +
[docs] def to_dict(self, include_plane=True, enforce_upper_left=False): + """Get Face3D as a dictionary. + + Args: + include_plane: Set to True to include the Face3D plane in the + dictionary, which will preserve the underlying orientation + of the face plane. Default True. + enforce_upper_left: Set to True to ensure that the boundary vertices all + start from the upper-left corner. This takes extra time to compute but + ensures that the vertices in the dictionary are directly usable in an + EnergyPlus simulations. Default: False. + """ + base = {'type': 'Face3D'} + if not enforce_upper_left: + base['boundary'] = [pt.to_array() for pt in self.boundary] + else: + base['boundary'] = [pt.to_array() for pt in + self._upper_left_counter_clockwise_boundary()] + if include_plane: + base['plane'] = self.plane.to_dict() + if self.has_holes: + base['holes'] = [[pt.to_array() for pt in hole] + for hole in self.holes] + return base
+ +
[docs] @staticmethod + def extract_all_from_stl(file_path): + """Get a list of Face3Ds imported from all of the triangles in 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. + """ + from ladybug_geometry.interop.stl import STL # avoid circular import + stl_obj = STL.from_file(file_path) + all_faces = [] + for verts, normal in zip(stl_obj.face_vertices, stl_obj.face_normals): + all_faces.append(Face3D(verts, plane=Plane(normal, verts[0]))) + return all_faces
+ + def _check_vertices_input(self, vertices, loop_name='boundary'): + if not isinstance(vertices, tuple): + vertices = tuple(vertices) + assert len(vertices) >= 3, 'There must be at least 3 vertices for a Face3D {}.' \ + ' Got {}'.format(loop_name, len(vertices)) + for vert in vertices: + assert isinstance(vert, Point3D), \ + 'Expected Point3D for Face3D {} vertex. Got {}.'.format( + loop_name, type(vert)) + return vertices + + def _check_number_mesh_grid(self, input, name): + assert isinstance(input, (float, int)), '{} for Face3D.get_mesh_grid' \ + ' must be a number. Got {}.'.format(name, type(input)) + + def _move(self, vertices, mov_vec): + return tuple(pt.move(mov_vec) for pt in vertices) + + def _rotate(self, vertices, axis, angle, origin): + return tuple(pt.rotate(axis, angle, origin) for pt in vertices) + + def _rotate_xy(self, vertices, angle, origin): + return tuple(pt.rotate_xy(angle, origin) for pt in vertices) + + def _reflect(self, vertices, normal, origin): + return tuple(pt.reflect(normal, origin) for pt in reversed(vertices)) + + def _scale(self, vertices, factor, origin): + if origin is None: + return tuple( + Point3D(pt.x * factor, pt.y * factor, pt.z * factor) + for pt in vertices) + else: + return tuple(pt.scale(factor, origin) for pt in vertices) + + def _face_transform(self, verts, plane): + """Transform face in a way that transfers properties and avoids checks.""" + _new_face = Face3D(verts, plane, enforce_right_hand=False) + self._transfer_properties(_new_face) + _new_face._polygon2d = self._polygon2d + _new_face._mesh2d = self._mesh2d + return _new_face + + def _face_transform_reflect(self, verts, plane): + """Reflect face in a way that transfers properties and avoids checks.""" + _new_face = Face3D(verts, plane, enforce_right_hand=False) + self._transfer_properties(_new_face) + return _new_face + + def _face_transform_scale(self, verts, plane, factor): + """Scale face in a way that transfers properties and avoids checks.""" + _new_face = Face3D(verts, plane, enforce_right_hand=False) + self._transfer_properties_scale(_new_face, factor) + return _new_face + + def _transfer_properties(self, new_face): + """Transfer properties from this face to a new face. + + This is used by the transform methods that don't alter the relationship of + face vertices to one another (move, rotate, reflect). + """ + new_face._perimeter = self._perimeter + new_face._area = self._area + new_face._is_convex = self._is_convex + new_face._is_self_intersecting = self._is_self_intersecting + + def _transfer_properties_scale(self, new_face, factor): + """Transfer properties from this face to a new face. + + This is used by the methods that scale the face. + """ + new_face._is_convex = self._is_convex + new_face._is_self_intersecting = self._is_self_intersecting + if self._perimeter is not None: + new_face._perimeter = self._perimeter * factor + if self._area is not None: + new_face._area = self._area * factor ** 2 + + def _calculate_min_max(self): + """Calculate maximum and minimum Point3D for this object.""" + min_pt = [self.boundary[0].x, self.boundary[0].y, self.boundary[0].z] + max_pt = [self.boundary[0].x, self.boundary[0].y, self.boundary[0].z] + + for v in self.boundary[1:]: + if v.x < min_pt[0]: + min_pt[0] = v.x + elif v.x > max_pt[0]: + max_pt[0] = v.x + if v.y < min_pt[1]: + min_pt[1] = v.y + elif v.y > max_pt[1]: + max_pt[1] = v.y + if v.z < min_pt[2]: + min_pt[2] = v.z + elif v.z > max_pt[2]: + max_pt[2] = v.z + + self._min = Point3D(min_pt[0], min_pt[1], min_pt[2]) + self._max = Point3D(max_pt[0], max_pt[1], max_pt[2]) + + def _remove_colinear(self, pts_3d, pts_2d, tolerance): + """Remove colinear vertices from a list of Point2D. + + This method determines co-linearity by checking whether the area of the + triangle formed by 3 vertices is less than the tolerance. + """ + new_vertices = [] # list to hold the new vertices + skip = 0 # track the number of vertices being skipped/removed + # loop through vertices and remove all cases of colinear verts + for i, _v in enumerate(pts_2d): + _a = pts_2d[i - 2 - skip].determinant(pts_2d[i - 1]) + \ + pts_2d[i - 1].determinant(_v) + _v.determinant(pts_2d[i - 2 - skip]) + if abs(_a) >= tolerance: # vertex is not colinear + new_vertices.append(pts_3d[i - 1]) + skip = 0 + else: # vertex is colinear + skip += 1 + # catch case of last two vertices being equal but distinct from first point + if skip != 0 and pts_3d[-2].is_equivalent(pts_3d[-1], tolerance): + _a = pts_2d[-3].determinant(pts_2d[-1]) + \ + pts_2d[-1].determinant(pts_2d[0]) + pts_2d[0].determinant(pts_2d[-3]) + if abs(_a) >= tolerance: + new_vertices.append(pts_3d[-1]) + return new_vertices + + def _is_sub_face(self, face): + """Check if a face is a sub-face of this face, bypassing coplanar check. + + Args: + face: Another face for which sub-face equivalency will be tested. + """ + verts2d = tuple(self.plane.xyz_to_xy(_v) for _v in face.vertices) + sub_poly = Polygon2D(verts2d) + + if not self.has_holes: + return self.polygon2d.is_polygon_inside(sub_poly) + else: + if not self.boundary_polygon2d.is_polygon_inside(sub_poly): + return False + for hole_poly in self.hole_polygon2d: + if not hole_poly.is_polygon_outside(sub_poly): + return False + return True + + def _vertices_between_points(self, start_pt, end_pt, tolerance): + """Get the vertices between a start and end point. + + This method is used by the extract_rectangle method. + """ + new_verts = [start_pt] + vert_ind = self.vertices.index(start_pt) + found_other = False + while found_other is False: + vert_ind -= 1 + new_verts.append(self[vert_ind]) + if self[vert_ind].is_equivalent(end_pt, tolerance): + found_other = True + return new_verts + + def _diagonal_along_self(self, direction_vector, tolerance): + """Get the diagonal oriented along this face and always starts on the left.""" + tol_pt = Vector3D(1.0e-7, 1.0e-7, 1.0e-7) # closer than float tolerance + diagonal = LineSegment3D.from_end_points(self.min + tol_pt, self.max - tol_pt) + # invert the diagonal XY if it is not oriented with the face plane + if self._plane.distance_to_point(diagonal.p) > tolerance: + start = Point3D(diagonal.p1.x, diagonal.p2.y, diagonal.p1.z) + end = Point3D(diagonal.p2.x, diagonal.p1.y, diagonal.p2.z) + diagonal = LineSegment3D.from_end_points(start, end) + # flip if there's a horizontal direction_vector to ensure always starts on left + if direction_vector.x != 0 and self.normal.y > 0: + diagonal = diagonal.flip() + return diagonal + + def _get_fin_extrusion_vector(self, depth, angle, contour_vector): + """Get the vector with which to extrude fins.""" + extru_vec = self.plane.n * depth + if angle != 0: + # interpret the complement of the 2D contour_vector into a 3D axis + cont_vec_complement = Vector2D(contour_vector.y, -contour_vector.x) + ref_plane = Plane(self._plane.n, Point3D(0, 0, 0), self._plane.x) + if ref_plane.y.z < 0: + ref_plane = ref_plane.rotate(ref_plane.n, math.pi, ref_plane.o) + axis = ref_plane.xy_to_xyz(cont_vec_complement).normalize() + # rotate the extrusion vector around the axis + extru_vec = extru_vec.rotate(axis, angle) + return extru_vec + + def _get_extrusion_fins(self, contours, extru_vec, offset): + """Get fins from the contours and extrusion vector.""" + if offset != 0: + off_vec = self.plane.n * offset + contours = tuple(seg.move(off_vec) for seg in contours) + return tuple(Face3D.from_extrusion(seg, extru_vec) for seg in contours) + + def _split_with_rectangle(self, edge_1, edge_2, tolerance): + """Split this shape using two parallel edges of the face. + + Result will be None if no rectangle can be obtained. + + Returns: + rectangle_points: A tuple of 4 points that make the rectangle. + other_faces: A list of faces for the other parts of this Face that + are not a part of the rectangle. + """ + # compute the 4 points defining the rectangle + close_pt_1 = closest_point3d_on_line3d(edge_1.p1, edge_2) + close_pt_2 = closest_point3d_on_line3d(edge_2.p2, edge_1) + close_pt_3 = closest_point3d_on_line3d(edge_1.p2, edge_2) + close_pt_4 = closest_point3d_on_line3d(edge_2.p1, edge_1) + + # check that there is overlap between the top and bottom curves + if close_pt_1.is_equivalent(edge_2.p1, tolerance) or \ + close_pt_3.is_equivalent(edge_2.p2, tolerance): + return None + + # check that the two sides of the rectangle are inside the polygon. + mid_pt_1 = self.plane.xyz_to_xy( + LineSegment3D.from_end_points(close_pt_1, close_pt_2).midpoint) + mid_pt_2 = self.plane.xyz_to_xy( + LineSegment3D.from_end_points(close_pt_3, close_pt_4).midpoint) + if self.polygon2d.point_relationship(mid_pt_1, tolerance) == -1 or \ + self.polygon2d.point_relationship(mid_pt_2, tolerance) == -1: + return None + + # get extra faces outside of the rectangle + other_faces = [] + edge_pts_1 = self._vertices_between_points(edge_1.p1, edge_2.p2, tolerance) + if close_pt_1.is_equivalent(edge_2.p2, tolerance) is False: + edge_pts_1.append(close_pt_1) + other_faces.append(Face3D(edge_pts_1, self.plane)) + elif close_pt_2.is_equivalent(edge_1.p1, tolerance) is False: + edge_pts_1.append(close_pt_2) + other_faces.append(Face3D(edge_pts_1, self.plane)) + elif len(edge_pts_1) > 2: + other_faces.append(Face3D(edge_pts_1, self.plane)) + + edge_pts_2 = self._vertices_between_points(edge_2.p1, edge_1.p2, tolerance) + if close_pt_3.is_equivalent(edge_2.p1, tolerance) is False: + edge_pts_2.append(close_pt_3) + other_faces.append(Face3D(edge_pts_2, self.plane)) + elif close_pt_4.is_equivalent(edge_1.p2, tolerance) is False: + edge_pts_2.append(close_pt_4) + other_faces.append(Face3D(edge_pts_2, self.plane)) + elif len(edge_pts_2) > 2: + other_faces.append(Face3D(edge_pts_2, self.plane)) + + # check that any new faces are not self intersecting + for new_face in other_faces: + if new_face.is_self_intersecting: + return None + + # return the rectangle edges and the extra faces + return (close_pt_1, close_pt_2, close_pt_3, close_pt_4), other_faces + + def _point_on_face(self, tolerance): + """Get a point that is always reliably on this face. + + The point will be close to the edge of the Face but it will always + be inside its boundary for all concave and holed geometries. Furthermore, + it is relatively fast compared with computing the pole_of_inaccessibility. + """ + try: + face = self.remove_colinear_vertices(tolerance) + move_vec = self._inward_pointing_vec(face) + except (AssertionError, ZeroDivisionError): # zero area Face3D; use center + return self.center + + move_vec = move_vec * (tolerance + 0.00001) + point_on_face = face.boundary[0] + move_vec + vert2d = face.plane.xyz_to_xy(point_on_face) + if not face.polygon2d.is_point_inside(vert2d): + point_on_face = face.boundary[0] - move_vec + return point_on_face + + def _upper_oriented_plane(self): + """Get a version of this Face3D's plane where Y is oriented towards positive Z. + + If the Face3D is horizontal, the plane will be the World XY. + """ + if self._plane.n.z == 1 or self._plane.n.z == -1: # no vertex is above another + ref_plane = Plane(self._plane.n, self._plane.o, Vector3D(1, 0, 0)) + else: + proj_y = Vector3D(0, 0, 1).project(self._plane.n) + proj_x = proj_y.rotate(self._plane.n, math.pi / -2) + ref_plane = Plane(self._plane.n, self._plane.o, proj_x) + return ref_plane + + def _corner_point(self, x_corner='min', y_corner='min'): + """Get a Point3D that is in a particular corner of this Face3D. + + Args: + x_corner: Either "min" or "max" depending on the desired corner. + y_corner: Either "min" or "max" depending on the desired corner. + """ + # get a correctly-oriented polygon + ref_plane = self._upper_oriented_plane() + polygon = Polygon2D(tuple(ref_plane.xyz_to_xy(v) for v in self._boundary)) + # sort points so that they start with the correct corner + x_pt = getattr(polygon, x_corner) + y_pt = getattr(polygon, y_corner) + return ref_plane.xy_to_xyz(Point2D(x_pt.x, y_pt.y)) + + def _corner_point_and_polygon(self, points_3d, x_corner='min', y_corner='min'): + """Get a Point2D and corresponding Polygon in a particular corner of this Face3D. + + Args: + points_3d: A list of Point3Ds for the output Polygon. + x_corner: Either "min" or "max" depending on the desired corner. + y_corner: Either "min" or "max" depending on the desired corner. + """ + if self.is_horizontal(0.01): # EnergyPlus tolerance + polygon = Polygon2D(tuple(Point2D(v.x, v.y) for v in points_3d)) + if self._plane.n.z < 0: # flip the direction of what counts as "right" + x_corner = 'max' if x_corner == 'min' else 'min' + x_pt = getattr(self, x_corner) + y_pt = getattr(self, y_corner) + else: + # get a 2d polygon in the face plane that has a positive Y axis. + proj_y = Vector3D(0, 0, 1).project(self._plane.n) + proj_x = proj_y.rotate(self._plane.n, math.pi / -2) + ref_plane = Plane(self._plane.n, self._plane.o, proj_x) + polygon = Polygon2D(tuple(ref_plane.xyz_to_xy(v) for v in points_3d)) + x_pt = getattr(polygon, x_corner) + y_pt = getattr(polygon, y_corner) + return Point2D(x_pt.x, y_pt.y), polygon + + def _counter_clockwise_verts(self, polygon): + """Get aligned lists of counter-clockwise 2D and 3D vertices.""" + if self.is_clockwise: + return tuple(reversed(self.vertices)), tuple(reversed(polygon.vertices)) + else: + return self.vertices, polygon.vertices + + def _upper_left_counter_clockwise_boundary(self): + """Get this face's boundary starting from upper left and moving counterclockwise. + + Horizontal faces will treat the positive Y axis as up. All other faces + treat the positive Z axis as up. + + Unlike the upper_left_counter_clockwise_vertices property, this property + does not include any holes in the Face3D. + """ + corner_pt, polygon = self._corner_point_and_polygon(self._boundary, 'min', 'max') + if self.is_clockwise: + verts3d, verts2d = \ + tuple(reversed(self.boundary)), tuple(reversed(polygon.vertices)) + else: + verts3d, verts2d = self.boundary, polygon.vertices + return self._corner_pt_verts(corner_pt, verts3d, verts2d) + + @staticmethod + def _inward_pointing_vec(face): + """Get a unit vector pointing inward/outward from the first vertex of a face.""" + v1 = face.boundary[-1] - face.boundary[0] + v2 = face.boundary[1] - face.boundary[0] + if v1.angle(v2) == math.pi: # colinear vertices; prevent averaging to zero + return v1.rotate(face.normal, math.pi / 2).normalize() + else: # average the two edge vectors together + avg_coords = ((v1.x + v2.x) / 2), ((v1.y + v2.y) / 2), ((v1.z + v2.z) / 2) + return Vector3D(*avg_coords).normalize() + + @staticmethod + def _plane_from_vertices(verts): + """Get a plane from a list of vertices. + + Args: + verts: The vertices to be used to extract the normal. + """ + try: + # walk around the shape and get cross products + cprods, base_vert = [], verts[0] + for i in range(len(verts) - 2): + verts_3 = (base_vert, verts[i + 1], verts[i + 2]) + cprods.append(Face3D._normal_from_3pts(*verts_3)) + # sum together the cross products + normal = [0, 0, 0] + for cprodx in cprods: + normal[0] += cprodx[0] + normal[1] += cprodx[1] + normal[2] += cprodx[2] + # normalize the vector + if normal != [0, 0, 0]: + ds = math.sqrt(normal[0] ** 2 + normal[1] ** 2 + normal[2] ** 2) + normal_vec = Vector3D(normal[0] / ds, normal[1] / ds, normal[2] / ds) + else: # zero area Face3D; default to positive Z axis + normal_vec = Vector3D(0, 0, 1) + except Exception as e: + raise ValueError('Incorrect vertices input for Face3D:\n\t{}'.format(e)) + return Plane(normal_vec, verts[0]) + + @staticmethod + def _normal_from_3pts(pt1, pt2, pt3): + """Get a tuple representing a normal vector from 3 vertices. + + The vector will have a magnitude of 0 if vertices are colinear. + This method effectively performs the cross product of two unit vectors but + the ladybug_geometry objects are not used in order to remove assertions + and increase speed. + """ + # get two vectors for the two edges the 3 points form + v1 = (pt2.x - pt1.x, pt2.y - pt1.y, pt2.z - pt1.z) + v2 = (pt3.x - pt1.x, pt3.y - pt1.y, pt3.z - pt1.z) + # get the cross product of the two edge vectors + return (v1[1] * v2[2] - v1[2] * v2[1], + -v1[0] * v2[2] + v1[2] * v2[0], + v1[0] * v2[1] - v1[1] * v2[0]) + + @staticmethod + def _corner_pt_verts(corner_pt, verts3d, verts2d): + """Get verts3d starting from the one closes to the corner_pt.""" + first_pt_index = 0 + min_dist = verts2d[0].distance_to_point(corner_pt) + for pt_index, pt in enumerate(verts2d[1:]): + new_dist = pt.distance_to_point(corner_pt) + if new_dist < min_dist: + first_pt_index = pt_index + 1 + min_dist = new_dist + if first_pt_index != 0: + verts3d = verts3d[first_pt_index:] + verts3d[:first_pt_index] + return verts3d + + def __copy__(self): + _new_face = Face3D(self.vertices, self.plane) + self._transfer_properties(_new_face) + _new_face._holes = self._holes + _new_face._polygon2d = self._polygon2d + _new_face._mesh2d = self._mesh2d + _new_face._mesh3d = self._mesh3d + return _new_face + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return tuple(hash(pt) for pt in self._vertices) + (hash(self._plane),) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Face3D) and self.__key() == other.__key() + + def __repr__(self): + return 'Face3D ({} vertices)'.format(len(self))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/line.html b/docs/_modules/ladybug_geometry/geometry3d/line.html new file mode 100644 index 00000000..2da0a9a8 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/line.html @@ -0,0 +1,1379 @@ + + + + + + + ladybug_geometry.geometry3d.line — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.line

+# coding=utf-8
+"""3D Line Segment"""
+from __future__ import division
+
+from .pointvector import Point3D, Vector3D
+from ._1d import Base1DIn3D
+
+
+
[docs]class LineSegment3D(Base1DIn3D): + """3D line segment object. + + Args: + p: A Point3D representing the first point of the line segment. + v: A Vector3D representing the vector to the second point. + + Properties: + * p + * v + * p1 + * p2 + * min + * max + * midpoint + * endpoints + * length + * vertices + """ + __slots__ = () + + def __init__(self, p, v): + """Initialize LineSegment3D.""" + Base1DIn3D.__init__(self, p, v) + +
[docs] @classmethod + def from_end_points(cls, p1, p2): + """Initialize a line segment from a start point and and end point. + + Args: + p1: A Point3D representing the first point of the line segment. + p2: A Point3D representing the second point of the line segment. + """ + return cls(p1, p2 - p1)
+ +
[docs] @classmethod + def from_sdl(cls, s, d, length): + """Initialize a line segment from a start point, direction, and length. + + Args: + s: A Point3D representing the start point of the line segment. + d: A Vector3D representing the direction of the line segment. + length: A number representing the length of the line segment. + """ + return cls(s, d * length / d.magnitude)
+ +
[docs] @classmethod + def from_array(cls, line_array): + """ Create a LineSegment3D from a nested array of two endpoint coordinates. + + Args: + line_array: Nested tuples ((pt1.x, pt1.y, pt.z), (pt2.x, pt2.y, pt.z)), + where pt1 and pt2 represent the endpoints of the line segment. + """ + return LineSegment3D.from_end_points(*tuple(Point3D(*pt) for pt in line_array))
+ +
[docs] @classmethod + def from_line_segment2d(cls, line2d, z=0): + """Initialize a new LineSegment3D from an LineSegment2D and a z value. + + Args: + line2d: A LineSegment2D to be used to generate the LineSegment3D. + z: A number for the Z coordinate value of the line. + """ + base_p = Point3D(line2d.p.x, line2d.p.y, z) + base_v = Vector3D(line2d.v.x, line2d.v.y, 0) + return cls(base_p, base_v)
+ + @property + def p1(self): + """First point (same as p).""" + return self.p + + @property + def p2(self): + """Second point.""" + return Point3D(self.p.x + self.v.x, self.p.y + self.v.y, self.p.z + self.v.z) + + @property + def midpoint(self): + """Midpoint.""" + return self.point_at(0.5) + + @property + def endpoints(self): + """Tuple of endpoints """ + return (self.p1, self.p2) + + @property + def length(self): + """The length of the line segment.""" + return self.v.magnitude + + @property + def vertices(self): + """Tuple of both vertices in this object.""" + return (self.p1, self.p2) + +
[docs] def is_horizontal(self, tolerance): + """Test whether this line segment is horizontal within a certain tolerance. + + Args: + tolerance: The maximum difference between the z values of the start and + end coordinates at which the line segment is considered horizontal. + """ + return abs(self.v.z) <= tolerance
+ +
[docs] def is_vertical(self, tolerance): + """Test whether this line segment is vertical within a certain tolerance. + + Args: + tolerance: The maximum difference between the x and y values of the start + and end coordinates at which the line segment is considered horizontal. + """ + return abs(self.v.x) <= tolerance and abs(self.v.y) <= tolerance
+ +
[docs] def flip(self): + """Get a copy of this line segment that is flipped.""" + return LineSegment3D(self.p2, self.v.reverse())
+ +
[docs] def move(self, moving_vec): + """Get a line segment that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the ray. + """ + return LineSegment3D(self.p.move(moving_vec), self.v)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a line segment by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + return LineSegment3D(self.p.rotate(axis, angle, origin), + self.v.rotate(axis, angle))
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a line segment rotated counterclockwise in the XY plane by a certain angle. + + Args: + angle: An angle in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + return LineSegment3D(self.p.rotate_xy(angle, origin), + self.v.rotate_xy(angle))
+ +
[docs] def reflect(self, normal, origin): + """Get a line segment reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the line segment will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + return LineSegment3D(self.p.reflect(normal, origin), self.v.reflect(normal))
+ +
[docs] def scale(self, factor, origin=None): + """Scale a line segment by a factor from an origin point. + + Args: + factor: A number representing how much the line segment should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + return LineSegment3D(self.p.scale(factor, origin), self.v * factor)
+ +
[docs] def subdivide(self, distances): + """Get Point3D values along the line that subdivide it based on input distances. + + Args: + distances: A list of distances along the line at which to subdivide it. + This can also be a single number that will be repeated until the + end of the line. + """ + if isinstance(distances, (float, int)): + distances = [distances] + # this assert prevents the while loop from being infinite + assert sum(distances) > 0, 'Segment subdivisions must be greater than 0' + line_length = self.length + dist = distances[0] + index = 0 + sub_pts = [self.p] + while dist < line_length: + sub_pts.append(self.point_at_length(dist)) + if index < len(distances) - 1: + index += 1 + dist += distances[index] + sub_pts.append(self.p2) + return sub_pts
+ +
[docs] def subdivide_evenly(self, number): + """Get Point3D values along the line that divide it into evenly-spaced segments. + + Args: + number: Integer for the number of segments into which the line will + be divided. + """ + # this assert prevents the while loop from being infinite + assert number > 0, 'Segment subdivisions must be greater than 0' + interval = 1 / number + parameter = interval + sub_pts = [self.p] + while parameter <= 1: + sub_pts.append(self.point_at(parameter)) + parameter += interval + if len(sub_pts) != number + 1: # tolerance issue with last point + sub_pts.append(self.p2) + return sub_pts
+ +
[docs] def point_at(self, parameter): + """Get a Point3D at a given fraction along the line segment. + + Args: + parameter: The fraction between the start and end point where the + desired point lies. For example, 0.5 will yield the midpoint. + """ + return self.p + self.v * parameter
+ +
[docs] def point_at_length(self, length): + """Get a Point3D at a given distance along the line segment. + + Args: + length: The distance along the line from the start point where the + desired point lies. + """ + return self.p + self.v * (length / self.length)
+ +
[docs] def split_with_plane(self, plane): + """Split this LineSegment3D in 2 smaller LineSegment3Ds using a Plane. + + Args: + plane: A Plane that will be used to split this line segment. + + Returns: + A list of two LineSegment3D objects if the split was successful. + Will be a list with 1 LineSegment3D if no intersection exists. + """ + _plane_int = self.intersect_plane(plane) + if _plane_int is not None: + return [LineSegment3D.from_end_points(self.p1, _plane_int), + LineSegment3D.from_end_points(_plane_int, self.p2)] + return [self]
+ +
[docs] def to_dict(self): + """Get LineSegment3D as a dictionary.""" + base = Base1DIn3D.to_dict(self) + base['type'] = 'LineSegment3D' + return base
+ +
[docs] def to_array(self): + """ A nested list representing the two line endpoint coordinates.""" + return (self.p1.to_array(), self.p2.to_array())
+ + def _u_in(self, u): + return u >= 0.0 and u <= 1.0 + + def __abs__(self): + return abs(self.v) + + def __copy__(self): + return LineSegment3D(self.p, self.v) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (hash(self.p), hash(self.v)) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, LineSegment3D) and self.__key() == other.__key() + + def __repr__(self): + return 'LineSegment3D (<%.2f, %.2f, %.2f> to <%.2f, %.2f, %.2f>)' % \ + (self.p.x, self.p.y, self.p.z, + self.p.x + self.v.x, self.p.y + self.v.y, self.p.z + self.v.z)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/mesh.html b/docs/_modules/ladybug_geometry/geometry3d/mesh.html new file mode 100644 index 00000000..7df89f4e --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/mesh.html @@ -0,0 +1,1850 @@ + + + + + + + ladybug_geometry.geometry3d.mesh — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.mesh

+# coding=utf-8
+"""3D Mesh"""
+from __future__ import division
+
+from .._mesh import MeshBase
+from ..geometry2d.mesh import Mesh2D
+from .pointvector import Point3D, Vector3D
+from .line import LineSegment3D
+from .polyline import Polyline3D
+from .plane import Plane
+
+try:
+    from itertools import izip as zip  # python 2
+except ImportError:
+    xrange = range  # python 3
+
+
+
[docs]class Mesh3D(MeshBase): + """3D Mesh object. + + Args: + vertices: A list or tuple of Point3D objects for vertices. + faces: A list of tuples with each tuple having either 3 or 4 integers. + These integers correspond to indices within the list of vertices. + colors: An optional list of colors that correspond to either the faces + of the mesh or the vertices of the mesh. Default is None. + + Properties: + * vertices + * faces + * colors + * is_color_by_face + * min + * max + * center + * area + * face_areas + * face_centroids + * face_area_centroids + * face_vertices + * face_normals + * vertex_normals + * vertex_connected_faces + * face_edges + * edges + * naked_edges + * internal_edges + * non_manifold_edges + """ + __slots__ = ('_min', '_max', '_center', '_face_normals', '_vertex_normals') + + def __init__(self, vertices, faces, colors=None): + """Initialize Mesh3D.""" + self._vertices = self._check_vertices_input(vertices) + self._faces = self._check_faces_input(faces) + + self._is_color_by_face = False # default if colors is None + self.colors = colors + self._min = None + self._max = None + self._center = None + self._area = None + self._face_areas = None + self._face_centroids = None + self._face_area_centroids = None + self._face_normals = None + self._vertex_normals = None + self._vertex_connected_faces = None + self._edge_indices = None + self._edge_types = None + self._edges = None + self._naked_edges = None + self._internal_edges = None + self._non_manifold_edges = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Mesh3D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Mesh3D", + "vertices": [(0, 0, 0), (10, 0, 0), (0, 10, 0)], + "faces": [(0, 1, 2)], + "colors": [{"r": 255, "g": 0, "b": 0}] + } + """ + colors = None + if 'colors' in data and data['colors'] is not None and len(data['colors']) != 0: + try: + from ladybug.color import Color + except ImportError: + raise ImportError('Colors are specified in input Mesh2D dictionary ' + 'but failed to import ladybug.color') + colors = tuple(Color.from_dict(col) for col in data['colors']) + fcs = tuple(tuple(f) for f in data['faces']) # cast to immutable type + return cls(tuple(Point3D.from_array(pt) for pt in data['vertices']), fcs, colors)
+ +
[docs] @classmethod + def from_face_vertices(cls, faces, purge=True): + """Create a mesh from a list of faces with each face defined by Point3Ds. + + Args: + faces: A list of faces with each face defined as a list of 3 or 4 Point3D. + purge: A boolean to indicate if duplicate vertices should be shared between + faces. Default is True to purge duplicate vertices, which can be slow + for large lists of faces but results in a higher-quality mesh with + a smaller size in memory. Default is True. + """ + vertices, face_collector = cls._interpret_input_from_face_vertices(faces, purge) + return cls(tuple(vertices), tuple(face_collector))
+ +
[docs] @classmethod + def from_mesh2d(cls, mesh_2d, plane=None): + """Create a Mesh3D from a Mesh2D and a Plane in which the mesh exists. + + Args: + mesh_2d: A Mesh2D object. + plane: A Plane object to represent the plane in which the Mesh2D exists + within 3D space. If None, the WorldXY plane will be used. + """ + assert isinstance(mesh_2d, Mesh2D), 'Expected Mesh2D for from_mesh_2d. ' \ + 'Got {}.'.format(type(mesh_2d)) + if plane is None: + return cls(tuple(Point3D(pt.x, pt.y, 0) for pt in mesh_2d.vertices), + mesh_2d.faces, mesh_2d.colors) + else: + assert isinstance(plane, Plane), 'Expected Plane. Got {}'.format(type(plane)) + _verts3d = tuple(plane.xy_to_xyz(_v) for _v in mesh_2d.vertices) + return cls(_verts3d, mesh_2d.faces, mesh_2d.colors)
+ +
[docs] @classmethod + def from_stl(cls, file_path): + """Create a Mesh3D 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. + """ + from ladybug_geometry.interop.stl import STL # avoid circular import + face_vertices = STL.from_file(file_path).face_vertices + return cls.from_face_vertices(face_vertices)
+ +
[docs] @classmethod + def from_obj(cls, file_path): + """Create a Mesh3D from an OBJ file. + + Args: + file_path: Path to an OBJ file as a text string. + """ + from ladybug_geometry.interop.obj import OBJ # avoid circular import + transl_obj = OBJ.from_file(file_path) + return cls(transl_obj.vertices, transl_obj.faces, transl_obj.vertex_colors)
+ + @property + def min(self): + """A Point3D for the minimum bounding box vertex around this mesh.""" + if self._min is None: + self._calculate_min_max() + return self._min + + @property + def max(self): + """A Point3D for the maximum bounding box vertex around this mesh.""" + if self._max is None: + self._calculate_min_max() + return self._max + + @property + def center(self): + """A Point3D for the center of the bounding box around this mesh.""" + if self._center is None: + min, max = self.min, self.max + self._center = Point3D( + (min.x + max.x) / 2, (min.y + max.y) / 2, (min.z + max.z) / 2) + return self._center + + @property + def face_areas(self): + """A tuple of face areas that parallels the faces property.""" + if self._face_normals is None: + self._calculate_face_areas_and_normals() + elif isinstance(self._face_areas, (float, int)): # same area for each face + self._face_areas = tuple(self._face_areas for face in self.faces) + return self._face_areas + + @property + def face_normals(self): + """Tuple of Vector3D objects for all face normals.""" + if self._face_normals is None: + self._calculate_face_areas_and_normals() + elif isinstance(self._face_normals, Vector3D): # same normal for each face + self._face_normals = tuple(self._face_normals for face in self.faces) + return self._face_normals + + @property + def vertex_normals(self): + """Tuple of Vector3D objects for all vertex normals.""" + if not self._vertex_normals: + self._calculate_vertex_normals() + elif isinstance(self._vertex_normals, Vector3D): # same normal for each vertex + self._vertex_normals = tuple(self._vertex_normals for face in self.vertices) + return self._vertex_normals + + @property + def face_edges(self): + """List of polylines with one Polyline3D for each face. + + This is faster to compute compared to the edges and results in effectively + the same type of wireframe visualization. + """ + _all_verts = self._vertices + f_edges = [] + for face in self._faces: + verts = tuple(_all_verts[v] for v in face) + (_all_verts[face[0]],) + f_edges.append(Polyline3D(verts)) + return f_edges + + @property + def edges(self): + """"Tuple of all edges in this Mesh3D as LineSegment3D objects. + + Note that this method will return only the unique edges in the mesh without + any duplicates. This is sometimes desirable but can take a lot of time + to compute for large meshes. For a faster property, use face_edges.""" + if self._edges is None: + if self._edge_indices is None: + self._compute_edge_info() + self._edges = tuple(LineSegment3D.from_end_points( + self.vertices[seg[0]], self.vertices[seg[1]]) + for seg in self._edge_indices) + return self._edges + + @property + def naked_edges(self): + """"Tuple of all naked edges in this Mesh3D as LineSegment3D objects. + + Naked edges belong to only one face in the mesh (they are not + shared between faces). + """ + if self._naked_edges is None: + self._naked_edges = self._get_edge_type(0) + return self._naked_edges + + @property + def internal_edges(self): + """"Tuple of all internal edges in this Mesh3D as LineSegment3D objects. + + Internal edges are shared between two faces in the mesh. + """ + if self._internal_edges is None: + self._internal_edges = self._get_edge_type(1) + return self._internal_edges + + @property + def non_manifold_edges(self): + """"Tuple of all non-manifold edges in this mesh as LineSegment3D objects. + + Non-manifold edges are shared between three or more faces. + """ + if self._non_manifold_edges is None: + if self._edges is None: + self.edges + nm_edges = [] + for i, type in enumerate(self._edge_types): + if type > 1: + nm_edges.append(self._edges[i]) + self._non_manifold_edges = tuple(nm_edges) + return self._non_manifold_edges + +
[docs] def remove_vertices(self, pattern): + """Get a version of this mesh where vertices are removed according to a pattern. + + Args: + pattern: A list of boolean values denoting whether a vertex should + remain in the mesh (True) or be removed from the mesh (False). + The length of this list must match the number of this mesh's vertices. + + Returns: + A tuple with two elements. + + - new_mesh: + A mesh where the vertices have been removed according + to the input pattern. + + - face_pattern: + A list of boolean values that corresponds to the + original mesh faces noting whether the face is in the new mesh + (True) or has been removed from the new mesh (False). + """ + _new_verts, _new_faces, _new_colors, _new_f_cent, _new_f_area, face_pattern = \ + self._remove_vertices(pattern) + + new_mesh = Mesh3D(_new_verts, _new_faces, _new_colors) + new_mesh._face_centroids = _new_f_cent + new_mesh._face_areas = _new_f_area + return new_mesh, face_pattern
+ +
[docs] def remove_faces(self, pattern): + """Get a version of this mesh where faces are removed according to a pattern. + + Args: + pattern: A list of boolean values denoting whether a face should + remain in the mesh (True) or be removed from the mesh (False). + The length of this list must match the number of this mesh's faces. + + Returns: + A tuple with two elements. + + - new_mesh: + A mesh where the faces have been removed according + to the input pattern. + + - vertex_pattern: + A list of boolean values that corresponds to the + original mesh vertices noting whether the vertex is in the new mesh + (True) or has been removed from the new mesh (False). + """ + vertex_pattern = self._vertex_pattern_from_remove_faces(pattern) + _new_verts, _new_faces, _new_colors, _new_f_cent, _new_f_area, face_pattern = \ + self._remove_vertices(vertex_pattern, pattern) + + new_mesh = Mesh3D(_new_verts, _new_faces, _new_colors) + new_mesh._face_centroids = _new_f_cent + new_mesh._face_areas = _new_f_area + return new_mesh, vertex_pattern
+ +
[docs] def remove_faces_only(self, pattern): + """Get a version of this mesh where faces are removed and vertices are unaltered. + + This is faster than the Mesh3D.remove_faces method but will likely result + a lower-quality mesh where several vertices exist in the mesh that are not + referenced by any face. This may be preferred if pure speed of removing + faces is a priority over smallest size of the mesh in memory. + + Args: + pattern: A list of boolean values denoting whether a face should + remain in the mesh (True) or be removed from the mesh (False). + The length of this list must match the number of this mesh's faces. + + Returns: + new_mesh -- A mesh where the faces have been removed according + to the input pattern. + """ + _new_faces, _new_colors, _new_f_cent, _new_f_area = \ + self._remove_faces_only(pattern) + + new_mesh = Mesh3D(self.vertices, _new_faces, _new_colors) + new_mesh._face_centroids = _new_f_cent + new_mesh._face_areas = _new_f_area + return new_mesh
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a mesh by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the point will be rotated. + """ + _verts = tuple(pt.rotate(axis, angle, origin) for pt in self.vertices) + return self._mesh_transform(_verts)
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a mesh rotated counterclockwise in the XY plane by a certain angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the point will be rotated. + """ + _verts = tuple(pt.rotate_xy(angle, origin) for pt in self.vertices) + return self._mesh_transform(_verts)
+ +
[docs] def scale(self, factor, origin=None): + """Scale a mesh by a factor from an origin point. + + Args: + factor: A number representing how much the mesh should be scaled. + origin: A Point representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + if origin is None: + _verts = tuple( + Point3D(pt.x * factor, pt.y * factor, pt.z * factor) + for pt in self.vertices) + else: + _verts = tuple(pt.scale(factor, origin) for pt in self.vertices) + return self._mesh_scale(_verts, factor)
+ +
[docs] def offset_mesh(self, distance): + """Get a Mesh3D that has been offset from this one by a certain difference. + + Effectively, this method moves each mesh vertex along the vertex normal + by the offset distance. + + Args: + distance: A number for the distance to offset the mesh. + """ + new_verts = tuple(pt.move(norm * distance) for pt, norm in + zip(self.vertices, self.vertex_normals)) + return Mesh3D(new_verts, self.faces, self._colors)
+ +
[docs] def height_field_mesh(self, values, domain): + """Get a Mesh3D that has faces or vertices offset according to a list of values. + + Args: + values: A list of values that has a length matching the number of faces + or vertices in this mesh. + domain: A tuple or list of two numbers for the upper and lower distances + that the mesh vertices should be offset. (ie. (0, 3)) + """ + assert isinstance(domain, (tuple, list)), 'Expected tuple for domain. '\ + 'Got {}.'.format(type(domain)) + assert len(domain) == 2, 'Expected domain to be in the format (min, max). ' \ + 'Got {}.'.format(domain) + + if len(values) == len(self.faces): + remap_vals = Mesh3D._remap_values(values, domain[0], domain[-1]) + vert_remap_vals = [] + for vf in self.vertex_connected_faces: + v = 0 + for j in vf: + v += remap_vals[j] + try: + v /= len(vf) # average the vertex value over its connected faces + except ZeroDivisionError: + pass # lone vertex without any faces + vert_remap_vals.append(v) + new_verts = tuple(pt.move(norm * dist) for pt, norm, dist in + zip(self.vertices, self.vertex_normals, vert_remap_vals)) + elif len(values) == len(self.vertices): + remap_vals = Mesh3D._remap_values(values, domain[0], domain[-1]) + new_verts = tuple(pt.move(norm * dist) for pt, norm, dist in + zip(self.vertices, self.vertex_normals, remap_vals)) + else: + raise ValueError( + 'Input values for height_field_mesh ({}) does not match the number of' + ' mesh faces ({}) nor the number of vertices ({}).' + .format(len(values), len(self.faces), len(self.vertices))) + return Mesh3D(new_verts, self.faces, self._colors)
+ +
[docs] def to_dict(self): + """Get Mesh3D as a dictionary.""" + base = {'type': 'Mesh3D', + 'vertices': [pt.to_array() for pt in self.vertices], + 'faces': self.faces} + if self.colors is not None: + base['colors'] = [col.to_dict() for col in self.colors] + return base
+ +
[docs] def to_stl(self, folder, name=None): + """Write the Mesh3D to an ASCII STL file. + + Args: + folder: A text string for the directory where the STL will be written. + name: A text string for the name of the STL file. + """ + from ladybug_geometry.interop.stl import STL # avoid circular import + stl_obj = STL(self.face_vertices, self.face_normals) + return stl_obj.to_file(folder, name)
+ +
[docs] def to_obj(self, folder, name, include_colors=True, include_normals=False, + triangulate_quads=False, include_mtl=False): + """Write the Mesh3D to an ASCII OBJ file. + + Args: + folder: A text string for the directory where the OBJ will be written. + name: A text string for the name of the OBJ file. + include_colors: Boolean to note whether the Mesh3D colors should be + included in the OBJ file. (Default: True). + include_normals: Boolean to note whether the vertex normals should be + included in the OBJ file. (Default: False). + triangulate_quads: Boolean to note whether quad faces should be + triangulated upon export to OBJ. This may be needed for certain + software platforms that require the mesh to be composed entirely + of triangles (eg. Radiance). (Default: False). + include_mtl: Boolean to note whether an .mtl file should be automatically + generated next to the .obj file in the output folder. All materials + in the mtl file will be diffuse white, with the assumption that + these will be customized later. (Default: False). + """ + from ladybug_geometry.interop.obj import OBJ # avoid circular import + transl_obj = OBJ.from_mesh3d(self, include_colors, include_normals) + return transl_obj.to_file(folder, name, triangulate_quads, include_mtl)
+ +
[docs] @staticmethod + def join_meshes(meshes): + """Join an array of Mesh3Ds into a single Mesh3D. + + Args: + meshes: An array of meshes to be joined into one. + + Returns: + A single Mesh3D object derived from the input meshes. + """ + # set up empty lists of objects to be filled + verts = [] + faces = [] + colors = [] + + # loop through all of the meshes and get new faces + total_v_i = 0 + for mesh in meshes: + verts.extend(mesh._vertices) + for fc in mesh._faces: + faces.append(tuple(v_i + total_v_i for v_i in fc)) + total_v_i += len(mesh._vertices) + if mesh._colors: + colors.extend(mesh._colors) + + # create the new mesh + if len(colors) != 0: + new_mesh = Mesh3D(verts, faces, colors) + else: + new_mesh = Mesh3D(verts, faces) + return new_mesh
+ + def _calculate_min_max(self): + """Calculate maximum and minimum Point3D for this object.""" + min_pt = [self.vertices[0].x, self.vertices[0].y, self.vertices[0].z] + max_pt = [self.vertices[0].x, self.vertices[0].y, self.vertices[0].z] + + for v in self.vertices[1:]: + if v.x < min_pt[0]: + min_pt[0] = v.x + elif v.x > max_pt[0]: + max_pt[0] = v.x + if v.y < min_pt[1]: + min_pt[1] = v.y + elif v.y > max_pt[1]: + max_pt[1] = v.y + if v.z < min_pt[2]: + min_pt[2] = v.z + elif v.z > max_pt[2]: + max_pt[2] = v.z + + self._min = Point3D(min_pt[0], min_pt[1], min_pt[2]) + self._max = Point3D(max_pt[0], max_pt[1], max_pt[2]) + + def _calculate_face_areas_and_normals(self): + """Calculate face areas and normals from vertices.""" + _f_norm = [] + _f_area = [] + for face in self.faces: + pts = tuple(self._vertices[i] for i in face) + if len(face) == 3: + n, a = self._calculate_normal_and_area_for_triangle(pts) + else: + n, a = self._calculate_normal_and_area_for_quad(pts) + _f_norm.append(n) + _f_area.append(a) + self._face_normals = tuple(_f_norm) + self._face_areas = tuple(_f_area) + + def _calculate_vertex_normals(self): + """Calculate vertex normals. + + This is accomplished by normalizing the average of the surface normals + of the faces that contain that vertex. This particular method weights + this average by the area of each face, though this does not always need + to be the case as noted here: + https://en.wikipedia.org/wiki/Vertex_normal + """ + # find shared faces for each vertices + mapper = [[] for v in xrange(len(self.vertices))] + for c, face in enumerate(self.faces): + for i in face: + mapper[i].append(c) + # now calculate vertex normal based on face normals + vn = [] + fn = self.face_normals + fa = self.face_areas + for fi in mapper: + x, y, z = 0, 0, 0 + for n, a in zip(tuple(fn[i] for i in fi), tuple(fa[i] for i in fi)): + x += n.x * a + y += n.y * a + z += n.z * a + _v = Vector3D(x, y, z) + vn.append(_v.normalize()) + self._vertex_normals = tuple(vn) + + def _get_edge_type(self, edge_type): + """Get all of the edges of a certain type in this mesh.""" + if self._edges is None: + self.edges + sel_edges = [] + for i, type in enumerate(self._edge_types): + if type == edge_type: + sel_edges.append(self._edges[i]) + return tuple(sel_edges) + + def _tri_face_centroid(self, face): + """Compute the centroid of a triangular face.""" + return Mesh3D._tri_centroid(tuple(self._vertices[i] for i in face)) + + def _quad_face_centroid(self, face): + """Compute the centroid of a quadrilateral face.""" + return Mesh3D._quad_centroid(tuple(self._vertices[i] for i in face)) + + def _mesh_transform(self, verts): + """Transform mesh in a way that transfers properties and avoids extra checks.""" + _new_mesh = Mesh3D(verts, self.faces) + self._transfer_properties(_new_mesh) + return _new_mesh + + def _mesh_transform_move(self, verts): + """Move mesh in a way that transfers properties and avoids extra checks.""" + _new_mesh = Mesh3D(verts, self.faces) + self._transfer_properties(_new_mesh) + _new_mesh._face_normals = self._face_normals + _new_mesh._vertex_normals = self._vertex_normals + return _new_mesh + + def _mesh_scale(self, verts, factor): + """Scale mesh in a way that transfers properties and avoids extra checks.""" + _new_mesh = Mesh3D(verts, self.faces) + self._transfer_properties_scale(_new_mesh, factor) + _new_mesh._face_normals = self._face_normals + _new_mesh._vertex_normals = self._vertex_normals + return _new_mesh + + def _check_vertices_input(self, vertices): + """Check the input vertices.""" + if not isinstance(vertices, tuple): + vertices = tuple(vertices) + for vert in vertices: + assert isinstance(vert, Point3D), \ + 'Expected Point3D for {} vertex. Got {}.'.format( + self.__class__.__name__, type(vert)) + return vertices + + @staticmethod + def _calculate_normal_and_area_for_triangle(pts): + """Calculate normal and area for three points. + + Returns: + n = Normalized normal vector for the triangle. + a = Area of the triangle. + """ + v1 = pts[1] - pts[0] + v2 = pts[2] - pts[0] + n = v1.cross(v2) + a = n.magnitude / 2 + return n.normalize(), a + + @staticmethod + def _calculate_normal_and_area_for_quad(pts): + """Calculate normal and area for four points. + + This method uses an area-weighted average of the two triangle normals + that compose the quad face. + + Returns: + n = Normalized normal vector for the quad. + a = Area of the quad. + """ + # TODO: Get this method to work for concave quads. + # This method is only reliable when quads are convex since we assume + # either diagonal of the quad splits it into two triangles. + # It seems Rhino never produces concave quads when it automatically meshes + # but we will likely want to add support for this if meshes have other origins + v1 = pts[1] - pts[0] + v2 = pts[2] - pts[0] + n1 = v1.cross(v2) + + v3 = pts[3] - pts[2] + v4 = pts[1] - pts[2] + n2 = v3.cross(v4) + + a = (n1.magnitude + n2.magnitude) / 2 + n = Vector3D((n1.x + n2.x) / 2, (n1.y + n2.y) / 2, (n1.z + n2.z) / 2) + return n.normalize(), a + + @staticmethod + def _face_center(verts): + """Get the center of a list of Point3D vertices.""" + _cent_x = sum([v.x for v in verts]) + _cent_y = sum([v.y for v in verts]) + _cent_z = sum([v.z for v in verts]) + v_count = len(verts) + return Point3D(_cent_x / v_count, _cent_y / v_count, _cent_z / v_count) + + @staticmethod + def _tri_centroid(verts): + """Get the centroid of a list of 3 Point3D vertices.""" + _cent_x = sum([v.x for v in verts]) + _cent_y = sum([v.y for v in verts]) + _cent_z = sum([v.z for v in verts]) + return Point3D(_cent_x / 3, _cent_y / 3, _cent_z / 3) + + @staticmethod + def _quad_centroid(verts): + """Get the centroid of a list of 4 Point3D vertices.""" + # TODO: Get this method to recognize concave quads. + # This method is only reliable when quads are convex since we assume + # either diagonal of the quad splits it into two triangles. + # It seems Rhino never produces concave quads when it automatically meshes + _tri_verts = ((verts[0], verts[1], verts[2]), (verts[2], verts[3], verts[0])) + _tri_c = [Mesh3D._tri_centroid(tri) for tri in _tri_verts] + _tri_a = [Mesh3D._get_tri_area(tri) for tri in _tri_verts] + _tot_a = sum(_tri_a) + try: + _cent_x = (_tri_c[0].x * _tri_a[0] + _tri_c[1].x * _tri_a[1]) / _tot_a + _cent_y = (_tri_c[0].y * _tri_a[0] + _tri_c[1].y * _tri_a[1]) / _tot_a + _cent_z = (_tri_c[0].z * _tri_a[0] + _tri_c[1].z * _tri_a[1]) / _tot_a + except ZeroDivisionError: + _cent_x = sum([v.x for v in verts]) / 4 + _cent_y = sum([v.y for v in verts]) / 4 + _cent_z = sum([v.z for v in verts]) / 4 + return Point3D(_cent_x, _cent_y, _cent_z) + + @staticmethod + def _get_tri_area(pts): + """Get the area of a triangle from three Point3D objects.""" + v1 = pts[1] - pts[0] + v2 = pts[2] - pts[0] + n1 = v1.cross(v2) + return n1.magnitude / 2 + + @staticmethod + def _remap_values(values, tmin, tmax): + """Remap a set of values to offset distances within a domain.""" + omin = min(values) + omax = max(values) + odiff = omax - omin + tdiff = tmax - tmin + if odiff == 0: + return [tmin] * len(values) + else: + return [(v - omin) * tdiff / odiff + tmin for v in values] + + def __copy__(self): + _new_mesh = Mesh3D(self.vertices, self.faces) + self._transfer_properties(_new_mesh) + _new_mesh._face_centroids = self._face_centroids + _new_mesh._face_normals = self._face_normals + _new_mesh._vertex_normals = self._vertex_normals + return _new_mesh + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return tuple(hash(pt) for pt in self._vertices) + \ + tuple(hash(face) for face in self._faces) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Mesh3D) and self.__key() == other.__key() + + def __repr__(self): + return 'Mesh3D ({} faces) ({} vertices)'.format( + len(self.faces), len(self))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/plane.html b/docs/_modules/ladybug_geometry/geometry3d/plane.html new file mode 100644 index 00000000..13204586 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/plane.html @@ -0,0 +1,1542 @@ + + + + + + + ladybug_geometry.geometry3d.plane — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.plane

+# coding=utf-8
+"""Plane"""
+from __future__ import division
+
+from .pointvector import Point3D, Vector3D
+from .ray import Ray3D
+from ..intersection3d import intersect_line3d_plane, intersect_line3d_plane_infinite, \
+    intersect_plane_plane, closest_point3d_on_plane, \
+    closest_point3d_between_line3d_plane
+from ..geometry2d.pointvector import Point2D, Vector2D
+from ..geometry2d.ray import Ray2D
+
+import math
+
+
+
[docs]class Plane(object): + """Plane object. + + Args: + n: A Vector3D representing the normal of the plane. + o: A Point3D representing the origin point of the plane. + x: An optional Vector3D for the X-Axis of the Plane. + Note that this vector must be orthogonal to the input normal vector. + If None, the default will find an X-Axis in the world XY plane. + + Properties: + * n + * o + * k + * x + * y + * altitude + * azimuth + * min + * max + """ + __slots__ = ('_n', '_o', '_k', '_x', '_y', '_altitude', '_azimuth') + + def __init__(self, n=Vector3D(0, 0, 1), o=Point3D(0, 0, 0), x=None): + """Initialize Plane.""" + assert isinstance(n, Vector3D), \ + "Expected Vector3D for plane normal. Got {}.".format(type(n)) + assert isinstance(o, Point3D), \ + "Expected Point3D for plane origin. Got {}.".format(type(o)) + self._n = n.normalize() + self._o = o + self._k = self._n.dot(self._o) + + if x is None: + if self._n.x == 0 and self._n.y == 0: + self._x = Vector3D(1, 0, 0) + else: + x = Vector3D(self._n.y, -self._n.x, 0) + x = x.normalize() + self._x = x + else: + assert isinstance(x, Vector3D), \ + "Expected Vector3D for plane X-axis. Got {}.".format(type(x)) + x = x.normalize() + assert abs(self._n.x * x.x + self._n.y * x.y + self._n.z * x.z) < 1e-2, \ + 'Plane X-axis and normal vector are not orthogonal. Got angle of {} ' \ + 'degrees between them.'.format(math.degrees(self._n.angle(x))) + self._x = x + self._y = self._n.cross(self._x) + self._altitude = None + self._azimuth = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Plane from a dictionary. + + .. code-block:: python + + { + "type": "Plane" + "n": (0, 0, 1), + "o": (0, 10, 0), + "x": (1, 0, 0) + } + """ + x = None + if 'x' in data and data['x'] is not None: + x = Vector3D.from_array(data['x']) + return cls(Vector3D.from_array(data['n']), + Point3D.from_array(data['o']), x)
+ +
[docs] @classmethod + def from_three_points(cls, o, p2, p3): + """Initialize a Plane from three Point3D objects that are not co-linear. + + Args: + o: A Point3D representing the origin point of the plane. + p2: A Point3D representing a point the plane. + p3: A Point3D representing a point the plane. + """ + return cls((p2 - o).cross(p3 - o), o)
+ +
[docs] @classmethod + def from_normal_k(cls, n, k): + """Initialize a Plane from a normal vector and a scalar constant. + + Args: + o: A Point3D representing the origin point of the plane. + k: Scalar constant relating origin point to normal vector + """ + # get an arbitrary point on the plane for the origin + if n.z: + o = Point3D(0., 0., k / n.z) + elif n.y: + o = Point3D(0., k / n.y, 0.) + else: + o = Point3D(k / n.x, 0., 0.) + return cls(n, o)
+ + @property + def n(self): + """Normal vector. This vector will always be normalized (magnitude = 1).""" + return self._n + + @property + def o(self): + """Origin point.""" + return self._o + + @property + def k(self): + """Scalar constant relating origin point to normal vector.""" + return self._k + + @property + def x(self): + """Plane X-Axis. This vector will always be normalized (magnitude = 1).""" + return self._x + + @property + def y(self): + """Plane Y-Axis. This vector will always be normalized (magnitude = 1).""" + return self._y + + @property + def azimuth(self): + """Get the azimuth of the plane (between 0 and 2 * Pi). + + This will be zero if the plane is perfectly horizontal. + """ + if self._azimuth is None: + try: + n_vec = Vector2D(0, 1) + self._azimuth = n_vec.angle_clockwise(Vector2D(self.n.x, self.n.y)) + except ZeroDivisionError: # plane is perfectly horizontal + self._azimuth = 0 + return self._azimuth + + @property + def altitude(self): + """Get the altitude of the plane (between Pi/2 and -Pi/2).""" + if self._altitude is None: + self._altitude = self.n.angle(Vector3D(0, 0, -1)) - math.pi / 2 + return self._altitude + + @property + def min(self): + """Returns the Plane origin.""" + return self._o + + @property + def max(self): + """Returns the Plane origin.""" + return self._o + +
[docs] def flip(self): + """Get a flipped version of this plane (facing the opposite direction).""" + return Plane(self.n.reverse(), self.o, self.x)
+ +
[docs] def move(self, moving_vec): + """Get a plane that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the plane. + """ + return Plane(self.n, self.o.move(moving_vec), self.x)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a plane by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + return Plane(self.n.rotate(axis, angle), + self.o.rotate(axis, angle, origin), + self.x.rotate(axis, angle))
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a plane rotated counterclockwise in the world XY plane by a certain angle. + + Args: + angle: An angle in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + return Plane(self.n.rotate_xy(angle), + self.o.rotate_xy(angle, origin), + self.x.rotate_xy(angle))
+ +
[docs] def reflect(self, normal, origin): + """Get a plane reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the plane will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + return Plane(self.n.reflect(normal), + self.o.reflect(normal, origin), + self.x.reflect(normal))
+ +
[docs] def scale(self, factor, origin=None): + """Scale a plane by a factor from an origin point. + + Args: + factor: A number representing how much the plane should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + return Plane(self.n, self.o.scale(factor, origin), self.x)
+ +
[docs] def xyz_to_xy(self, point): + """Get a Point2D in the coordinate system of this plane from a Point3D. + + Note that the input Point3D should lie within this plane object in order + for the result to be valid. + """ + _diff = Vector3D(point.x - self.o.x, point.y - self.o.y, point.z - self.o.z) + return Point2D(self.x.dot(_diff), self.y.dot(_diff))
+ +
[docs] def xy_to_xyz(self, point): + """Get a Point3D from a Point2D in the coordinate system of this plane.""" + # This method returns the same result as the following code: + # self.o + (self.x * point.x) + (self.y * point.y) + # It has been written explicitly to cut out the isinstance() checks for speed + _u = (self.x.x * point.x, self.x.y * point.x, self.x.z * point.x) + _v = (self.y.x * point.y, self.y.y * point.y, self.y.z * point.y) + return Point3D( + self.o.x + _u[0] + _v[0], self.o.y + _u[1] + _v[1], self.o.z + _u[2] + _v[2])
+ +
[docs] def is_point_above(self, point): + """Test if a given point is above or below this plane. + + Above is defined as being on the side of the plane that the plane normal + is pointing towards. + + Args: + point: A Point3D object to test. + + Returns: + True is point is above; False if below. + """ + vec = Vector3D(point.x - self.o.x, point.y - self.o.y, point.z - self.o.z) + return self.n.dot(vec) > 0
+ +
[docs] def closest_point(self, point): + """Get the closest Point3D on this plane to another Point3D. + + Args: + point: A Point3D object to which the closest point on this plane + will be computed. + + Returns: + Point3D for the closest point on this plane to the input point. + """ + return closest_point3d_on_plane(point, self)
+ +
[docs] def distance_to_point(self, point): + """Get the minimum distance between this plane and the input point. + + Args: + point: A Point3D object to which the minimum distance will be computed. + + Returns: + The distance to the input point. + """ + close_pt = self.closest_point(point) + return point.distance_to_point(close_pt)
+ +
[docs] def closest_points_between_line(self, line_ray): + """Get the two closest Point3D between this plane and a Line3D or Ray3D. + + Args: + line_ray: A Line3D or Ray3D object to which the closest points + will be computed. + + Returns: + Two Point3D objects representing + + 1) The closest point on the input line_ray to this plane. + 2) The closest point on this plane to the input line_ray. + + Will be None if the line_ray intersects this plant + """ + return closest_point3d_between_line3d_plane(line_ray, self)
+ +
[docs] def distance_to_line(self, line_ray): + """Get the minimum distance between this plane and the input Line3D or Ray3D. + + Args: + line_ray: A Line3D or Ray3D object to which the minimum distance + will be computed. + + Returns: + The minimum distance to the input line_ray. + """ + result = self.closest_points_between_line(line_ray) + if result is None: # intersection + return 0 + else: + return result[0].distance_to_point(result[1])
+ +
[docs] def project_point(self, point, projection_direction=None): + """Project a point onto this Plane given a certain projection direction. + + Args: + point: A Point3D to be projected onto the plane + projection_direction: A Line3D or Ray3D object to set the direction + of projection. If None, this Plane's normal will be + used. (Default: None). + + Returns: + Point3D for the projected point. Will be None if the projection_direction + is parallel to the plane. + """ + int_ray = Ray3D(point, self.n) if projection_direction is None \ + else Ray3D(point, projection_direction) + return intersect_line3d_plane_infinite(int_ray, self)
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersection between this plane and the input Line3D or Ray3D. + + Args: + line_ray: A Line3D or Ray3D object for which intersection will be computed. + + Returns: + Point3D for the intersection. Will be None if no intersection exists. + """ + return intersect_line3d_plane(line_ray, self)
+ +
[docs] def intersect_arc(self, arc): + """Get the intersection between this Plane and an Arc3D. + + Args: + plane: A Plane object for which intersection will be computed. + + Returns: + A list of 2 Point3D objects if a full intersection exists. + A list with a single Point3D object if the line is tangent or intersects + only once. None if no intersection exists. + """ + _plane_int_ray = self.intersect_plane(arc.plane) + if _plane_int_ray is not None: + _p12d = arc.plane.xyz_to_xy(_plane_int_ray.p) + _p22d = arc.plane.xyz_to_xy(_plane_int_ray.p + _plane_int_ray.v) + _v2d = _p22d - _p12d + _int_ray2d = Ray2D(_p12d, _v2d) + _int_pt2d = arc.arc2d.intersect_line_infinite(_int_ray2d) + if _int_pt2d is not None: + return [arc.plane.xy_to_xyz(pt) for pt in _int_pt2d] + return None
+ +
[docs] def intersect_plane(self, plane): + """Get the intersection between this Plane and another Plane. + + Args: + plane: A Plane object for which intersection will be computed. + + Returns: + Ray3D for the intersection. Will be None if planes are parallel. + """ + result = intersect_plane_plane(self, plane) + if result is not None: + return Ray3D(result[0], result[1]) + return None
+ +
[docs] def is_coplanar(self, plane): + """Test if another Plane object is perfectly coplanar with this Plane. + + Args: + plane: A Plane object for which co-planarity will be tested. + + Returns: + True if plane is coplanar. False if it is not coplanar. + """ + if self.n == plane.n: + return self.k == plane.k + elif self.n == plane.n.reverse(): + return self.k == -plane.k + return False
+ +
[docs] def is_coplanar_tolerance(self, plane, tolerance, angle_tolerance): + """Test if another Plane object is coplanar within a certain tolerance. + + Args: + plane: A Plane object for which co-planarity will be tested. + tolerance: The distance between the two planes at which point they can + be considered coplanar. + angle_tolerance: The angle in radians that the plane normals can + differ from one another in order for the planes to be considered + coplanar. + + Returns: + True if plane is coplanar. False if it is not coplanar. + """ + if self.n.angle(plane.n) <= angle_tolerance or \ + self.n.angle(plane.n.reverse()) <= angle_tolerance: + return self.distance_to_point(plane.o) <= tolerance + return False
+ +
[docs] def duplicate(self): + """Get a copy of this object.""" + return self.__copy__()
+ +
[docs] def to_dict(self): + """Get Plane as a dictionary.""" + return {'type': 'Plane', + 'n': self.n.to_array(), + 'o': self.o.to_array(), + 'x': self.x.to_array()}
+ + def __copy__(self): + return Plane(self.n, self.o, self.x) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self.n, self.o, self.x) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Plane) and self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + return 'Plane (<%.2f, %.2f, %.2f> normal) (<%.2f, %.2f, %.2f> origin)' % \ + (self.n.x, self.n.y, self.n.z, self.o.x, self.o.y, self.o.z)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/pointvector.html b/docs/_modules/ladybug_geometry/geometry3d/pointvector.html new file mode 100644 index 00000000..febb933b --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/pointvector.html @@ -0,0 +1,1626 @@ + + + + + + + ladybug_geometry.geometry3d.pointvector — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.pointvector

+# coding=utf-8
+"""3D Vector and 3D Point"""
+from __future__ import division
+
+from ..geometry2d.pointvector import Vector2D
+
+import math
+import operator
+
+
+
[docs]class Vector3D(object): + """3D Vector object. + + Args: + x: Number for the X coordinate. + y: Number for the Y coordinate. + z: Number for the Z coordinate. + + Properties: + * x + * y + * z + * magnitude + * magnitude_squared + * is_zero + """ + __slots__ = ('_x', '_y', '_z') + + def __init__(self, x=0, y=0, z=0): + """Initialize 3D Vector.""" + self._x = self._cast_to_float(x) + self._y = self._cast_to_float(y) + self._z = self._cast_to_float(z) + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Vector3D/Point3D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "x": 10, + "y": 0, + "z": 0 + } + """ + return cls(data['x'], data['y'], data['z'])
+ +
[docs] @classmethod + def from_array(cls, array): + """Initialize a Vector3D/Point3D from an array. + + Args: + array: A tuple or list with three numbers representing the x, y and z + values of the point. + """ + return cls(array[0], array[1], array[2])
+ +
[docs] @classmethod + def from_vector2d(cls, vector2d, z=0): + """Initialize a new Vector3D from an Vector2D and a z value. + + Args: + line2d: A Vector2D to be used to generate the Vector3D. + z: A number for the Z coordinate value of the line. + """ + return cls(vector2d.x, vector2d.y, z)
+ + @property + def x(self): + """Get the X coordinate.""" + return self._x + + @property + def y(self): + """Get the Y coordinate.""" + return self._y + + @property + def z(self): + """Get the Z coordinate.""" + return self._z + + @property + def magnitude(self): + """Get the magnitude of the vector.""" + return self.__abs__() + + @property + def magnitude_squared(self): + """Get the magnitude squared of the vector.""" + return self.x ** 2 + self.y ** 2 + self.z ** 2 + + @property + def min(self): + """Always equal to (0, 0, 0). + + This property exists to help with bounding box calculations. + """ + return Point3D(0, 0, 0) + + @property + def max(self): + """Always equal to (0, 0, 0). + + This property exists to help with bounding box calculations. + """ + return Point3D(0, 0, 0) + +
[docs] def is_zero(self, tolerance): + """Boolean to note whether the vector is within a given zero tolerance. + + Args: + tolerance: The tolerance below which the vector is considered to + be a zero vector. + """ + return abs(self.x) <= tolerance and abs(self.y) <= tolerance and \ + abs(self.z) <= tolerance
+ +
[docs] def is_equivalent(self, other, tolerance): + """Test whether this object is equivalent to another within a certain tolerance. + + Note that if you want to test whether the coordinate values are perfectly + equal to one another, the == operator can be used. + + Args: + other: Another Point3D or Vector3D for which geometric equivalency + will be tested. + tolerance: The minimum difference between the coordinate values of two + objects at which they can be considered geometrically equivalent. + + Returns: + True if equivalent. False if not equivalent. + """ + return abs(self.x - other.x) <= tolerance and \ + abs(self.y - other.y) <= tolerance and \ + abs(self.z - other.z) <= tolerance
+ +
[docs] def normalize(self): + """Get a copy of the vector that is a unit vector (magnitude=1).""" + d = self.magnitude + try: + return Vector3D(self.x / d, self.y / d, self.z / d) + except ZeroDivisionError: + return self.duplicate()
+ +
[docs] def reverse(self): + """Get a copy of this vector that is reversed.""" + return self.__neg__()
+ +
[docs] def dot(self, other): + """Get the dot product of this vector with another.""" + return self.x * other.x + self.y * other.y + self.z * other.z
+ +
[docs] def cross(self, other): + """Get the cross product of this vector and another vector.""" + return Vector3D(self.y * other.z - self.z * other.y, + -self.x * other.z + self.z * other.x, + self.x * other.y - self.y * other.x)
+ +
[docs] def angle(self, other): + """Get the smallest angle between this vector and another.""" + try: + return math.acos(self.dot(other) / (self.magnitude * other.magnitude)) + except ValueError: # python floating tolerance can cause math domain error + if self.dot(other) < 0: + return math.acos(-1) + return math.acos(1)
+ +
[docs] def rotate(self, axis, angle): + """Get a vector rotated around an axis through an angle. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle in radians. + """ + return Vector3D._rotate(self, axis, angle)
+ +
[docs] def rotate_xy(self, angle): + """Get a vector rotated counterclockwise in the XY plane by a certain angle. + + Args: + angle: An angle in radians. + """ + vec_2 = Vector2D._rotate(self, angle) + return Vector3D(vec_2.x, vec_2.y, self.z)
+ +
[docs] def reflect(self, normal): + """Get a vector that is reflected across a plane with the input normal vector. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the vector will be reflected. THIS VECTOR MUST BE NORMALIZED. + """ + return Vector3D._reflect(self, normal)
+ +
[docs] def project(self, normal): + """Get a vector projected into a plane with a given normal. + + Args: + normal: A Vector3D representing the normal vector of the plane into which + the plane will be projected. THIS VECTOR MUST BE NORMALIZED. + """ + return self - normal * self.dot(normal)
+ +
[docs] def duplicate(self): + """Get a copy of this vector.""" + return self.__copy__()
+ +
[docs] def to_dict(self): + """Get Vector3D as a dictionary.""" + return {'type': 'Vector3D', + 'x': self.x, + 'y': self.y, + 'z': self.z}
+ +
[docs] def to_array(self): + """Get Vector3D/Point3D as a tuple of three numbers""" + return (self.x, self.y, self.z)
+ + def _cast_to_float(self, value): + """Ensure that an input coordinate value is a float.""" + try: + number = float(value) + except Exception: + raise TypeError( + 'Coordinates must be numbers. Got {}: {}.'.format(type(value), value)) + return number + + @staticmethod + def _reflect(vec, normal): + """Hidden reflection method used by both Point3D and Vector3D.""" + d = 2 * (vec.x * normal.x + vec.y * normal.y + vec.z * normal.z) + return Vector3D(vec.x - d * normal.x, + vec.y - d * normal.y, + vec.z - d * normal.z) + + @staticmethod + def _rotate(vec, axis, angle): + """Hidden rotation method used by both Point3D and Vector3D.""" + # Adapted from equations published by Glenn Murray. + # http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.html + x, y, z = vec.x, vec.y, vec.z + u, v, w = axis.x, axis.y, axis.z + + # Extracted common factors for simplicity and efficiency + r2 = u ** 2 + v ** 2 + w ** 2 + r = math.sqrt(r2) + ct = math.cos(angle) + st = math.sin(angle) / r + dt = (u * x + v * y + w * z) * (1 - ct) / r2 + return Vector3D((u * dt + x * ct + (-w * y + v * z) * st), + (v * dt + y * ct + (w * x - u * z) * st), + (w * dt + z * ct + (-v * x + u * y) * st)) + + def __copy__(self): + return self.__class__(self.x, self.y, self.z) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self.x, self.y, self.z) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, (Vector3D, Point3D)) and \ + self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + + def __nonzero__(self): + return self.x != 0 or self.y != 0 or self.z != 0 + + def __len__(self): + return 3 + + def __getitem__(self, key): + return (self.x, self.y, self.z)[key] + + def __iter__(self): + return iter((self.x, self.y, self.z)) + + def __add__(self, other): + # Vector + Point -> Point + # Vector + Vector -> Vector + if isinstance(other, Point3D): + return Point3D(self.x + other.x, self.y + other.y, self.z + other.z) + elif isinstance(other, Vector3D): + return Vector3D(self.x + other.x, self.y + other.y, self.z + other.z) + else: + raise TypeError('Cannot add {} and {}'.format( + self.__class__.__name__, type(other))) + + __radd__ = __add__ + + def __sub__(self, other): + # Vector - Point -> Point + # Vector - Vector -> Vector + if isinstance(other, Point3D): + return Point3D(self.x - other.x, self.y - other.y, self.z - other.z) + elif isinstance(other, Vector3D): + return Vector3D(self.x - other.x, self.y - other.y, self.z - other.z) + else: + raise TypeError('Cannot subtract {} and {}'.format( + self.__class__.__name__, type(other))) + + def __rsub__(self, other): + if isinstance(other, (Vector3D, Point3D)): + return Vector3D(other.x - self.x, other.y - self.y, other.z - self.z) + else: + assert hasattr(other, '__len__') and len(other) == 3, \ + 'Cannot subtract types {} and {}'.format( + self.__class__.__name__, type(other)) + return Vector3D(other.x - self[0], other.y - self[1], other.z - self[2]) + + def __mul__(self, other): + if isinstance(other, (int, float)): + return Vector3D(self.x * other, self.y * other, self.z * other) + elif isinstance(other, Vector3D): + return Vector3D(self.x * other.x, self.y * other.y, self.z * other.z) + elif isinstance(other, Point3D): + return Point3D(self.x * other.x, self.y * other.y, self.z * other.z) + else: + raise TypeError('Cannot multiply {} and {}'.format( + self.__class__.__name__, type(other))) + + __rmul__ = __mul__ + + def __div__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector3D(self.x / other, self.y / other, self.z / other) + + def __rdiv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector3D(other / self.x, other / self.y, other / self.z) + + def __floordiv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector3D(operator.floordiv(self.x, other), + operator.floordiv(self.y, other), + operator.floordiv(self.z, other)) + + def __rfloordiv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector3D(operator.floordiv(other, self.x), + operator.floordiv(other, self.y), + operator.floordiv(other, self.z)) + + def __truediv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector3D(operator.truediv(self.x, other), + operator.truediv(self.y, other), + operator.truediv(self.z, other)) + + def __rtruediv__(self, other): + assert type(other) in (int, float), \ + 'Cannot divide types {} and {}'.format(self.__class__.__name__, type(other)) + return Vector3D(operator.truediv(other, self.x), + operator.truediv(other, self.y), + operator.truediv(other, self.z)) + + def __neg__(self): + return Vector3D(-self.x, -self.y, -self.z) + + __pos__ = __copy__ + + def __abs__(self): + return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + """Vector3D representation.""" + return 'Vector3D (%.2f, %.2f, %.2f)' % (self.x, self.y, self.z)
+ + +
[docs]class Point3D(Vector3D): + """3D Point object. + + Args: + x: Number for the X coordinate. + y: Number for the Y coordinate. + z: Number for the Z coordinate. + + Properties: + * x + * y + * z + """ + __slots__ = () + +
[docs] @classmethod + def from_point2d(cls, point2d, z=0): + """Initialize a new Point3D from an Point2D and a z value. + + Args: + line2d: A Point2D to be used to generate the Point3D. + z: A number for the Z coordinate value of the line. + """ + return cls(point2d.x, point2d.y, z)
+ + @property + def min(self): + """Always equal to the point itself. + + This property exists to help with bounding box calculations. + """ + return self + + @property + def max(self): + """Always equal to the point itself. + + This property exists to help with bounding box calculations. + """ + return self + +
[docs] def move(self, moving_vec): + """Get a point that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the point. + """ + return Point3D(self.x + moving_vec.x, + self.y + moving_vec.y, + self.z + moving_vec.z)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a point by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the point will be rotated. + """ + return Vector3D._rotate(self - origin, axis, angle) + origin
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a point rotated counterclockwise in the XY plane by a certain angle. + + Args: + angle: An angle in radians. + origin: A Point3D for the origin around which the point will be rotated. + """ + trans_self = self - origin + vec_2 = Vector2D._rotate(trans_self, angle) + return Vector3D(vec_2.x, vec_2.y, trans_self.z) + origin
+ +
[docs] def reflect(self, normal, origin): + """Get a point reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the point will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + return Vector3D._reflect(self - origin, normal) + origin
+ +
[docs] def scale(self, factor, origin=None): + """Scale a point by a factor from an origin point. + + Args: + factor: A number representing how much the point should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + if origin is None: + return Point3D(self.x * factor, self.y * factor, self.z * factor) + else: + return (factor * (self - origin)) + origin
+ +
[docs] def project(self, normal, origin): + """Get a point that is projected into a plane with a given normal and origin. + + Args: + normal: A Vector3D representing the normal vector of the plane into which + the plane will be projected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin the plane into which the + point will be projected. + """ + trans_self = self - origin + return self - normal * trans_self.dot(normal)
+ +
[docs] def distance_to_point(self, point): + """Get the distance from this point to another Point3D.""" + vec = (self.x - point.x, self.y - point.y, self.z - point.z) + return math.sqrt(vec[0] ** 2 + vec[1] ** 2 + vec[2] ** 2)
+ +
[docs] def to_dict(self): + """Get Point3D as a dictionary.""" + return {'type': 'Point3D', + 'x': self.x, + 'y': self.y, + 'z': self.z}
+ + def __add__(self, other): + # Point + Vector -> Point + # Point + Point -> Vector + if isinstance(other, Point3D): + return Vector3D(self.x + other.x, self.y + other.y, self.z + other.z) + elif isinstance(other, Vector3D): + return Point3D(self.x + other.x, self.y + other.y, self.z + other.z) + else: + raise TypeError('Cannot add Point3D and {}'.format(type(other))) + + def __sub__(self, other): + # Point - Vector -> Point + # Point - Point -> Vector + if isinstance(other, Point3D): + return Vector3D(self.x - other.x, self.y - other.y, self.z - other.z) + elif isinstance(other, Vector3D): + return Point3D(self.x - other.x, self.y - other.y, self.z - other.z) + else: + raise TypeError('Cannot subtract Point3D and {}'.format(type(other))) + + def __repr__(self): + """Point3D representation.""" + return 'Point3D (%.2f, %.2f, %.2f)' % (self.x, self.y, self.z)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/polyface.html b/docs/_modules/ladybug_geometry/geometry3d/polyface.html new file mode 100644 index 00000000..577a2945 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/polyface.html @@ -0,0 +1,1980 @@ + + + + + + + ladybug_geometry.geometry3d.polyface — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.polyface

+# coding=utf-8
+"""Object with Multiple Planar Faces in 3D Space"""
+from __future__ import division
+
+from .pointvector import Vector3D, Point3D
+from .ray import Ray3D
+from .line import LineSegment3D
+from .polyline import Polyline3D
+from .plane import Plane
+from .face import Face3D
+from ._2d import Base2DIn3D
+
+try:
+    from itertools import izip as zip  # python 2
+except ImportError:
+    xrange = range  # python 3
+
+
+
[docs]class Polyface3D(Base2DIn3D): + """Object with Multiple Planar Faces in 3D Space. Includes solids and polyhedra. + + Args: + vertices: A list of Point3D objects representing the vertices of + this PolyFace. + face_indices: A list of lists with one list for each face of the polyface. + Each face list must contain at least one tuple of integers corresponding + to indices within the vertices list. Additional tuples of integers may + follow this one such that the first tuple denotes the boundary of the + face while each subsequent tuple denotes a hole in the face. + edge_information: Optional edge information, which will speed up the + creation of the Polyface object if it is available but should be left + as None if it is unknown. If None, edge_information will be computed + from the vertices and face_indices inputs. Edge information + should be formatted as a dictionary with two keys as follows: + + * 'edge_indices': + An array objects that each contain two integers. + These integers correspond to indices within the vertices list and + each tuple represents a line segment for an edge of the polyface. + * 'edge_types': + An array of integers for each edge that parallels the edge_indices + list. An integer of 0 denotes a naked edge, an integer of 1 + denotes an internal edge. Anything higher is a non-manifold edge. + + Properties: + * vertices + * faces + * edges + * naked_edges + * internal_edges + * non_manifold_edges + * face_indices + * edge_indices + * edge_types + * min + * max + * center + * area + * volume + * is_solid + """ + __slots__ = ('_faces', '_edges', + '_naked_edges', '_internal_edges', '_non_manifold_edges', + '_face_indices', '_edge_indices', '_edge_types', + '_area', '_volume', '_is_solid') + + def __init__(self, vertices, face_indices, edge_information=None): + """Initialize Polyface3D.""" + # assign input properties + Base2DIn3D.__init__(self, vertices) + self._face_indices = tuple(tuple(tuple(loop) for loop in face) + for face in face_indices) + + if edge_information is not None: # unpack the input edge information + edge_i = edge_information['edge_indices'] + edge_t = edge_information['edge_types'] + else: # autocalculate the edge information from the vertices and faces + edge_i = [] + edge_t = [] + for face in face_indices: + for fi in face: + for i, vi in enumerate(fi): + try: # this can get slow for large number of vertices + ind = edge_i.index((vi, fi[i - 1])) + edge_t[ind] += 1 + except ValueError: # make sure reversed edge isn't there + try: + ind = edge_i.index((fi[i - 1], vi)) + edge_t[ind] += 1 + except ValueError: # add a new edge + if fi[i - 1] != vi: # avoid cases of same start and end + edge_i.append((fi[i - 1], vi)) + edge_t.append(0) + self._edge_indices = edge_i if isinstance(edge_i, tuple) else tuple(edge_i) + self._edge_types = edge_t if isinstance(edge_t, tuple) else tuple(edge_t) + + # determine solidity of the polyface by checking for internal edges + self._is_solid = True + for edge in self._edge_types: + if edge != 1: + self._is_solid = False + break + + # assign default properties + self._faces = None + self._edges = None + self._naked_edges = None + self._internal_edges = None + self._non_manifold_edges = None + self._area = None + self._volume = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Face3D from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Polyface3D", + "vertices": [(0, 0, 0), (10, 0, 0), (10, 10, 0), (0, 10, 0)], + "face_indices": [[(0, 1, 2)], [(3, 0, 1)]], + "edge_information": { + "edge_indices":[(0, 1), (1, 2), (2, 0), (2, 3), (3, 0)], + "edge_types":[0, 0, 1, 0, 0] + } + } + """ + if 'edge_information' in data and data['edge_information'] is not None: + edge_information = data['edge_information'] + else: + edge_information = None + + return cls(tuple(Point3D.from_array(pt) for pt in data['vertices']), + data['face_indices'], edge_information)
+ +
[docs] @classmethod + def from_faces(cls, faces, tolerance): + """Initialize Polyface3D from a list of Face3D objects. + + Note that the Polyface3D.faces property of the resulting polyface will + have an order of faces that matches the order input to this classmethod. + + Args: + faces: A list of Face3D objects representing the boundary of this Polyface. + tolerance: The maximum difference between x, y, and z values at which + the vertex of two adjacent faces is considered the same. + """ + # extract unique vertices from the faces + vertices = [] # collection of vertices as point objects + face_indices = [] # collection of face indices + for f in faces: + ind = [] + loops = (f.boundary,) if not f.has_holes else (f.boundary,) + f.holes + for j, loop in enumerate(loops): + ind.append([]) + for v in loop: + found = False + for i, vert in enumerate(vertices): + if v.is_equivalent(vert, tolerance): + found = True + ind[j].append(i) + break + if not found: # add new point + vertices.append(v) + ind[j].append(len(vertices) - 1) + face_indices.append(tuple(ind)) + + # get the polyface object and assign correct faces to it + face_obj = cls(vertices, face_indices) + if face_obj._is_solid: + face_obj._faces = cls.get_outward_faces(faces, 0.01) + else: + face_obj._faces = tuple(faces) + return face_obj
+ +
[docs] @classmethod + def from_box(cls, width, depth, height, base_plane=None): + """Initialize Polyface3D from parameters describing a box. + + Initializing a polyface this way has the added benefit of having its + faces property quickly calculated. + + Args: + width: A number for the width of the box (in the X direction). + depth: A number for the depth of the box (in the Y direction). + height: A number for the height of the box (in the Z direction). + base_plane: A Plane object from which to generate the box. + If None, default is the WorldXY plane. + """ + assert isinstance(width, (float, int)), 'Box width must be a number.' + assert isinstance(depth, (float, int)), 'Box depth must be a number.' + assert isinstance(height, (float, int)), 'Box height must be a number.' + if base_plane is not None: + assert isinstance(base_plane, Plane), \ + 'base_plane must be Plane. Got {}.'.format(type(base_plane)) + else: + base_plane = Plane(Vector3D(0, 0, 1), Point3D()) + _o = base_plane.o + _w_vec = base_plane.x * width + _d_vec = base_plane.y * depth + _h_vec = base_plane.n * height + _verts = (_o, _o + _d_vec, _o + _d_vec + _w_vec, _o + _w_vec, + _o + _h_vec, _o + _d_vec + _h_vec, + _o + _d_vec + _w_vec + _h_vec, _o + _w_vec + _h_vec) + _face_indices = ([(0, 1, 2, 3)], [(2, 1, 5, 6)], [(6, 7, 3, 2)], + [(0, 3, 7, 4)], [(0, 4, 5, 1)], [(7, 6, 5, 4)]) + _edge_indices = ((3, 0), (0, 1), (1, 2), (2, 3), (0, 4), (4, 5), + (5, 1), (3, 7), (7, 4), (6, 2), (5, 6), (6, 7)) + polyface = cls(_verts, _face_indices, {'edge_indices': _edge_indices, + 'edge_types': [1] * 12}) + verts = tuple(tuple(_verts[i] for i in face[0]) for face in _face_indices) + bottom = Face3D(verts[0], base_plane.flip(), enforce_right_hand=False) + middle = tuple(Face3D(v, enforce_right_hand=False) for v in verts[1:5]) + top = Face3D(verts[5], base_plane.move(_h_vec), enforce_right_hand=False) + polyface._faces = (bottom,) + middle + (top,) + polyface._volume = width * depth * height + return polyface
+ +
[docs] @classmethod + def from_offset_face(cls, face, offset): + """Initialize a solid Polyface3D from a Face3D offset along its normal. + + The resulting polyface will always be offset in the direction of + the face normal. + + When a polyface is initialized this way, the first face of the + Polyface3D.faces will always be the input face used to create the + object, the last face will be the offset version of the face, and all + other faces will form the extrusion connecting the two. + + Args: + face: A Face3D to serve as a base for the polyface. + offset: A number for the distance to offset the face to make a solid. + """ + assert isinstance(face, Face3D), \ + 'face must be a Face3D. Got {}.'.format(type(face)) + assert isinstance(offset, (float, int)), \ + 'height must be a number. Got {}.'.format(type(offset)) + # compute vertices, face indices, and edges of the extrusion + extru_vec = face.normal * offset + verts, face_ind_extru, edge_indices = \ + Polyface3D._verts_faces_edges_from_boundary(face.boundary, extru_vec) + if face.has_holes: + _st_i = len(verts) + for i, hole in enumerate(face.hole_polygon2d): + hole_verts = face._holes[i] if hole.is_clockwise else \ + tuple(reversed(face._holes[i])) + verts_2, face_ind_extru_2, edge_indices_2 = \ + Polyface3D._verts_faces_edges_from_boundary( + hole_verts, extru_vec, _st_i) + verts.extend(verts_2) + face_ind_extru.extend(face_ind_extru_2) + edge_indices.extend(edge_indices_2) + _st_i += len(hole_verts * 2) + face_ind_extru = [[fc] for fc in face_ind_extru] + # compute the final faces (accounting for top and bottom) + if not face.has_holes: + len_faces = len(face.boundary) + face_ind_bottom = [tuple(reversed(xrange(len_faces)))] + face_ind_top = [tuple( + reversed(xrange(len_faces * 2 - 1, len_faces - 1, -1)))] + else: + face_verts_bottom = [list(reversed(face.boundary))] + list(face.holes) + face_verts_top = [[pt.move(extru_vec) for pt in reversed(loop)] + for loop in face_verts_bottom] + face_ind_bottom = [tuple(verts.index(pt) for pt in loop) + for loop in face_verts_bottom] + face_ind_top = [tuple(verts.index(pt) for pt in loop) + for loop in face_verts_top] + faces_ind = [face_ind_bottom] + face_ind_extru + [face_ind_top] + # create the polyface and assign known properties. + polyface = cls(verts, faces_ind, {'edge_indices': edge_indices, + 'edge_types': [1] * len(edge_indices)}) + polyface._volume = face.area * offset + face_verts = tuple( + tuple(tuple(verts[i] for i in loop) for loop in f) for f in faces_ind) + if not face.has_holes: + polyface._faces = tuple(Face3D(v[0], enforce_right_hand=False) + for v in face_verts) + else: + mid_faces = [Face3D(v[0], enforce_right_hand=False) + for v in face_verts[1:-1]] + bottom_face = face.flip() + top_face = face.move(extru_vec) + polyface._faces = tuple([bottom_face] + mid_faces + [top_face]) + return polyface
+ + @property + def vertices(self): + """Tuple of all vertices in this polyface. + + Note that, in the case of a polyface with holes, some vertices will be repeated + since this property effectively traces out a single boundary around the + whole shape, winding inward to cut out the holes. + """ + return self._vertices + + @property + def faces(self): + """Tuple of all Face3D objects making up this polyface.""" + if self._faces is None: + faces = [] + for face in self._face_indices: + boundary = tuple(self.vertices[i] for i in face[0]) + if len(face) == 1: + faces.append(Face3D(boundary)) + else: + holes = tuple(tuple(self.vertices[i] for i in f) for f in face[1:]) + faces.append(Face3D(boundary=boundary, holes=holes)) + if self._is_solid: + self._faces = Polyface3D.get_outward_faces(faces, 0.01) + else: + self._faces = tuple(faces) + return self._faces + + @property + def edges(self): + """"Tuple of all edges in this polyface as LineSegment3D objects.""" + if self._edges is None: + self._edges = tuple(LineSegment3D.from_end_points( + self.vertices[seg[0]], self.vertices[seg[1]]) + for seg in self._edge_indices) + return self._edges + + @property + def naked_edges(self): + """"Tuple of all naked edges in this polyface as LineSegment3D objects. + + Naked edges belong to only one face in the polyface (they are not + shared between faces). + """ + if self._naked_edges is None: + self._naked_edges = self._get_edge_type(0) + return self._naked_edges + + @property + def internal_edges(self): + """"Tuple of all internal edges in this polyface as LineSegment3D objects. + + Internal edges are shared between two faces in the polyface. + """ + if self._internal_edges is None: + self._internal_edges = self._get_edge_type(1) + return self._internal_edges + + @property + def non_manifold_edges(self): + """"Tuple of all non-manifold edges in this polyface as LineSegment3D objects. + + Non-manifold edges are shared between three or more faces and are therefore + not allowed in solid polyfaces. + """ + if self._non_manifold_edges is None: + if self._edges is None: + self.edges + nm_edges = [] + for i, type in enumerate(self._edge_types): + if type > 1: + nm_edges.append(self._edges[i]) + self._non_manifold_edges = tuple(nm_edges) + return self._non_manifold_edges + + @property + def face_indices(self): + """Tuple of face tuples with integers corresponding to indices of vertices.""" + return self._face_indices + + @property + def edge_indices(self): + """Tuple of edge tuples with integers corresponding to indices of vertices.""" + return self._edge_indices + + @property + def edge_types(self): + """Tuple of integers for each edge that denotes the type of edge. + + 0 denotes a naked edge, 1 denotes an internal edge, and anything higher is a + non-manifold edge. + """ + return self._edge_types + + @property + def edge_information(self): + """Dictionary with keys edge_indices, edge_types and corresponding properties. + """ + return {'edge_indices': self._edge_indices, 'edge_types': self._edge_types} + + @property + def area(self): + """The total surface area of the polyface.""" + if self._area is None: + self._area = sum([face.area for face in self.faces]) + return self._area + + @property + def volume(self): + """The volume enclosed by the polyface. + + Note that, if this polyface is not solid (with all face normals pointing + outward), the value of this property will not be valid. + """ + if self._volume is None: + # formula taken from https://en.wikipedia.org/wiki/Polyhedron#Volume + _v = 0 + for i, face in enumerate(self.faces): + _v += face[0].dot(face.normal) * face.area + self._volume = _v / 3 + return self._volume + + @property + def is_solid(self): + """A boolean to note whether the polyface is solid (True) or is open (False). + + Note that all solid polyface objects will have faces pointing outwards. + """ + return self._is_solid + +
[docs] def merge_overlapping_edges(self, tolerance, angle_tolerance): + """Get this object with overlapping naked edges merged into single internal edges + + This can be used to determine if a polyface is truly solid since this check + is not performed by default when the Polyface3D is created from_faces. + In the default test of edge conditions, overlapping colinear edges are + considered naked when the could be interpreted a single internal edge + such as the case below: + + .. code-block:: shell + + | 1 | + A|______________________|C + | B| | + | | | + | 2 | 3 | + + If Face 1 only has edge AC and not two separate edges for AB and BC, the + creation of the polyface will yield naked edges for AC, AB, and BC, meaning + the shape would not be considered solid when it might actually be so. This + merge_overlapping_edges method overcomes this by replacing the entire set + of 3 naked edges above a single internal edge running from A to C. + + Args: + tolerance: The minimum distance between a vertex and the boundary segments + at which point the vertex is considered colinear. + angle_tolerance: The max angle in radians that vertices are allowed to + differ from one another in order to consider them colinear. + """ + # get naked edges + naked_edges = list(self.naked_edges) + if len(naked_edges) == 0: + return self + + # establish lists that will be iteratively edited + remove_i = [] + add_edges = [] + naked_edge_i = [] + naked_edge_ind = [] + for i, x in enumerate(self.edge_types): + if x == 0: + naked_edge_i.append(i) + naked_edge_ind.append(self._edge_indices[i]) + + while len(naked_edge_i) > 1: + # get all of the edges that are colinear with the first edge + coll_edges = list(naked_edge_ind[0]) + coll_i = [naked_edge_i[0]] + kept_i, maybe_kept_i = [], [] + for edge, ind, nei, i in zip( + naked_edges[1:], naked_edge_ind[1:], naked_edge_i[1:], + xrange(1, len(naked_edges))): + try: + if edge.is_colinear(naked_edges[0], tolerance, angle_tolerance): + coll_edges.extend(ind) + coll_i.append(nei) + maybe_kept_i.append(i) + else: + kept_i.append(i) + except ZeroDivisionError: # duplicate vertices resulted in 0 length edge + coll_edges.extend(ind) + coll_i.append(nei) + maybe_kept_i.append(i) + + # determine if colinear edges create a full double line along the edge + if len(coll_edges) == 1: # definitely a naked edge + overlapping = False + else: # all colinear edges are likely a part of the same loop + final_vi = [] + coll_edges_sort = sorted(coll_edges) + overlapping = True + for i in range(0, len(coll_edges_sort), 2): + final_vi.append(coll_edges_sort[i]) + if coll_edges_sort[i] != coll_edges_sort[i + 1]: + overlapping = False + break + # there's still a chance we have multiple colinear loops + if not overlapping: + dup = {x for x in coll_edges if coll_edges.count(x) > 1} + if coll_edges[0] in dup and coll_edges[1] in dup: + rebuilt_edges = [(coll_edges[i], coll_edges[i + 1]) + for i in range(0, len(coll_edges), 2)] + loop_i = set(rebuilt_edges[0]) + edge_to_check = rebuilt_edges[1:] + more_to_check = True + while more_to_check: + for i, r_seg in enumerate(edge_to_check): + if (r_seg[0] in loop_i or r_seg[1] in loop_i): + loop_i.add(r_seg[0]) + loop_i.add(r_seg[1]) + del edge_to_check[i] + break + else: + more_to_check = False + if all(li in dup for li in loop_i): # we have found a loop! + final_vi = list(loop_i) + new_coll_i = [coll_i[0]] + zip_obj = zip(rebuilt_edges[1:], coll_i[1:], maybe_kept_i) + for ind, nei, i in zip_obj: + if ind[0] in loop_i: + new_coll_i.append(nei) + else: + kept_i.append(i) + kept_i.sort() + coll_i = new_coll_i + overlapping = True + + # if fully overlapping edges have been found, remake them into one + if overlapping: + remove_i.extend(coll_i) # remove overlapping edges from the list + verts = [self.vertices[j] for j in final_vi] + dir_vec = verts[0] - verts[1] + if dir_vec.x != 0: + vert_coor = [v.x for v in verts] + elif dir_vec.y != 0: + vert_coor = [v.y for v in verts] + else: + vert_coor = [v.z for v in verts] + vert_coor, final_vi = zip(*sorted(zip(vert_coor, final_vi))) + add_edges.append((final_vi[0], final_vi[-1])) + + # delete the colinear vertices that have been accounted for + naked_edges = [naked_edges[i] for i in kept_i] + naked_edge_ind = [naked_edge_ind[i] for i in kept_i] + naked_edge_i = [naked_edge_i[i] for i in kept_i] + + # create the new edge information and the new polyface + new_edge_indices = list(self.edge_indices) + new_edge_types = list(self.edge_types) + add_i = [] + for i in range(len(new_edge_indices)): + if i not in remove_i: + add_i.append(i) + new_edge_indices = [new_edge_indices[i] for i in add_i] + new_edge_types = [new_edge_types[i] for i in add_i] + for new_edge in add_edges: + new_edge_indices.append(new_edge) + new_edge_types.append(1) + _new_polyface = Polyface3D( + self._vertices, self._face_indices, + {'edge_indices': new_edge_indices, 'edge_types': new_edge_types} + ) + + # if the result is still not solid, perform a check on the remaining edges + if len(_new_polyface.naked_edges) != 0 and \ + len(_new_polyface.non_manifold_edges) == 0: + # it's possible that the remaining gaps are smaller than the tolerance + small_gaps = True + joined_edges = Polyline3D.join_segments(_new_polyface.naked_edges, tolerance) + for bnd in joined_edges: + if isinstance(bnd, Polyline3D) and bnd.is_closed(tolerance): + test_face = Face3D(bnd.vertices) + if test_face.check_planar(tolerance, False): + min_, max_ = test_face.min, test_face.max + max_dim = max(max_.x - min_.x, max_.y - min_.y, max_.z - min_.z) + tol_area = tolerance * max_dim + if test_face.area > tol_area: + small_gaps = False + break + else: + small_gaps = False + break + else: + small_gaps = False + break + if small_gaps: # the gaps are small enough to make the Polyface closed + new_edge_types = [1 for _ in new_edge_types] + _new_polyface = Polyface3D( + self._vertices, self._face_indices, + {'edge_indices': new_edge_indices, 'edge_types': new_edge_types} + ) + + return _new_polyface
+ +
[docs] def move(self, moving_vec): + """Get a polyface that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the polyface. + """ + _verts = tuple(pt.move(moving_vec) for pt in self.vertices) + _new_pface = Polyface3D(_verts, self.face_indices, self.edge_information) + if self._faces is not None: + _new_pface._faces = tuple(face.move(moving_vec) for face in self._faces) + _new_pface._volume = self._volume + return _new_pface
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a polyface by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + _verts = tuple(pt.rotate(axis, angle, origin) for pt in self.vertices) + _new_pface = Polyface3D(_verts, self.face_indices, self.edge_information) + if self._faces is not None: + _new_pface._faces = tuple(face.rotate(axis, angle, origin) + for face in self._faces) + _new_pface._volume = self._volume + return _new_pface
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a polyface rotated counterclockwise in the world XY plane by an angle. + + Args: + angle: An angle in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + _verts = tuple(pt.rotate_xy(angle, origin) for pt in self.vertices) + _new_pface = Polyface3D(_verts, self.face_indices, self.edge_information) + if self._faces is not None: + _new_pface._faces = tuple(face.rotate_xy(angle, origin) + for face in self._faces) + _new_pface._volume = self._volume + return _new_pface
+ +
[docs] def reflect(self, normal, origin): + """Get a polyface reflected across a plane with the input normal and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the polyface will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + _verts = tuple(pt.reflect(normal, origin) for pt in self.vertices) + _new_pface = Polyface3D(_verts, self.face_indices, self.edge_information) + if self._faces is not None: + _new_pface._faces = tuple(face.reflect(normal, origin) + for face in self._faces) + _new_pface._volume = self._volume + return _new_pface
+ +
[docs] def scale(self, factor, origin=None): + """Scale a polyface by a factor from an origin point. + + Args: + factor: A number representing how much the polyface should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + if origin is None: + _verts = tuple(Point3D(pt.x * factor, pt.y * factor, pt.z * factor) + for pt in self._vertices) + else: + _verts = tuple(pt.scale(factor, origin) for pt in self.vertices) + _new_pface = Polyface3D(_verts, self.face_indices, self.edge_information) + if self._faces is not None: + _new_pface._faces = tuple(face.scale(factor, origin) + for face in self._faces) + _new_pface._volume = self._volume * factor ** 3 \ + if self._volume is not None else None + return _new_pface
+ +
[docs] def is_point_inside(self, point, test_vector=Vector3D(1, 0, 0)): + """Test whether a Point3D lies inside or outside the polyface. + + Note that, if this polyface is not solid, the result will always be False. + + Args: + point: A Point3D for which the inside/outside relationship will be tested. + test_vector: Optional vector to set the direction in which intersections + with the polyface faces will be evaluated to determine if the + point is inside. Default is the X-unit vector. + + Returns: + A boolean denoting whether the point lies inside (True) or outside (False). + """ + if not self.is_solid: + return False + test_ray = Ray3D(point, test_vector) + n_int = 0 + for _f in self.faces: + if _f.intersect_line_ray(test_ray): + n_int += 1 + if n_int % 2 == 0: + return False + return True
+ +
[docs] def does_intersect_line_ray_exist(self, line_ray): + """Boolean for whether an intersection exists between the input Line3D or Ray3D. + + Args: + line_ray: A Line3D or Ray3D object for which intersection will be evaluated. + + Returns: + True if an intersection exists. False if it does not exist. + """ + for face in self.faces: + _int = face.intersect_line_ray(line_ray) + if _int is not None: + return True + return False
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersections between this polyface and the input Line3D or Ray3D. + + Args: + line_ray: A Line3D or Ray3D object for which intersection will be computed. + + Returns: + A list of Point3D for the intersection. Will be an empty list if no + intersection exists. + """ + _inters = [] + for face in self.faces: + _int = face.intersect_line_ray(line_ray) + if _int is not None: + _inters.append(_int) + return _inters
+ +
[docs] def intersect_plane(self, plane): + """Get the intersection between this polyface and the input plane. + + Args: + plane: A Plane object for which intersection will be computed. + + Returns: + List of LineSegment3D objects for the intersection. + Will be an empty list if no intersection exists. + """ + _inters = [] + for face in self.faces: + _int = face.intersect_plane(plane) + if _int is not None: + _inters.extend(_int) + return _inters
+ +
[docs] @staticmethod + def overlapping_bounding_boxes(polyface1, polyface2, tolerance): + """Check if the bounding boxes of two polyfaces overlap within a tolerance. + + This is particularly useful as a check before performing computationally + intense processes between two polyfaces like intersection or checking for + adjacency. Checking the overlap of the bounding boxes is extremely quick + given this method's use of the Separating Axis Theorem. + + Args: + polyface1: The first polyface to check. + polyface2: The second polyface to check. + tolerance: Distance within which two points are considered to be co-located. + """ + # Bounding box check using the Separating Axis Theorem + polyf1_width = polyface1.max.x - polyface1.min.x + polyf2_width = polyface2.max.x - polyface2.min.x + dist_btwn_x = abs(polyface1.center.x - polyface2.center.x) + x_gap_btwn_box = dist_btwn_x - (0.5 * polyf1_width) - (0.5 * polyf2_width) + + polyf1_depth = polyface1.max.y - polyface1.min.y + polyf2_depth = polyface2.max.y - polyface2.min.y + dist_btwn_y = abs(polyface1.center.y - polyface2.center.y) + y_gap_btwn_box = dist_btwn_y - (0.5 * polyf1_depth) - (0.5 * polyf2_depth) + + polyf1_height = polyface1.max.z - polyface1.min.z + polyf2_height = polyface2.max.z - polyface2.min.z + dist_btwn_z = abs(polyface1.center.z - polyface2.center.z) + z_gap_btwn_box = dist_btwn_z - (0.5 * polyf1_height) - (0.5 * polyf2_height) + + if x_gap_btwn_box > tolerance or y_gap_btwn_box > tolerance or \ + z_gap_btwn_box > tolerance: + return False # no overlap + return True # overlap exists
+ +
[docs] @staticmethod + def get_outward_faces(faces, tolerance): + """Turn a list of faces forming a solid into one where they all point outward. + + Note that, if the input faces do not form a closed solid, there may be some + output faces that are not pointing outward. However, if the gaps in the + combined solid are within the input tolerance, this should not be an issue. + + Also, note that this method runs automatically for any solid polyface + (meaning every solid polyface automatically has outward-facing faces). So there + is no need to rerun this method for faces from a solid polyface. + + Args: + faces: A list of Face3D objects that together form a solid. + tolerance: Optional tolerance for the permissable size of gap between + faces at which point the faces are considered to have a single edge. + + Returns: + outward_faces -- A list of the input Face3D objects that all point outwards + (provided the input faces form a solid). + """ + outward_faces = [] + for i, face in enumerate(faces): + # construct a ray with the face normal and a point on the face + point_on_face = face._point_on_face(tolerance) + test_ray = Ray3D(point_on_face, face.normal) + + # if the ray intersects with an even number of other faces, it is correct + n_int = 0 + for _f in faces[i + 1:]: + if _f.intersect_line_ray(test_ray): + n_int += 1 + for _f in faces[:i]: + if _f.intersect_line_ray(test_ray): + n_int += 1 + if n_int % 2 == 0: + outward_faces.append(face) + else: + outward_faces.append(face.flip()) + return outward_faces
+ +
[docs] def to_dict(self, include_edge_information=True): + """Get Polyface3D as a dictionary. + + Args: + include_edge_information: Set to True to include the edge_information + in the dictionary, which will allow for fast initialization when + it is de-serialized. Default True. + """ + base = {'type': 'Polyface3D', + 'vertices': [v.to_array() for v in self.vertices], + 'face_indices': self.face_indices} + if include_edge_information: + base['edge_information'] = self.edge_information + return base
+ + def _get_edge_type(self, edge_type): + """Get all of the edges of a certain type in this polyface.""" + if self._edges is None: + self.edges + sel_edges = [] + for i, type in enumerate(self._edge_types): + if type == edge_type: + sel_edges.append(self._edges[i]) + return tuple(sel_edges) + + @staticmethod + def _verts_faces_edges_from_boundary(cclock_verts, extru_vec, st_i=0): + """Get vertices and face indices for a given Face3D loop (boundary or hole).""" + verts = list(cclock_verts) + [pt.move(extru_vec) for pt in cclock_verts] + len_faces = len(cclock_verts) + faces_ind = [] + for i in xrange(st_i, st_i + len_faces - 1): + faces_ind.append((i, i + 1, i + len_faces + 1, i + len_faces)) + faces_ind.append((st_i + len_faces - 1, st_i, + st_i + len_faces, st_i + len_faces * 2 - 1)) + edge_i1 = [(st_i + i, st_i + i + 1) for i in xrange(len_faces - 1)] + edge_i2 = [(st_i + i, st_i + i + len_faces) for i in xrange(len_faces)] + edge_i3 = [(st_i + len_faces + i, st_i + len_faces + i + 1) + for i in xrange(len_faces - 1)] + edge_indices = edge_i1 + [(st_i + len_faces - 1, st_i)] + edge_i2 + edge_i3 + \ + [(st_i + len_faces * 2 - 1, st_i + len_faces)] + return verts, faces_ind, edge_indices + + def __copy__(self): + _new_poly = Polyface3D(self.vertices, self.face_indices, self.edge_information) + _new_poly._faces = self._faces + return _new_poly + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return tuple(hash(pt) for pt in self._vertices) + \ + tuple(hash(face) for face in self._face_indices) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Polyface3D) and self.__key() == other.__key() + + def __repr__(self): + return 'Polyface3D ({} faces) ({} vertices)'.format( + len(self.faces), len(self))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/polyline.html b/docs/_modules/ladybug_geometry/geometry3d/polyline.html new file mode 100644 index 00000000..dd6015ff --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/polyline.html @@ -0,0 +1,1416 @@ + + + + + + + ladybug_geometry.geometry3d.polyline — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.polyline

+# coding=utf-8
+"""3D Polyline"""
+from __future__ import division
+
+from ..geometry2d.pointvector import Point2D
+from ..geometry2d.polyline import Polyline2D
+
+from ._2d import Base2DIn3D
+from .pointvector import Point3D
+from .line import LineSegment3D
+from .plane import Plane
+from ..intersection3d import intersect_line3d_plane
+from .._polyline import _group_vertices
+
+
+
[docs]class Polyline3D(Base2DIn3D): + """3D polyline object. + + Args: + vertices: A list of Point3D objects representing the vertices of the polyline. + interpolated: Boolean to note whether the polyline should be interpolated + between the input vertices when it is translated to other interfaces. + Note that this property has no bearing on the geometric calculations + performed by this library and is only present in order to assist with + display/translation. + + Properties: + * vertices + * segments + * min + * max + * center + * p1 + * p2 + * length + * interpolated + """ + __slots__ = ('_interpolated', '_segments', '_length') + + def __init__(self, vertices, interpolated=False): + """Initialize Polyline3D.""" + Base2DIn3D.__init__(self, vertices) + self._interpolated = interpolated + self._segments = None + self._length = None + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Polyline3D from a dictionary. + + Args: + data: A python dictionary in the following format. + + .. code-block:: python + + { + "type": "Polyline3D", + "vertices": [(0, 0, 0), (10, 0, 2), (0, 10, 4)] + } + """ + interp = data['interpolated'] if 'interpolated' in data else False + return cls(tuple(Point3D.from_array(pt) for pt in data['vertices']), interp)
+ +
[docs] @classmethod + def from_array(cls, point_array): + """Create a Polyline3D from a nested array of vertex coordinates. + + Args: + point_array: nested array of point arrays. + """ + return Polyline3D(Point3D(*point) for point in point_array)
+ +
[docs] @classmethod + def from_polyline2d(cls, polyline2d, plane=None): + """Create a closed Polyline3D from a Polyline2D and a plane. + + Args: + polyline2d: A Polyline2D object to be converted to a Polyline3D. + plane: A Plane in which the Polyline2D sits. If None, the WorldXY + plane will be used. + """ + plane = Plane() if plane is None else plane + return Polyline3D((plane.xy_to_xyz(pt) for pt in polyline2d.vertices), + polyline2d.interpolated)
+ + @property + def segments(self): + """Tuple of all line segments in the polyline.""" + if self._segments is None: + self._segments = \ + tuple(LineSegment3D.from_end_points(vert, self._vertices[i + 1]) + for i, vert in enumerate(self._vertices[:-1])) + return self._segments + + @property + def p1(self): + """Starting point of the Polyline3D.""" + return self._vertices[0] + + @property + def p2(self): + """End point of the Polyline3D.""" + return self._vertices[-1] + + @property + def length(self): + """The length of the polyline.""" + if self._length is None: + self._length = sum([seg.length for seg in self.segments]) + return self._length + + @property + def interpolated(self): + """Boolean noting whether the polyline should be interpolated upon translation. + + Note that this property has no bearing on the geometric calculations + performed by this library and is only present in order to assist with + display/translation. + """ + return self._interpolated + +
[docs] def is_closed(self, tolerance): + """Test whether this polyline is closed to within the tolerance. + + Args: + tolerance: The minimum difference between vertices below which vertices + are considered the same. + """ + return self._vertices[0].is_equivalent(self._vertices[-1], tolerance)
+ +
[docs] def remove_colinear_vertices(self, tolerance): + """Get a version of this polyline without colinear or duplicate vertices. + + Args: + tolerance: The minimum distance that a vertex can be from a line + before it is considered colinear. + """ + if len(self.vertices) == 3: + return self # Polyline3D cannot have fewer than 3 vertices + new_vertices = [self.vertices[0]] # first vertex is always ok + for i, _v in enumerate(self.vertices[1:-1]): + if (self[i] - _v).cross(self[i + 2] - _v).magnitude >= tolerance: + new_vertices.append(_v) + new_vertices.append(self[-1]) # last vertex is always ok + _new_poly = Polyline3D(new_vertices) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def reverse(self): + """Get a copy of this polyline where the vertices are reversed.""" + _new_poly = Polyline3D(tuple(pt for pt in reversed(self.vertices))) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def move(self, moving_vec): + """Get a polyline that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the polyline. + """ + _new_poly = Polyline3D(tuple(pt.move(moving_vec) for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a polyline by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the point will be rotated. + """ + _new_poly = Polyline3D(tuple(pt.rotate(axis, angle, origin) + for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a polyline rotated counterclockwise in the XY plane by a certain angle. + + Args: + angle: An angle in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + _new_p = Polyline3D(tuple(pt.rotate_xy(angle, origin) for pt in self.vertices)) + self._transfer_properties(_new_p) + return _new_p
+ +
[docs] def reflect(self, normal, origin): + """Get a polyline reflected across a plane with the input normal and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the polyline will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + _new_poly = Polyline3D(tuple(pt.reflect(normal, origin) for pt in self.vertices)) + self._transfer_properties(_new_poly) + return _new_poly
+ +
[docs] def scale(self, factor, origin=None): + """Scale a polyline by a factor from an origin point. + + Args: + factor: A number representing how much the polyline should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + if origin is None: + _new_poly = Polyline3D(tuple( + Point3D(pt.x * factor, pt.y * factor, pt.z * factor) + for pt in self.vertices)) + else: + _new_poly = Polyline3D(tuple( + pt.scale(factor, origin) for pt in self.vertices)) + _new_poly._interpolated = self._interpolated + return _new_poly
+ +
[docs] def intersect_plane(self, plane): + """Get the intersections between this polyline and a Plane. + + Args: + plane: A Plane that will be intersected with this object. + + Returns: + A list with Point3D objects for the intersections. + List will be empty if no intersection exists. + """ + intersections = [] + for _s in self.segments: + inters = intersect_line3d_plane(_s, plane) + if inters is not None: + intersections.append(inters) + return intersections
+ +
[docs] def split_with_plane(self, plane): + """Split this Polyline3D into Polyline3Ds and LineSegment3Ds using a Plane. + + Args: + plane: A Plane that will be used to split this polyline. + + Returns: + A list of Polyline3D and LineSegment3D objects if the split was successful. + Will be a list with 1 Polyline3D if no intersection exists. + """ + # group the vertices based on when they cross the plane + grouped_verts = [[self._vertices[0]]] + for _s in self.segments: + inters = intersect_line3d_plane(_s, plane) + if inters is None: + grouped_verts[-1].append(_s.p2) + else: # intersection; start a new group + grouped_verts[-1].append(inters) + grouped_verts.append([inters, _s.p2]) + + # make new Polyline3D and LineSegment3D objects based on the groups + return self._grouped_verts_to_objs(grouped_verts, self._interpolated)
+ +
[docs] def to_dict(self): + """Get Polyline3D as a dictionary.""" + base = {'type': 'Polyline3D', + 'vertices': [pt.to_array() for pt in self.vertices]} + if self.interpolated: + base['interpolated'] = self.interpolated + return base
+ +
[docs] def to_array(self): + """Get a list of lists where each sub-list represents a Point3D vertex.""" + return tuple(pt.to_array() for pt in self.vertices)
+ +
[docs] def to_polyline2d(self): + """Get a Polyline2D in the XY plane derived from this 3D polyline.""" + return Polyline2D( + (Point2D(pt.x, pt.y) for pt in self.vertices), self.interpolated)
+ +
[docs] @staticmethod + def join_segments(segments, tolerance): + """Get an array of Polyline3Ds from a list of LineSegment3Ds. + + Args: + segments: An array of LineSegment3D objects. + tolerance: The minimum difference in X, Y, and Z values at which Point2Ds + are considered equivalent. Segments with points that match within the + tolerance will be joined. + + Returns: + An array of Polyline3D and LineSegment3D objects assembled from the + joined segments. + """ + # group the vertices that make up polylines + grouped_verts = _group_vertices(segments, tolerance) + # create the Polyline3D and LineSegment3D objects + return Polyline3D._grouped_verts_to_objs(grouped_verts)
+ + def _transfer_properties(self, new_polyline): + """Transfer properties from this polyline to a new polyline.""" + new_polyline._interpolated = self._interpolated + new_polyline._length = self._length + + @staticmethod + def _grouped_verts_to_objs(grouped_verts, interpolated=False): + joined_lines = [] + for v_list in grouped_verts: + if len(v_list) == 2: + joined_lines.append(LineSegment3D.from_end_points(v_list[0], v_list[1])) + else: + joined_lines.append(Polyline3D(v_list, interpolated)) + return joined_lines + + def __copy__(self): + return Polyline3D(self._vertices, self._interpolated) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return tuple(hash(pt) for pt in self._vertices) + (self._interpolated,) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Polyline3D) and self.__key() == other.__key() + + def __repr__(self): + return 'Polyline3D ({} vertices)'.format(len(self))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/ray.html b/docs/_modules/ladybug_geometry/geometry3d/ray.html new file mode 100644 index 00000000..6ef5c868 --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/ray.html @@ -0,0 +1,1228 @@ + + + + + + + ladybug_geometry.geometry3d.ray — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.ray

+# coding=utf-8
+"""3D Ray"""
+from __future__ import division
+
+from .pointvector import Point3D, Vector3D
+from ._1d import Base1DIn3D
+
+
+
[docs]class Ray3D(Base1DIn3D): + """3D Ray object. + + Args: + p: A Point3D representing the base of the ray. + v: A Vector3D representing the direction of the ray. + + Properties: + * p + * v + * min + * max + """ + __slots__ = () + + def __init__(self, p, v): + """Initialize Ray3D.""" + Base1DIn3D.__init__(self, p, v) + +
[docs] @classmethod + def from_array(cls, ray_array): + """ Create a Ray3D from a nested array with a point and a vector. + + Args: + ray_array: Nested tuples ((p.x, p.y), (v.x, v.y)). + """ + return Ray3D(Point3D(*ray_array[0]), Vector3D(*ray_array[1]))
+ +
[docs] @classmethod + def from_ray2d(cls, ray2d, z=0): + """Initialize a new Ray3D from an Ray2D and a z value. + + Args: + line2d: A Ray2D to be used to generate the Ray3D. + z: A number for the Z coordinate value of the line. + """ + base_p = Point3D(ray2d.p.x, ray2d.p.y, z) + base_v = Vector3D(ray2d.v.x, ray2d.v.y, 0) + return cls(base_p, base_v)
+ +
[docs] def reverse(self): + """Get a copy of this ray that is reversed.""" + return Ray3D(self.p, self.v.reverse())
+ +
[docs] def move(self, moving_vec): + """Get a ray that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the ray. + """ + return Ray3D(self.p.move(moving_vec), self.v)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate a ray by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + return Ray3D(self.p.rotate(axis, angle, origin), self.v.rotate(axis, angle))
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a ray rotated counterclockwise in the XY plane by a certain angle. + + Args: + angle: An angle in radians. + origin: A Point3D for the origin around which the object will be rotated. + """ + return Ray3D(self.p.rotate_xy(angle, origin), self.v.rotate_xy(angle))
+ +
[docs] def reflect(self, normal, origin): + """Get a ray reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the ray will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + return Ray3D(self.p.reflect(normal, origin), self.v.reflect(normal))
+ +
[docs] def scale(self, factor, origin=None): + """Scale a ray by a factor from an origin point. + + Args: + factor: A number representing how much the ray should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + return Ray3D(self.p.scale(factor, origin), self.v * factor)
+ +
[docs] def scale_world_origin(self, factor): + """Scale a ray by a factor from the world origin. Faster than Ray2D.scale. + + Args: + factor: A number representing how much the ray should be scaled. + """ + return Ray3D(self.p.scale_world_origin(factor), self.v * factor)
+ +
[docs] def to_dict(self): + """Get Ray3D as a dictionary.""" + base = Base1DIn3D.to_dict(self) + base['type'] = 'Ray3D' + return base
+ +
[docs] def to_array(self): + """A nested array representing the start point and vector.""" + return (self.p.to_array(), self.v.to_array())
+ + def _u_in(self, u): + return u >= 0.0 + + def __copy__(self): + return Ray3D(self.p, self.v) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (hash(self.p), hash(self.v)) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Ray3D) and self.__key() == other.__key() + + def __repr__(self): + return 'Ray3D (point <%.2f, %.2f, %.2f>) (vector <%.2f, %.2f, %.2f>)' % \ + (self.p.x, self.p.y, self.p.z, self.v.x, self.v.y, self.v.z)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/geometry3d/sphere.html b/docs/_modules/ladybug_geometry/geometry3d/sphere.html new file mode 100644 index 00000000..a124e98d --- /dev/null +++ b/docs/_modules/ladybug_geometry/geometry3d/sphere.html @@ -0,0 +1,1302 @@ + + + + + + + ladybug_geometry.geometry3d.sphere — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.geometry3d.sphere

+# coding=utf-8
+"""Sphere"""
+from __future__ import division
+
+from .pointvector import Point3D
+from .plane import Plane
+from .arc import Arc3D
+from .line import LineSegment3D
+from ..intersection3d import intersect_line3d_sphere, intersect_plane_sphere
+
+
+import math
+
+
+
[docs]class Sphere(object): + """Sphere object. + + Args: + center: A Point3D representing the center of the arc. + radius: A number representing the radius of the sphere. + + Properties: + * center + * radius + * min + * max + * diameter + * circumference + * area + * volume + """ + __slots__ = ('_center', '_radius') + + def __init__(self, center, radius): + """Initialize Sphere.""" + assert isinstance(center, Point3D), \ + "Expected Point3D. Got {}.".format(type(center)) + assert radius > 0, 'Sphere radius must be greater than 0. Got {}.'.format(radius) + self._center = center + self._radius = radius + +
[docs] @classmethod + def from_dict(cls, data): + """Create a Sphere from a dictionary. + + Args: + data: A python dictionary in the following format + + .. code-block:: python + + { + "type": "Sphere" + "center": (10, 0, 0), + "radius": 5 + } + """ + return cls(Point3D.from_array(data['center']), data['radius'])
+ + @property + def center(self): + """Center of sphere.""" + return self._center + + @property + def radius(self): + """Radius of sphere.""" + return self._radius + + @property + def min(self): + """A Point3D for the minimum bounding box vertex around this geometry.""" + return Point3D(self.center.x - self.radius, self.center.y - self.radius, + self.center.z - self.radius) + + @property + def max(self): + """A Point3D for the maximum bounding box vertex around this geometry.""" + return Point3D(self.center.x + self.radius, self.center.y + self.radius, + self.center.z + self.radius) + + @property + def diameter(self): + """Diameter of sphere""" + return self.radius * 2 + + @property + def circumference(self): + """Circumference of sphere""" + return 2 * math.pi * self.radius + + @property + def area(self): + """Surface area of sphere""" + return 4 * math.pi * self.radius ** 2 + + @property + def volume(self): + """Volume of sphere""" + return 4 / 3 * math.pi * self.radius ** 3 + +
[docs] def move(self, moving_vec): + """Get a sphere that has been moved along a vector. + + Args: + moving_vec: A Vector3D with the direction and distance to move the sphere. + """ + return Sphere(self.center.move(moving_vec), self.radius)
+ +
[docs] def rotate(self, axis, angle, origin): + """Rotate this sphere by a certain angle around an axis and origin. + + Right hand rule applies: + If axis has a positive orientation, rotation will be clockwise. + If axis has a negative orientation, rotation will be counterclockwise. + + Args: + axis: A Vector3D axis representing the axis of rotation. + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the sphere will be rotated. + """ + return Sphere(self.center.rotate(axis, angle, origin), self.radius)
+ +
[docs] def rotate_xy(self, angle, origin): + """Get a sphere that is rotated counterclockwise in the world XY plane by an angle. + + Args: + angle: An angle for rotation in radians. + origin: A Point3D for the origin around which the sphere will be rotated. + """ + return Sphere(self.center.rotate_xy(angle, origin), self.radius)
+ +
[docs] def reflect(self, normal, origin): + """Get a sphere reflected across a plane with the input normal vector and origin. + + Args: + normal: A Vector3D representing the normal vector for the plane across + which the arc will be reflected. THIS VECTOR MUST BE NORMALIZED. + origin: A Point3D representing the origin from which to reflect. + """ + return Sphere(self.center.reflect(normal, origin), self.radius)
+ +
[docs] def scale(self, factor, origin=None): + """Scale a sphere by a factor from an origin point. + + Args: + factor: A number representing how much the sphere should be scaled. + origin: A Point3D representing the origin from which to scale. + If None, it will be scaled from the World origin (0, 0, 0). + """ + return Sphere(self.center.scale(factor, origin), self.radius * factor)
+ +
[docs] def intersect_plane(self, plane): + """Get the intersection of a plane with this Sphere object + + Args: + plane: A Plane object. + + Returns: + Arc3D representing a full circle if it exists. + None if no full intersection exists. + """ + ip = intersect_plane_sphere(plane, self) # ip = [center pt, vector, radius] + return None if ip is None or isinstance(ip, Point3D) else \ + Arc3D(Plane(ip[1], ip[0]), ip[2])
+ +
[docs] def intersect_line_ray(self, line_ray): + """Get the intersection between this Sphere object and a Ray2D/LineSegment2D. + + Args: + line_ray: A LineSegment3D or Ray3D that will be extended infinitely + for intersection. + + Returns: + A LineSegment3D object if a full intersection exists. + A Point if a tangent intersection exists. + None if no full intersection exists. + """ + il = intersect_line3d_sphere(line_ray, self) + return None if il is None else \ + il if isinstance(il, Point3D) else \ + LineSegment3D.from_end_points(il[0], il[1])
+ +
[docs] def duplicate(self): + """Get a copy of this object.""" + return self.__copy__()
+ +
[docs] def to_dict(self): + """Get Sphere as a dictionary.""" + return {'type': 'Sphere', + 'center': self.center.to_array(), + 'radius': self.radius}
+ + def __copy__(self): + return Sphere(self._center, self._radius) + + def __key(self): + """A tuple based on the object properties, useful for hashing.""" + return (self._center, self._radius) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + return isinstance(other, Sphere) and self.__key() == other.__key() + + def __ne__(self, other): + return not self.__eq__(other) + +
[docs] def ToString(self): + """Overwrite .NET ToString.""" + return self.__repr__()
+ + def __repr__(self): + return 'Sphere (center {}) (radius {}))'.format(self.center, self.radius)
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/interop/obj.html b/docs/_modules/ladybug_geometry/interop/obj.html new file mode 100644 index 00000000..6d40efd1 --- /dev/null +++ b/docs/_modules/ladybug_geometry/interop/obj.html @@ -0,0 +1,1609 @@ + + + + + + + ladybug_geometry.interop.obj — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.interop.obj

+# coding=utf-8
+"""A class that supports the import and export of OBJ data to/from ladybug_geometry.
+"""
+import os
+
+try:
+    from itertools import izip as zip  # python 2
+    writemode = 'wb'
+except ImportError:
+    writemode = 'w'  # python 3
+
+from ladybug_geometry.geometry2d.pointvector import Point2D
+from ladybug_geometry.geometry3d.pointvector import Vector3D, Point3D
+
+
+
[docs]class OBJ(object): + """A class that supports the import and export of OBJ data to/from ladybug_geometry. + + Note that ladybug_geometry Mesh3D can be easily created from this OBJ by + taking the vertices and normals. + + Args: + vertices: A list or tuple of Point3D objects for vertices. + faces: A list of tuples with each tuple having either 3 or 4 integers. + These integers correspond to indices within the list of vertices. + vertex_texture_map: An optional list or tuple of Point2D that align with the + vertices input. All coordinate values of the Point2D should be between + 0 and 1 and are intended to map to the XY system of images to be mapped + onto the OBJ mesh. If None, the OBJ file is written without + textures. (Default: None). + vertex_normals: An optional list or tuple of Vector3D that align with the + vertices input and describe the normal vector to be used at each vertex. + If None, the OBJ file is written without normals. (Default: None). + vertex_colors: An optional list of colors that align with the vertices input. + Note that these are written into the OBJ alongside the vertex + coordinates separately from the texture map. Not all programs support + importing OBJs with this color information but Rhino does. (Default: None). + material_structure: A list of tuples where each tuple contains two elements. + The first is the identifier of a material that is used in the OBJ and + the second is the index of the face where the application of the new + material begins. If None, everything will be assumed to have the + same diffuse material. (Default: None). + + Properties: + * vertices + * faces + * vertex_texture_map + * vertex_normals + * vertex_colors + * material_structure + """ + + __slots__ = ( + '_vertices', '_faces', '_vertex_texture_map', '_vertex_normals', + '_vertex_colors', '_material_structure' + ) + + def __init__( + self, vertices, faces, vertex_texture_map=None, vertex_normals=None, + vertex_colors=None, material_structure=None + ): + self._vertices = self._check_vertices_input(vertices) + self._faces = self._check_faces_input(faces) + self.vertex_texture_map = vertex_texture_map + self.vertex_normals = vertex_normals + self.vertex_colors = vertex_colors + self.material_structure = material_structure + +
[docs] @classmethod + def from_file(cls, file_path): + """Create an OBJ object from a .obj file. + + Args: + file_path: Path to an OBJ file as a text string. Note that, if the file + includes texture mapping coordinates or vertex normals, the number + of texture coordinates and normals must align with the number of + vertices to be importable. Nearly all OBJ files follow this standard. + If any of the OBJ mesh faces contain more than 4 vertices, only + the first 4 vertices will be counted. + """ + vertices, faces, vertex_texture_map, vertex_normals, vertex_colors = \ + [], [], [], [], [] + mat_struct = [] + with open(file_path, 'r') as fp: + for line in fp: + if line.startswith('#'): + continue + wds = line.split() + if len(wds) > 0: + first_word = wds[0] + if first_word == 'v': # start of a new vertex + vert = Point3D(float(wds[1]), float(wds[2]), float(wds[3])) + vertices.append(vert) + if len(wds) > 4: + vertex_colors.append(tuple(wds[4:])) + elif first_word == 'f': # start of a new face + face = [] + for fv in wds[1:]: + face.append(int(fv.split('/')[0]) - 1) + if len(face) > 4: # truncate for compatibility with Mesh3D + face = face[:4] + faces.append(tuple(face)) + elif first_word == 'vn': # start of a new vertex normal + norm = Vector3D(float(wds[1]), float(wds[2]), float(wds[3])) + vertex_normals.append(norm) + elif first_word == 'vt': # start of a new texture coordinate + texture = Point2D(float(wds[1]), float(wds[2])) + vertex_texture_map.append(texture) + elif first_word == 'usemtl': # start of a new material application + mat_struct.append((wds[1], len(faces))) + return cls(vertices, faces, vertex_texture_map, vertex_normals, + vertex_colors, mat_struct)
+ +
[docs] @classmethod + def from_mesh3d(cls, mesh, include_colors=True, include_normals=False): + """Create an OBJ object from a ladybug_geometry Mesh3D. + + If colors are specified on the Mesh3D, they will be correctly transferred + to the resulting OBJ object as long as include_colors is True. + + Args: + mesh: A ladybug_geometry Mesh3D object to be converted to an OBJ object. + include_colors: Boolean to note whether the Mesh3D colors should be + transferred to the OBJ object. (Default: True). + include_normals: Boolean to note whether the vertex normals should be + included in the resulting OBJ object. (Default: False). + """ + if include_colors and mesh.is_color_by_face: + # we need to duplicate vertices to preserve colors + vertices, faces, colors = [], [], [] + v_ct = 0 + for face_verts, col in zip(mesh.face_vertices, mesh.colors): + vertices.extend(face_verts) + if len(face_verts) == 4: + faces.append((v_ct, v_ct + 1, v_ct + 2, v_ct + 3)) + colors.extend([col] * 4) + v_ct += 4 + else: + faces.append((v_ct, v_ct + 1, v_ct + 2)) + colors.extend([col] * 3) + v_ct += 3 + if include_normals: + msh_norms = mesh.vertex_normals + vert_normals = [] + for face in mesh.faces: + for fi in face: + vert_normals.append(msh_norms[fi]) + return cls(vertices, faces, vertex_normals=msh_norms, + vertex_colors=colors) + return cls(vertices, faces, vertex_colors=colors) + vertex_colors = mesh.colors if include_colors else None + if include_normals: + return cls(mesh.vertices, mesh.faces, vertex_normals=mesh.vertex_normals, + vertex_colors=vertex_colors) + return cls(mesh.vertices, mesh.faces, vertex_colors=vertex_colors)
+ +
[docs] @classmethod + def from_mesh3ds(cls, meshes, material_ids=None, include_normals=False): + """Create an OBJ object from a list of ladybug_geometry Mesh3D. + + Mesh3D colors are ignored when using this method with the assumption that + materials are used to specify how the meshes should be rendered. + + Args: + meshes: A list of ladybug_geometry Mesh3D objects to be converted + into an OBJ object. + material_ids: An optional list of strings that aligns with the input + meshes and denote materials assigned to each mesh. This list of + material IDs will be automatically converted into an efficient + material_structure for the OBJ object where materials used for + multiple meshes only include one reference to the material. If + None, the OBJ will have no material structure. (Default: None). + include_normals: Boolean to note whether the vertex normals should be + included in the resulting OBJ object. (Default: False). + """ + # sort the meshes by material ID to ensure efficient material structure + if material_ids is not None: + assert len(material_ids) == len(meshes), 'Length of OBJ material_ids ({}) ' \ + 'does not match the length of meshes ({}).'.format( + len(material_ids), len(meshes)) + meshes = [x for _, x in sorted(zip(material_ids, meshes))] + material_ids = sorted(material_ids) + + # gather all vertices, faces, and (optionally) normals together + vertices, faces, normals, mat_struct = [], [], [], [] + v_count = 0 + if material_ids is not None: + last_mat = None + for mesh, mat_id in zip(meshes, material_ids): + if mat_id != last_mat: + mat_struct.append((mat_id, len(faces))) + last_mat = mat_id + vertices.extend(mesh.vertices) + if include_normals: + normals.extend(mesh.vertex_normals) + if v_count == 0: + faces.extend(mesh.faces) + else: + for f in mesh.faces: + faces.append(tuple(fi + v_count for fi in f)) + v_count += len(mesh.vertices) + else: + for mesh in meshes: + vertices.extend(mesh.vertices) + if include_normals: + normals.extend(mesh.vertex_normals) + if v_count == 0: + faces.extend(mesh.faces) + else: + for f in mesh.faces: + faces.append(tuple(fi + v_count for fi in f)) + v_count += len(mesh.vertices) + + return cls( + vertices, faces, vertex_normals=normals, material_structure=mat_struct)
+ + @property + def vertices(self): + """Tuple of Point3D for all vertices in the OBJ.""" + return self._vertices + + @property + def faces(self): + """Tuple of tuples for all faces in the OBJ.""" + return self._faces + + @property + def vertex_texture_map(self): + """Get or set a tuple of Point2D for texture image coordinates for each vertex. + + Will be None if no texture map is assigned. + """ + return self._vertex_texture_map + + @vertex_texture_map.setter + def vertex_texture_map(self, value): + if value is not None: + assert isinstance(value, (list, tuple)), 'vertex_texture_map should be ' \ + 'a list or tuple. Got {}'.format(type(value)) + if isinstance(value, list): + value = tuple(value) + if len(value) == 0: + value = None + elif len(value) != len(self.vertices): + raise ValueError( + 'Number of items in vertex_texture_map ({}) does not match number' + 'of OBJ vertices ({}).'.format(len(value), len(self.vertices))) + else: + for vert in value: + assert isinstance(vert, Point2D), 'Expected Point2D for OBJ ' \ + 'vertex texture. Got {}.'.format(type(vert)) + self._vertex_texture_map = value + + @property + def vertex_normals(self): + """Get or set a tuple of Vector3D for vertex normals. + + Will be None if no vertex normals are assigned. + """ + return self._vertex_normals + + @vertex_normals.setter + def vertex_normals(self, value): + if value is not None: + assert isinstance(value, (list, tuple)), \ + 'vertex_normals should be a list or tuple. Got {}'.format(type(value)) + if isinstance(value, list): + value = tuple(value) + if len(value) == 0: + value = None + elif len(value) != len(self.vertices): + raise ValueError( + 'Number of OBJ vertex_normals ({}) does not match the number of' + ' OBJ vertices ({}).'.format(len(value), len(self.vertices))) + else: + for norm in value: + assert isinstance(norm, Vector3D), 'Expected Vector3D for OBJ ' \ + 'vertex normal. Got {}.'.format(type(norm)) + self._vertex_normals = value + + @property + def vertex_colors(self): + """Get or set a list of colors for the OBJ. Will be None if no colors assigned. + """ + return self._vertex_colors + + @vertex_colors.setter + def vertex_colors(self, value): + if value is not None: + assert isinstance(value, (list, tuple)), \ + 'vertex_normals should be a list or tuple. Got {}'.format(type(value)) + if isinstance(value, list): + value = tuple(value) + if len(value) == 0: + value = None + elif len(value) != len(self.vertices): + raise ValueError( + 'Number of OBJ vertex_normals ({}) does not match the number of' + ' OBJ vertices ({}).'.format(len(value), len(self.vertices))) + self._vertex_colors = value + + @property + def material_structure(self): + """Get or set a tuple of tuples that specify the material structure of the obj. + + Each sub-tuple contains two elements. The first is the identifier of a + material that is used in the OBJ and the second is the index of the face + where the application of the new material begins. If None, everything + will be assumed to have the same diffuse material. + """ + return self._material_structure + + @material_structure.setter + def material_structure(self, value): + if value is not None: + assert isinstance(value, (list, tuple)), \ + 'vertex_normals should be a list or tuple. Got {}'.format(type(value)) + if len(value) == 0: + value = None + else: + for mt in value: + assert isinstance(mt, tuple), 'Expected tuple for OBJ material ' \ + 'structure. Got {}.'.format(type(mt)) + assert len(mt) == 2, 'OBJ material structure must have 2 items. ' \ + 'Got {}.'.format(len(mt)) + assert isinstance(mt[0], str), 'Expected String for OBJ material ' \ + 'identifier. Got {}.'.format(type(mt[0])) + try: + self._faces[mt[1]] + except IndexError: + raise IndexError( + 'OBJ material index {} does not correspond to any face. ' + 'There are {} faces in the mesh.'.format( + mt[1], len(self._faces))) + except TypeError: + raise TypeError( + 'OBJ material must use integers to reference faces. ' + 'Got {}.'.format(type(mt[1]))) + value = sorted(value, key=lambda x: x[1]) + value = tuple(value) + self._material_structure = value + +
[docs] def to_file(self, folder, name, triangulate_quads=False, include_mtl=False): + """Write the OBJ object to an ASCII text file. + + Args: + folder: A text string for the directory where the OBJ will be written. + name: A text string for the name of the OBJ file. Note that, if an image + texture is meant to be assigned to this OBJ, the image should have + the same name as the one input here except with the .mtl extension + instead of the .obj extension. + triangulate_quads: Boolean to note whether quad faces should be + triangulated upon export to OBJ. This may be needed for certain + software platforms that require the mesh to be composed entirely + of triangles (eg. Radiance). (Default: False). + include_mtl: Boolean to note whether an .mtl file should be automatically + generated from the material structure written next to the .obj + file in the output folder. All materials in the mtl file will + be diffuse white, with the assumption that these will be + customized later. (Default: False). + """ + # set up a name and folder + file_name = name if name.lower().endswith('.obj') else '{}.obj'.format(name) + obj_file = os.path.join(folder, file_name) + mtl_file = '{}.mtl'.format(name) if not name.lower().endswith('.obj') else \ + '{}.mtl'.format(name[:-4]) + + # write everything into the OBJ file + with open(obj_file, writemode) as outfile: + # add a comment at the top to note where the OBJ is written from + outfile.write('# OBJ file written by ladybug geometry\n\n') + + # add material file name if include_mtl is true + if self._material_structure is not None or include_mtl: + if include_mtl: + outfile.write('mtllib ' + mtl_file + '\n') + if self._material_structure is None: + outfile.write('usemtl diffuse_0\n') + + # loop through the vertices and add them to the file + if self.vertex_colors is None: + for v in self.vertices: + outfile.write('v {} {} {}\n'.format(v.x, v.y, v.z)) + else: # write the vertex colors alongside the vertices + if len(self.vertex_colors[0]) > 3: + for v, c in zip(self.vertices, self.vertex_colors): + outfile.write( + 'v {} {} {} {} {} {}\n'.format( + v.x, v.y, v.z, c[0], c[1], c[2]) + ) + else: # might be a grayscale weight + for v, c in zip(self.vertices, self.vertex_colors): + outfile.write( + 'v {} {} {} {}\n'.format(v.x, v.y, v.z, ' '.join(c)) + ) + + # loop through the texture vertices, if present, and add them to the file + if self.vertex_texture_map is not None: + for vt in self.vertex_texture_map: + outfile.write('vt {} {}\n'.format(vt.x, vt.y)) + + # loop through the normals, if present, and add them to the file + if self.vertex_normals is not None: + for vn in self.vertex_normals: + outfile.write('vn {} {} {}\n'.format(vn.x, vn.y, vn.z)) + + # triangulate the faces if requested + formatted_faces, formatted_mats = self.faces, self.material_structure + if triangulate_quads: + formatted_faces = [] + if formatted_mats is None or len(formatted_mats) == 1: + for f in self.faces: + if len(f) > 3: + formatted_faces.append((f[0], f[1], f[2])) + formatted_faces.append((f[2], f[3], f[0])) + else: + formatted_faces.append(f) + else: + mat_ind = [mat[1] for mat in formatted_mats] + for i, f in enumerate(self.faces): + if len(f) > 3: + formatted_faces.append((f[0], f[1], f[2])) + formatted_faces.append((f[2], f[3], f[0])) + for j, m in enumerate(formatted_mats): + if m[1] > i: + mat_ind[j] = mat_ind[j] + 1 + else: + formatted_faces.append(f) + formatted_mats = \ + [(mn[0], mi) for mn, mi in zip(formatted_mats, mat_ind)] + + # loop through the faces and get all lines of text for them + face_txt = [] + if self.vertex_texture_map is None and self.vertex_normals is None: + for f in formatted_faces: + face_txt.append('f ' + ' '.join(str(fi + 1) for fi in f) + '\n') + else: + if self.vertex_texture_map is not None and \ + self.vertex_normals is not None: + f_map = '{0}/{0}/{0}' + elif self.vertex_texture_map is None and \ + self.vertex_normals is not None: + f_map = '{0}//{0}' + else: + f_map = '{0}/{0}' + for f in formatted_faces: + face_txt.append( + 'f ' + ' '.join(f_map.format(fi + 1) for fi in f) + '\n' + ) + + # write the faces into the file with the material structure + if formatted_mats is not None: # insert the materials + for mat in reversed(formatted_mats): + face_txt.insert(mat[1], 'usemtl {}\n'.format(mat[0])) + for f_lin in face_txt: + outfile.write(f_lin) + + # write the MTL file if requested + if include_mtl: + mat_struct = [('diffuse_0', 0)] if self._material_structure is None else \ + self._material_structure + mtl_fp = os.path.join(folder, mtl_file) + with open(mtl_fp, writemode) as mtl_f: + mtl_f.write('# Ladybug Geometry\n') + for mat in reversed(mat_struct): + mtl_str = \ + 'newmtl {}\n' \ + 'Ka 0.0000 0.0000 0.0000\n' \ + 'Kd 1.0000 1.0000 1.0000\n' \ + 'Ks 0.0000 0.0000 0.0000\n' \ + 'Tf 0.0000 0.0000 0.0000\n' \ + 'd 1.0000\n' \ + 'Ns 0.0000\n'.format(mat[0]) + mtl_f.write(mtl_str) + + return obj_file
+ + def _check_vertices_input(self, vertices): + """Check the input vertices.""" + if not isinstance(vertices, tuple): + vertices = tuple(vertices) + for vert in vertices: + assert isinstance(vert, Point3D), \ + 'Expected Point3D for OBJ vertex. Got {}.'.format(type(vert)) + return vertices + + def _check_faces_input(self, faces): + """Check input faces for correct formatting.""" + if not isinstance(faces, tuple): + faces = tuple(faces) + assert len(faces) > 0, 'OBJ mesh must have at least one face.' + for f in faces: + assert isinstance(f, tuple), \ + 'Expected tuple for Mesh face. Got {}.'.format(type(f)) + assert len(f) >= 3, \ + 'OBJ mesh face must have 3 or more vertices. Got {}.'.format(len(f)) + for ind in f: + try: + self._vertices[ind] + except IndexError: + raise IndexError( + 'mesh face index {} does not correspond to any vertex. There ' + 'are {} vertices in the mesh.'.format(ind, len(self._vertices))) + except TypeError: + raise TypeError( + 'Mesh face must use integers to reference vertices. ' + 'Got {}.'.format(type(ind))) + return faces + + def __len__(self): + return len(self._vertices) + + def __getitem__(self, key): + return self._vertices[key] + + def __iter__(self): + return iter(self._vertices) + + def __repr__(self): + return 'OBJ ({} vertices) ({} faces)'.format( + len(self._vertices), len(self._faces))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/interop/stl.html b/docs/_modules/ladybug_geometry/interop/stl.html new file mode 100644 index 00000000..392db6f6 --- /dev/null +++ b/docs/_modules/ladybug_geometry/interop/stl.html @@ -0,0 +1,1398 @@ + + + + + + + ladybug_geometry.interop.stl — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.interop.stl

+# coding=utf-8
+"""A class that supports the import and export of STL data to/from ladybug_geometry.
+
+The methods in the object below are inspired from pySTL module.
+
+[1] Daniel Balzerson. 2013. pySTL - Python code for working with .STL
+(sterolithography) files. https://github.com/proverbialsunrise/pySTL
+"""
+import os
+import struct
+import re
+
+try:
+    from itertools import izip as zip  # python 2
+    writemode = 'wb'
+except ImportError:
+    writemode = 'w'  # python 3
+
+from ladybug_geometry.geometry3d.pointvector import Vector3D, Point3D
+
+
+
[docs]class STL(object): + """A class that supports the import and export of STL data to/from ladybug_geometry. + + Args: + face_vertices: A list of tuples where each tuple is a triangular face + of three Point3Ds. + face_normals: A list of Vector3Ds for the normals of the faces in the STL. + name: Text string for the name of the solid object in the STL + file. (Default: polyhedron). + + Properties: + * name + * face_vertices + * face_normals + """ + + __slots__ = ('_name', '_face_vertices', '_face_normals') + + def __init__(self, face_vertices, face_normals, name='polyhedron'): + self.name = name + self._face_normals = None # bypass check on first time + self.face_vertices = face_vertices + self.face_normals = face_normals + +
[docs] @classmethod + def from_file(cls, file_path): + """Create an STL object from a .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. + """ + face_vertices, face_normals, name = cls._load_stl(file_path) + return cls(face_vertices, face_normals, name)
+ +
[docs] @classmethod + def from_mesh3d(cls, mesh, name='polyhedron'): + """Create an STL object from a ladybug_geometry Mesh3D object. + + All quad faces will be automatically triangulated by using this method. + + Args: + mesh: A ladybug_geometry Mesh3D object to be converted to an OBJ object. + name: Text string for the name of the solid object in the STL + file. (Default: polyhedron). + """ + face_vertices, face_normals = [], [] + for f, fn in zip(mesh.faces, mesh.face_normals): + if len(f) == 3: + face_vertices.append(tuple(mesh._vertices[i] for i in f)) + face_normals.append(fn) + else: # it's a quad mesh to be triangulated + vts1 = (mesh._vertices[f[0]], mesh._vertices[f[1]], mesh._vertices[f[2]]) + vts2 = (mesh._vertices[f[2]], mesh._vertices[f[3]], mesh._vertices[f[0]]) + face_vertices.append(vts1) + face_vertices.append(vts2) + face_normals.append(fn) + face_normals.append(fn) + return cls(face_vertices, face_normals, name)
+ + @property + def name(self): + """Get the name of the solid object in the STL file.""" + return self._name + + @name.setter + def name(self, value): + input_name = 'STL object name' + 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) <= 80, 'Input {} "{}" must be less than 80 characters.'.format( + input_name, value) + self._name = value + + @property + def face_vertices(self): + """Get a list of tuples where each tuple is a triangular face of three Point3Ds. + """ + return self._face_vertices + + @face_vertices.setter + def face_vertices(self, val): + assert isinstance(val, (list, tuple)), \ + 'face_vertices should be a list or tuple. Got {}'.format(type(val)) + if isinstance(val, list): + val = tuple(val) + for f in val: + assert len(f) == 3, 'All face_vertices of an STL must be triangles. ' \ + 'Got face of length {}.'.format(len(f)) + self._face_vertices = val + self._check_faces_match() + + @property + def face_normals(self): + """Get a list of Vector3Ds for the normals of the faces in the STL.""" + return self._face_normals + + @face_normals.setter + def face_normals(self, val): + assert isinstance(val, (list, tuple)), \ + 'face_normals should be a list or tuple. Got {}'.format(type(val)) + if isinstance(val, list): + val = tuple(val) + self._face_normals = val + self._check_faces_match() + +
[docs] def to_file(self, folder, name=None): + """Write the STL object to an ASCII STL file. + + Args: + folder: A text string for the directory where the STL will be written. + name: A text string for the name of the STL file. If None, the name + of the STL object will be used. (Default: None). + """ + # set up a name and folder + if name is None: + name = self.name + file_name = name if name.lower().endswith('.stl') else '{}.stl'.format(name) + stl_file = os.path.join(folder, file_name) + # loop through the faces and normals to write them to the file + with open(stl_file, writemode) as fp: + fp.write('solid {:s}\n'.format(self.name)) + for facet, nm in zip(self.face_vertices, self.face_normals): + fp.write( + ' facet normal {0:.6E} {1:.6E} {2:.6E}\n'.format(nm.x, nm.y, nm.z) + ) + fp.write(' outer loop\n') + for pt in facet: + fp.write( + ' vertex {0:.6E} {1:.6E} {2:.6E}\n'.format(pt.x, pt.y, pt.z) + ) + fp.write(' endloop\n') + fp.write(' endfacet\n') + fp.write('endsolid {:s}\n'.format(self.name)) + return stl_file
+ + def _check_faces_match(self): + if self._face_normals is not None: + assert len(self._face_vertices) == len(self._face_normals), \ + 'Number of STL face_vertices ({}) does not match the number ' \ + 'of _face_normals ({}).'.format( + len(self._face_vertices), len(self._face_normals)) + + @staticmethod + def _load_stl(file_path): + """Load data 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. + + Returns: + A tuple with three elements. + + - face_vertices: + A list of tuples where each tuple is a triangular face of three Point3Ds. + + - face_normals: + A list of Vector3Ds for the normals of the faces in the STL. + + - name: + Text string for the name of the STL object + """ + # check the first bytes of the file to determine whether it's ASCII or binary + assert os.path.isfile(file_path), 'Failed to find %s' % file_path + with open(file_path, 'rb') as fp: + header = fp.read(80) # 80 characters should have the full name + first_word = header[0:5] + # load the STL data depending on whether it is ASCII or binary + if first_word.decode('utf-8') == 'solid': + return STL._load_text_stl(file_path) + else: + return STL._load_binary_stl(file_path) + + @staticmethod + def _load_text_stl(file_path): + """Read text stl file and extract triangular faces.""" + _face_vertices, _face_normals, _name = [], [], 'polyhedron' + with open(file_path, 'r') as fp: + for line in fp: + words = line.split() + if len(words) > 0: + first_word = words[0] + if first_word == 'facet': # start of a new face + vertices = [] + norm = Vector3D( + float(words[2]), float(words[3]), float(words[4]) + ) + _face_normals.append(norm) + elif first_word == 'vertex': # vertex of a face + vertices.append( + Point3D(float(words[1]), float(words[2]), float(words[3])) + ) + elif first_word == 'endloop': # end of a face + _face_vertices.append(tuple(vertices)) + elif first_word == 'solid': # very start of the file + try: + _name = words[1] + except IndexError: # no name assigned; leave the default one + pass + return _face_vertices, _face_normals, _name + + @staticmethod + def _load_binary_stl(file_path): + """Read binary stl file and extract triangular faces.""" + _face_vertices, _face_normals, _name = [], [], 'polyhedron' + with open(file_path, 'rb') as fp: + # interpret the 80-character header as the name of the object + _name = fp.read(80).decode('utf-8').strip() + # ignore the total face count in the first 4 characters + struct.unpack('I', fp.read(4))[0] + + # loop through the file contents and load all vertices and vectors + count = 0 + while True: + try: + # read the face normal + p = fp.read(12) + if len(p) == 12: + norm = Vector3D( + struct.unpack('f', p[0:4])[0], + struct.unpack('f', p[4:8])[0], + struct.unpack('f', p[8:12])[0] + ) + else: + break + # read the first vertex + p = fp.read(12) + if len(p) == 12: + p1 = Point3D( + struct.unpack('f', p[0:4])[0], + struct.unpack('f', p[4:8])[0], + struct.unpack('f', p[8:12])[0] + ) + else: + break + # read the second vertex + p = fp.read(12) + if len(p) == 12: + p2 = Point3D( + struct.unpack('f', p[0:4])[0], + struct.unpack('f', p[4:8])[0], + struct.unpack('f', p[8:12])[0] + ) + else: + break + # read the third vertex + p = fp.read(12) + if len(p) == 12: + p3 = Point3D( + struct.unpack('f', p[0:4])[0], + struct.unpack('f', p[4:8])[0], + struct.unpack('f', p[8:12])[0] + ) + else: + break + # add the triangle to the face vertices + _face_vertices.append((p1, p2, p3)) + _face_normals.append(norm) + count += 1 + fp.read(2) + + if len(p) == 0: + break # no more points to read + except EOFError: + break # we have reached the end of the file + return _face_vertices, _face_normals, _name + + def __len__(self): + return len(self._face_vertices) + + def __getitem__(self, key): + return self._face_vertices[key] + + def __iter__(self): + return iter(self._face_vertices) + + def __repr__(self): + return 'STL ({} faces)'.format(len(self._face_vertices))
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/intersection2d.html b/docs/_modules/ladybug_geometry/intersection2d.html new file mode 100644 index 00000000..e40273d0 --- /dev/null +++ b/docs/_modules/ladybug_geometry/intersection2d.html @@ -0,0 +1,1461 @@ + + + + + + + ladybug_geometry.intersection2d — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.intersection2d

+# coding=utf-8
+"""Utility functions for computing intersections between geometry in 2D space.
+
+Taken mostly from the euclid package available at
+https://pypi.org/project/euclid/
+"""
+from __future__ import division
+import math
+
+from .geometry2d.pointvector import Point2D, Vector2D
+
+
+def _isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
+    """Implementation of the math.isclose method from Python 3.5 onward."""
+    return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
+
+
+
[docs]def intersect_line2d(line_ray_a, line_ray_b): + """Get the intersection between any Ray2D or LineSegment2D objects as a Point2D. + + This function calculates scaling parameters for ua and ub where: + A.p + ua * A.v = B.p + ub * B.v + Which represents the intersection point between line A and line B. + + The derivation of ua is achieved by crossing both sides of the above equation + with the direction vector of B, and rearranging the formula: + + .. code-block:: python + + A.p + ua * A.v = B.p + ub * B.v + (A.p + ua * A.v) x B.v = (B.p + ub * B.v) x B.v # Cross both sides with B.v + (A.p x B.v) + (ua * A.v x B.v) = (B.p x B.v) + (ub * B.v x B.v) # B.v x B.v = 0 + ua = (B.p - A.p) x B.v / (A.v x B.v) + + Args: + line_ray_a: A LineSegment2D or Ray2D object. + line_ray_b: Another LineSegment2D or Ray2D to intersect. + + Returns: + Point2D of intersection if it exists. None if no intersection exists. + """ + # d is the determinant between lines, if 0 lines are collinear + d = line_ray_b.v.y * line_ray_a.v.x - line_ray_b.v.x * line_ray_a.v.y + if d == 0: + return None + + # (dx, dy) = A.p - B.p + dy = line_ray_a.p.y - line_ray_b.p.y + dx = line_ray_a.p.x - line_ray_b.p.x + + # Find parameters ua and ub for intersection between two lines + + # Calculate scaling parameter for line_ray_b + ua = (line_ray_b.v.x * dy - line_ray_b.v.y * dx) / d + # Checks the bounds of ua to ensure it obeys ray/line behavior + if not line_ray_a._u_in(ua): + return None + + # Calculate scaling parameter for line_ray_b + ub = (line_ray_a.v.x * dy - line_ray_a.v.y * dx) / d + # Checks the bounds of ub to ensure it obeys ray/line behavior + if not line_ray_b._u_in(ub): + return None + + return Point2D(line_ray_a.p.x + ua * line_ray_a.v.x, + line_ray_a.p.y + ua * line_ray_a.v.y)
+ + +
[docs]def intersect_line_segment2d(line_a, line_b): + """Get the intersection between two LineSegment2D objects as a Point2D. + + This function is identical to intersect_line2d but has some extra checks to + avoid certain cases of floating point tolerance issues. It is only intended + to work with LineSegment2D and not Ray2D. + + Args: + line_a: A LineSegment2D object. + line_b: Another LineSegment2D intersect. + + Returns: + Point2D of intersection if it exists. None if no intersection exists. + """ + # d is the determinant between lines, if 0 lines are collinear + d = line_b.v.y * line_a.v.x - line_b.v.x * line_a.v.y + if d == 0: + return None + + # (dx, dy) = A.p - B.p + dy = line_a.p.y - line_b.p.y + dx = line_a.p.x - line_b.p.x + + # Find parameters ua and ub for intersection between two lines + + # Calculate scaling parameter for line_b + ua = (line_b.v.x * dy - line_b.v.y * dx) / d + # Checks the bounds of ua to ensure it obeys ray/line behavior + if not line_a._u_in(ua): + return None + + # Calculate scaling parameter for line_b + ub = (line_a.v.x * dy - line_a.v.y * dx) / d + # Checks the bounds of ub to ensure it obeys ray/line behavior + if not line_b._u_in(ub): + return None + + # compute the intersection point + int_pta = Point2D(line_a.p.x + ua * line_a.v.x, line_a.p.y + ua * line_a.v.y) + int_ptb = Point2D(line_b.p.x + ub * line_b.v.x, line_b.p.y + ub * line_b.v.y) + + # if the two points are unequal, there's a floating point tolerance issue + if _isclose(int_pta.x, int_ptb.x) and _isclose(int_pta.y, int_ptb.y): + return int_pta + return None
+ + +
[docs]def intersect_line2d_infinite(line_ray_a, line_ray_b): + """Get intersection between a Ray2D/LineSegment2D and another extended infinitely. + + Args: + line_ray_a: A LineSegment2D or Ray2D object. + line_ray_b: A LineSegment2D or Ray2D that will be extended infinitely + for intersection. + + Returns: + Point2D of intersection if it exists. None if no intersection exists. + """ + d = line_ray_b.v.y * line_ray_a.v.x - line_ray_b.v.x * line_ray_a.v.y + if d == 0: + return None + dy = line_ray_a.p.y - line_ray_b.p.y + dx = line_ray_a.p.x - line_ray_b.p.x + ua = (line_ray_b.v.x * dy - line_ray_b.v.y * dx) / d + if not line_ray_a._u_in(ua): + return None + return Point2D(line_ray_a.p.x + ua * line_ray_a.v.x, + line_ray_a.p.y + ua * line_ray_a.v.y)
+ + +
[docs]def does_intersection_exist_line2d(line_ray_a, line_ray_b): + """Boolean denoting whether an intersection exists between Ray2D or LineSegment2D. + + This is slightly faster than actually computing the intersection but should only be + used in cases where the actual point of intersection is not needed. + + Args: + line_ray_a: A LineSegment2D or Ray2D object. + line_ray_b: Another LineSegment2D or Ray2D to intersect. + + Returns: + True if an intersection exists. False if no intersection exists. + """ + d = line_ray_b.v.y * line_ray_a.v.x - line_ray_b.v.x * line_ray_a.v.y + if d == 0: + return False + dy = line_ray_a.p.y - line_ray_b.p.y + dx = line_ray_a.p.x - line_ray_b.p.x + ua = (line_ray_b.v.x * dy - line_ray_b.v.y * dx) / d + if not line_ray_a._u_in(ua): + return False + ub = (line_ray_a.v.x * dy - line_ray_a.v.y * dx) / d + if not line_ray_b._u_in(ub): + return False + return True
+ + +
[docs]def intersect_line2d_arc2d(line_ray, arc): + """Get the intersection between any Ray2D/LineSegment2D and an Arc2D. + + Args: + line_ray: A LineSegment2D or Ray2D object. + arc: An Arc2D object along which the closest point will be determined. + + Returns: + A list of 2 Point2D objects if a full intersection exists. + A list with a single Point2D object if the line is tangent or intersects + only once. None if no intersection exists. + """ + a = line_ray.v.magnitude_squared + b = 2 * (line_ray.v.x * (line_ray.p.x - arc.c.x) + + line_ray.v.y * (line_ray.p.y - arc.c.y)) + c = arc.c.magnitude_squared + line_ray.p.magnitude_squared - \ + 2 * arc.c.dot(line_ray.p) - arc.r ** 2 + det = b ** 2 - 4 * a * c + if det < 0: + return None + sq = math.sqrt(det) + u1 = (-b + sq) / (2 * a) + u2 = (-b - sq) / (2 * a) + pt1 = Point2D(line_ray.p.x + u1 * line_ray.v.x, + line_ray.p.y + u1 * line_ray.v.y) if line_ray._u_in(u1) else None + pt2 = Point2D(line_ray.p.x + u2 * line_ray.v.x, + line_ray.p.y + u2 * line_ray.v.y) if line_ray._u_in(u2) else None + + if u1 == u2: # Tangent + pt = Point2D(line_ray.p.x + u1 * line_ray.v.x, + line_ray.p.y + u1 * line_ray.v.y) + return pt if arc._pt_in(pt) else None + + pts = [p for p in (pt1, pt2) if p is not None and arc._pt_in(p)] + return pts if len(pts) != 0 else None
+ + +
[docs]def intersect_line2d_infinite_arc2d(line_ray, arc): + """Get intersection between an Arc2D and a Ray2D/LineSegment2D extended infinitely. + + Args: + line_ray: A LineSegment2D or Ray2D that will be extended infinitely + for intersection. + arc: An Arc2D object along which the closest point will be determined. + + Returns: + A list of 2 Point2D objects if a full intersection exists. + A list with a single Point2D object if the line is tangent or intersects + only once. None if no intersection exists. + """ + a = line_ray.v.magnitude_squared + b = 2 * (line_ray.v.x * (line_ray.p.x - arc.c.x) + + line_ray.v.y * (line_ray.p.y - arc.c.y)) + c = arc.c.magnitude_squared + line_ray.p.magnitude_squared - \ + 2 * arc.c.dot(line_ray.p) - arc.r ** 2 + det = b ** 2 - 4 * a * c + if det < 0: + return None + sq = math.sqrt(det) + u1 = (-b + sq) / (2 * a) + u2 = (-b - sq) / (2 * a) + + if u1 == u2: # Tangent + pt = Point2D(line_ray.p.x + u1 * line_ray.v.x, line_ray.p.y + u1 * line_ray.v.y) + return pt if arc._pt_in(pt) else None + + pt1 = Point2D(line_ray.p.x + u1 * line_ray.v.x, line_ray.p.y + u1 * line_ray.v.y) + pt2 = Point2D(line_ray.p.x + u2 * line_ray.v.x, line_ray.p.y + u2 * line_ray.v.y) + pts = [p for p in (pt1, pt2) if arc._pt_in(p)] + return pts if len(pts) != 0 else None
+ + +
[docs]def closest_point2d_on_line2d(point, line_ray): + """Get the closest Point2D on a LineSegment2D or Ray2D to the input point. + + Args: + point: A Point2D object. + line_ray: A LineSegment2D or Ray2D object along which the closest point + will be determined. + + Returns: + Point2D for the closest point on the line_ray to point. + """ + d = line_ray.v.magnitude_squared + if d == 0: # zero-length segment; just return the end point + return line_ray.p + u = ((point.x - line_ray.p.x) * line_ray.v.x + + (point.y - line_ray.p.y) * line_ray.v.y) / d + if not line_ray._u_in(u): + u = max(min(u, 1.0), 0.0) + return Point2D(line_ray.p.x + u * line_ray.v.x, line_ray.p.y + u * line_ray.v.y)
+ + +
[docs]def closest_point2d_on_line2d_infinite(point, line_ray): + """Get the closest Point2D on a Ray2D/LineSegment2D extended infinitely. + + Args: + point: A Point2D object. + line_ray: A LineSegment2D or Ray2D object that will be extended infinitely + to determine where the closest point lies. + + Returns: + Point2D for the closest point on the line_ray to point. + """ + d = line_ray.v.magnitude_squared + if d == 0: # zero-length segment; just return the end point + return line_ray.p + u = ((point.x - line_ray.p.x) * line_ray.v.x + + (point.y - line_ray.p.y) * line_ray.v.y) / d + return Point2D(line_ray.p.x + u * line_ray.v.x, line_ray.p.y + u * line_ray.v.y)
+ + +
[docs]def closest_point2d_between_line2d(line_ray_a, line_ray_b): + """Get the two closest Point2D between two LineSegment2D objects. + + When the closest point on one of the segments lies in the middle of the + segment, this will be accounted for. Also note that the line segments + should not intersect for the result to be valid. + + Args: + line_ray_a: A LineSegment2D object. + line_ray_b: Another LineSegment2D to which closest points will + be determined. + + Returns: + A tuple with two elements + + - dists[0]: The distance between the two LineSegment2D objects. + - pts[0]: A tuple of two Point2D objects representing: + + 1) The point on line_ray_a that is closest to line_ray_b + 2) The point on line_ray_b that is closest to line_ray_a + """ + # one of the 4 endpoints must be a closest point + pt_1 = closest_point2d_on_line2d(line_ray_a.p, line_ray_b) + dist_1 = pt_1.distance_to_point(line_ray_a.p) + a_p2 = line_ray_a.p2 + pt_2 = closest_point2d_on_line2d(a_p2, line_ray_b) + dist_2 = pt_2.distance_to_point(a_p2) + pt_3 = closest_point2d_on_line2d(line_ray_b.p, line_ray_a) + dist_3 = pt_3.distance_to_point(line_ray_b.p) + b_p2 = line_ray_b.p2 + pt_4 = closest_point2d_on_line2d(b_p2, line_ray_a) + dist_4 = pt_4.distance_to_point(b_p2) + + # sort the closest points based on their distance + dists = [dist_1, dist_2, dist_3, dist_4] + pts = [(line_ray_a.p, pt_1), (a_p2, pt_2), (pt_3, line_ray_b.p), (pt_4, b_p2)] + dists, i = zip(*sorted(zip(dists, range(len(pts))))) + return dists[0], pts[i[0]]
+ + +
[docs]def closest_end_point2d_between_line2d(line_a, line_b): + """Get the two closest end Point2Ds between two LineSegment2D objects. + + The result will always be composed of endpoints of the segments and will not + account for cases where the closest point lies in the middle of a segment. + For cases where such middle points are important, the + closest_point2d_between_line2d method should be used. + + Args: + line_a: A LineSegment2D object. + line_b: Another LineSegment2D to which closest points will + be determined. + + Returns: + A tuple with two elements + + - dist: The distance between the two endpoints objects. + - pts: A tuple of two Point2D objects representing: + + 1) The end point on line_a that is closest to line_b + 2) The end point on line_b that is closest to line_a + """ + # one of the 4 endpoints must be a closest point + pts = [ + (line_a.p1, line_b.p1), (line_a.p1, line_b.p2), + (line_a.p2, line_b.p1), (line_a.p2, line_b.p2) + ] + dists = [p1.distance_to_point(p2) for p1, p2 in pts] + # sort the closest points based on their distance + dists, i = zip(*sorted(zip(dists, range(len(pts))))) + return dists[0], pts[i[0]]
+ + +
[docs]def closest_point2d_on_arc2d(point, arc): + """Get the closest Point2D on a Arc2D to the input point. + + Args: + point: A Point2D object. + arc: An Arc2D object along which the closest point will be determined. + + Returns: + Point2D for the closest point on arc to point. + """ + v = point - arc.c + v = v.normalize() * arc.r + if arc.is_circle: + return Point2D(arc.c.x + v.x, arc.c.y + v.y) + else: + a = Vector2D(1, 0).angle_counterclockwise(v) + if (not arc.is_inverted and arc.a1 < a < arc.a2) or \ + (arc.is_inverted and arc.a1 > a > arc.a2): + return Point2D(arc.c.x + v.x, arc.c.y + v.y) + else: + if arc.p1.distance_to_point(point) <= arc.p2.distance_to_point(point): + return arc.p1 + return arc.p2
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/intersection3d.html b/docs/_modules/ladybug_geometry/intersection3d.html new file mode 100644 index 00000000..dd820d02 --- /dev/null +++ b/docs/_modules/ladybug_geometry/intersection3d.html @@ -0,0 +1,1333 @@ + + + + + + + ladybug_geometry.intersection3d — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.intersection3d

+# coding=utf-8
+"""Utility functions for computing intersections between geometry in 3D space.
+
+Taken mostly from the euclid package available at
+https://pypi.org/project/euclid/
+"""
+from __future__ import division
+
+from .geometry3d.pointvector import Point3D
+
+import math
+
+
+
[docs]def intersect_line3d_plane(line_ray, plane): + """Get the intersection between a Ray3D/LineSegment3D and a Plane. + + Args: + line_ray: A LineSegment3D or Ray3D object. + plane: A Plane object to intersect. + + Returns: + Point3D of intersection if it exists. None if no intersection exists. + """ + d = plane.n.dot(line_ray.v) + if not d: # parallel + return None + u = (plane.k - plane.n.dot(line_ray.p)) / d + if not line_ray._u_in(u): # line or ray does not have its domain in the plane + return None + return Point3D(line_ray.p.x + u * line_ray.v.x, + line_ray.p.y + u * line_ray.v.y, + line_ray.p.z + u * line_ray.v.z)
+ + +
[docs]def intersect_line3d_plane_infinite(line_ray, plane): + """Get the intersection between a Plane and Ray2D/LineSegment2D extended infinitely. + + Args: + line_ray: ALineSegment2D or Ray2D that will be extended infinitely + for intersection. + plane: A Plane object to intersect. + + Returns: + Point3D of intersection if it exists. None if no intersection exists. + """ + d = plane.n.dot(line_ray.v) + if not d: # parallel + return None + u = (plane.k - plane.n.dot(line_ray.p)) / d + return Point3D(line_ray.p.x + u * line_ray.v.x, + line_ray.p.y + u * line_ray.v.y, + line_ray.p.z + u * line_ray.v.z)
+ + +
[docs]def intersect_plane_plane(plane_a, plane_b): + """Get the intersection between two Plane objects. + + Args: + plane_a: A Plane object. + plane_b: Another Plane object to intersect. + + Returns: + Two objects that define the intersection between two planes + + 1) A Point3D that lies along the intersection of the two planes. + 2) A Vector3D that describes the direction of the intersection. + + Will be None if no intersection exists (planes are parallel). + """ + n1_m = plane_a.n.magnitude_squared + n2_m = plane_b.n.magnitude_squared + n1d2 = plane_a.n.dot(plane_b.n) + det = n1_m * n2_m - n1d2 ** 2 + if det == 0: # parallel + return None + c1 = (plane_a.k * n2_m - plane_b.k * n1d2) / det + c2 = (plane_b.k * n1_m - plane_a.k * n1d2) / det + return Point3D(c1 * plane_a.n.x + c2 * plane_b.n.x, + c1 * plane_a.n.y + c2 * plane_b.n.y, + c1 * plane_a.n.z + c2 * plane_b.n.z), plane_a.n.cross(plane_b.n)
+ + +
[docs]def closest_point3d_on_line3d(point, line_ray): + """Get the closest Point3D on a LineSegment3D or Ray3D to the input point. + + Args: + point: A Point3D object. + line_ray: A LineSegment3D or Ray3D object along which the closest point + will be determined. + + Returns: + Point3D for the closest point on line_ray to point. + """ + d = line_ray.v.magnitude_squared + if d == 0: # zero-length segment; just return the end point + return line_ray.p + u = ((point.x - line_ray.p.x) * line_ray.v.x + + (point.y - line_ray.p.y) * line_ray.v.y + + (point.z - line_ray.p.z) * line_ray.v.z) / d + if not line_ray._u_in(u): + u = max(min(u, 1.0), 0.0) + return Point3D(line_ray.p.x + u * line_ray.v.x, + line_ray.p.y + u * line_ray.v.y, + line_ray.p.z + u * line_ray.v.z)
+ + +
[docs]def closest_point3d_on_line3d_infinite(point, line_ray): + """Get the closest Point3D on an infinite extension of a LineSegment3D or Ray3D. + + Args: + point: A Point3D object. + line_ray: A LineSegment3D or Ray3D object along which the closest point + will be determined. + + Returns: + Point3D for the closest point on the line_ray to the point. + """ + d = line_ray.v.magnitude_squared + if d == 0: # zero-length segment; just return the end point + return line_ray.p + u = ((point.x - line_ray.p.x) * line_ray.v.x + + (point.y - line_ray.p.y) * line_ray.v.y + + (point.z - line_ray.p.z) * line_ray.v.z) / d + return Point3D(line_ray.p.x + u * line_ray.v.x, + line_ray.p.y + u * line_ray.v.y, + line_ray.p.z + u * line_ray.v.z)
+ + +
[docs]def closest_point3d_on_plane(point, plane): + """Get the closest Point3D on a Plane to the input point. + + Args: + point: A Point3D object. + plane: A Plane object in which the closest point will be determined. + + Returns: + Point3D for the closest point on the plane to point. + """ + n = plane.n + d = point.dot(plane.n) - plane.k + return Point3D(point.x - n.x * d, point.y - n.y * d, point.z - n.z * d)
+ + +
[docs]def closest_point3d_between_line3d_plane(line_ray, plane): + """Get the two closest Point3D between a LineSegment3D/Ray3D and a Plane. + + Args: + line_ray: A LineSegment3D or Ray3D object along which the closest point + will be determined. + plane: A Plane object on which a closest point will be determined. + + Returns: + Two Point3D objects representing + + 1) The point on the line_ray that is closest to the plane. + 2) The point on the plane that is closest to the line_ray. + + Will be None if there is an intersection between line_ray and the plane + """ + d = plane.n.dot(line_ray.v) + if not d: # parallel, choose an endpoint + return line_ray.p, closest_point3d_on_plane(line_ray.p, plane) + u = (plane.k - plane.n.dot(line_ray.p)) / d + if not line_ray._u_in(u): # intersects out of range of L, choose nearest endpoint + u = max(min(u, 1.0), 0.0) + close_pt = Point3D(line_ray.p.x + u * line_ray.v.x, + line_ray.p.y + u * line_ray.v.y, + line_ray.p.z + u * line_ray.v.z) + return close_pt, closest_point3d_on_plane(close_pt, plane) + return None # intersection
+ + +
[docs]def intersect_line3d_sphere(line_ray, sphere): + """Get the intersection between this Sphere object and a Ray2D/LineSegment2D. + + Args: + line_ray: A LineSegment3D or Ray3D for intersection. + sphere: A Sphere to intersect. + + Returns: + Two Point3D objects if a full intersection exists. + A Point3D if a point of tangency exists. + Will be None if no intersection exists. + + """ + L = line_ray + S = sphere + a = L.v.magnitude_squared + b = 2 * (L.v.x * (L.p.x - S.center.x) + + L.v.y * (L.p.y - S.center.y) + + L.v.z * (L.p.z - S.center.z)) + c = S.center.magnitude_squared + \ + L.p.magnitude_squared - \ + 2 * S.center.dot(L.p) - \ + S.radius ** 2 + det = b ** 2 - 4 * a * c + if det < 0: + return None + sq = math.sqrt(det) + u1 = (-b + sq) / (2 * a) + u2 = (-b - sq) / (2 * a) + if not L._u_in(u1): + u1 = max(min(u1, 1.0), 0.0) + if not L._u_in(u2): + u2 = max(min(u2, 1.0), 0.0) + p1 = Point3D(L.p.x + u1 * L.v.x, L.p.y + u1 * L.v.y, L.p.z + u1 * L.v.z) + if u1 == u2: + return p1 + else: + p2 = Point3D(L.p.x + u2 * L.v.x, L.p.y + u2 * L.v.y, L.p.z + u2 * L.v.z) + return p1, p2
+ + +
[docs]def intersect_plane_sphere(plane, sphere): + """Get the intersection of a plane with this Sphere object + + Args: + plane: A Plane object. + sphere: A Sphere to intersect. + + Returns: + If a full intersection exists + + 1) A Point3D that represents the center of the intersection circle. + 2) A Vector3D that represents the normal of the intersection circle. + 3) A number that represents the radius of the intersection circle. + + A Point3D Object if a point of tangency exists. + None if no intersection exists. + """ + r = sphere.radius + pt_c = sphere.center + pt_o = plane.o + v_n = plane.n.normalize() + + # Resulting circle radius. Radius² = r² - [(c-p).n]² + d = (pt_o - pt_c).dot(v_n) + if abs(r) < abs(d): # No intersection if (r ** 2 - d ** 2) negative + return None + cut_r = math.sqrt(r ** 2 - d ** 2) + + # Intersection circle center point. Center_point = p - [(c-p).n]n + cut_center = pt_c + (d * v_n) + + return (cut_center, v_n, cut_r) if cut_r != 0 else cut_center
+
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_modules/ladybug_geometry/triangulation.html b/docs/_modules/ladybug_geometry/triangulation.html new file mode 100644 index 00000000..4bb4e237 --- /dev/null +++ b/docs/_modules/ladybug_geometry/triangulation.html @@ -0,0 +1,1795 @@ + + + + + + + ladybug_geometry.triangulation — ladybug geometry documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +

Source code for ladybug_geometry.triangulation

+# coding=utf-8
+"""Core triangulation functions used by various geometry modules.
+
+The functions here are derived from the earcut-python library available at
+https://github.com/joshuaskelly/earcut-python
+
+The earcut-python library is, itself, a pure Python port of the earcut JavaScript
+triangulation library maintained by Mapbox. The original project can be found at
+https://github.com/mapbox/earcut
+
+The version here is based off of the JavaScript earcut 2.1.1 release, and is
+functionally identical.
+"""
+from __future__ import division
+
+
+
[docs]def earcut(data, hole_indices=None, dim=2): + """Triangulate a list of vertices that make up a shape, either with or without holes. + + Args: + data: A flat array of vertex coordinates like [x0,y0, x1,y1, x2,y2, ...]. + hole_indices: A flat array of the starting indices for each hole. For example, + [5, 8] for a 12-vertex input would mean one hole with vertices 5-7 + and another with 8-11. If a single vertex is passed as as a hole, + Earcut treats it as a Steiner point. If None, no holes will be assumed + for the shape. (Default: None). + dim: An integer for the number of coordinates per vertex in the input + array. For example, 3 means each vertex exists in 3D space with + XX, Y, Z coordinates. (Default: 2 for 2D coordinates). + """ + dim = dim or 2 + hasHoles = hole_indices and len(hole_indices) + outerLen = hole_indices[0] * dim if hasHoles else len(data) + outerNode = _linked_list(data, 0, outerLen, dim, True) + triangles = [] + + if not outerNode: + return triangles + + minX = None + minY = None + maxX = None + maxY = None + x = None + y = None + size = None + + if hasHoles: + outerNode = _eliminate_holes(data, hole_indices, outerNode, dim) + + # if the shape is not too simple, we'll use z-order curve hash later + if (len(data) > 80 * dim): # calculate polygon bbox + minX = maxX = data[0] + minY = maxY = data[1] + + for i in range(dim, outerLen, dim): + x = data[i] + y = data[i + 1] + if x < minX: + minX = x + if y < minY: + minY = y + if x > maxX: + maxX = x + if y > maxY: + maxY = y + + # minX, minY and size are later used to transform coords into integers + # integers are used for z-order calculation + size = max(maxX - minX, maxY - minY) + + _earcut_linked(outerNode, triangles, dim, minX, minY, size) + + return triangles
+ + +def _linked_list(data, start, end, dim, clockwise): + """Create a circular doubly linked list from polygon points. + + Points will be in the specified winding order. + """ + i = None + last = None + + if (clockwise == (_signed_area(data, start, end, dim) > 0)): + for i in range(start, end, dim): + last = _insert_node(i, data[i], data[i + 1], last) + + else: + for i in reversed(range(start, end, dim)): + last = _insert_node(i, data[i], data[i + 1], last) + + if (last and _equals(last, last.next)): + _remove_node(last) + last = last.next + + return last + + +def _signed_area(data, start, end, dim): + """Get the signed area from a list of coordinate data.""" + sum = 0 + j = end - dim + + for i in range(start, end, dim): + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]) + j = i + + return sum + + +def _filter_points(start, end=None): + """Eliminate colinear or duplicate points.""" + if not start: + return start + if not end: + end = start + + p = start + again = True + + while again or p != end: + again = False + + if (not p.steiner and (_equals(p, p.next) or _area(p.prev, p, p.next) == 0)): + _remove_node(p) + p = end = p.prev + if (p == p.next): + return None + + again = True + + else: + p = p.next + + return end + + +def _earcut_linked(ear, triangles, dim, minX, minY, size, _pass=None): + """Main ear slicing loop which triangulates a polygon (given as a linked list).""" + if not ear: + return + + # interlink polygon nodes in z-order + if not _pass and size: + _index_curve(ear, minX, minY, size) + + stop = ear + prev = None + next = None + + # iterate through ears, slicing them one by one + while ear.prev != ear.next: + prev = ear.prev + next = ear.next + + if _is_ear_hashed(ear, minX, minY, size) if size else _is_ear(ear): + # cut off the triangle + triangles.append(prev.i // dim) + triangles.append(ear.i // dim) + triangles.append(next.i // dim) + + _remove_node(ear) + + # skipping the next vertex leads to less sliver triangles + ear = next.next + stop = next.next + + continue + + ear = next + + # if we looped through the whole remaining polygon and can't find any more ears + if ear == stop: + # try filtering points and slicing again + if not _pass: + _earcut_linked(_filter_points(ear), triangles, dim, minX, minY, size, 1) + + # if this didn't work, try curing all small self-intersections locally + elif _pass == 1: + ear = _cure_local_intersections(ear, triangles, dim) + _earcut_linked(ear, triangles, dim, minX, minY, size, 2) + + # as a last resort, try splitting the remaining polygon into two + elif _pass == 2: + _split_earcut(ear, triangles, dim, minX, minY, size) + + break + + +def _is_ear(ear): + """Check whether a polygon node forms a valid ear with adjacent nodes.""" + a = ear.prev + b = ear + c = ear.next + + if _area(a, b, c) >= 0: + return False # reflex, can't be an ear + + # now make sure we don't have other points inside the potential ear + p = ear.next.next + + while p != ear.prev: + if _point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) and \ + _area(p.prev, p, p.next) >= 0: + return False + p = p.next + + return True + + +def _is_ear_hashed(ear, minX, minY, size): + """Check whether a polygon node forms a valid ear using hashes.""" + a = ear.prev + b = ear + c = ear.next + + if _area(a, b, c) >= 0: + return False # reflex, can't be an ear + + # triangle bbox; min & max are calculated like this for speed + minTX = (a.x if a.x < c.x else c.x) if a.x < b.x else (b.x if b.x < c.x else c.x) + minTY = (a.y if a.y < c.y else c.y) if a.y < b.y else (b.y if b.y < c.y else c.y) + maxTX = (a.x if a.x > c.x else c.x) if a.x > b.x else (b.x if b.x > c.x else c.x) + maxTY = (a.y if a.y > c.y else c.y) if a.y > b.y else (b.y if b.y > c.y else c.y) + + # z-order range for the current triangle bbox; + minZ = _z_order(minTX, minTY, minX, minY, size) + maxZ = _z_order(maxTX, maxTY, minX, minY, size) + + # first look for points inside the triangle in increasing z-order + p = ear.nextZ + + while p and p.z <= maxZ: + if p != ear.prev and p != ear.next and \ + _point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) and \ + _area(p.prev, p, p.next) >= 0: + return False + p = p.nextZ + + # then look for points in decreasing z-order + p = ear.prevZ + + while p and p.z >= minZ: + if p != ear.prev and p != ear.next and \ + _point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) and \ + _area(p.prev, p, p.next) >= 0: + return False + p = p.prevZ + + return True + + +def _cure_local_intersections(start, triangles, dim): + """Go through all polygon nodes and cure small local self-intersections.""" + do = True + p = start + + while do or p != start: + do = False + + a = p.prev + b = p.next.next + + if not _equals(a, b) and _intersects(a, p, p.next, b) and \ + _locally_inside(a, b) and _locally_inside(b, a): + triangles.append(a.i // dim) + triangles.append(p.i // dim) + triangles.append(b.i // dim) + + # remove two nodes involved + _remove_node(p) + _remove_node(p.next) + + p = start = b + + p = p.next + + return p + + +def _split_earcut(start, triangles, dim, minX, minY, size): + """try splitting polygon into two and triangulate them independently.""" + # look for a valid diagonal that divides the polygon into two + do = True + a = start + + while do or a != start: + do = False + b = a.next.next + + while b != a.prev: + if a.i != b.i and _is_valid_diagonal(a, b): + # split the polygon in two by the diagonal + c = _split_polygon(a, b) + + # filter colinear points around the cuts + a = _filter_points(a, a.next) + c = _filter_points(c, c.next) + + # run earcut on each half + _earcut_linked(a, triangles, dim, minX, minY, size) + _earcut_linked(c, triangles, dim, minX, minY, size) + return + + b = b.next + + a = a.next + + +def _eliminate_holes(data, hole_indices, outerNode, dim): + """Link holes into the outer loop, producing a single-ring polygon without holes.""" + queue = [] + i = None + _len = len(hole_indices) + start = None + end = None + _list = None + + for i in range(len(hole_indices)): + start = hole_indices[i] * dim + end = hole_indices[i + 1] * dim if i < _len - 1 else len(data) + _list = _linked_list(data, start, end, dim, False) + + if (_list == _list.next): + _list.steiner = True + + queue.append(_get_leftmost(_list)) + + queue = sorted(queue, key=lambda i: i.x) + + # process holes from left to right + for i in range(len(queue)): + _eliminate_hole(queue[i], outerNode) + outerNode = _filter_points(outerNode, outerNode.next) + + return outerNode + + +def _eliminate_hole(hole, outerNode): + """Find a bridge between vertices that connects hole with an outer ring. + + Return a shape with the hole linked into it.""" + outerNode = _find_hole_bridge(hole, outerNode) + if outerNode: + b = _split_polygon(outerNode, hole) + _filter_points(b, b.next) + + +def _find_hole_bridge(hole, outerNode): + """David Eberly's algorithm for finding a bridge between hole and outer polygon.""" + do = True + p = outerNode + hx = hole.x + hy = hole.y + qx = float('-inf') + m = None + + # find a segment intersected by a ray from the hole's leftmost point to the left; + # segment's endpoint with lesser x will be potential connection point + while do or p != outerNode: + do = False + if hy <= p.y and hy >= p.next.y and p.next.y - p.y != 0: + x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y) + + if x <= hx and x > qx: + qx = x + + if (x == hx): + if hy == p.y: + return p + if hy == p.next.y: + return p.next + + m = p if p.x < p.next.x else p.next + + p = p.next + + if not m: + return None + + if hx == qx: + return m.prev # hole touches outer segment; pick lower endpoint + + # check points inside the triangle of hole point, segment intersection and endpoint + # if there are no points found, we have a valid connection + # otherwise choose the point of the minimum angle with the ray as connection point + + stop = m + mx = m.x + my = m.y + tanMin = float('inf') + tan = None + + p = m.next + + while p != stop: + hx_or_qx = hx if hy < my else qx + qx_or_hx = qx if hy < my else hx + + if hx >= p.x and p.x >= mx and \ + _point_in_triangle(hx_or_qx, hy, mx, my, qx_or_hx, hy, p.x, p.y): + try: + tan = abs(hy - p.y) / (hx - p.x) # tangential + except ZeroDivisionError: + break + + if (tan < tanMin or (tan == tanMin and p.x > m.x)) and \ + _locally_inside(p, hole): + m = p + tanMin = tan + + p = p.next + + return m + + +def _index_curve(start, minX, minY, size): + """Interlink polygon nodes in z-order.""" + do = True + p = start + + while do or p != start: + do = False + + if p.z is None: + p.z = _z_order(p.x, p.y, minX, minY, size) + + p.prevZ = p.prev + p.nextZ = p.next + p = p.next + + p.prevZ.nextZ = None + p.prevZ = None + + _sort_linked(p) + + +def _sort_linked(_list): + """Simon Tatham's linked list merge sort algorithm. + + More information available at https://www.chiark.greenend.org.uk/ + """ + do = True + i = None + p = None + q = None + e = None + tail = None + numMerges = None + pSize = None + qSize = None + inSize = 1 + + while do or numMerges > 1: + do = False + p = _list + _list = None + tail = None + numMerges = 0 + + while p: + numMerges += 1 + q = p + pSize = 0 + for i in range(inSize): + pSize += 1 + q = q.nextZ + if not q: + break + + qSize = inSize + + while pSize > 0 or (qSize > 0 and q): + + if pSize == 0: + e = q + q = q.nextZ + qSize -= 1 + + elif (qSize == 0 or not q): + e = p + p = p.nextZ + pSize -= 1 + + elif (p.z <= q.z): + e = p + p = p.nextZ + pSize -= 1 + + else: + e = q + q = q.nextZ + qSize -= 1 + + if tail: + tail.nextZ = e + + else: + _list = e + + e.prevZ = tail + tail = e + + p = q + + tail.nextZ = None + inSize *= 2 + + return _list + + +def _z_order(x, y, minX, minY, size): + """Z-order of a point given coords and size of the data bounding box.""" + # coords are transformed into non-negative 15-bit integer range + x = int(32767 * (x - minX) // size) + y = int(32767 * (y - minY) // size) + + x = (x | (x << 8)) & 0x00FF00FF + x = (x | (x << 4)) & 0x0F0F0F0F + x = (x | (x << 2)) & 0x33333333 + x = (x | (x << 1)) & 0x55555555 + + y = (y | (y << 8)) & 0x00FF00FF + y = (y | (y << 4)) & 0x0F0F0F0F + y = (y | (y << 2)) & 0x33333333 + y = (y | (y << 1)) & 0x55555555 + + return x | (y << 1) + + +def _get_leftmost(start): + """Find the leftmost node of a polygon ring.""" + do = True + p = start + leftmost = start + + while do or p != start: + do = False + if p.x < leftmost.x: + leftmost = p + p = p.next + + return leftmost + + +def _point_in_triangle(ax, ay, bx, by, cx, cy, px, py): + """Check if a point lies within a convex triangle.""" + return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 and \ + (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 and \ + (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0 + + +def _is_valid_diagonal(a, b): + """Check if a diagonal between two polygon nodes is valid. + + A valid diagonal is defined as one that lies in polygon interior. + """ + return a.next.i != b.i and a.prev.i != b.i and not _intersects_polygon(a, b) and \ + _locally_inside(a, b) and _locally_inside(b, a) and _middle_inside(a, b) + + +def _area(p, q, r): + """Get the signed area of a triangle.""" + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y) + + +def _equals(p1, p2): + """Check if two points are equal.""" + return p1.x == p2.x and p1.y == p2.y + + +def _intersects(p1, q1, p2, q2): + """Check if two segments intersect.""" + if (_equals(p1, q1) and _equals(p2, q2)) or (_equals(p1, q2) and _equals(p2, q1)): + return True + + return _area(p1, q1, p2) > 0 != _area(p1, q1, q2) > 0 and \ + _area(p2, q2, p1) > 0 != _area(p2, q2, q1) > 0 + + +def _intersects_polygon(a, b): + """Check if a polygon diagonal intersects any polygon segments.""" + do = True + p = a + + while do or p != a: + do = False + init_int = p.i != a.i and p.next.i != a.i and p.i != b.i and p.next.i != b.i + if init_int and _intersects(p, p.next, a, b): + return True + + p = p.next + + return False + + +def _locally_inside(a, b): + """Check if a polygon diagonal is locally inside the polygon.""" + if _area(a.prev, a, a.next) < 0: + return _area(a, b, a.next) >= 0 and _area(a, a.prev, b) >= 0 + else: + return _area(a, b, a.prev) < 0 or _area(a, a.next, b) < 0 + + +def _middle_inside(a, b): + """Check if the middle point of a polygon diagonal is inside a polygon.""" + do = True + p = a + inside = False + px = (a.x + b.x) / 2 + py = (a.y + b.y) / 2 + + while do or p != a: + do = False + if ((p.y > py) != (p.next.y > py)) and \ + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x): + inside = not inside + + p = p.next + + return inside + + +def _split_polygon(a, b): + """Link two polygon vertices with a bridge. + + If the vertices belong to the same ring, the polygon will be split into two. + If one belongs to the outer ring and another to a hole, the hole will be merged + into a single ring. + """ + a2 = _Node(a.i, a.x, a.y) + b2 = _Node(b.i, b.x, b.y) + an = a.next + bp = b.prev + + a.next = b + b.prev = a + + a2.next = an + an.prev = a2 + + b2.next = a2 + a2.prev = b2 + + bp.next = b2 + b2.prev = bp + + return b2 + + +def _insert_node(i, x, y, last): + """Create a node and optionally link it with previous one. + + Linking is done in a circular doubly linked list. + """ + p = _Node(i, x, y) + + if not last: + p.prev = p + p.next = p + + else: + p.next = last.next + p.prev = last + last.next.prev = p + last.next = p + + return p + + +def _remove_node(p): + """Remove a node from a list.""" + p.next.prev = p.prev + p.prev.next = p.next + + if p.prevZ: + p.prevZ.nextZ = p.nextZ + + if p.nextZ: + p.nextZ.prevZ = p.prevZ + + +class _Node(object): + """Node within a coordinate array.""" + + def __init__(self, i, x, y): + # vertex index in coordinates array + self.i = i + + # vertex coordinates + self.x = x + self.y = y + + # previous and next vertex nodes in a polygon ring + self.prev = None + self.next = None + + # z-order curve value + self.z = None + + # previous and next nodes in z-order + self.prevZ = None + self.nextZ = None + + # indicates whether this is a steiner point + self.steiner = False +
+ +
+ +
+
+
+
+

+ Back to top + +

+

+ © Copyright 2024, Ladybug Tools.
+ Created using Sphinx 5.3.0.
+

+
+
+ + \ No newline at end of file diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt new file mode 100644 index 00000000..92d8cc6b --- /dev/null +++ b/docs/_sources/index.rst.txt @@ -0,0 +1,27 @@ +Welcome to Ladybug Geometry's documentation! +============================================ + +.. image:: http://www.ladybug.tools/assets/img/ladybug.png + +Ladybug geometry is a Python library that houses geometry objects used throughout the +Ladybug Tools core libraries. + +Installation +============ + +``pip install -U ladybug-geometry`` + + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + +.. include:: modules.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_sources/ladybug_geometry.boolean.rst.txt b/docs/_sources/ladybug_geometry.boolean.rst.txt new file mode 100644 index 00000000..a351694c --- /dev/null +++ b/docs/_sources/ladybug_geometry.boolean.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.boolean module +================================ + +.. automodule:: ladybug_geometry.boolean + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.bounding.rst.txt b/docs/_sources/ladybug_geometry.bounding.rst.txt new file mode 100644 index 00000000..e672f627 --- /dev/null +++ b/docs/_sources/ladybug_geometry.bounding.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.bounding module +================================= + +.. automodule:: ladybug_geometry.bounding + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.dictutil.rst.txt b/docs/_sources/ladybug_geometry.dictutil.rst.txt new file mode 100644 index 00000000..54ae664a --- /dev/null +++ b/docs/_sources/ladybug_geometry.dictutil.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.dictutil module +================================= + +.. automodule:: ladybug_geometry.dictutil + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.arc.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.arc.rst.txt new file mode 100644 index 00000000..1be20e47 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.arc.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry2d.arc module +======================================= + +.. automodule:: ladybug_geometry.geometry2d.arc + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.line.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.line.rst.txt new file mode 100644 index 00000000..9cbc64eb --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.line.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry2d.line module +======================================== + +.. automodule:: ladybug_geometry.geometry2d.line + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.mesh.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.mesh.rst.txt new file mode 100644 index 00000000..744e00f5 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.mesh.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry2d.mesh module +======================================== + +.. automodule:: ladybug_geometry.geometry2d.mesh + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.pointvector.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.pointvector.rst.txt new file mode 100644 index 00000000..fb39081a --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.pointvector.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry2d.pointvector module +=============================================== + +.. automodule:: ladybug_geometry.geometry2d.pointvector + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.polygon.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.polygon.rst.txt new file mode 100644 index 00000000..8f0c1431 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.polygon.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry2d.polygon module +=========================================== + +.. automodule:: ladybug_geometry.geometry2d.polygon + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.polyline.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.polyline.rst.txt new file mode 100644 index 00000000..1cdda199 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.polyline.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry2d.polyline module +============================================ + +.. automodule:: ladybug_geometry.geometry2d.polyline + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.ray.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.ray.rst.txt new file mode 100644 index 00000000..1d2c3c01 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.ray.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry2d.ray module +======================================= + +.. automodule:: ladybug_geometry.geometry2d.ray + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry2d.rst.txt b/docs/_sources/ladybug_geometry.geometry2d.rst.txt new file mode 100644 index 00000000..0819d347 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry2d.rst.txt @@ -0,0 +1,24 @@ +ladybug\_geometry.geometry2d package +==================================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + ladybug_geometry.geometry2d.arc + ladybug_geometry.geometry2d.line + ladybug_geometry.geometry2d.mesh + ladybug_geometry.geometry2d.pointvector + ladybug_geometry.geometry2d.polygon + ladybug_geometry.geometry2d.polyline + ladybug_geometry.geometry2d.ray + +Module contents +--------------- + +.. automodule:: ladybug_geometry.geometry2d + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.arc.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.arc.rst.txt new file mode 100644 index 00000000..1677c324 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.arc.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.arc module +======================================= + +.. automodule:: ladybug_geometry.geometry3d.arc + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.cone.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.cone.rst.txt new file mode 100644 index 00000000..7b3160cc --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.cone.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.cone module +======================================== + +.. automodule:: ladybug_geometry.geometry3d.cone + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.cylinder.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.cylinder.rst.txt new file mode 100644 index 00000000..4cdc9129 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.cylinder.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.cylinder module +============================================ + +.. automodule:: ladybug_geometry.geometry3d.cylinder + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.face.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.face.rst.txt new file mode 100644 index 00000000..5c28d374 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.face.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.face module +======================================== + +.. automodule:: ladybug_geometry.geometry3d.face + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.line.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.line.rst.txt new file mode 100644 index 00000000..b03c533f --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.line.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.line module +======================================== + +.. automodule:: ladybug_geometry.geometry3d.line + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.mesh.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.mesh.rst.txt new file mode 100644 index 00000000..bbcdb3c6 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.mesh.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.mesh module +======================================== + +.. automodule:: ladybug_geometry.geometry3d.mesh + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.plane.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.plane.rst.txt new file mode 100644 index 00000000..40d28b44 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.plane.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.plane module +========================================= + +.. automodule:: ladybug_geometry.geometry3d.plane + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.pointvector.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.pointvector.rst.txt new file mode 100644 index 00000000..93eedff6 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.pointvector.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.pointvector module +=============================================== + +.. automodule:: ladybug_geometry.geometry3d.pointvector + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.polyface.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.polyface.rst.txt new file mode 100644 index 00000000..2390b569 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.polyface.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.polyface module +============================================ + +.. automodule:: ladybug_geometry.geometry3d.polyface + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.polyline.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.polyline.rst.txt new file mode 100644 index 00000000..40018e21 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.polyline.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.polyline module +============================================ + +.. automodule:: ladybug_geometry.geometry3d.polyline + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.ray.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.ray.rst.txt new file mode 100644 index 00000000..98508335 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.ray.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.ray module +======================================= + +.. automodule:: ladybug_geometry.geometry3d.ray + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.rst.txt new file mode 100644 index 00000000..f0092fdb --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.rst.txt @@ -0,0 +1,29 @@ +ladybug\_geometry.geometry3d package +==================================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + ladybug_geometry.geometry3d.arc + ladybug_geometry.geometry3d.cone + ladybug_geometry.geometry3d.cylinder + ladybug_geometry.geometry3d.face + ladybug_geometry.geometry3d.line + ladybug_geometry.geometry3d.mesh + ladybug_geometry.geometry3d.plane + ladybug_geometry.geometry3d.pointvector + ladybug_geometry.geometry3d.polyface + ladybug_geometry.geometry3d.polyline + ladybug_geometry.geometry3d.ray + ladybug_geometry.geometry3d.sphere + +Module contents +--------------- + +.. automodule:: ladybug_geometry.geometry3d + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.geometry3d.sphere.rst.txt b/docs/_sources/ladybug_geometry.geometry3d.sphere.rst.txt new file mode 100644 index 00000000..1315dfe2 --- /dev/null +++ b/docs/_sources/ladybug_geometry.geometry3d.sphere.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.geometry3d.sphere module +========================================== + +.. automodule:: ladybug_geometry.geometry3d.sphere + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.interop.obj.rst.txt b/docs/_sources/ladybug_geometry.interop.obj.rst.txt new file mode 100644 index 00000000..9ef56c87 --- /dev/null +++ b/docs/_sources/ladybug_geometry.interop.obj.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.interop.obj module +==================================== + +.. automodule:: ladybug_geometry.interop.obj + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.interop.rst.txt b/docs/_sources/ladybug_geometry.interop.rst.txt new file mode 100644 index 00000000..0f86219f --- /dev/null +++ b/docs/_sources/ladybug_geometry.interop.rst.txt @@ -0,0 +1,19 @@ +ladybug\_geometry.interop package +================================= + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + ladybug_geometry.interop.obj + ladybug_geometry.interop.stl + +Module contents +--------------- + +.. automodule:: ladybug_geometry.interop + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.interop.stl.rst.txt b/docs/_sources/ladybug_geometry.interop.stl.rst.txt new file mode 100644 index 00000000..21fd2b51 --- /dev/null +++ b/docs/_sources/ladybug_geometry.interop.stl.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.interop.stl module +==================================== + +.. automodule:: ladybug_geometry.interop.stl + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.intersection2d.rst.txt b/docs/_sources/ladybug_geometry.intersection2d.rst.txt new file mode 100644 index 00000000..baa85431 --- /dev/null +++ b/docs/_sources/ladybug_geometry.intersection2d.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.intersection2d module +======================================= + +.. automodule:: ladybug_geometry.intersection2d + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.intersection3d.rst.txt b/docs/_sources/ladybug_geometry.intersection3d.rst.txt new file mode 100644 index 00000000..96f7bcfd --- /dev/null +++ b/docs/_sources/ladybug_geometry.intersection3d.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.intersection3d module +======================================= + +.. automodule:: ladybug_geometry.intersection3d + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.rst.txt b/docs/_sources/ladybug_geometry.rst.txt new file mode 100644 index 00000000..c5760428 --- /dev/null +++ b/docs/_sources/ladybug_geometry.rst.txt @@ -0,0 +1,33 @@ +ladybug\_geometry package +========================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + ladybug_geometry.geometry2d + ladybug_geometry.geometry3d + ladybug_geometry.interop + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + ladybug_geometry.boolean + ladybug_geometry.bounding + ladybug_geometry.dictutil + ladybug_geometry.intersection2d + ladybug_geometry.intersection3d + ladybug_geometry.triangulation + +Module contents +--------------- + +.. automodule:: ladybug_geometry + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/ladybug_geometry.triangulation.rst.txt b/docs/_sources/ladybug_geometry.triangulation.rst.txt new file mode 100644 index 00000000..aeb86043 --- /dev/null +++ b/docs/_sources/ladybug_geometry.triangulation.rst.txt @@ -0,0 +1,7 @@ +ladybug\_geometry.triangulation module +====================================== + +.. automodule:: ladybug_geometry.triangulation + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/modules.rst.txt b/docs/_sources/modules.rst.txt new file mode 100644 index 00000000..886020fa --- /dev/null +++ b/docs/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +ladybug_geometry +================ + +.. toctree:: + :maxdepth: 4 + + ladybug_geometry 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 0000000000000000000000000000000000000000..3bf6484a29d8da269f9bc874b25493a45fae3bae GIT binary patch literal 8777 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 B7G?kd literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a9969993201f9cee63cf9f49217646347297b643 GIT binary patch literal 12799 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 literal 0 HcmV?d00001 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 + $('