diff --git a/experiments/EasyEDA/easyeda_api.py b/experiments/EasyEDA/easyeda_api.py index ed446c9b..0cb7172b 100644 --- a/experiments/EasyEDA/easyeda_api.py +++ b/experiments/EasyEDA/easyeda_api.py @@ -1,322 +1,20 @@ -# Author: uPesy -# License: AGPL v3 -# Project: https://github.com/uPesy/easyeda2kicad.py -# Global imports -from dataclasses import dataclass -import logging +import sys import os -import requests -import re -import textwrap -# import pprint -import json 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" -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" +component_id = 'C181094' - -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: + download_easyeda_3d_model(component_id, 'test') + exit(0) +# +if True: a = EasyedaApi() res = a.get_cad_data_of_component(component_id) print(pprint.pformat(res)) @@ -324,8 +22,8 @@ if False: logging.error('Not found') exit(1) c = Easyeda3dModelImporter(res, True) - # print("********************************************") - # print(pprint.pformat(c.__dict__)) + print("********************************************") + print(pprint.pformat(c.__dict__)) with open('model.pkl', 'wb') as file: pickle.dump(c, file) else: