import math import numpy as np # ---------------------------------------- # 1. Shared Configuration # ---------------------------------------- CONFIG = { 'xy_span': [[0.0, 10.0], [0.0, 10.0]], # Square span for fan vault as per paper 'thickness': 0.2, 'max_rise': 1.5, 'discretisation_level': 40, # For envelope meshes (higher for smoother curves) 'form_discretisation': 10, # Default (legacy) 'form_discretisation_x': 12, # Number of ribs/segments in X direction 'form_discretisation_y': 8, # Number of ribs/segments in Y direction 'solver': 'IPOPT', # Preferred solver 'support_type': 'corners', # 'corners' or 'perimeter' 'vault_type': 'fan' # 'cross' or 'fan' } # ---------------------------------------- # 2. Shared Geometric Logic # ---------------------------------------- def crossvault_middle_hc(x, y, x_span, y_span, hc, tol=1e-6): """ Calculate the z-coordinate of the middle surface of a cross vault using a circular arc quadrant logic. """ x0, x1 = x_span y0, y1 = y_span rx = (x1 - x0) / 2 ry = (y1 - y0) / 2 z = np.zeros(len(x)) for i in range(len(x)): xi, yi = x[i], y[i] xi = max(x0, min(x1, xi)) yi = max(y0, min(y1, yi)) # Quadrant logic for crossvault if yi <= y0 + (y1 - y0) / (x1 - x0) * (xi - x0) + tol and yi >= y1 - (y1 - y0) / (x1 - x0) * (xi - x0) - tol: dx = abs(xi - (x0 + rx)) z[i] = hc * math.sqrt(max(0, 1 - (dx/rx)**2)) elif yi >= y0 + (y1 - y0) / (x1 - x0) * (xi - x0) - tol and yi >= y1 - (y1 - y0) / (x1 - x0) * (xi - x0) - tol: dy = abs(yi - (y0 + ry)) z[i] = hc * math.sqrt(max(0, 1 - (dy/ry)**2)) elif yi >= y0 + (y1 - y0) / (x1 - x0) * (xi - x0) - tol and yi <= y1 - (y1 - y0) / (x1 - x0) * (xi - x0) + tol: dx = abs(xi - (x0 + rx)) z[i] = hc * math.sqrt(max(0, 1 - (dx/rx)**2)) elif yi <= y0 + (y1 - y0) / (x1 - x0) * (xi - x0) + tol and yi <= y1 - (y1 - y0) / (x1 - x0) * (xi - x0) + tol: dy = abs(yi - (y0 + ry)) z[i] = hc * math.sqrt(max(0, 1 - (dy/ry)**2)) return z def fanvault_middle_hc(x, y, x_span, y_span, hc): """ Calculate the z-coordinate of the middle surface of a fan vault. The surface is a union of four surfaces of revolution (one at each corner). """ x0, x1 = x_span y0, y1 = y_span xm = (x0 + x1) / 2 ym = (y0 + y1) / 2 # Distance from corner to center rmax = math.sqrt((xm - x0)**2 + (ym - y0)**2) # Circular profile radius R = (rmax**2 + hc**2) / (2 * hc) z = np.zeros(len(x)) for i in range(len(x)): xi, yi = x[i], y[i] # Find nearest corner if xi <= xm: xc = x0 else: xc = x1 if yi <= ym: yc = y0 else: yc = y1 r = math.sqrt((xi - xc)**2 + (yi - yc)**2) # Profile: reaching hc at rmax # We ensure it doesn't exceed hc by clipping r r_eff = min(r, rmax) # z(r) = sqrt(R^2 - (rmax - r)^2) - (R - hc) z[i] = math.sqrt(max(0, R**2 - (rmax - r_eff)**2)) - (R - hc) return z