The self-weight caused the cable-net to sag downwards. However, we want to reach a target shape that is higher up and has a slight crease in the middle (see figure). Thus, you will learn how to form find the cable net towards a target geometry by controlling variable force densities.
Procedure
o. Paths
We will build upon the form found mesh data structure from the self-weight form-finding section. We will output the resulting mesh in a new JSON file.
For cleaning, put all the form finding with self-weight from B1. Selfweight into a function.
Since the initial self-weight corresponds to the already form-found shape (not considering the changed force densities yet) the iteration would stop immediately. But make sure the form-finding iteration runs at least once, so add the and not k == 0
# ==============================================================================# Helpers# ==============================================================================...deffofin_selfweight(mesh):# ==============================================================================# Fofin for selfweight# ==============================================================================# define maximum iterations and tolerance for residuals tol =0.001 kmax =10# store previous selfweight loads (zero) loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))# !!! here already not zero anymore but must at least run one iteration# compute selfweight for the current geometryselfweight(mesh)# for all k smaller than kmaxfor k inrange(kmax):# recompute the residuals with difference of selfweight from updated to previous geometry residuals =update_residual(mesh, loads_previous)# stopping criteria if updated residual is smaller than tolerance or at least onceprint('k', k, 'residuals', residuals)if residuals < tol andnot k ==0:print('Convergence!')break# form finding with selfweight loads and geomtry updatefofin(mesh)# store previous selfweight loads (computed before the updated geometry) loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))# recompute selfweight for the updated geometryselfweight(mesh)
# ==============================================================================# Fofin for selfweight# ==============================================================================fofin_selfweight(mesh)# ==============================================================================# Visualize# ==============================================================================baselayer ="CSD2::B2::FormFindingForceDensities"artist =MeshArtist(mesh, layer=baselayer+"::Mesh")artist.clear_layer()artist.draw_vertices(color={vertex: (255, 0, 0) for vertex in mesh.vertices_where({'is_anchor': True})})artist.draw_edges()artist.draw_faces()draw_reactions(mesh, baselayer=baselayer)draw_residuals(mesh, baselayer=baselayer, scale=10)draw_forces(mesh, baselayer=baselayer)draw_loads(mesh, baselayer=baselayer, scale=10)
The geometry is identical to the output of B1. Selfweight. But we want to get closer to our target geometry!
b. Explore Higher Force Densities of All Edges
So we can try what happens if we increase the force densities of all edges. Play around with it!
With doubled force densities in all the edges, we get a geometry that lies in between the initial and target shape (and without a crease yet).
...# ==============================================================================# Variable Force Denisties# ==============================================================================# explore increased force denisties for all edgesmesh.edges_attribute('q', 2)# ==============================================================================# Fofin for selfweight# ==============================================================================fofin_selfweight(mesh)
Visualise the new geometry in a new layer, so that you can compare the difference.
Remind yourself, that every time the entire iterative form-finding procedure under self-weight is computed.
c. Find a Continuous Cable
Find a single continuous cable starting from a starting edge. Select the middle edge at the support kink.
To find out the edge key, check its name in Rhino in the Properties: Objects window. (Alternatively, you could also select the edge in Rhino with a different code snippet).
# ==============================================================================# Single Cable# ==============================================================================# find center cable to create creasecenter_start = (6,120)center_cable = mesh.edge_loop(center_start)
Increase the force densities in the middle cable to introduce the crease. Play with different magnitudes.
# ==============================================================================# Variable Force Denisties# ==============================================================================...# increase force densities of center cable to create creasemesh.edges_attribute('q', 6, keys=center_cable)
Same here, visualise the new geometry in a new layer, so that you can compare the difference.
We have now introduced the crease, however, the cable net is still below the target geometry. We could increase the force density of all edges even more. But this would lead to unnecessary high forces and reaction forces all over. Thus, we want to only increase the force densities in the cable in the short direction. Let's create a list of all edge in the short direction:
e1. Shorter Boundary
First, we have to understand which one is in the shorter direction. We check it on two boundaries starting from a corner vertex:
# ==============================================================================# All Cables in the Short Direction# ==============================================================================# select starting cornercorners =list(mesh.vertices_where({'vertex_degree': 2}))corner = corners[0]# check in which direction the edge is shortercorner_edges = mesh.vertex_neighbors(corner)edgeA = (corner, corner_edges[0])edgeB = (corner, corner_edges[1])loopA = mesh.edge_loop(edgeA)loopB = mesh.edge_loop(edgeB)iflen(loopA)<=len(loopB): loop = loopA start = edgeAelse: loop = loopB start = edgeB
Now from each of the parallel starts, we want all continuous cables:
# get all cables for all parallel startscables = []for start in starts: cable = mesh.edge_loop(start) cables.append(cable)
These cables are edges in a list of lists, so if you want to iterate over them, you must flatten the list down.
from compas.utilities import flatten...# ==============================================================================# Visualize Rhino# ==============================================================================# color the cables in greenedgecolor ={}for edge inflatten(cables):if edge notin mesh.edges(): edge = (edge[1], edge[0]) edgecolor[edge]= (0,255,0)# color the starts redfor edge in starts:if edge notin mesh.edges(): edge = (edge[1], edge[0]) edgecolor[edge]= (255,0,0)baselayer ="CSD2::B2::FormFindingForceDensities_short cables"artist =MeshArtist(mesh, layer=baselayer+"::Mesh")artist.clear_layer()artist.draw_vertices()artist.draw_edges(color=edgecolor)artist.draw()
f. Higher Force Densities in Edge in One Direction
So now, let's set the force densities of these short cables to a higher value:
Remember to use the compas flatten function for the nested list.
# ==============================================================================# Variable Force Denisties# ==============================================================================...# increase force densities more in short direction onlymesh.edges_attribute('q', 3, keys=flatten(cables))
Visualise the results including the forces and export the JSON file.
# ==============================================================================# Fofin for selfweight# ==============================================================================fofin_selfweight(mesh)# ==============================================================================# Visualize# ==============================================================================baselayer ="CSD2::B2::FormFindingForceDensities_qshort_3"artist =MeshArtist(mesh, layer=baselayer+"::Mesh")artist.clear_layer()artist.draw_vertices(color={vertex: (255, 0, 0) for vertex in mesh.vertices_where({'is_anchor': True})})artist.draw_edges()artist.draw_faces()draw_reactions(mesh, baselayer=baselayer)draw_residuals(mesh, baselayer=baselayer, scale=10)draw_forces(mesh, baselayer=baselayer)draw_loads(mesh, baselayer=baselayer, scale=10)# ==============================================================================# Export# ==============================================================================mesh.to_json(FILE_O)