|
9 | 9 | if (sys.version_info > (3, 0)): # python 3 |
10 | 10 | xrange = range |
11 | 11 |
|
12 | | -from ladybug_geometry.geometry3d.pointvector import Point3D, Vector3D |
13 | | -from ladybug_geometry.geometry3d.plane import Plane |
14 | | -from ladybug_geometry.geometry3d.mesh import Mesh3D |
15 | | -from ladybug_geometry.geometry2d.pointvector import Point2D |
16 | | -from ladybug_geometry.geometry2d.mesh import Mesh2D |
| 12 | +from ladybug_geometry.geometry2d import Point2D, Mesh2D |
| 13 | +from ladybug_geometry.geometry3d import Point3D, Vector3D, Polyline3D, Plane, Mesh3D |
17 | 14 |
|
18 | 15 | from .color import Color, Colorset, ColorRange |
19 | 16 |
|
@@ -441,6 +438,79 @@ def color_map_2d(self, width=800, height=600): |
441 | 438 | color_mtx.append([black] * total_w) |
442 | 439 | return color_mtx |
443 | 440 |
|
| 441 | + def mesh_contours(self, mesh, tolerance): |
| 442 | + """Get Polyline3Ds for contours of a Mesh3D associated with this legend's values. |
| 443 | +
|
| 444 | + Args: |
| 445 | + mesh: A ladybug-geometry Mesh3D for which contours will be derived. |
| 446 | + The number of faces or the number of vertices must match the |
| 447 | + number of values associated with this Legend. |
| 448 | + tolerance: The minimum difference between mesh vertices at which point |
| 449 | + they are considered equivalent. |
| 450 | +
|
| 451 | + Returns: |
| 452 | + A tuple with two elements. |
| 453 | +
|
| 454 | + - contours -- A list of lists where each sub-list represents |
| 455 | + contours associated with a specific threshold. Contours are |
| 456 | + composed of Polyline3D and LineSegment3D. |
| 457 | +
|
| 458 | + - thresholds -- list of numbers for the threshold value associated |
| 459 | + with each contour. The length of this list matches the contours. |
| 460 | + """ |
| 461 | + # check the input values and provide defaults |
| 462 | + val_count = len(self.values) |
| 463 | + face_match = val_count == len(mesh.faces) |
| 464 | + assert face_match or val_count == len(mesh.vertices), \ |
| 465 | + 'Number of values ({}) must match the number of mesh faces ({}) or ' \ |
| 466 | + 'the number of mesh vertices ({}).'.format( |
| 467 | + val_count, len(mesh.faces), len(mesh.vertices)) |
| 468 | + |
| 469 | + # figure out the thresholds to be used for the contour lines |
| 470 | + min_val, max_val = self.legend_parameters.min, self.legend_parameters.max |
| 471 | + if min_val == max_val: |
| 472 | + return [], [] # no contours to be generated |
| 473 | + thresholds = list(self.segment_numbers) |
| 474 | + if self.is_max_default: |
| 475 | + thresholds.pop(-1) # no need to make a contour |
| 476 | + if self.is_min_default: |
| 477 | + thresholds.pop(0) # no need to make a contour |
| 478 | + if len(thresholds) == 0: # ensure there is at least one threshold |
| 479 | + thresholds = [(max_val + min_val) / 2] |
| 480 | + |
| 481 | + # loop through the thresholds and generate contour lines |
| 482 | + contours = [] |
| 483 | + init_naked_edges = mesh.naked_edges |
| 484 | + for abs_thresh in thresholds: |
| 485 | + # remove faces below the threshold |
| 486 | + pattern = [val > abs_thresh for val in self.values] |
| 487 | + if all(v for v in pattern): |
| 488 | + contours.append([]) |
| 489 | + continue # full mesh in contour; not a useful line |
| 490 | + elif all(not v for v in pattern): |
| 491 | + contours.append([]) |
| 492 | + continue # none of the mesh lies in the contour; not a useful line |
| 493 | + sub_mesh, _ = mesh.remove_faces(pattern) if face_match else \ |
| 494 | + mesh.remove_vertices(pattern) |
| 495 | + |
| 496 | + # create the contour lines |
| 497 | + contour_segs = [] |
| 498 | + for seg in sub_mesh.naked_edges: |
| 499 | + for i_seg in init_naked_edges: |
| 500 | + if seg.p1.is_equivalent(i_seg.p1, tolerance) and \ |
| 501 | + seg.p2.is_equivalent(i_seg.p2, tolerance): |
| 502 | + break |
| 503 | + else: # we have found a new segment for contouring |
| 504 | + contour_segs.append(seg) |
| 505 | + polylines = Polyline3D.join_segments(contour_segs, tolerance) |
| 506 | + final_contours = [] |
| 507 | + for cont in polylines: |
| 508 | + if isinstance(cont, Polyline3D): |
| 509 | + cont = Polyline3D(cont.vertices, True) |
| 510 | + final_contours.append(cont) |
| 511 | + contours.append(final_contours) |
| 512 | + return contours, thresholds |
| 513 | + |
444 | 514 | def duplicate(self): |
445 | 515 | """Return a copy of the current legend.""" |
446 | 516 | return self.__copy__() |
|
0 commit comments