Coverage for JLC2KiCadLib / footprint / model3d.py: 88%

106 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-04 22:17 +0100

1import logging 

2import os 

3import re 

4 

5import requests 

6from KicadModTree import Model 

7 

8wrl_header = """#VRML V2.0 utf8 

9#created by JLC2KiCad_lib using the JLCPCB library 

10#for more info see https://github.com/TousstNicolas/JLC2KICAD_lib 

11""" 

12 

13 

14def mil2mm(data): 

15 return float(data) / 3.937 

16 

17 

18def get_StepModel( 

19 component_uuid, 

20 footprint_info, 

21 kicad_mod, 

22 translationX, 

23 translationY, 

24 translationZ, 

25 rotation, 

26): 

27 logging.info("Downloading STEP Model ...") 

28 

29 # `qAxj6KHrDKw4blvCG8QJPs7Y` is a constant in 

30 # https://modules.lceda.cn/smt-gl-engine/0.8.22.6032922c/smt-gl-engine.js 

31 # and points to the bucket containing the step files. 

32 

33 response = requests.get( 

34 f"https://modules.easyeda.com/qAxj6KHrDKw4blvCG8QJPs7Y/{component_uuid}" 

35 ) 

36 

37 if response.status_code != requests.codes.ok: 

38 logging.error("request error, no Step model found") 

39 return 

40 

41 ensure_footprint_lib_directories_exist(footprint_info) 

42 filename = ( 

43 f"{footprint_info.output_dir}/" 

44 f"{footprint_info.footprint_lib}/" 

45 f"{footprint_info.model_dir}/" 

46 f"{footprint_info.footprint_name}.step" 

47 ) 

48 with open(filename, "wb") as f: 

49 f.write(response.content) 

50 

51 logging.info(f"STEP model created at {filename}") 

52 

53 if footprint_info.model_base_variable: 

54 if footprint_info.model_base_variable.startswith("$"): 

55 path_name = ( 

56 f'"{footprint_info.model_base_variable}/' 

57 f"{footprint_info.model_dir}/" 

58 f'{footprint_info.footprint_name}.step"' 

59 ) 

60 else: 

61 path_name = ( 

62 f'"$({footprint_info.model_base_variable})/' 

63 f"{footprint_info.model_dir}/" 

64 f'{footprint_info.footprint_name}.step"' 

65 ) 

66 else: 

67 path_name = f"{footprint_info.model_dir}/{footprint_info.footprint_name}.step" 

68 

69 translationX = (translationX - footprint_info.origin[0]) / 100 

70 translationY = -(translationY - footprint_info.origin[1]) / 100 

71 translationZ = float(translationZ) / 100 

72 

73 kicad_mod.append( 

74 Model( 

75 filename=path_name, 

76 at=[translationX, translationY, translationZ], 

77 rotate=[-float(axis_rotation) for axis_rotation in rotation.split(",")], 

78 ) 

79 ) 

80 logging.info(f"added {path_name} to footprint") 

81 

82 

83def get_WrlModel( 

84 component_uuid, 

85 footprint_info, 

86 kicad_mod, 

87 translationX, 

88 translationY, 

89 translationZ, 

90 rotation, 

91): 

92 logging.info("Creating WRL model ...") 

93 

94 response = requests.get( 

95 f"https://easyeda.com/analyzer/api/3dmodel/{component_uuid}" 

96 ) 

97 if response.status_code == requests.codes.ok: 

98 text = response.content.decode() 

99 else: 

100 logging.error("request error, no 3D model found") 

101 return () 

102 

103 wrl_content = wrl_header 

104 

105 # get material list 

106 pattern = "newmtl .*?endmtl" 

107 matchs = re.findall(pattern=pattern, string=text, flags=re.DOTALL) 

108 

109 materials = {} 

110 for match in matchs: 

111 material = {} 

112 material_id = "" 

113 for value in match.split("\n"): 

114 if value[0:6] == "newmtl": 

115 material_id = value.split(" ")[1] 

116 elif value[0:2] == "Ka": 

117 material["ambientColor"] = value.split(" ")[1:] 

118 elif value[0:2] == "Kd": 

119 material["diffuseColor"] = value.split(" ")[1:] 

120 elif value[0:2] == "Ks": 

121 material["specularColor"] = value.split(" ")[1:] 

122 elif value[0] == "d": 

123 material["transparency"] = value.split(" ")[1] 

124 

125 materials[material_id] = material 

126 

127 # get vertices list 

128 pattern = "v (.*?)\n" 

129 matchs = re.findall(pattern=pattern, string=text, flags=re.DOTALL) 

130 

131 vertices = [] 

132 for vertice in matchs: 

133 vertices.append( 

134 " ".join( 

135 [str(round(float(coord) / 2.54, 4)) for coord in vertice.split(" ")] 

136 ) 

137 ) 

138 

139 # get shape list 

140 shapes = text.split("usemtl")[1:] 

141 for shape in shapes: 

142 lines = shape.split("\n") 

143 material = materials[lines[0].replace(" ", "")] 

144 index_counter = 0 

145 link_dict = {} 

146 coordIndex = [] 

147 points = [] 

148 for line in lines[1:]: 

149 if len(line) > 0: 

150 face = [int(index) for index in line.replace("//", "").split(" ")[1:]] 

151 face_index = [] 

152 for index in face: 

153 if index not in link_dict: 

154 link_dict[index] = index_counter 

155 face_index.append(str(index_counter)) 

156 points.append(vertices[index - 1]) 

157 index_counter += 1 

158 else: 

159 face_index.append(str(link_dict[index])) 

160 face_index.append("-1") 

161 coordIndex.append(",".join(face_index) + ",") 

162 points.insert(-1, points[-1]) 

163 

164 shape_str = f""" 

165Shape{{ 

166 appearance Appearance {{ 

167 material Material {{  

168 diffuseColor {" ".join(material["diffuseColor"])}  

169 specularColor {" ".join(material["specularColor"])} 

170 ambientIntensity 0.2 

171 transparency {material["transparency"]} 

172 shininess 0.5 

173 }} 

174 }} 

175 geometry IndexedFaceSet {{ 

176 ccw TRUE  

177 solid FALSE 

178 coord DEF co Coordinate {{ 

179 point [ 

180 {(", ").join(points)} 

181 ] 

182 }} 

183 coordIndex [ 

184 {"".join(coordIndex)} 

185 ] 

186 }} 

187}}""" 

188 

189 wrl_content += shape_str 

190 

191 ensure_footprint_lib_directories_exist(footprint_info) 

192 

193 filename = ( 

194 f"{footprint_info.output_dir}/" 

195 f"{footprint_info.footprint_lib}/" 

196 f"{footprint_info.model_dir}/" 

197 f"{footprint_info.footprint_name}.wrl" 

198 ) 

199 with open(filename, "w") as f: 

200 f.write(wrl_content) 

201 

202 if footprint_info.model_base_variable: 

203 if footprint_info.model_base_variable.startswith("$"): 

204 path_name = ( 

205 f'"{footprint_info.model_base_variable}/' 

206 f"{footprint_info.model_dir}/" 

207 f'{footprint_info.footprint_name}.wrl"' 

208 ) 

209 else: 

210 path_name = ( 

211 f'"$({footprint_info.model_base_variable})/' 

212 f"{footprint_info.model_dir}/" 

213 f'{footprint_info.footprint_name}.wrl"' 

214 ) 

215 else: 

216 path_name = f"{footprint_info.model_dir}/{footprint_info.footprint_name}.wrl" 

217 

218 translationX = (translationX - footprint_info.origin[0]) / 100 

219 translationY = -(translationY - footprint_info.origin[1]) / 100 

220 translationZ = float(translationZ) / 100 

221 

222 # Check if a model has already been added to the footprint to prevent duplicates 

223 if any(isinstance(child, Model) for child in kicad_mod.getAllChilds()): 

224 logging.info("WRL model created at {filename}") 

225 logging.info( 

226 "WRL model was not added to the footprint to prevent duplicates with STEP " 

227 "model" 

228 ) 

229 else: 

230 kicad_mod.append( 

231 Model( 

232 filename=path_name, 

233 at=[translationX, translationY, translationZ], 

234 rotate=[-float(axis_rotation) for axis_rotation in rotation.split(",")], 

235 ) 

236 ) 

237 logging.info(f"added {path_name} to footprint") 

238 

239 

240def ensure_footprint_lib_directories_exist(footprint_info): 

241 if not os.path.exists( 

242 f"{footprint_info.output_dir}/{footprint_info.footprint_lib}" 

243 ): 

244 os.makedirs(f"{footprint_info.output_dir}/{footprint_info.footprint_lib}") 

245 

246 if not os.path.exists( 

247 f"{footprint_info.output_dir}/{footprint_info.footprint_lib}/{footprint_info.model_dir}" 

248 ): 

249 os.makedirs( 

250 f"{footprint_info.output_dir}/{footprint_info.footprint_lib}/{footprint_info.model_dir}" 

251 )