Skip to content

Commit 0f4b669

Browse files
authored
Merge pull request #43 from ricardoavelino/fill
Add Fill Mesh commands
2 parents 3d74d20 + 2de6bc5 commit 0f4b669

File tree

5 files changed

+89
-28
lines changed

5 files changed

+89
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
* Added comprehensive mesh creation functions in `diagram_rectangular.py`:
3636
* Added circular mesh creation functions in `diagram_circular.py` and corrected oculus and diagonal properties.
3737
* Added arch mesh creation functions in `diagram_arch.py`:
38+
* Added option to add `fill` to envelope. TODO: properly deal with 'pz' summation in future
3839

3940
### Changed
4041

src/compas_tna/envelope/dome.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,18 +225,17 @@ def dome_bound_react(x, y, thk, fixed, center=(5.0, 5.0), radius=5.0):
225225
226226
Returns
227227
-------
228-
b : array
228+
b : array (nb x 2)
229229
The reaction bounds
230230
"""
231-
232231
[xc, yc] = center[:2]
233232
b = zeros((len(fixed), 2))
234233

235234
for i in range(len(fixed)):
236235
i_ = fixed[i]
237236
theta = math.atan2((y[i_] - yc), (x[i_] - xc))
238-
x_ = abs(thk / 2 * math.cos(theta))
239-
y_ = abs(thk / 2 * math.sin(theta))
237+
x_ = thk / 2 * math.cos(theta)
238+
y_ = thk / 2 * math.sin(theta)
240239
b[i, 0] = x_
241240
b[i, 1] = y_
242241

src/compas_tna/envelope/envelope.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
class Envelope(Data):
1111
"""Pure geometric envelope representing masonry structure boundaries."""
1212

13-
def __init__(self, rho: Optional[float] = 20.0, is_parametric: bool = False, **kwargs):
13+
def __init__(self, rho: Optional[float] = 20.0, rho_fill: Optional[float] = 14.0, is_parametric: bool = False, **kwargs):
1414
super().__init__(**kwargs)
1515

1616
self.rho = rho
17+
self.fill = None
18+
self.rho_fill = rho_fill
1719
self._is_parametric = is_parametric
1820

1921
# Computed properties (cached)
@@ -25,6 +27,7 @@ def __init__(self, rho: Optional[float] = 20.0, is_parametric: bool = False, **k
2527
def __data__(self):
2628
data = {}
2729
data["rho"] = self.rho
30+
data["rho_fill"] = self.rho_fill
2831
data["is_parametric"] = self.is_parametric
2932
return data
3033

@@ -89,6 +92,11 @@ def apply_selfweight_to_formdiagram(self, formdiagram: FormDiagram) -> None:
8992

9093
raise NotImplementedError("Implement apply_selfweight_to_formdiagram for specific envelope type.")
9194

95+
def apply_fill_weight_to_formdiagram(self, formdiagram: FormDiagram) -> None:
96+
"""Apply selfweight to the nodes of a form diagram based on the appropriate method."""
97+
98+
raise NotImplementedError("Implement apply_fill_weight_to_formdiagram for specific envelope type.")
99+
92100
def apply_bounds_to_formdiagram(self, formdiagram: FormDiagram) -> None:
93101
"""Apply envelope bounds to a form diagram based on the appropriate method."""
94102

src/compas_tna/envelope/meshenvelope.py

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@
99
from compas_tna.envelope import Envelope
1010

1111

12+
def griddata_project(xy: list[list[float]], xyz_target: list[list[float]], method="linear"):
13+
"""Project a point cloud onto a target point cloud using griddata interpolation.
14+
15+
Parameters
16+
----------
17+
xy : list[list[float]]
18+
The XY coordinates of the points to project.
19+
xyz_target : list[list[float]]
20+
The XYZ coordinates of the target points.
21+
method : str, optional
22+
The method to use for interpolation. Default is "linear".
23+
24+
Returns
25+
-------
26+
list[float]
27+
The projected Z coordinates.
28+
"""
29+
xy = asarray(xy)
30+
xy_target = asarray(xyz_target)[:, :2]
31+
z_target = asarray(xyz_target)[:, 2]
32+
return griddata(xy_target, z_target, xy, method=method).tolist()
33+
34+
1235
def interpolate_middle_mesh(intrados: Mesh, extrados: Mesh) -> Mesh:
1336
"""Interpolate a middle mesh between intrados and extrados meshes.
1437
@@ -30,25 +53,18 @@ def interpolate_middle_mesh(intrados: Mesh, extrados: Mesh) -> Mesh:
3053
# Use the intrados as base topology
3154
middle = intrados.copy()
3255

33-
# Get point clouds for interpolation
34-
intrados_points = asarray(intrados.vertices_attributes("xyz"))
35-
extrados_points = asarray(extrados.vertices_attributes("xyz"))
56+
# Get Z coordinates from both surfaces based on the same XY coordinates
57+
zi = middle.vertices_attribute("z")
58+
ze = griddata_project(middle.vertices_attributes("xy"), extrados.vertices_attributes("xyz"), method="linear")
3659

37-
# Get XY coordinates of middle mesh
38-
middle_xy = asarray(middle.vertices_attributes("xy"))
39-
40-
# Interpolate Z coordinates from both surfaces
41-
zi = griddata(intrados_points[:, :2], intrados_points[:, 2], middle_xy, method="linear")
42-
ze = griddata(extrados_points[:, :2], extrados_points[:, 2], middle_xy, method="linear")
43-
44-
# First loop: set middle Z as average
60+
# First loop: set middle Z as average of intrados and extrados
4561
for i, key in enumerate(middle.vertices()):
4662
middle_z = (zi[i] + ze[i]) / 2.0
4763
middle.vertex_attribute(key, "z", middle_z)
4864

4965
# Second loop: calculate and set thickness using correct normals
5066
for i, key in enumerate(middle.vertices()):
51-
nx, ny, nz = middle.vertex_normal(key)
67+
_, _, nz = middle.vertex_normal(key)
5268
z_diff = ze[i] - zi[i]
5369
if abs(nz) > 0.1:
5470
thickness = abs(z_diff) * abs(nz)
@@ -158,7 +174,6 @@ def project_mesh_to_target_vertica_nearest(mesh: Mesh, target: Mesh) -> None:
158174
mesh.vertex_attributes(vertex, "xyz", new_point)
159175

160176

161-
# TODO: What if the target is a surface and not a mesh?
162177
def project_mesh_to_target_vertical(mesh: Mesh, target: Mesh) -> None:
163178
"""Project a mesh vertically (in Z direction) onto a target mesh.
164179
@@ -174,14 +189,7 @@ def project_mesh_to_target_vertical(mesh: Mesh, target: Mesh) -> None:
174189
None
175190
The mesh is modified in place.
176191
"""
177-
# Get point clouds for interpolation
178-
target_points = asarray(target.vertices_attributes("xyz"))
179-
180-
# Get XY coordinates of middle mesh
181-
mesh_xy = asarray(mesh.vertices_attributes("xy"))
182-
183-
# Interpolate Z coordinates from both surfaces
184-
z_target = griddata(target_points[:, :2], target_points[:, 2], mesh_xy, method="linear").tolist()
192+
z_target = griddata_project(mesh.vertices_attributes("xy"), target.vertices_attributes("xyz"), method="linear")
185193

186194
for key, i in enumerate(mesh.vertices()):
187195
mesh.vertex_attribute(key, "z", z_target[i])
@@ -477,7 +485,7 @@ def compute_area(self) -> float:
477485
return self.middle.area()
478486

479487
# =============================================================================
480-
# TNA-specific operations (accept formdiagram as parameter)
488+
# Loads operations
481489
# =============================================================================
482490

483491
def apply_selfweight_to_formdiagram(self, formdiagram: FormDiagram, normalize=True) -> None:
@@ -539,6 +547,51 @@ def apply_selfweight_to_formdiagram(self, formdiagram: FormDiagram, normalize=Tr
539547

540548
print(f"Selfweight applied to form diagram. Total load: {sum(abs(formdiagram.vertex_attribute(vertex, 'pz')) for vertex in formdiagram.vertices())}")
541549

550+
def apply_fill_weight_to_formdiagram(self, formdiagram: FormDiagram) -> None:
551+
"""Apply fill weight to the nodes of a form diagram based on the fill surface and local thicknesses."""
552+
if self.fill is None or self.extrados is None:
553+
raise ValueError("Fill mesh is not set. Please set the fill mesh and extrados before applying fill weight.")
554+
555+
# Step 2: Copy the form diagram for projection
556+
form_fill = formdiagram.copy() # For upper bound (extrados)
557+
form_ub = formdiagram.copy() # For lower bound (intrados)
558+
form_zero = formdiagram.copy() # For zero bound (extrados)
559+
form_zero.vertices_attribute("z", 0.0)
560+
561+
# Step 3: Project form diagram onto extrados (upper bound)
562+
project_mesh_to_target_vertical(form_fill, self.fill)
563+
project_mesh_to_target_vertical(form_ub, self.extrados)
564+
565+
fill_weight = 0.0
566+
567+
# Step 4: Collect heights and assign to form diagram
568+
for vertex in formdiagram.vertices():
569+
if vertex in form_ub.vertices() and vertex in form_fill.vertices():
570+
# Get z coordinates from projected meshes
571+
_, _, z_fill = form_fill.vertex_coordinates(vertex)
572+
_, _, z_ub = form_ub.vertex_coordinates(vertex)
573+
a0 = form_zero.vertex_area(vertex)
574+
575+
if z_fill < z_ub:
576+
z_fill = z_ub
577+
578+
zdiff = z_fill - z_ub
579+
pz_fill = -a0 * zdiff * self.rho_fill
580+
pz0 = formdiagram.vertex_attribute(vertex, "pz")
581+
formdiagram.vertex_attribute(vertex, "pz", pz_fill + pz0)
582+
fill_weight += abs(pz_fill)
583+
584+
# Store z_fill
585+
formdiagram.vertex_attribute(vertex, "zfill", z_fill)
586+
else:
587+
print(f"Warning: Vertex {vertex} not found in projected meshes")
588+
589+
print(f"Fill weight applied to form diagram. Total load: {fill_weight}")
590+
591+
# =============================================================================
592+
# Envelope and target projection operations
593+
# =============================================================================
594+
542595
def apply_bounds_to_formdiagram(self, formdiagram: FormDiagram) -> None:
543596
"""Apply envelope bounds to a form diagram based on the intrados and extrados surfaces.
544597

src/compas_tna/envelope/parametricenvelope.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def apply_reaction_bounds_to_formdiagram(self, formdiagram: FormDiagram) -> None
208208
None
209209
The FormDiagram is modified in place.
210210
"""
211-
fixed: list[int] = formdiagram.vertices_where({"is_support": True})
211+
fixed: list[int] = list(formdiagram.vertices_where({"is_support": True}))
212212
xy = np.array(formdiagram.vertices_attributes("xy"))
213213
bound_react = self.compute_bound_react(xy[:, 0], xy[:, 1], self.thickness, fixed)
214214
for i, key in enumerate(fixed):

0 commit comments

Comments
 (0)