[Experiments][EasyEDA] Modified the test to use the current code
- Keeps the debug, but uses the same code in KiBot
This commit is contained in:
parent
624f7ff68f
commit
98bdcd29a8
|
|
@ -1,322 +1,20 @@
|
||||||
# Author: uPesy
|
import sys
|
||||||
# License: AGPL v3
|
|
||||||
# Project: https://github.com/uPesy/easyeda2kicad.py
|
|
||||||
# Global imports
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import re
|
|
||||||
import textwrap
|
|
||||||
#
|
|
||||||
import pprint
|
import pprint
|
||||||
import json
|
|
||||||
import pickle
|
import pickle
|
||||||
|
import logging
|
||||||
|
dname = os.path.dirname(os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
|
||||||
|
sys.path.insert(0, dname)
|
||||||
|
|
||||||
|
from kibot.EasyEDA.easyeda_3d import *
|
||||||
|
|
||||||
API_ENDPOINT = "https://easyeda.com/api/products/{lcsc_id}/components?version=6.4.19.5"
|
component_id = 'C181094'
|
||||||
ENDPOINT_3D_MODEL = "https://easyeda.com/analyzer/api/3dmodel/{uuid}"
|
|
||||||
VRML_HEADER = "#VRML V2.0 utf8\n# 3D model generated by KiBot (using easyeda2kicad.py code)\n"
|
|
||||||
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0"
|
|
||||||
|
|
||||||
|
|
||||||
class EasyedaApi:
|
|
||||||
def __init__(self):
|
|
||||||
self.headers = {"Accept-Encoding": "gzip, deflate",
|
|
||||||
"Accept": "application/json, text/javascript, */*; q=0.01",
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
||||||
"User-Agent": USER_AGENT}
|
|
||||||
|
|
||||||
def get_info_from_easyeda_api(self, lcsc_id: str):
|
|
||||||
r = requests.get(url=API_ENDPOINT.format(lcsc_id=lcsc_id), headers=self.headers)
|
|
||||||
api_response = r.json()
|
|
||||||
|
|
||||||
if not api_response or "code" in api_response and api_response["success"] is False:
|
|
||||||
logging.debug(f"{api_response}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def get_cad_data_of_component(self, lcsc_id: str):
|
|
||||||
cp_cad_info = self.get_info_from_easyeda_api(lcsc_id=lcsc_id)
|
|
||||||
if cp_cad_info == {}:
|
|
||||||
return {}
|
|
||||||
return cp_cad_info["result"]
|
|
||||||
|
|
||||||
def get_raw_3d_model_obj(self, uuid: str):
|
|
||||||
r = requests.get(url=ENDPOINT_3D_MODEL.format(uuid=uuid), headers={"User-Agent": self.headers["User-Agent"]})
|
|
||||||
if r.status_code != requests.codes.ok:
|
|
||||||
logging.error(f"No 3D model data found for uuid:{uuid} on easyeda")
|
|
||||||
return None
|
|
||||||
return r.content.decode()
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_mm(dim: float):
|
|
||||||
return float(dim) * 10 * 0.0254
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Ee3dModelBase:
|
|
||||||
x: float = 0.0
|
|
||||||
y: float = 0.0
|
|
||||||
z: float = 0.0
|
|
||||||
|
|
||||||
def convert_to_mm(self) -> None:
|
|
||||||
self.x = convert_to_mm(self.x)
|
|
||||||
self.y = convert_to_mm(self.y)
|
|
||||||
self.z = convert_to_mm(self.z)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Ee3dModel:
|
|
||||||
name: str
|
|
||||||
uuid: str
|
|
||||||
translation: Ee3dModelBase
|
|
||||||
rotation: Ee3dModelBase
|
|
||||||
raw_obj: str = None
|
|
||||||
|
|
||||||
def convert_to_mm(self) -> None:
|
|
||||||
self.translation.convert_to_mm()
|
|
||||||
# self.translation.z = self.translation.z
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Ki3dModelBase:
|
|
||||||
x: float = 0.0
|
|
||||||
y: float = 0.0
|
|
||||||
z: float = 0.0
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Ki3dModel:
|
|
||||||
name: str
|
|
||||||
translation: Ki3dModelBase
|
|
||||||
rotation: Ki3dModelBase
|
|
||||||
raw_wrl: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class Easyeda3dModelImporter:
|
|
||||||
def __init__(self, easyeda_cp_cad_data, download_raw_3d_model=True):
|
|
||||||
self.input = easyeda_cp_cad_data
|
|
||||||
self.download_raw_3d_model = download_raw_3d_model
|
|
||||||
self.output = self.create_3d_model()
|
|
||||||
|
|
||||||
def create_3d_model(self):
|
|
||||||
ee_data = self.input["packageDetail"]["dataStr"]["shape"] if isinstance(self.input, dict) else self.input
|
|
||||||
|
|
||||||
model_3d_info = self.get_3d_model_info(ee_data=ee_data)
|
|
||||||
if model_3d_info:
|
|
||||||
model_3d = self.parse_3d_model_info(info=model_3d_info)
|
|
||||||
if self.download_raw_3d_model:
|
|
||||||
model_3d.raw_obj = EasyedaApi().get_raw_3d_model_obj(uuid=model_3d.uuid)
|
|
||||||
return model_3d
|
|
||||||
|
|
||||||
logging.warning("No 3D model available for this component")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_3d_model_info(self, ee_data: str):
|
|
||||||
for line in ee_data:
|
|
||||||
ee_designator = line.split("~")[0]
|
|
||||||
if ee_designator == "SVGNODE":
|
|
||||||
raw_json = line.split("~")[1:][0]
|
|
||||||
return json.loads(raw_json)["attrs"]
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def parse_3d_model_info(self, info: dict):
|
|
||||||
return Ee3dModel(
|
|
||||||
name=info["title"],
|
|
||||||
uuid=info["uuid"],
|
|
||||||
translation=Ee3dModelBase(
|
|
||||||
x=info["c_origin"].split(",")[0],
|
|
||||||
y=info["c_origin"].split(",")[1],
|
|
||||||
z=info["z"],
|
|
||||||
),
|
|
||||||
rotation=Ee3dModelBase(*(info["c_rotation"].split(","))),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_materials(obj_data: str):
|
|
||||||
|
|
||||||
material_regex = "newmtl .*?endmtl"
|
|
||||||
matchs = re.findall(pattern=material_regex, string=obj_data, flags=re.DOTALL)
|
|
||||||
|
|
||||||
materials = {}
|
|
||||||
for match in matchs:
|
|
||||||
material = {}
|
|
||||||
for value in match.splitlines():
|
|
||||||
if value.startswith("newmtl"):
|
|
||||||
material_id = value.split(" ")[1]
|
|
||||||
elif value.startswith("Ka"):
|
|
||||||
material["ambient_color"] = value.split(" ")[1:]
|
|
||||||
elif value.startswith("Kd"):
|
|
||||||
material["diffuse_color"] = value.split(" ")[1:]
|
|
||||||
elif value.startswith("Ks"):
|
|
||||||
material["specular_color"] = value.split(" ")[1:]
|
|
||||||
elif value.startswith("d"):
|
|
||||||
material["transparency"] = value.split(" ")[1]
|
|
||||||
|
|
||||||
materials[material_id] = material
|
|
||||||
return materials
|
|
||||||
|
|
||||||
|
|
||||||
def get_vertices(obj_data: str):
|
|
||||||
vertices_regex = "v (.*?)\n"
|
|
||||||
matchs = re.findall(pattern=vertices_regex, string=obj_data, flags=re.DOTALL)
|
|
||||||
|
|
||||||
return [" ".join([str(round(float(coord) / 2.54, 4)) for coord in vertice.split(" ")]) for vertice in matchs]
|
|
||||||
|
|
||||||
|
|
||||||
def map_materials(mats):
|
|
||||||
""" An heuristic to map generic colors to the names used by KiCad """
|
|
||||||
mat_map = {}
|
|
||||||
# Look for grey, black and yellow materials.
|
|
||||||
greys = []
|
|
||||||
blacks = []
|
|
||||||
golden = []
|
|
||||||
for id, mat in mats.items():
|
|
||||||
r, g, b = map(float, mat['diffuse_color'])
|
|
||||||
if abs(r-g) < 0.02 and abs(g-b) < 0.02:
|
|
||||||
# Same RGB components
|
|
||||||
if r < 0.97 and r > 0.6:
|
|
||||||
# In the 0.6 - 0.97 range
|
|
||||||
greys.append(id)
|
|
||||||
if r < 0.3 and r > 0.1:
|
|
||||||
blacks.append(id)
|
|
||||||
elif r > 0.8 and g < 0.8 and g > 0.7 and b < 0.5:
|
|
||||||
golden.append(id)
|
|
||||||
if (r == 0 and g == 0 and b == 0) or (r == 1 and g == 1 and b == 1):
|
|
||||||
mat['transparency'] = 1
|
|
||||||
else:
|
|
||||||
mat['transparency'] = 0
|
|
||||||
mat['diffuse_color'] = ('%5.3f' % r, '%5.3f' % g, '%5.3f' % b)
|
|
||||||
mat['specular_color'] = tuple('%5.3f' % float(c) for c in mat['specular_color'])
|
|
||||||
# Use greys for the pins and metal body
|
|
||||||
c_greys = len(greys)
|
|
||||||
if c_greys == 1:
|
|
||||||
mat_map[greys[0]] = 'PIN-01'
|
|
||||||
elif c_greys > 1:
|
|
||||||
# More than one grey, sort by specular level
|
|
||||||
greys = sorted(greys, key=lambda x: mats[x]['specular_color'][0], reverse=True)
|
|
||||||
mat_map[greys[0]] = 'PIN-01'
|
|
||||||
mat_map[greys[1]] = 'MET-01'
|
|
||||||
# Use black for the plastic body
|
|
||||||
c_blacks = len(blacks)
|
|
||||||
if c_blacks == 1:
|
|
||||||
mat_map[blacks[0]] = 'IC-BODY-EPOXY-01'
|
|
||||||
elif c_blacks > 1:
|
|
||||||
blacks = sorted(blacks, key=lambda x: mats[x]['diffuse_color'][0], reverse=True)
|
|
||||||
for c, b in enumerate(blacks):
|
|
||||||
mat_map[b] = 'IC-BODY-EPOXY-%02d' % c+1
|
|
||||||
# Use yellow for golden pins
|
|
||||||
if golden:
|
|
||||||
mat_map[golden[0]] = 'PIN-02'
|
|
||||||
return mat_map
|
|
||||||
|
|
||||||
|
|
||||||
def generate_wrl_model(model_3d: Ee3dModel):
|
|
||||||
materials = get_materials(obj_data=model_3d.raw_obj)
|
|
||||||
vertices = get_vertices(obj_data=model_3d.raw_obj)
|
|
||||||
mat_map = map_materials(materials)
|
|
||||||
|
|
||||||
raw_wrl = VRML_HEADER
|
|
||||||
# Define all the materials
|
|
||||||
for id, mat in materials.items():
|
|
||||||
id = mat_map.get(id, 'MATERIAL_'+id)
|
|
||||||
mat_str = textwrap.dedent(
|
|
||||||
f"""
|
|
||||||
Shape {{
|
|
||||||
appearance Appearance {{
|
|
||||||
material DEF {id} Material {{
|
|
||||||
ambientIntensity 0.2
|
|
||||||
diffuseColor {' '.join(mat['diffuse_color'])}
|
|
||||||
specularColor {' '.join(mat['specular_color'])}
|
|
||||||
shininess 0.5
|
|
||||||
transparency {mat['transparency']}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}"""
|
|
||||||
)
|
|
||||||
raw_wrl += mat_str
|
|
||||||
# Define the shapes
|
|
||||||
shapes = model_3d.raw_obj.split("usemtl")[1:]
|
|
||||||
for shape in shapes:
|
|
||||||
lines = shape.splitlines()
|
|
||||||
material_id = lines[0].replace(" ", "")
|
|
||||||
material_id = mat_map.get(material_id, 'MATERIAL_'+material_id)
|
|
||||||
index_counter = 0
|
|
||||||
link_dict = {}
|
|
||||||
coord_index = []
|
|
||||||
points = []
|
|
||||||
for line in lines[1:]:
|
|
||||||
if len(line) > 0:
|
|
||||||
face = [int(index) for index in line.replace("//", "").split(" ")[1:]]
|
|
||||||
face_index = []
|
|
||||||
for index in face:
|
|
||||||
if index not in link_dict:
|
|
||||||
link_dict[index] = index_counter
|
|
||||||
face_index.append(str(index_counter))
|
|
||||||
points.append(vertices[index - 1])
|
|
||||||
index_counter += 1
|
|
||||||
else:
|
|
||||||
face_index.append(str(link_dict[index]))
|
|
||||||
face_index.append("-1")
|
|
||||||
coord_index.append(",".join(face_index) + ",")
|
|
||||||
points.insert(-1, points[-1])
|
|
||||||
|
|
||||||
shape_str = textwrap.dedent(
|
|
||||||
f"""
|
|
||||||
Shape {{
|
|
||||||
geometry IndexedFaceSet {{
|
|
||||||
ccw TRUE
|
|
||||||
solid FALSE
|
|
||||||
coord DEF co Coordinate {{
|
|
||||||
point [
|
|
||||||
{(", ").join(points)}
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
coordIndex [
|
|
||||||
{"".join(coord_index)}
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
appearance Appearance {{material USE {material_id}}}
|
|
||||||
}}"""
|
|
||||||
)
|
|
||||||
|
|
||||||
raw_wrl += shape_str
|
|
||||||
|
|
||||||
return Ki3dModel(translation=None, rotation=None, name=model_3d.name, raw_wrl=raw_wrl)
|
|
||||||
|
|
||||||
|
|
||||||
class Exporter3dModelKicad:
|
|
||||||
def __init__(self, model_3d: Ee3dModel):
|
|
||||||
self.input = model_3d
|
|
||||||
self.output = generate_wrl_model(model_3d=model_3d) if model_3d and model_3d.raw_obj else None
|
|
||||||
|
|
||||||
def export(self, lib_path: str):
|
|
||||||
if self.output is None:
|
|
||||||
return None
|
|
||||||
fname = os.path.join(lib_path, self.output.name+'.wrl')
|
|
||||||
with open(fname, "w", encoding="utf-8") as my_lib:
|
|
||||||
my_lib.write(self.output.raw_wrl)
|
|
||||||
return fname
|
|
||||||
|
|
||||||
|
|
||||||
def download_easyeda_3d_model(lcsc_id, dest_path):
|
|
||||||
api = EasyedaApi()
|
|
||||||
data = api.get_cad_data_of_component(lcsc_id)
|
|
||||||
if not data:
|
|
||||||
return None
|
|
||||||
importer = Easyeda3dModelImporter(data, True)
|
|
||||||
exporter = Exporter3dModelKicad(model_3d=importer.output)
|
|
||||||
os.makedirs(dest_path, exist_ok=True)
|
|
||||||
exporter.export(dest_path)
|
|
||||||
|
|
||||||
|
|
||||||
component_id = 'C2895617'
|
|
||||||
download_easyeda_3d_model(component_id, 'test')
|
|
||||||
exit(0)
|
|
||||||
#
|
|
||||||
if False:
|
if False:
|
||||||
|
download_easyeda_3d_model(component_id, 'test')
|
||||||
|
exit(0)
|
||||||
|
#
|
||||||
|
if True:
|
||||||
a = EasyedaApi()
|
a = EasyedaApi()
|
||||||
res = a.get_cad_data_of_component(component_id)
|
res = a.get_cad_data_of_component(component_id)
|
||||||
print(pprint.pformat(res))
|
print(pprint.pformat(res))
|
||||||
|
|
@ -324,8 +22,8 @@ if False:
|
||||||
logging.error('Not found')
|
logging.error('Not found')
|
||||||
exit(1)
|
exit(1)
|
||||||
c = Easyeda3dModelImporter(res, True)
|
c = Easyeda3dModelImporter(res, True)
|
||||||
# print("********************************************")
|
print("********************************************")
|
||||||
# print(pprint.pformat(c.__dict__))
|
print(pprint.pformat(c.__dict__))
|
||||||
with open('model.pkl', 'wb') as file:
|
with open('model.pkl', 'wb') as file:
|
||||||
pickle.dump(c, file)
|
pickle.dump(c, file)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue