Spaces:
Runtime error
Runtime error
File size: 8,071 Bytes
7f0988f a4a063c 9ef9c61 9f8d935 a4a063c 7f0988f a4a063c 7873a7f a4a063c 7873a7f a4a063c ed61678 a4a063c 7873a7f a4a063c 7f0988f a4a063c 7f0988f 9ef9c61 21d8cb3 9ef9c61 9f8d935 9ef9c61 7f0988f 9f8d935 21d8cb3 9f8d935 21d8cb3 9f8d935 21d8cb3 9f8d935 21d8cb3 9f8d935 ed61678 9f8d935 7f0988f 9f8d935 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | import json
import math
import numpy as np
from compas_tna.diagrams import FormDiagram
from compas_tna.envelope import ParametricEnvelope
from compas_tno.analysis import Analysis
from code.vault_shared import crossvault_middle_hc, fanvault_middle_hc, CONFIG
# ----------------------------------------
# 0. Shims and Helpers
# ----------------------------------------
class GeneralVaultEnvelope(ParametricEnvelope):
def __init__(self, x_span, y_span, thickness, hc, vault_type='cross', discretisation=20, **kwargs):
super(GeneralVaultEnvelope, self).__init__(thickness=thickness, is_parametric=True, **kwargs)
self.x_span = x_span
self.y_span = y_span
self.hc = hc
self.vault_type = vault_type
self.discretisation = discretisation
self.middle = None
def update_envelope(self):
from compas_tna.diagrams.diagram_rectangular import create_cross_mesh
n = self.discretisation if isinstance(self.discretisation, int) else self.discretisation[0]
self.middle = create_cross_mesh(x_span=self.x_span, y_span=self.y_span, n=n)
for vertex in self.middle.vertices():
x, y = self.middle.vertex_attributes(vertex, names=["x", "y"])
z = self.compute_middle([x], [y])[0]
self.middle.vertex_attribute(vertex, "z", z)
def compute_middle(self, x, y):
if self.vault_type == 'fan':
return fanvault_middle_hc(x, y, self.x_span, self.y_span, self.hc)
return crossvault_middle_hc(x, y, self.x_span, self.y_span, self.hc)
def compute_bounds(self, x, y, thickness=None):
t = thickness if thickness is not None else self.thickness
z_mid = self.compute_middle(x, y)
ub = z_mid + t/2
lb = z_mid - t/2
return ub, lb
def mesh_to_data(mesh):
return {
'vertices': [mesh.vertex_coordinates(v) for v in mesh.vertices()],
'faces': [mesh.face_vertices(f) for f in mesh.faces()]
}
def diagram_to_wire_data(diagram):
return {
'vertices': [diagram.vertex_coordinates(v) for v in diagram.vertices()],
'edges': [list(edge) for edge in diagram.edges()]
}
def create_custom_fan(x_span, y_span, nx, ny, n_hoops):
from compas.datastructures import Mesh
from compas_tna.diagrams import FormDiagram
x0, x1 = x_span
y0, y1 = y_span
xm, ym = (x0 + x1) / 2, (y0 + y1) / 2
mesh = Mesh()
vertex_map = {}
def get_vertex(mesh, x, y):
key = (round(x, 6), round(y, 6))
if key in vertex_map:
return vertex_map[key]
idx = mesh.add_vertex(x=x, y=y, z=0)
vertex_map[key] = idx
return idx
# Quadrant configurations: (corner, nx, ny)
quads = [
((x0, y0), nx, ny), # Q1: BL
((x1, y0), nx, ny), # Q2: BR
((x1, y1), nx, ny), # Q3: TR
((x0, y1), nx, ny) # Q4: TL
]
for c, qnx, qny in quads:
xc, yc = c
# Boundary points for this fan quadrant
qpts = []
# Directions towards center lines
dx = xm - xc
dy = ym - yc
# Edge 1: Along the boundary where x varies (at y=ym)
# For Q1: (x0, ym) to (xm, ym)
for i in range(qnx + 1):
t = i / qnx
qpts.append((xc + t * dx, ym))
# Edge 2: Along the boundary where y varies (at x=xm)
# From (xm, ym) to (xm, yc)
for i in range(1, qny + 1):
t = i / qny
qpts.append((xm, ym - t * dy))
# Build fan layers (hoops)
prev_hoop = [get_vertex(mesh, xc, yc)] * len(qpts)
for j in range(1, n_hoops + 1):
curr_hoop = []
th = j / n_hoops
for px, py in qpts:
vx = xc + th * (px - xc)
vy = yc + th * (py - yc)
curr_hoop.append(get_vertex(mesh, vx, vy))
# Add faces
for i in range(len(qpts) - 1):
if j == 1:
mesh.add_face([prev_hoop[i], curr_hoop[i+1], curr_hoop[i]])
else:
mesh.add_face([prev_hoop[i], prev_hoop[i+1], curr_hoop[i+1], curr_hoop[i]])
prev_hoop = curr_hoop
return FormDiagram.from_mesh(mesh)
# ----------------------------------------
# 1. Main Simulation Function
# ----------------------------------------
def run_tna_simulation(config=None):
if config is None:
config = CONFIG
xy_span = config['xy_span']
thickness = config['thickness']
max_rise_at_crown = config['max_rise']
vault_type = config['vault_type']
vault = GeneralVaultEnvelope(xy_span[0], xy_span[1], thickness, max_rise_at_crown,
vault_type=vault_type,
discretisation=config['discretisation_level'])
discr_x = config.get('form_discretisation_x', config.get('form_discretisation', 10))
discr_y = config.get('form_discretisation_y', config.get('form_discretisation', 10))
n_hoops = config.get('form_discretisation', 10)
if vault_type == 'fan':
# form = FormDiagram.create_fan(x_span=xy_span[0], y_span=xy_span[1], n_fans=discretisation, n_hoops=discretisation)
form = create_custom_fan(x_span=xy_span[0], y_span=xy_span[1], nx=discr_x, ny=discr_y, n_hoops=n_hoops)
else:
# For cross vault, we still use symmetric n for now unless specifically requested to refactor its topology
n_cross = max(discr_x, discr_y)
form = FormDiagram.create_cross(x_span=xy_span[0], y_span=xy_span[1], n=n_cross)
# Support logic
if config['support_type'] == 'corners':
for vertex in form.vertices():
x, y = form.vertex_attributes(vertex, names=['x', 'y'])
is_corner = False
for cx in xy_span[0]:
for cy in xy_span[1]:
if abs(x - cx) < 1e-6 and abs(y - cy) < 1e-6:
is_corner = True
break
if is_corner:
form.vertex_attribute(vertex, 'is_support', True)
else:
for vertex in form.vertices():
if form.is_vertex_on_boundary(vertex):
form.vertex_attribute(vertex, 'is_support', True)
# Set starting point
for vertex in form.vertices():
x, y = form.vertex_attributes(vertex, names=["x", "y"])
z_mid = vault.compute_middle([x], [y])[0]
form.vertex_attribute(vertex, "z", z_mid)
# Solve Min Thrust
print(f"Solving Min Thrust for {vault_type}...")
form_min = form.copy()
analysis_min = Analysis.create_minthrust_analysis(form_min, vault, printout=False, solver=config['solver'])
analysis_min.apply_selfweight()
analysis_min.apply_envelope()
analysis_min.set_up_optimiser()
analysis_min.run()
# Solve Max Thrust
print(f"Solving Max Thrust for {vault_type}...")
form_max = form.copy()
analysis_max = Analysis.create_maxthrust_analysis(form_max, vault, printout=False, solver=config['solver'])
analysis_max.apply_selfweight()
analysis_max.apply_envelope()
analysis_max.set_up_optimiser()
analysis_max.run()
# Generate intrados/extrados
vault.update_envelope()
intrados = vault.middle.copy()
extrados = vault.middle.copy()
for v in intrados.vertices():
z = intrados.vertex_attribute(v, 'z')
intrados.vertex_attribute(v, 'z', z - thickness/2)
extrados.vertex_attribute(v, 'z', z + thickness/2)
data = {
'intrados': mesh_to_data(intrados),
'extrados': mesh_to_data(extrados),
'thrust_min': diagram_to_wire_data(form_min),
'thrust_max': diagram_to_wire_data(form_max),
'form_min': form_min,
'form_max': form_max
}
return data
if __name__ == '__main__':
f_min, f_max = run_tna_simulation()
# Optional: still export if run as script
f_min.to_json('thrust_min.json')
f_max.to_json('thrust_max.json')
print("Simulated and exported thrust diagrams.")
|