Coverage for pyrc \ core \ connecting.py: 6%

109 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-13 16:59 +0200

1# ------------------------------------------------------------------------------- 

2# Copyright (C) 2026 Joel Kimmich, Tim Jourdan 

3# ------------------------------------------------------------------------------ 

4# License 

5# This file is part of PyRC, distributed under GPL-3.0-or-later. 

6# ------------------------------------------------------------------------------ 

7 

8from rtree import index 

9from vpython import color 

10 

11from pyrc.core.components.resistor import Resistor 

12from pyrc.core.resistors import CombinedResistor 

13 

14 

15def connect_cells_with_resistors(cells: list, tolerance: float = 1e-9) -> list[Resistor]: 

16 """ 

17 Connect cells that share touching surfaces with resistor objects using AABB tree. 

18 

19 Parameters 

20 ---------- 

21 cells : list 

22 List of Cell objects to connect 

23 tolerance : float 

24 Floating point tolerance for boundary comparison 

25 

26 Returns 

27 ------- 

28 list : 

29 List of created Resistor objects 

30 """ 

31 

32 resistors = [] 

33 connections = set() 

34 

35 # Build R-tree spatial index with cell bounding boxes (3D) 

36 p = index.Property() 

37 p.dimension = 3 

38 idx = index.Index(properties=p) 

39 

40 for i, cell in enumerate(cells): 

41 bounds = cell.boundaries # [-x, x, -y, y, -z, z] 

42 # rtree expects (minx, miny, minz, maxx, maxy, maxz) 

43 bbox = (bounds[0], bounds[2], bounds[4], bounds[1], bounds[3], bounds[5]) 

44 idx.insert(i, bbox) 

45 

46 # Check each cell against candidates from spatial index 

47 for i, cell1 in enumerate(cells): 

48 bounds1 = cell1.boundaries 

49 

50 # Expand bbox slightly for tolerance to catch touching surfaces 

51 bbox_query = ( 

52 bounds1[0] - tolerance, 

53 bounds1[2] - tolerance, 

54 bounds1[4] - tolerance, 

55 bounds1[1] + tolerance, 

56 bounds1[3] + tolerance, 

57 bounds1[5] + tolerance, 

58 ) 

59 

60 # Query overlapping bounding boxes 

61 for j in idx.intersection(bbox_query): 

62 if j <= i: # Avoid duplicate checks and self-comparison 

63 continue 

64 

65 cell2 = cells[j] 

66 

67 # Check if already connected 

68 connection_id = tuple(sorted([id(cell1), id(cell2)])) 

69 if connection_id in connections: 

70 continue 

71 

72 # Check if cells have touching surfaces 

73 bounds2 = cell2.boundaries 

74 if check_contact_between_cells(bounds1, bounds2, tolerance): 

75 # Create resistor and connect cells 

76 resistor = CombinedResistor() 

77 resistor.double_connect(cell1, cell2) 

78 

79 resistors.append(resistor) 

80 connections.add(connection_id) 

81 

82 return resistors 

83 

84 

85# vibe coded algorithms to get the connected surfaces visualized. 

86# it is not updated for this project yet 

87 

88 

89def check_contact_between_cells(bounds1: list, bounds2: list, tolerance: float | int): 

90 """ 

91 Check if two cells have touching surfaces. 

92 

93 Parameters 

94 ---------- 

95 bounds1 : list 

96 Boundaries of cell1 [-x, x, -y, y, -z, z] 

97 bounds2 : list 

98 Boundaries of cell2 [-x, x, -y, y, -z, z] 

99 tolerance : float 

100 Floating point tolerance 

101 

102 Returns 

103 ------- 

104 bool 

105 True if surfaces touch with overlapping area, False otherwise 

106 """ 

107 

108 # X-axis: check if faces are coplanar and overlap in y,z 

109 if abs(bounds1[1] - bounds2[0]) < tolerance or abs(bounds1[0] - bounds2[1]) < tolerance: 

110 y_overlap = min(bounds1[3], bounds2[3]) - max(bounds1[2], bounds2[2]) 

111 z_overlap = min(bounds1[5], bounds2[5]) - max(bounds1[4], bounds2[4]) 

112 if y_overlap > tolerance and z_overlap > tolerance: 

113 return True 

114 

115 # Y-axis: check if faces are coplanar and overlap in x,z 

116 if abs(bounds1[3] - bounds2[2]) < tolerance or abs(bounds1[2] - bounds2[3]) < tolerance: 

117 x_overlap = min(bounds1[1], bounds2[1]) - max(bounds1[0], bounds2[0]) 

118 z_overlap = min(bounds1[5], bounds2[5]) - max(bounds1[4], bounds2[4]) 

119 if x_overlap > tolerance and z_overlap > tolerance: 

120 return True 

121 

122 # Z-axis: check if faces are coplanar and overlap in x,y 

123 if abs(bounds1[5] - bounds2[4]) < tolerance or abs(bounds1[4] - bounds2[5]) < tolerance: 

124 x_overlap = min(bounds1[1], bounds2[1]) - max(bounds1[0], bounds2[0]) 

125 y_overlap = min(bounds1[3], bounds2[3]) - max(bounds1[2], bounds2[2]) 

126 if x_overlap > tolerance and y_overlap > tolerance: 

127 return True 

128 

129 return False 

130 

131 

132def visualize_contact_area(bounds1: list, bounds2: list, tolerance: float, contact_color=color.red): 

133 """ 

134 Create a thin box and cylinder representing the contact area between two cells. 

135 

136 Parameters 

137 ---------- 

138 bounds1 : list 

139 Boundaries of cell1 [-x, x, -y, y, -z, z] 

140 bounds2 : list 

141 Boundaries of cell2 [-x, x, -y, y, -z, z] 

142 tolerance : float 

143 Floating point tolerance 

144 contact_color : vpython.color 

145 Color for contact area visualization 

146 

147 Returns 

148 ------- 

149 tuple or None 

150 (box, cylinder) representing contact area, or None if no contact 

151 """ 

152 from vpython import box, cylinder, vector 

153 

154 thickness = tolerance * 10 

155 

156 # Calculate overlap ranges once 

157 x_min, x_max = max(bounds1[0], bounds2[0]), min(bounds1[1], bounds2[1]) 

158 y_min, y_max = max(bounds1[2], bounds2[2]), min(bounds1[3], bounds2[3]) 

159 z_min, z_max = max(bounds1[4], bounds2[4]), min(bounds1[5], bounds2[5]) 

160 

161 x_overlap = x_max - x_min 

162 y_overlap = y_max - y_min 

163 z_overlap = z_max - z_min 

164 

165 pos = None 

166 size = None 

167 cyl_axis = None 

168 

169 # X-axis: cell1 +x face touches cell2 -x face 

170 if abs(bounds1[1] - bounds2[0]) < tolerance < y_overlap and z_overlap > tolerance: 

171 pos = vector(bounds1[1], (y_min + y_max) / 2, (z_min + z_max) / 2) 

172 size = vector(thickness, y_overlap, z_overlap) 

173 delta1_x = bounds1[1] - bounds1[0] 

174 delta2_x = bounds2[1] - bounds2[0] 

175 cyl_length = delta1_x / 4 + delta2_x / 4 

176 cyl_axis = vector(cyl_length, 0, 0) 

177 

178 # X-axis: cell1 -x face touches cell2 +x face 

179 elif abs(bounds1[0] - bounds2[1]) < tolerance < y_overlap and z_overlap > tolerance: 

180 pos = vector(bounds1[0], (y_min + y_max) / 2, (z_min + z_max) / 2) 

181 size = vector(thickness, y_overlap, z_overlap) 

182 delta1_x = bounds1[1] - bounds1[0] 

183 delta2_x = bounds2[1] - bounds2[0] 

184 cyl_length = delta1_x / 4 + delta2_x / 4 

185 cyl_axis = vector(-cyl_length, 0, 0) 

186 

187 # Y-axis: cell1 +y face touches cell2 -y face 

188 elif abs(bounds1[3] - bounds2[2]) < tolerance < x_overlap and z_overlap > tolerance: 

189 pos = vector((x_min + x_max) / 2, bounds1[3], (z_min + z_max) / 2) 

190 size = vector(x_overlap, thickness, z_overlap) 

191 delta1_y = bounds1[3] - bounds1[2] 

192 delta2_y = bounds2[3] - bounds2[2] 

193 cyl_length = delta1_y / 4 + delta2_y / 4 

194 cyl_axis = vector(0, cyl_length, 0) 

195 

196 # Y-axis: cell1 -y face touches cell2 +y face 

197 elif abs(bounds1[2] - bounds2[3]) < tolerance < x_overlap and z_overlap > tolerance: 

198 pos = vector((x_min + x_max) / 2, bounds1[2], (z_min + z_max) / 2) 

199 size = vector(x_overlap, thickness, z_overlap) 

200 delta1_y = bounds1[3] - bounds1[2] 

201 delta2_y = bounds2[3] - bounds2[2] 

202 cyl_length = delta1_y / 4 + delta2_y / 4 

203 cyl_axis = vector(0, -cyl_length, 0) 

204 

205 # Z-axis: cell1 +z face touches cell2 -z face 

206 elif abs(bounds1[5] - bounds2[4]) < tolerance < x_overlap and y_overlap > tolerance: 

207 pos = vector((x_min + x_max) / 2, (y_min + y_max) / 2, bounds1[5]) 

208 size = vector(x_overlap, y_overlap, thickness) 

209 delta1_z = bounds1[5] - bounds1[4] 

210 delta2_z = bounds2[5] - bounds2[4] 

211 cyl_length = delta1_z / 4 + delta2_z / 4 

212 cyl_axis = vector(0, 0, cyl_length) 

213 

214 # Z-axis: cell1 -z face touches cell2 +z face 

215 elif abs(bounds1[4] - bounds2[5]) < tolerance < x_overlap and y_overlap > tolerance: 

216 pos = vector((x_min + x_max) / 2, (y_min + y_max) / 2, bounds1[4]) 

217 size = vector(x_overlap, y_overlap, thickness) 

218 delta1_z = bounds1[5] - bounds1[4] 

219 delta2_z = bounds2[5] - bounds2[4] 

220 cyl_length = delta1_z / 4 + delta2_z / 4 

221 cyl_axis = vector(0, 0, -cyl_length) 

222 

223 if pos is not None and size is not None and cyl_axis is not None: 

224 contact_box = box(pos=pos, size=size, color=contact_color, opacity=0.7) 

225 

226 # Calculate cylinder diameter as 1/8 of minimal box dimension (excluding thickness) 

227 box_dims = [abs(s) for s in [size.x, size.y, size.z] if abs(s) > thickness * 2] 

228 cyl_radius = min(box_dims) / 16 if box_dims else thickness 

229 

230 contact_cylinder = cylinder(pos=pos, axis=cyl_axis, radius=cyl_radius, color=contact_color) 

231 

232 return contact_box, contact_cylinder 

233 

234 return None