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 | class Structure:
"""Legacy dict-based structural graph, used to build and verify equilibrium of hand-authored structures."""
red_color = "#E40714"
blue_color = "#0578BF"
def __init__(self, nodes=None, edges=None):
if nodes is None:
nodes = {}
if edges is None:
edges = {}
self.nodes = nodes
self.edges = edges
def _set_dict_attr(self, dict):
for key, random_value in dict.items():
value = self.kwargs.get(key, random_value)
dict[key] = value
setattr(self, key, value)
def _set_node(
self,
node_idx,
coords,
load,
sem_type,
external_force=None,
support=False,
origin=False,
):
"""Save node data in global node dictionary"""
node_dict = {
"coordinates": coords,
"load": load,
"semantic_type": sem_type,
"support": support,
"origin": origin,
}
if external_force is not None:
node_dict["external_force"] = external_force
self.nodes[node_idx] = node_dict
def _set_edge(self, edge, force, cem_type, sem_type):
"""Save edge data in global edge dictionary"""
start_coords = self.nodes[edge[0]]["coordinates"]
end_coords = self.nodes[edge[1]]["coordinates"]
length = np.abs(np.linalg.norm(end_coords - start_coords))
edge_dict = {
"force": force,
"length": length,
"cem_type": cem_type,
"semantic_type": sem_type,
}
self.edges[edge] = edge_dict
def _are_connected(self, edge1, edge2):
connected = edge1[0] in edge2 or edge1[1] in edge2
return connected
def _force_direction(self, start_node_idx, end_node_idx):
"""Compute the normed direction vector from start node to end node"""
direction = (
self.nodes[end_node_idx]["coordinates"]
- self.nodes[start_node_idx]["coordinates"]
)
if np.linalg.norm(direction) != 0:
direction = direction / np.linalg.norm(direction)
return direction
def _resultant(self, node_idx, load_factor=1.0, ignore_sem_type=None):
"""Compute the resultant of a node"""
# Initialize zero resultant
resultant = np.zeros(3)
# Add load and external force
resultant += load_factor * self.nodes[node_idx]["load"]
if "external_force" in self.nodes[node_idx]:
resultant += self.nodes[node_idx]["external_force"]
# Add force vector for all connected edges
for edge, attributes in self.edges.items():
if node_idx in edge:
if (
ignore_sem_type is not None
and self.edges[edge]["semantic_type"] == ignore_sem_type
):
continue
other_node_idx = edge[1] if edge[0] == node_idx else edge[0]
direction = self._force_direction(node_idx, other_node_idx)
force_vector = direction * attributes["force"]
resultant += force_vector
return resultant
def _verify_equilibrium(self, threshold=1e-3, verbose=False):
"""Verify global and local equilibrium"""
equilibrium = True
total_external_force = np.zeros(3)
for node_idx in self.nodes:
# Verify global equilibrium
total_external_force += self.nodes[node_idx]["load"]
if "external_force" in self.nodes[node_idx]:
total_external_force += self.nodes[node_idx]["external_force"]
# Verify local equilibrium
resultant = self._resultant(node_idx)
if np.linalg.norm(resultant) > threshold:
equilibrium = False
if verbose:
print(f"no node equilibrium in node {node_idx} ({resultant})")
if np.linalg.norm(total_external_force) > threshold:
equilibrium = False
if verbose:
print(f"no global equilibrium ({total_external_force}")
return equilibrium
def load_path(self):
"""Compute the total load path of the structure"""
total_load_path = 0
for edge, attributes in self.edges.items():
load_path = attributes["length"] * np.abs(attributes["force"])
total_load_path += load_path
self.edges[edge]["load_path"] = load_path
return total_load_path
def average_member_length(self):
"""Compute the average member length of the structure"""
total_member_length = 0
for edge, attributes in self.edges.items():
total_member_length += attributes["length"]
average_member_length = total_member_length / len(self.edges)
return average_member_length
|