Skip to content

Commit

Permalink
fix(context): Add method to snap ContextShade to a grid
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswmackey committed May 6, 2024
1 parent b5b809d commit 3687c20
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 5 deletions.
60 changes: 59 additions & 1 deletion dragonfly/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import division
import math

from ladybug_geometry.geometry3d import Face3D, Mesh3D
from ladybug_geometry.geometry3d import Point3D, Face3D, Mesh3D

from honeybee.shade import Shade
from honeybee.shademesh import ShadeMesh
Expand Down Expand Up @@ -200,6 +200,64 @@ def scale(self, factor, origin=None):
for shd_geo in self._geometry)
self.properties.scale(factor, origin)

def snap_to_grid(self, grid_increment, tolerance=0.01):
"""Snap this object to the nearest XY grid node defined by an increment.
Note that, even though ContextShade geometry is defined using 3D vertices,
only the X and Y coordinates will be snapped, which is consistent with
how the Room2D.snap_to_grid method works.
All properties assigned to the ContextShade will be preserved and any
degenerate geometries are automatically cleaned out of the result.
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 ContextShade that you
wish to resolve.
tolerance: The minimum difference between the coordinate values at
which they are considered co-located. (Default: 0.01,
suitable for objects in meters).
"""
# define a list to hold all of the new geometry
new_geometry = []

# loop through the current geometry and snap the vertices
for geo in self._geometry:
if isinstance(geo, Face3D):
new_boundary, new_holes = [], None
for pt in geo.boundary:
new_x = grid_increment * round(pt.x / grid_increment)
new_y = grid_increment * round(pt.y / grid_increment)
new_boundary.append(Point3D(new_x, new_y, pt.z))
if geo.holes is not None:
new_holes = []
for hole in geo.holes:
new_hole = []
for pt in hole:
new_x = grid_increment * round(pt.x / grid_increment)
new_y = grid_increment * round(pt.y / grid_increment)
new_hole.append(Point3D(new_x, new_y, pt.z))
new_holes.append(new_hole)
n_geo = Face3D(new_boundary, geo.plane, new_holes)
try: # catch all degeneracy in the process
n_geo = n_geo.remove_duplicate_vertices(tolerance)
new_geometry.append(n_geo)
except: # degenerate geometry
pass
elif isinstance(geo, Mesh3D):
new_vertices = []
for pt in geo.vertices:
new_x = grid_increment * round(pt.x / grid_increment)
new_y = grid_increment * round(pt.y / grid_increment)
new_vertices.append(Point3D(new_x, new_y, pt.z))
n_geo = Mesh3D(new_vertices, geo.faces)
new_geometry.append(n_geo)

# rebuild the new floor geometry and assign it to the Room2D
if len(new_geometry) != 0:
self._geometry = new_geometry

def to_honeybee(self):
"""Convert Dragonfly ContextShade to a list of Honeybee Shades and ShadeMeshes.
"""
Expand Down
21 changes: 17 additions & 4 deletions tests/context_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

from honeybee.shade import Shade

from ladybug_geometry.geometry2d.pointvector import Point2D
from ladybug_geometry.geometry3d.pointvector import Point3D, Vector3D
from ladybug_geometry.geometry3d.plane import Plane
from ladybug_geometry.geometry3d.face import Face3D
from ladybug_geometry.geometry2d import Point2D
from ladybug_geometry.geometry3d import Point3D, Vector3D, Plane, Face3D, Mesh3D

import pytest

Expand Down Expand Up @@ -104,6 +102,21 @@ def test_reflect():
assert test_1[0][1].z == pytest.approx(2, rel=1e-3)


def test_snap_to_grid():
"""Test the ContextShade snap_to_grid method."""
pts1 = (Point3D(1.1, 1.1, 4), Point3D(2.1, 1.1, 4), Point3D(2.1, 2.1, 4), Point3D(1.1, 2.1, 4))
plane = Plane(Vector3D(0, 0, 1), Point3D(0, 0, 2))
face = Face3D(pts1, plane)
pts2 = (Point3D(0, 0, 2), Point3D(0, 2.1, 2), Point3D(2.1, 2.1, 2),
Point3D(2.1, 0, 2), Point3D(4.1, 0, 2))
mesh = Mesh3D(pts2, [(0, 1, 2, 3), (2, 3, 4)])
awning_canopy = ContextShade('Awning_Canopy', [face, mesh])

awning_canopy.snap_to_grid(1.0, 0.01)
assert awning_canopy[0].vertices != pts1
assert awning_canopy[1].vertices != pts2


def test_to_honeybee():
"""Test the to_honeybee method."""
tree_canopy_geo1 = Face3D.from_regular_polygon(6, 6, Plane(o=Point3D(5, -10, 6)))
Expand Down

0 comments on commit 3687c20

Please sign in to comment.