Skip to content

Commit

Permalink
fix(docs): Improve several aspects of the documentation
Browse files Browse the repository at this point in the history
I am also adding some code that handles the case of duplicated vertices within a Polyface. This way, the edges of such a polyface can still be merged to check solidity and outward faces can still be obtained.
  • Loading branch information
chriswmackey authored and Chris Mackey committed Oct 28, 2020
1 parent 5cbe483 commit b3a4ebf
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 59 deletions.
121 changes: 77 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,90 @@
[![Python 2.7](https://img.shields.io/badge/python-2.7-green.svg)](https://www.python.org/downloads/release/python-270/) [![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/) [![IronPython](https://img.shields.io/badge/ironpython-2.7-red.svg)](https://github.com/IronLanguages/ironpython2/releases/tag/ipy-2.7.8/)

# ladybug-geometry
Ladybug geometry is a Python library that houses geometry objects used throughout the Ladybug Tools core libraries.

## [API Documentation](https://www.ladybug.tools/ladybug-geometry/docs/ladybug_geometry.html)
Ladybug geometry is a Python library that houses geometry objects used throughout the
Ladybug Tools core libraries.

## Installation

`pip install -U ladybug-geometry`

## [API Documentation](https://www.ladybug.tools/ladybug-geometry/docs/)

## Local Development

1. Clone this repo locally
```console
git clone git@github.com:ladybug-tools/ladybug-geometry.git

# or

git clone https://github.com/ladybug-tools/ladybug-geometry.git
```

2. Install dependencies:
```console
cd ladybug-geometry
pip install -r dev-requirements.txt
pip install -r requirements.txt
```

3. Run Tests:
```console
python -m pytests tests/
```

4. Generate Documentation:
```console
sphinx-apidoc -f -e -d 4 -o ./docs ./ladybug_geometry
sphinx-build -b html ./docs ./docs/_build/docs
```

## Currently Supported Capabilities of this Library

- Vector Math
- Calculate Bounding Box (Min, Max, Center)
- Compute Area + Perimeter of Planar Geometry
- Check Concavity and Clockwise Ordering of 2D Geometry
- Triangulate Planar Geometry
- Compute Triangle + Quad Areas, Centroids, and Normals
- Move Geometry
- Rotate Geometry Around an Axis
- Mirror Geometry
- Scale Geometry from a Base Point
- Is Point Inside 2D Polygon
- 3D Face Intersection with a Ray or Line
- Mesh Grid Generation from a 3D Face
- Windows Based on Ratio with a Face
- Solve Adjacencies
- Generate Louvers, Fins and Overhangs from a Face
- Check if a 3D PolyFace is a Closed Solid
- Ensure All Faces of a Solid PolyFace are Point Outwards
- Join Polylines and Polyfaces
- Check if a Point is Inside a Closed 3D Polyface
- Boolean a Set of 2D Curves (joining the naked edges around them)

## Capabilities that should eventually be a part of this library

- [ ] Create Matching Zone Surfaces (intersection of surfaces with one another). OpenStudio's boost geometry has methods for this [as @saeranv shows here](https://github.com/mostaphaRoudsari/honeybee/issues/700)

## Officially Unsupported Capabilities for which One Must Rely on CAD Interfaces

- Conversion of Curved Surfaces to Planar Surfaces (including both single curvature and double curvature)
- Fancier Meshing (eg. gridded meshing that completely fills the base surface)
- Solid Boolean Unions (this should not be needed for anything in Ladybug Tools)

## Reasons for this Library

We initially debated whether geometry computation should be placed largely on the CAD plugins or
whether it should be inincluded in the core. As we developed the core libraries out, it became clear
that there are large advantages to having it in the core (ie. cross compatability between
whether it should be included in the core. As we developed the core libraries out, it became clear
that there are large advantages to having it in the core (ie. cross compatibility between
the CAD plugins, ability to process more inputs from command line, and shear speed
since the CAD libraries are made to address many more geometric use cases than are typically needed).
So we have decided to include geomtery computation as part of the Ladybug Tools core.
So we have decided to include geometry computation as part of the Ladybug Tools core.

We looked into using other geometry computation libraries for the core including:

- [Rhino3dm](https://github.com/mcneel/rhino3dm)
- [Blender API (bpy)](https://docs.blender.org/api/current/)
- [Topologic](https://topologic.app/Software/)
Expand All @@ -30,7 +101,7 @@ grid of points from a surface).
Furthermore, Blender library only works in Python3 and this would break our workflows for the
Grasshopper and Dynamo plugins, where rely on IronPython.
Topologic seems to have many things that we need but it appears that it has C dependencies, making
it unusable from ironpython. Furthermore, its dual license may create some difficulties for certain
it unusable from IronPython. Furthermore, its dual license may create some difficulties for certain
use cases of Ladybug Tools.

After considering it further, we realized that many of the calculations that we need can be done
Expand All @@ -39,41 +110,3 @@ is eventually converted to a planar format anyway, we made the decision that the
certain basic types of geometry computation for planar objects only. We planned to do this by taking the
most relevant parts of existing open source geometry libraries, including [euclid](https://pypi.org/project/euclid/)
and OpenStudio. Thus this repository was born!

For this library, we can borrow some of the math from the previous open source libaraies
listed above (Rhino3dm and Blender), as well as other projects like
[this PhD on Grid Generation for Radiance](https://www.radiance-online.org/community/workshops/2015-philadelphia/presentations/day1/STADICUtilities-Radiance%20Workshop2015.pdf)
to build tis core library.

## Things that Are or Will be a Part of this Library
### (We Can do Easily in Pure Python)
- [x] Vectormath ([already exists in Ladybug core](https://github.com/ladybug-tools/ladybug/blob/master/ladybug/euclid.py))
- [x] Calculate Bounding Box ([already exists in Butterfly core](https://github.com/ladybug-tools/butterfly/blob/master/butterfly/geometry.py))
- [x] Compute Triangle + Quad Areas, Center Points + Normals ([partly exists in Butterfly core](https://github.com/ladybug-tools/butterfly/blob/master/butterfly/geometry.py))
- [x] Compute Area + Perimeter of Planar Geometry (should be doable [by using this formula](https://www.mathopenref.com/coordpolygonarea.html))
- [x] Check Concavity of a 2D Geometry (already exists in legacy [find non-convex component](https://github.com/mostaphaRoudsari/honeybee/blob/master/src/Honeybee_Honeybee.py#L9340-L9410))
- [x] Convert Concave 2D Geometry to Convex Geometries (should be possible with the [ear clipping method](https://en.wikipedia.org/wiki/Polygon_triangulation))
- [x] Triangulate Planar Geometry ([possible by converting convex geometry to concave and using fan triangulation](https://en.wikipedia.org/wiki/Polygon_triangulation))
- [x] Move Geometry (can be taken from Rhino3dm)
- [x] Rotate Geometry Around a Base Point (can be taken from Rhino3dm)
- [x] Mirror Geometry (can be taken from Rhino3dm)
- [x] Scale Geometry from a Base Point (can be taken from Rhino3dm)
- [x] Is Point Inside 2D Polygon (look pretty straightforward from [this example](https://www.geeksforgeeks.org/how-to-check-if-a-given-point-lies-inside-a-polygon/))
- [x] Planar surface grid generation ([as done in this thesis](https://www.radiance-online.org/community/workshops/2015-philadelphia/presentations/day1/STADICUtilities-Radiance%20Workshop2015.pdf), which uses bounding box and is point inside)
- [x] Glazing Based on Ratio (currently implemented in legacy [glazing based on ratio component](https://github.com/mostaphaRoudsari/honeybee/blob/master/src/Honeybee_Glazing%20based%20on%20ratio.py))
- [x] Solve Adjacencies (possible with good geometric equivalency tests)
- [x] Generate louvers, fins and overhangs from a face. Should be easy by extruding intersected line segments generated from the Face3D.intersect_plane method that is already implemented.
- [x] Check if a 3D PolyFace is Closed ([should be possible by creating a 3D triangulated mesh](https://gamedev.stackexchange.com/questions/61878/how-check-if-an-arbitrary-given-mesh-is-a-single-closed-mesh/61886))
- [x] Ensure that all faces of a solid PolyFace are facing outward (possible with a 3D version of the current method that checks whether a point is inside a polygon. Essentially, shoot a ray from the center of a face using the face normal and make sure that the ray intersects 0 or an even number of other faces in the polyface)
- [x] Check if a Point is Inside a Closed 3D Geometry (useful for thermal comfort studies where points in a grid must be matched with zone results) (should be possible with a variation of the function that checks if a face is outward-facing except we use the point in question as the origin of the ray we shoot)
- [ ] Straight Skeleton Methods (currently implemented in [legacy core/perimeter component](https://github.com/mostaphaRoudsari/honeybee/blob/master/src/Honeybee_SplitFloor2ThermalZones.py) but should be expanded to accept concave geometry)
- [ ] Offset edge curve of a planar surface (can be done by translating vertices along the straight skeleton to make a "wavefront")

## Things That Should be a Part of this Library
### (We Think We Can Do Them But They Will Require Some Expertise)
- [ ] Create Matching Zone Surfaces (intersection of surfaces with one another). OpenStudio has methods for this [as @saeranv shows here](https://github.com/mostaphaRoudsari/honeybee/issues/700)
- [ ] Curve Boolean a set of 2D curves (useful for finding outer boundaries of set of THERM polygons, calculating building footprints from floor curves, and more). Should be possible with [this method here](https://stackoverflow.com/questions/2667748/how-do-i-combine-complex-polygons)

## Things that We Will Rely on the CAD Interface For:
- Conversion of Curved Surfaces to Planar Surfaces (ideally with methods for treating single curvature differently than double curvature)
- Solid Boolean Unions (we can probably get away with not needing this for anything in Ladybug Tools)
18 changes: 13 additions & 5 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
.. Ladybug Geometry documentation master file, created by
sphinx-quickstart on Wed Mar 13 20:09:41 2019.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
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``

Documentation
=============

.. toctree::
:maxdepth: 2
:caption: Contents:
Expand Down
33 changes: 23 additions & 10 deletions ladybug_geometry/geometry3d/polyface.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,11 +462,15 @@ def merge_overlapping_edges(self, tolerance, angle_tolerance):
for edge, ind, nei, i in zip(
naked_edges[1:], naked_edge_ind[1:], naked_edge_i[1:],
xrange(1, len(naked_edges))):
if edge.is_colinear(naked_edges[0], tolerance, angle_tolerance):
try:
if edge.is_colinear(naked_edges[0], tolerance, angle_tolerance):
coll_edges.extend(ind)
coll_i.append(nei)
else:
kept_i.append(i)
except ZeroDivisionError: # duplicate vertices resulted in 0 length edge
coll_edges.extend(ind)
coll_i.append(nei)
else:
kept_i.append(i)

# determine if colinear edges create a full double line along the edge
if len(coll_edges) == 1:
Expand Down Expand Up @@ -735,13 +739,11 @@ def get_outward_faces(faces, tolerance):
outward_faces = []
for i, face in enumerate(faces):
# construct a ray with the face normal and a point on the 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
move_vec = v1.rotate(face.normal, math.pi / 2).normalize()
else: # average the two edge vectors together
move_vec = Vector3D(
(v1.x + v2.x / 2), (v1.y + v2.y / 2), (v1.z + v2.z / 2)).normalize()
try:
move_vec = Polyface3D._inward_pointing_vec(face)
except ZeroDivisionError: # face has duplicated start vertices; remove them
face = face.remove_colinear_vertices(tolerance)
move_vec = Polyface3D._inward_pointing_vec(face)
move_vec = move_vec * (tolerance + 0.00001)
point_on_face = face.boundary[0] + move_vec
vert2d = face.plane.xyz_to_xy(point_on_face)
Expand Down Expand Up @@ -805,6 +807,17 @@ def _verts_faces_edges_from_boundary(cclock_verts, extru_vec, st_i=0):
[(st_i + len_faces * 2 - 1, st_i + len_faces)]
return verts, faces_ind, edge_indices

@staticmethod
def _inward_pointing_vec(face):
"""Get a unit vector pointing inward from the first vertex of a Face3D."""
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()

def __copy__(self):
_new_poly = Polyface3D(self.vertices, self.face_indices, self.edge_information)
_new_poly._faces = self._faces
Expand Down

0 comments on commit b3a4ebf

Please sign in to comment.