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

109 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-29 15:57 +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 __future__ import annotations 

9 

10from typing import TYPE_CHECKING 

11 

12from rtree import index 

13from vpython import color 

14 

15from pyrc.core.components.resistor import Resistor 

16from pyrc.core.resistors import CombinedResistor 

17 

18if TYPE_CHECKING: 

19 from pyrc.core.components.templates import Cell 

20 

21 

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

23 """ 

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

25 

26 Existing connections are skipped. 

27 

28 Parameters 

29 ---------- 

30 cells : list 

31 List of Cell objects to connect 

32 tolerance : float, default=1e-9 

33 Floating point tolerance for boundary comparison 

34 

35 Returns 

36 ------- 

37 list : 

38 List of created Resistor objects 

39 """ 

40 

41 resistors = [] 

42 connections = set() 

43 

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

45 p = index.Property() 

46 p.dimension = 3 

47 idx = index.Index(properties=p) 

48 

49 for i, cell in enumerate(cells): 

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

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

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

53 idx.insert(i, bbox) 

54 

55 # Check each cell against candidates from spatial index 

56 for i, cell1 in enumerate(cells): 

57 bounds1 = cell1.boundaries 

58 

59 # Expand bbox slightly for tolerance to catch touching surfaces 

60 bbox_query = ( 

61 bounds1[0] - tolerance, 

62 bounds1[2] - tolerance, 

63 bounds1[4] - tolerance, 

64 bounds1[1] + tolerance, 

65 bounds1[3] + tolerance, 

66 bounds1[5] + tolerance, 

67 ) 

68 

69 # Query overlapping bounding boxes 

70 for j in idx.intersection(bbox_query): 

71 cell2 = cells[j] 

72 

73 # Check if already connected 

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

75 if connection_id in connections: 

76 continue 

77 

78 # Check if cells have touching surfaces 

79 bounds2 = cell2.boundaries 

80 if check_contact_between_cells(bounds1, bounds2, tolerance): 

81 # Create resistor and connect cells 

82 resistor = CombinedResistor() 

83 resistor.double_connect(cell1, cell2) 

84 

85 resistors.append(resistor) 

86 connections.add(connection_id) 

87 

88 return resistors 

89 

90 

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

92# it is not updated for this project yet 

93 

94 

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

96 """ 

97 Check if two cells have touching surfaces. 

98 

99 Parameters 

100 ---------- 

101 bounds1 : list 

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

103 bounds2 : list 

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

105 tolerance : float 

106 Floating point tolerance 

107 

108 Returns 

109 ------- 

110 bool 

111 True if surfaces touch with overlapping area, False otherwise 

112 """ 

113 

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

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

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

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

118 if y_overlap > tolerance and z_overlap > tolerance: 

119 return True 

120 

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

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

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

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

125 if x_overlap > tolerance and z_overlap > tolerance: 

126 return True 

127 

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

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

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

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

132 if x_overlap > tolerance and y_overlap > tolerance: 

133 return True 

134 

135 return False 

136 

137 

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

139 """ 

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

141 

142 Parameters 

143 ---------- 

144 bounds1 : list 

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

146 bounds2 : list 

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

148 tolerance : float 

149 Floating point tolerance 

150 contact_color : vpython.color 

151 Color for contact area visualization 

152 

153 Returns 

154 ------- 

155 tuple or None 

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

157 """ 

158 from vpython import box, cylinder, vector 

159 

160 thickness = tolerance * 10 

161 

162 # Calculate overlap ranges once 

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

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

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

166 

167 x_overlap = x_max - x_min 

168 y_overlap = y_max - y_min 

169 z_overlap = z_max - z_min 

170 

171 pos = None 

172 size = None 

173 cyl_axis = None 

174 

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

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

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

178 size = vector(thickness, y_overlap, z_overlap) 

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

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

181 cyl_length = delta1_x / 4 + delta2_x / 4 

182 cyl_axis = vector(cyl_length, 0, 0) 

183 

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

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

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

187 size = vector(thickness, y_overlap, z_overlap) 

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

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

190 cyl_length = delta1_x / 4 + delta2_x / 4 

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

192 

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

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

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

196 size = vector(x_overlap, thickness, z_overlap) 

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

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

199 cyl_length = delta1_y / 4 + delta2_y / 4 

200 cyl_axis = vector(0, cyl_length, 0) 

201 

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

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

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

205 size = vector(x_overlap, thickness, z_overlap) 

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

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

208 cyl_length = delta1_y / 4 + delta2_y / 4 

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

210 

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

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

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

214 size = vector(x_overlap, y_overlap, thickness) 

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

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

217 cyl_length = delta1_z / 4 + delta2_z / 4 

218 cyl_axis = vector(0, 0, cyl_length) 

219 

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

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

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

223 size = vector(x_overlap, y_overlap, thickness) 

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

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

226 cyl_length = delta1_z / 4 + delta2_z / 4 

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

228 

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

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

231 

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

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

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

235 

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

237 

238 return contact_box, contact_cylinder 

239 

240 return None