diff --git a/CHANGELOG.md b/CHANGELOG.md index 637d4c1..c117e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added new functions to make patterns that import from compas_tna + ### Changed ### Removed diff --git a/commands/RV_pattern.py b/commands/RV_pattern.py index ca17462..d5a1237 100644 --- a/commands/RV_pattern.py +++ b/commands/RV_pattern.py @@ -10,11 +10,7 @@ from compas_rv.commands import make_pattern_from_rhinosurface from compas_rv.commands import make_pattern_from_skeleton from compas_rv.commands import make_pattern_from_triangulation -from compas_rv.patterns.circular import create_circular_radial_pattern -from compas_rv.patterns.circular import create_circular_radial_spaced_pattern -from compas_rv.patterns.circular import create_circular_spiral_pattern -from compas_rv.patterns.rectangular import create_cross_pattern -from compas_rv.patterns.rectangular import create_fan_pattern +from compas_rv.commands import make_pattern_from_template from compas_rv.session import RVSession @@ -75,34 +71,7 @@ def RunCommand(): raise NotImplementedError elif option == "Template": - option2 = rs.GetString( - message="Template Name", - strings=[ - "Radial", - "RadialSpaced", - "Spiral", - "Cross", - "Fan", - ], - ) - - if option2 == "Radial": - pattern = create_circular_radial_pattern() - - elif option2 == "RadialSpaced": - pattern = create_circular_radial_spaced_pattern() - - elif option2 == "Spiral": - pattern = create_circular_spiral_pattern() - - elif option2 == "Cross": - pattern = create_cross_pattern() - - elif option2 == "Fan": - pattern = create_fan_pattern() - - else: - raise NotImplementedError + pattern = make_pattern_from_template() else: return diff --git a/src/compas_rv/commands.py b/src/compas_rv/commands.py index 63dcbfe..4b20a47 100644 --- a/src/compas_rv/commands.py +++ b/src/compas_rv/commands.py @@ -12,6 +12,43 @@ from compas.geometry import NurbsCurve from compas.geometry import Point from compas_rv.datastructures import Pattern +from compas_tna.diagrams.diagram_circular import create_circular_radial_mesh +from compas_tna.diagrams.diagram_circular import create_circular_radial_spaced_mesh +from compas_tna.diagrams.diagram_circular import create_circular_spiral_mesh +from compas_tna.diagrams.diagram_rectangular import create_cross_mesh +from compas_tna.diagrams.diagram_rectangular import create_fan_mesh +from compas_tna.diagrams.diagram_rectangular import create_ortho_mesh +from compas_tna.diagrams.diagram_rectangular import create_parametric_fan_mesh + + +def get_location(message="Select location for pattern corner"): + option = rs.GetString(message, strings=["Origin", "Coordinates", "Point"]) + if not option: + return + + if option == "Origin": + point = (0, 0) + + elif option == "Coordinates": + x = rs.GetReal("X", 0.0, -1000.0, 1000.0) + if x is None: + return + + y = rs.GetReal("Y", 0.0, -1000.0, 1000.0) + if y is None: + return + + point = (x, y) + + elif option == "Point": + point = rs.GetPoint("Point") + if not point: + return + + else: + raise NotImplementedError + + return point[0], point[1] def make_pattern_from_rhinolines() -> Optional[Pattern]: @@ -165,3 +202,266 @@ def make_pattern_from_skeleton() -> Optional[Pattern]: pattern = skeleton.pattern.copy(cls=Pattern) return pattern + + +def make_pattern_from_template() -> Optional[Pattern]: + option = rs.GetString( + message="Template Name", + strings=[ + "Radial", + "RadialSpaced", + "Spiral", + "Cross", + "Fan", + "Ortho", + "Parametric", + ], + ) + if not option: + return + + if option == "Radial": + pattern = make_pattern_radial() + elif option == "RadialSpaced": + pattern = make_pattern_radial_spaced() + elif option == "Spiral": + pattern = make_pattern_spiral() + elif option == "Cross": + pattern = make_pattern_cross() + elif option == "Fan": + pattern = make_pattern_fan() + elif option == "Ortho": + pattern = make_pattern_ortho() + elif option == "Parametric": + pattern = make_pattern_parametric() + else: + NotImplementedError + + return pattern + + +def make_pattern_radial() -> Optional[Pattern]: + center = get_location(message="Select location for pattern center") + if not center: + return + + radius = rs.GetReal("Radius", number=1.0, minimum=0.0) + if not radius: + return + + rings = rs.GetInteger("Rings", 12, 4, 100) + if not rings: + return + + radials = rs.GetInteger("Radials", 24, 12, 100) + if not radials: + return + + oculus = rs.GetReal("Oculus", number=0.3, minimum=0.0) + if not oculus: + return + + pattern = create_circular_radial_mesh( + center=center, + radius=radius, + n_hoops=rings, + n_parallels=radials, + r_oculus=oculus, + ).copy(cls=Pattern) + + return pattern + + +def make_pattern_radial_spaced() -> Optional[Pattern]: + center = get_location(message="Select location for pattern center") + if not center: + return + + radius = rs.GetReal("Radius", number=5.0, minimum=0.0) + if not radius: + return + + rings = rs.GetInteger("Rings", 12, 4, 100) + if not rings: + return + + radials = rs.GetInteger("Radials", 24, 12, 100) + if not radials: + return + + oculus = rs.GetReal("Oculus", number=0.3, minimum=0.0) + if not oculus: + return + + pattern = create_circular_radial_spaced_mesh( + center=center, + radius=radius, + n_hoops=rings, + n_parallels=radials, + r_oculus=oculus, + ).copy(cls=Pattern) + + return pattern + + +def make_pattern_spiral() -> Optional[Pattern]: + center = get_location(message="Select location for pattern center") + if not center: + return + + radius = rs.GetReal("Radius", number=5.0, minimum=0.0) + if not radius: + return + + rings = rs.GetInteger("Rings", 12, 4, 100) + if not rings: + return + + radials = rs.GetInteger("Radials", 24, 12, 100) + if not radials: + return + + oculus = rs.GetReal("Oculus", number=0.3, minimum=0.0) + if not oculus: + return + + pattern = create_circular_spiral_mesh( + center=center, + radius=radius, + n_hoops=rings, + n_parallels=radials, + r_oculus=oculus, + ).copy(cls=Pattern) + + return pattern + + +def make_pattern_cross() -> Optional[Pattern]: + point = get_location() + if not point: + return + + x_size = rs.GetReal("X Size", 10, 0.0, 1000) + if not x_size: + return + + y_size = rs.GetReal("Y Size", 10, 0.0, 1000) + if not y_size: + return + + n = rs.GetInteger("Resolution", 10, 0) + if not n: + return + + x_span = (point[0], point[0] + x_size) + y_span = (point[1], point[1] + y_size) + + pattern = create_cross_mesh( + x_span=x_span, + y_span=y_span, + n=n, + ).copy(cls=Pattern) + + return pattern + + +def make_pattern_fan() -> Optional[Pattern]: + point = get_location() + if not point: + return + + x_size = rs.GetReal("X Size", 10, 0.0, 1000) + if not x_size: + return + + y_size = rs.GetReal("Y Size", 10, 0.0, 1000) + if not y_size: + return + + n_fans = rs.GetInteger("Number of Fans", 10, 2) + if not n_fans: + return + + n_hoops = rs.GetInteger("Number of Hoops", n_fans, 2) + if not n_hoops: + return + + x_span = (point[0], point[0] + x_size) + y_span = (point[1], point[1] + y_size) + + pattern = create_fan_mesh( + x_span=x_span, + y_span=y_span, + n_fans=n_fans, + n_hoops=n_hoops, + ).copy(cls=Pattern) + + return pattern + + +def make_pattern_ortho() -> Optional[Pattern]: + point = get_location() + if not point: + return + + x_size = rs.GetReal("X Size", 10, 0.0, 1000) + if not x_size: + return + + y_size = rs.GetReal("Y Size", 10, 0.0, 1000) + if not y_size: + return + + nx = rs.GetInteger("Number of X Faces", 10, 2) + if not nx: + return + + ny = rs.GetInteger("Number of Y Faces", nx, 2) + if not ny: + return + + x_span = (point[0], point[0] + x_size) + y_span = (point[1], point[1] + y_size) + + pattern = create_ortho_mesh( + x_span=x_span, + y_span=y_span, + nx=nx, + ny=ny, + ).copy(cls=Pattern) + + return pattern + + +def make_pattern_parametric() -> Optional[Pattern]: + point = get_location() + if not point: + return + + x_size = rs.GetReal("X Size", 10, 0.0, 1000) + if not x_size: + return + + y_size = rs.GetReal("Y Size", 10, 0.0, 1000) + if not y_size: + return + + n = rs.GetInteger("Resolution", 10, 2) + if not n: + return + + lambd = rs.GetReal("Lambda Inclination [0-1]", number=0.5, minimum=0.0, maximum=1.0) + if lambd is None: + return + + x_span = (point[0], point[0] + x_size) + y_span = (point[1], point[1] + y_size) + + pattern = create_parametric_fan_mesh( + x_span=x_span, + y_span=y_span, + n=n, + lambd=lambd, + ).copy(cls=Pattern) + + return pattern diff --git a/src/compas_rv/patterns/__init__.py b/src/compas_rv/patterns/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/compas_rv/patterns/circular.py b/src/compas_rv/patterns/circular.py deleted file mode 100644 index 3211bac..0000000 --- a/src/compas_rv/patterns/circular.py +++ /dev/null @@ -1,294 +0,0 @@ -import math - -from compas.geometry import intersection_line_line_xy -from compas_rv.datastructures import Pattern - - -def create_circular_radial_pattern( - center=[5.0, 5.0], - radius=5.0, - discretisation=[8, 20], - r_oculus=0.0, - diagonal=False, - partial_diagonal=False, -) -> Pattern: - """Construct a circular radial FormDiagram with hoops equally spaced in plan. - - Parameters - ---------- - center : list, optional - Planar coordinates of the form-diagram [xc, yc], by default [5.0, 5.0] - radius : float, optional - Radius of the form diagram, by default 5.0 - discretisation : list, optional - Number of hoops, and of parallels of the dome form diagram], by default [8, 20] - r_oculus : float, optional - Value of the radius of the oculus, if no oculus is present should be set to zero, by default 0.0 - diagonal : bool, optional - Activate diagonal in the quads, by default False - partial_diagonal : bool, optional - Activate partial diagonal in the quads, by default False - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - - """ - - xc = center[0] - yc = center[1] - n_radial = discretisation[0] - n_spikes = discretisation[1] - theta = 2 * math.pi / n_spikes - r_div = (radius - r_oculus) / n_radial - lines = [] - - for nr in range(n_radial + 1): - for nc in range(n_spikes): - if (r_oculus + nr * r_div) > 0.0: - # Meridian Elements - xa = xc + (r_oculus + nr * r_div) * math.cos(theta * nc) - xb = xc + (r_oculus + nr * r_div) * math.cos(theta * (nc + 1)) - ya = yc + (r_oculus + nr * r_div) * math.sin(theta * nc) - yb = yc + (r_oculus + nr * r_div) * math.sin(theta * (nc + 1)) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - if nr <= n_radial - 1: - # Radial Elements - xa = xc + (r_oculus + nr * r_div) * math.cos(theta * nc) - xb = xc + (r_oculus + (nr + 1) * r_div) * math.cos(theta * nc) - ya = yc + (r_oculus + nr * r_div) * math.sin(theta * nc) - yb = yc + (r_oculus + (nr + 1) * r_div) * math.sin(theta * nc) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - if diagonal: - for nr in range(n_radial): - for nc in range(n_spikes): - if (r_oculus + nr * r_div) > 0.0: - # Meridian Element i - xa = xc + (r_oculus + nr * r_div) * math.cos(theta * nc) - xb = xc + (r_oculus + nr * r_div) * math.cos(theta * (nc + 1)) - ya = yc + (r_oculus + nr * r_div) * math.sin(theta * nc) - yb = yc + (r_oculus + nr * r_div) * math.sin(theta * (nc + 1)) - - # Meridian Element i + 1 - xa_ = xc + (r_oculus + (nr + 1) * r_div) * math.cos(theta * nc) - xb_ = xc + (r_oculus + (nr + 1) * r_div) * math.cos(theta * (nc + 1)) - ya_ = yc + (r_oculus + (nr + 1) * r_div) * math.sin(theta * nc) - yb_ = yc + (r_oculus + (nr + 1) * r_div) * math.sin(theta * (nc + 1)) - - if partial_diagonal == "right": - if nc + 1 > n_spikes / 2: - lines.append([[xa, ya, 0.0], [xb_, yb_, 0.0]]) - else: - lines.append([[xa_, ya_, 0.0], [xb, yb, 0.0]]) - elif partial_diagonal == "left": - if nc + 1 > n_spikes / 2: - lines.append([[xa_, ya_, 0.0], [xb, yb, 0.0]]) - else: - lines.append([[xa, ya, 0.0], [xb_, yb_, 0.0]]) - elif partial_diagonal == "rotation": - lines.append([[xa, ya, 0.0], [xb_, yb_, 0.0]]) - elif partial_diagonal == "straight": - midx, midy, _ = intersection_line_line_xy([[xa, ya], [xb_, yb_]], [[xa_, ya_], [xb, yb]]) # type: ignore - lines.append([[xa, ya, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb_, yb_, 0.0]]) - lines.append([[xa_, ya_, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb, yb, 0.0]]) - else: - midx = (xa + xa_ + xb + xb_) / 4 - midy = (ya + ya_ + yb + yb_) / 4 - lines.append([[xa, ya, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb_, yb_, 0.0]]) - lines.append([[xa_, ya_, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb, yb, 0.0]]) - - pattern: Pattern = Pattern.from_lines(lines, delete_boundary_face=True) # type: ignore - return pattern - - -def create_circular_radial_spaced_pattern( - center=[5.0, 5.0], - radius=5.0, - discretisation=[8, 20], - r_oculus=0.0, - diagonal=False, - partial_diagonal=False, -) -> Pattern: - """Construct a circular radial FormDiagram with hoops not equally spaced in plan, but equally spaced with regards to the projection on a hemisphere. - - Parameters - ---------- - center : list, optional - Planar coordinates of the form-diagram [xc, yc], by default [5.0, 5.0] - radius : float, optional - Radius of the form diagram, by default 5.0 - discretisation : list, optional - Number of hoops, and of parallels of the dome form diagram], by default [8, 20] - r_oculus : float, optional - Value of the radius of the oculus, if no oculus is present should be set to zero, by default 0.0 - diagonal : bool, optional - Activate diagonal in the quads, by default False - partial_diagonal : bool, optional - Activate partial diagonal in the quads, by default False - - Returns - ------- - form : :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - - """ - xc = center[0] - yc = center[1] - n_radial = discretisation[0] - n_spikes = discretisation[1] - theta = 2 * math.pi / n_spikes - r_div = (radius - r_oculus) / n_radial - lines = [] - - for nr in range(n_radial + 1): - for nc in range(n_spikes): - if (r_oculus + nr) > 0: - # Meridian Elements - xa = xc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.cos(theta * nc) - xb = xc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.cos(theta * (nc + 1)) - ya = yc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.sin(theta * nc) - yb = yc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.sin(theta * (nc + 1)) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - if nr <= n_radial - 1: - # Radial Elements - xa = xc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.cos(theta * nc) - xb = xc + (r_oculus + radius * math.cos((n_radial - (nr + 1)) / n_radial * math.pi / 2)) * math.cos(theta * nc) - ya = yc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.sin(theta * nc) - yb = yc + (r_oculus + radius * math.cos((n_radial - (nr + 1)) / n_radial * math.pi / 2)) * math.sin(theta * nc) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - if diagonal: - for nr in range(n_radial): - for nc in range(n_spikes): - if (r_oculus + nr * r_div) > 0.0: - # Meridian Element i - xa = xc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.cos(theta * nc) - xb = xc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.cos(theta * (nc + 1)) - ya = yc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.sin(theta * nc) - yb = yc + (r_oculus + radius * math.cos((n_radial - nr) / n_radial * math.pi / 2)) * math.sin(theta * (nc + 1)) - - # radius * math.cos((n_radial - (nr + 1)) / n_radial * math.pi / 2) - - # Meridian Element i + 1 - xa_ = xc + (r_oculus + radius * math.cos((n_radial - (nr + 1)) / n_radial * math.pi / 2)) * math.cos(theta * nc) - xb_ = xc + (r_oculus + radius * math.cos((n_radial - (nr + 1)) / n_radial * math.pi / 2)) * math.cos(theta * (nc + 1)) - ya_ = yc + (r_oculus + radius * math.cos((n_radial - (nr + 1)) / n_radial * math.pi / 2)) * math.sin(theta * nc) - yb_ = yc + (r_oculus + radius * math.cos((n_radial - (nr + 1)) / n_radial * math.pi / 2)) * math.sin(theta * (nc + 1)) - if partial_diagonal == "right": - if nc + 1 > n_spikes / 2: - lines.append([[xa, ya, 0.0], [xb_, yb_, 0.0]]) - else: - lines.append([[xa_, ya_, 0.0], [xb, yb, 0.0]]) - elif partial_diagonal == "left": - if nc + 1 > n_spikes / 2: - lines.append([[xa_, ya_, 0.0], [xb, yb, 0.0]]) - else: - lines.append([[xa, ya, 0.0], [xb_, yb_, 0.0]]) - elif partial_diagonal == "straight": - midx, midy, _ = intersection_line_line_xy([[xa, ya], [xb_, yb_]], [[xa_, ya_], [xb, yb]]) # type: ignore - lines.append([[xa, ya, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb_, yb_, 0.0]]) - lines.append([[xa_, ya_, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb, yb, 0.0]]) - else: - midx = (xa + xa_ + xb + xb_) / 4 - midy = (ya + ya_ + yb + yb_) / 4 - lines.append([[xa, ya, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb_, yb_, 0.0]]) - lines.append([[xa_, ya_, 0.0], [midx, midy, 0.0]]) - lines.append([[midx, midy, 0.0], [xb, yb, 0.0]]) - - pattern: Pattern = Pattern.from_lines(lines, delete_boundary_face=True) # type: ignore - return pattern - - -def create_circular_spiral_pattern( - center=[5.0, 5.0], - radius=5.0, - discretisation=[8, 20], - r_oculus=0.0, -) -> Pattern: - """Construct a circular radial FormDiagram with hoops not equally spaced in plan, but equally spaced with regards to the projection on a hemisphere. - - Parameters - ---------- - center : list, optional - Planar coordinates of the form-diagram [xc, yc], by default [5.0, 5.0] - radius : float, optional - Radius of the form diagram, by default 5.0 - discretisation : list, optional - Number of hoops, and of parallels of the dome form diagram], by default [8, 20] - r_oculus : float, optional - Value of the radius of the oculus, if no oculus is present should be set to zero, by default 0.0 - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - - """ - xc = center[0] - yc = center[1] - n_radial = discretisation[0] - n_spikes = discretisation[1] - theta = 2 * math.pi / n_spikes - r_div = (radius - r_oculus) / n_radial - lines = [] - - for nr in range(n_radial + 1): - for nc in range(n_spikes): - if nr > 0.0: # This avoid the center... - if nr % 2 == 0: - # Diagonal to Up - xa = xc + (r_oculus + nr * r_div) * math.cos(theta * nc) - xb = xc + (r_oculus + (nr - 1) * r_div) * math.cos(theta * (nc + 1 / 2)) - ya = yc + (r_oculus + nr * r_div) * math.sin(theta * nc) - yb = yc + (r_oculus + (nr - 1) * r_div) * math.sin(theta * (nc + 1 / 2)) - - # Diagonal to Down - xa_ = xc + (r_oculus + nr * r_div) * math.cos(theta * nc) - xb_ = xc + (r_oculus + (nr - 1) * r_div) * math.cos(theta * (nc - 1 / 2)) - ya_ = yc + (r_oculus + nr * r_div) * math.sin(theta * nc) - yb_ = yc + (r_oculus + (nr - 1) * r_div) * math.sin(theta * (nc - 1 / 2)) - - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - lines.append([[xa_, ya_, 0.0], [xb_, yb_, 0.0]]) - else: - # Diagonal to Up - xa = xc + (r_oculus + nr * r_div) * math.cos(theta * (nc + 1 / 2)) - xb = xc + (r_oculus + (nr - 1) * r_div) * math.cos(theta * (nc + 1)) - ya = yc + (r_oculus + nr * r_div) * math.sin(theta * (nc + 1 / 2)) - yb = yc + (r_oculus + (nr - 1) * r_div) * math.sin(theta * (nc + 1)) - - # Diagonal to Down - xa_ = xc + (r_oculus + nr * r_div) * math.cos(theta * (nc + 1 / 2)) - xb_ = xc + (r_oculus + (nr - 1) * r_div) * math.cos(theta * (nc)) - ya_ = yc + (r_oculus + nr * r_div) * math.sin(theta * (nc + 1 / 2)) - yb_ = yc + (r_oculus + (nr - 1) * r_div) * math.sin(theta * (nc)) - - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - lines.append([[xa_, ya_, 0.0], [xb_, yb_, 0.0]]) - if nr == n_radial: - xa = xc + (r_oculus + nr * r_div) * math.cos(theta * nc) - xb = xc + (r_oculus + nr * r_div) * math.cos(theta * (nc + 1)) - ya = yc + (r_oculus + nr * r_div) * math.sin(theta * nc) - yb = yc + (r_oculus + nr * r_div) * math.sin(theta * (nc + 1)) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - if nr == 0 and r_oculus > 0.0: - # If oculus, this will be the compression ring - xa = xc + (r_oculus) * math.cos(theta * (nc)) - xb = xc + (r_oculus) * math.cos(theta * (nc + 1)) - ya = yc + (r_oculus) * math.sin(theta * (nc)) - yb = yc + (r_oculus) * math.sin(theta * (nc + 1)) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - pattern: Pattern = Pattern.from_lines(lines, delete_boundary_face=True) # type: ignore - return pattern diff --git a/src/compas_rv/patterns/rectangular.py b/src/compas_rv/patterns/rectangular.py deleted file mode 100644 index b5b5778..0000000 --- a/src/compas_rv/patterns/rectangular.py +++ /dev/null @@ -1,603 +0,0 @@ -import math - -from compas.geometry import distance_point_point_xy -from compas.geometry import mirror_points_line -from compas.geometry import rotate_points_xy -from compas_rv.datastructures import Pattern - - -def mirror_4x(line, line_hor, line_ver, lines): - """Helper to mirror an object 4 times.""" - lines.append(line) - a_mirror, b_mirror = mirror_points_line(line, line_hor) - lines.append([a_mirror, b_mirror]) - a_mirror, b_mirror = mirror_points_line([a_mirror, b_mirror], line_ver) - lines.append([a_mirror, b_mirror]) - a_mirror, b_mirror = mirror_points_line(line, line_ver) - lines.append([a_mirror, b_mirror]) - return lines - - -def mirror_8x(line, origin, line_hor, line_ver, lines): - """Helper to mirror an object 8 times.""" - lines = mirror_4x(line, line_hor, line_ver, lines) - rot = rotate_points_xy(line, math.pi / 2, origin=origin) - lines = mirror_4x(rot, line_hor, line_ver, lines) - return lines - - -def append_mirrored_lines(line, list_, line_hor, line_ver): - """Helper to mirror an object 8 times and add to the list""" - mirror_a = mirror_points_line(line, line_hor) - mirror_b = mirror_points_line(mirror_a, line_ver) - mirror_c = mirror_points_line(line, line_ver) - list_.append(mirror_a) - list_.append(mirror_b) - list_.append(mirror_c) - - -def create_cross_pattern( - xy_span=[[0.0, 10.0], [0.0, 10.0]], - discretisation=10, -) -> Pattern: - """Construct a FormDiagram based on cross discretiastion with orthogonal arrangement and diagonal. - - Parameters - ---------- - xy_span : list, optional - List with initial- and end-points of the vault, by default [[0.0, 10.0], [0.0, 10.0]] - discretisation : int, optional - Set the density of the grid in x and y directions, by default 10 - fix : str, optional - Option to select the constrained nodes: 'corners', 'all' are accepted., by default 'corners' - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - - Notes - ---------------------- - Position of the quadrants is as in the schema below: - - Q3 - Q2 Q1 - Q4 - """ - if isinstance(discretisation, list): - discretisation = discretisation[0] - - y1 = float(xy_span[1][1]) - y0 = float(xy_span[1][0]) - x1 = float(xy_span[0][1]) - x0 = float(xy_span[0][0]) - x_span = x1 - x0 - y_span = y1 - y0 - dx = x_span / discretisation - dy = y_span / discretisation - - lines = [] - - for i in range(discretisation + 1): - for j in range(discretisation + 1): - if i < discretisation and j < discretisation: - # Vertical Members: - xa = x0 + dx * i - ya = y0 + dy * j - xb = x0 + dx * (i + 1) - yb = y0 + dy * j - # Horizontal Members: - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * i - yd = y0 + dy * (j + 1) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - if i == j: - # Diagonal Members in + Direction: - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * (i + 1) - yd = y0 + dy * (j + 1) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - if i + j == discretisation: - # Diagonal Members in - Direction: - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * (i - 1) - yd = y0 + dy * (j + 1) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - if i == (discretisation - 1): - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * (i + 1) - yd = y0 + dy * (j - 1) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - else: - if i == discretisation and j < discretisation: - # Vertical Members on last column: - xa = x0 + dx * j - ya = y0 + dy * i - xb = x0 + dx * (j + 1) - yb = y0 + dy * i - # Horizontal Members: - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * i - yd = y0 + dy * (j + 1) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - - pattern: Pattern = Pattern.from_lines(lines, delete_boundary_face=True) # type: ignore - return pattern - - -def create_cross_diagonal_pattern( - xy_span=[[0.0, 10.0], [0.0, 10.0]], - partial_bracing_modules=None, - discretisation=10, -) -> Pattern: - """Construct a FormDiagram based on a mixture of cross and fan discretiastion - - Parameters - ---------- - xy_span : list, optional - List with initial- and end-points of the vault, by default [[0.0, 10.0], [0.0, 10.0]] - discretisation : int, optional - Set the density of the grid in x and y directions, by default 10 - partial_bracing_modules : str, optional - If partial bracing modules are included, by default None - fix : str, optional - Option to select the constrained nodes: 'corners', 'all' are accepted, by default 'corners' - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - - """ - if isinstance(discretisation, list): - discretisation = discretisation[0] - - y1 = float(xy_span[1][1]) - y0 = float(xy_span[1][0]) - x1 = float(xy_span[0][1]) - x0 = float(xy_span[0][0]) - x_span = x1 - x0 - y_span = y1 - y0 - dx = x_span / discretisation - dy = y_span / discretisation - - xc0 = x0 + x_span / 2 - yc0 = y0 + y_span / 2 - - nx = ny = int(discretisation / 2) - if partial_bracing_modules is None: - nstop = 0 - else: - nstop = nx - partial_bracing_modules # Test to stop - - line_hor = [[x0, yc0, 0.0], [xc0, yc0, 0.0]] - line_ver = [[xc0, y0, 0.0], [xc0, yc0, 0.0]] - origin = [xc0, yc0, 0.0] - - lines = [] - - for i in range(nx): - for j in range(ny + 1): - if j <= i: - if i >= nstop and j >= nstop: - # Diagonal Members: - xa = x0 + dx * i - ya = y0 + dy * 1 * (i - j) / (nx - j) + dy * j - xb = x0 + dx * (i + 1) - yb = y0 + dy * 1 * (i - j + 1) / (nx - j) + dy * j - lin = [[xa, ya, 0.0], [xb, yb, 0.0]] - lines = mirror_8x(lin, origin, line_hor, line_ver, lines) - - if i == j and i < nx - 1: - # Main diagonal: - xa = x0 + dx * i - ya = y0 + dy * j - xb = x0 + dx * (i + 1) - yb = y0 + dy * (j + 1) - lin = [[xa, ya, 0.0], [xb, yb, 0.0]] - lines = mirror_4x(lin, line_hor, line_ver, lines) - - # Horizontal Members: - xa = x0 + dx * i - ya = y0 + dy * j - xb = x0 + dx * (i + 1) - yb = y0 + dy * j - lin = [[xa, ya, 0.0], [xb, yb, 0.0]] - lines = mirror_8x(lin, origin, line_hor, line_ver, lines) - - i += 1 - # Vertical Members: - xa = x0 + dx * i - ya = y0 + dy * j - xb = x0 + dx * i - yb = y0 + dy * (j + 1) - - if i >= nstop and j >= nstop: - x_ = xa - y_ = y0 + dy * 1 * (i - j) / (nx - j) + dy * j - lin = [[xa, ya, 0.0], [x_, y_, 0.0]] - lines = mirror_8x(lin, origin, line_hor, line_ver, lines) - lin = [[x_, y_, 0.0], [xb, yb, 0.0]] - lines = mirror_8x(lin, origin, line_hor, line_ver, lines) - else: - lin = [[xa, ya, 0.0], [xb, yb, 0.0]] - lines = mirror_8x(lin, origin, line_hor, line_ver, lines) - - i -= 1 - - pattern: Pattern = Pattern.from_lines(lines, delete_boundary_face=True) # type: ignore - return pattern - - -def create_cross_with_diagonal_pattern( - xy_span=[[0.0, 10.0], [0.0, 10.0]], - discretisation=10, -) -> Pattern: - """Construct a FormDiagram based on cross discretiastion with diagonals. - - Parameters - ---------- - xy_span : list, optional - List with initial- and end-points of the vault, by default [[0.0, 10.0], [0.0, 10.0]] - discretisation : int, optional - Set the density of the grid in x and y directions, by default 10 - fix : str, optional - Option to select the constrained nodes: 'corners', 'all' are accepted, by default 'corners' - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - """ - - if isinstance(discretisation, list): - discretisation = discretisation[0] - - y1 = float(xy_span[1][1]) - y0 = float(xy_span[1][0]) - x1 = float(xy_span[0][1]) - x0 = float(xy_span[0][0]) - x_span = x1 - x0 - y_span = y1 - y0 - dx = x_span / discretisation - dy = y_span / discretisation - - lines = [] - - for i in range(discretisation + 1): - for j in range(discretisation + 1): - if i < discretisation and j < discretisation: - # Hor Members: - xa = x0 + dx * i - ya = y0 + dy * j - xb = x0 + dx * (i + 1) - yb = y0 + dy * j - # Ver Members: - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * i - yd = y0 + dy * (j + 1) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - # lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - if (i < discretisation / 2 and j < discretisation / 2) or (i >= discretisation / 2 and j >= discretisation / 2): - # Diagonal Members in + Direction: - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * (i + 1) - yd = y0 + dy * (j + 1) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - else: - # Diagonal Members in - Direction: - xc = x0 + dx * i - yc = y0 + dy * (j + 1) - xd = x0 + dx * (i + 1) - yd = y0 + dy * j - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - # if i == (discretisation - 1): - # xc = x0 + dx*i - # yc = y0 + dy*j - # xd = x0 + dx*(i + 1) - # yd = y0 + dy*(j - 1) - # lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - else: - if i == discretisation and j < discretisation: - # Vertical Members on last column: - xa = x0 + dx * j - ya = y0 + dy * i - xb = x0 + dx * (j + 1) - yb = y0 + dy * i - # Horizontal Members: - xc = x0 + dx * i - yc = y0 + dy * j - xd = x0 + dx * i - yd = y0 + dy * (j + 1) - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - - pattern: Pattern = Pattern.from_lines(lines, delete_boundary_face=True) # type: ignore - return pattern - - -def create_fan_pattern( - xy_span=[[0.0, 10.0], [0.0, 10.0]], - discretisation=[10, 10], -) -> Pattern: - """Helper to construct a FormDiagram based on fan discretiastion with straight lines to the corners. - - Parameters - ---------- - xy_span : list, optional - List with initial- and end-points of the vault, by default [[0.0, 10.0], [0.0, 10.0]] - discretisation : int, optional - Set the density of the grid in x and y directions, by default 10 - fix : str, optional - Option to select the constrained nodes: 'corners', 'all' are accepted, by default 'corners' - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - """ - - if isinstance(discretisation, int): - discretisation = [discretisation, discretisation] - if discretisation[0] % 2 != 0 or discretisation[1] % 2 != 0: - msg = "Warning!: discretisation of this form diagram has to be even." - raise ValueError(msg) - - y1 = float(xy_span[1][1]) - y0 = float(xy_span[1][0]) - x1 = float(xy_span[0][1]) - x0 = float(xy_span[0][0]) - - x_span = x1 - x0 - y_span = y1 - y0 - xc0 = x0 + x_span / 2 - yc0 = y0 + y_span / 2 - division_x = discretisation[0] - division_y = discretisation[1] - dx = float(x_span / division_x) - dy = float(y_span / division_y) - nx = int(division_x / 2) - ny = int(division_y / 2) - line_hor = [[x0, yc0, 0.0], [xc0, yc0, 0.0]] - line_ver = [[xc0, y0, 0.0], [xc0, yc0, 0.0]] - - lines = [] - - for i in range(nx): - for j in range(ny + 1): - # Diagonal Members: - xa = x0 + dx * i - ya = y0 + dy * j * i / nx - xb = x0 + dx * (i + 1) - yb = y0 + dy * j * (i + 1) / nx - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - a_mirror, b_mirror = mirror_points_line([[xa, ya, 0.0], [xb, yb, 0.0]], line_hor) - lines.append([a_mirror, b_mirror]) - a_mirror, b_mirror = mirror_points_line([a_mirror, b_mirror], line_ver) - lines.append([a_mirror, b_mirror]) - a_mirror, b_mirror = mirror_points_line([[xa, ya, 0.0], [xb, yb, 0.0]], line_ver) - lines.append([a_mirror, b_mirror]) - - xa_ = x0 + dx * j * i / nx - ya_ = y0 + dy * i - xb_ = x0 + dx * j * (i + 1) / nx - yb_ = y0 + dy * (i + 1) - lines.append([[xa_, ya_, 0.0], [xb_, yb_, 0.0]]) - - a_mirror, b_mirror = mirror_points_line([[xa_, ya_, 0.0], [xb_, yb_, 0.0]], line_hor) - lines.append([a_mirror, b_mirror]) - a_mirror, b_mirror = mirror_points_line([a_mirror, b_mirror], line_ver) - lines.append([a_mirror, b_mirror]) - a_mirror, b_mirror = mirror_points_line([[xa_, ya_, 0.0], [xb_, yb_, 0.0]], line_ver) - lines.append([a_mirror, b_mirror]) - - if j < ny: - # Vertical or Horizontal Members: - xc = x0 + dx * (i + 1) - yc = y0 + dy * j * (i + 1) / nx - xd = x0 + dx * (i + 1) - yd = y0 + dy * (j + 1) * (i + 1) / nx - lines.append([[xc, yc, 0.0], [xd, yd, 0.0]]) - - c_mirror, d_mirror = mirror_points_line([[xc, yc, 0.0], [xd, yd, 0.0]], line_hor) - lines.append([c_mirror, d_mirror]) - c_mirror, d_mirror = mirror_points_line([c_mirror, d_mirror], line_ver) - lines.append([c_mirror, d_mirror]) - c_mirror, d_mirror = mirror_points_line([[xc, yc, 0.0], [xd, yd, 0.0]], line_ver) - lines.append([c_mirror, d_mirror]) - - xc_ = x0 + dx * j * (i + 1) / nx - yc_ = y0 + dy * (i + 1) - xd_ = x0 + dx * (j + 1) * (i + 1) / nx - yd_ = y0 + dy * (i + 1) - lines.append([[xc_, yc_, 0.0], [xd_, yd_, 0.0]]) - - c_mirror, d_mirror = mirror_points_line([[xc_, yc_, 0.0], [xd_, yd_, 0.0]], line_hor) - lines.append([c_mirror, d_mirror]) - c_mirror, d_mirror = mirror_points_line([c_mirror, d_mirror], line_ver) - lines.append([c_mirror, d_mirror]) - c_mirror, d_mirror = mirror_points_line([[xc_, yc_, 0.0], [xd_, yd_, 0.0]], line_ver) - lines.append([c_mirror, d_mirror]) - - pattern: Pattern = Pattern.from_lines(lines, delete_boundary_face=True) # type: ignore - return pattern - - -def create_ortho_pattern( - xy_span=[[0.0, 10.0], [0.0, 10.0]], - discretisation=[10, 10], -) -> Pattern: - """Helper to construct a FormDiagram based on a simple orthogonal discretisation. - - Parameters - ---------- - xy_span : list, optional - List with initial- and end-points of the vault, by default [[0.0, 10.0], [0.0, 10.0]] - discretisation : int, optional - Set the density of the grid in x and y directions, by default 10 - fix : str, optional - Option to select the constrained nodes: 'corners', 'all' are accepted, by default 'corners' - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - """ - - if isinstance(discretisation, int): - discretisation = [discretisation, discretisation] - # if discretisation[0] % 2 != 0 or discretisation[1] % 2 != 0: - # msg = "Warning!: discretisation of this form diagram has to be even." - # raise ValueError(msg) - - y1 = float(xy_span[1][1]) - y0 = float(xy_span[1][0]) - x1 = float(xy_span[0][1]) - x0 = float(xy_span[0][0]) - x_span = x1 - x0 - y_span = y1 - y0 - division_x = discretisation[0] - division_y = discretisation[1] - dx = float(x_span / division_x) - dy = float(y_span / division_y) - - vertices = [] - faces = [] - - for j in range(division_y + 1): - for i in range(division_x + 1): - xi = x0 + dx * i - yi = y0 + dy * j - vertices.append([xi, yi, 0.0]) - if i < division_x and j < division_y: - p1 = j * (division_x + 1) + i - p2 = j * (division_x + 1) + i + 1 - p3 = (j + 1) * (division_x + 1) + i + 1 - p4 = (j + 1) * (division_x + 1) + i - face = [p1, p2, p3, p4, p1] - faces.append(face) - print(face) - - pattern: Pattern = Pattern.from_vertices_and_faces(vertices, faces) # type: ignore - return pattern - - -def create_parametric_pattern( - xy_span=[[0.0, 10.0], [0.0, 10.0]], - discretisation=10, - lambd=0.5, -) -> Pattern: - """Create a parametric form diagram based on the inclination lambda of the arches - - Parameters - ---------- - xy_span : [[float, float], [float, float]], optional - List with initial- and end-points of the vault, by default, by default [[0.0, 10.0], [0.0, 10.0]] - discretisation : int, optional - Set the density of the grid in x and y directions, by default 10 - lambd : float, optional - Inclination of the arches in the diagram (0.0 will result in cross and 1.0 in fan diagrams), by default 0.5 - fix : str, optional - Option to select the constrained nodes: 'corners', 'all' are accepted, by default 'corners' - - Returns - ------- - :class:`~compas_tno.diagrams.FormDiagram` - The FormDiagram created. - - Notes - --------- - Diagram implemented after `N. A. Nodargi et al., 2022 `_. - """ - if 0.0 > lambd or lambd > 1.0: - raise ValueError("Lambda should be between 0.0 and 1.0") - - x_span = xy_span[0][1] - xy_span[0][0] - y_span = xy_span[1][1] - xy_span[1][0] - - if abs(x_span - y_span) > 1e-6: - y_span = x_span = 10.0 - x0, x1 = y0, y1 = 0.0, 10.0 - else: - x0, x1 = xy_span[0][0], xy_span[0][1] - y0, y1 = xy_span[1][0], xy_span[1][1] - - xc = (x1 + x0) / 2 - yc = (y1 + y0) / 2 - - xc0 = x0 + x_span / 2 - yc0 = y0 + y_span / 2 - division_x = discretisation - division_y = discretisation - dx = float(x_span / division_x) - dy = float(y_span / division_y) - nx = int(division_x / 2) - line_hor = [[x0, yc0, 0.0], [xc0, yc0, 0.0]] - line_ver = [[xc0, y0, 0.0], [xc0, yc0, 0.0]] - - lines = [] - - for i in range(nx + 1): - j = i - - xa = xc - ya = xc - dy * j - xb = (ya - y0) * (1 - lambd) - yb = (ya - y0) * (1 - lambd) - - if distance_point_point_xy([xa, ya], [xb, yb]): - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - append_mirrored_lines([[xa, ya, 0.0], [xb, yb, 0.0]], lines, line_hor, line_ver) - - if i == 0: - xa = x0 - ya = y0 - - if distance_point_point_xy([xa, ya], [xb, yb]): - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - append_mirrored_lines([[xa, ya, 0.0], [xb, yb, 0.0]], lines, line_hor, line_ver) - - xa = xc - dx * j - xb = xc - dx * j - ya = yc - dy * j - yb = y0 - - if distance_point_point_xy([xa, ya], [xb, yb]): - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - append_mirrored_lines([[xa, ya, 0.0], [xb, yb, 0.0]], lines, line_hor, line_ver) - - xa = yc - dy * j - ya = yc - xb = (xa - x0) * (1 - lambd) - yb = xb * (y1 - y0) / (x1 - x0) + y0 - - if distance_point_point_xy([xa, ya], [xb, yb]): - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - append_mirrored_lines([[xa, ya, 0.0], [xb, yb, 0.0]], lines, line_hor, line_ver) - - xa = xc - dx * j - xb = x0 - ya = yc - dy * j - yb = yc - dy * j - - if distance_point_point_xy([xa, ya], [xb, yb]): - lines.append([[xa, ya, 0.0], [xb, yb, 0.0]]) - - append_mirrored_lines([[xa, ya, 0.0], [xb, yb, 0.0]], lines, line_hor, line_ver) - - # clean_lines = split_intersection_lines(lines)