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
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 22:17 +0100
1import logging
2import os
3import re
5import requests
6from KicadModTree import Model
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"""
14def mil2mm(data):
15 return float(data) / 3.937
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 ...")
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.
33 response = requests.get(
34 f"https://modules.easyeda.com/qAxj6KHrDKw4blvCG8QJPs7Y/{component_uuid}"
35 )
37 if response.status_code != requests.codes.ok:
38 logging.error("request error, no Step model found")
39 return
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)
51 logging.info(f"STEP model created at {filename}")
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"
69 translationX = (translationX - footprint_info.origin[0]) / 100
70 translationY = -(translationY - footprint_info.origin[1]) / 100
71 translationZ = float(translationZ) / 100
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")
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 ...")
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 ()
103 wrl_content = wrl_header
105 # get material list
106 pattern = "newmtl .*?endmtl"
107 matchs = re.findall(pattern=pattern, string=text, flags=re.DOTALL)
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]
125 materials[material_id] = material
127 # get vertices list
128 pattern = "v (.*?)\n"
129 matchs = re.findall(pattern=pattern, string=text, flags=re.DOTALL)
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 )
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])
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}}"""
189 wrl_content += shape_str
191 ensure_footprint_lib_directories_exist(footprint_info)
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)
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"
218 translationX = (translationX - footprint_info.origin[0]) / 100
219 translationY = -(translationY - footprint_info.origin[1]) / 100
220 translationZ = float(translationZ) / 100
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")
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}")
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 )