[Experiments][Added] EasyEDA 3D model downloader prototype
Related to #380
This commit is contained in:
parent
676ee9ecd9
commit
bd00731355
|
|
@ -0,0 +1,10 @@
|
|||
# LCSC 3D Models
|
||||
|
||||
This is part of the easyeda2kicad code.
|
||||
The whole code depends on pydantic and typing_extensions.
|
||||
The dependencies didn't even install using pip on Debian 11.6.
|
||||
|
||||
This is the portion of the code needed to download the 3D model for an LCSC component code.
|
||||
The generated WRL has colors, but not correct materials.
|
||||
|
||||
This is all related to the issue #380
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
# Author: uPesy
|
||||
# License: AGPL v3
|
||||
# Project: https://github.com/uPesy/easyeda2kicad.py
|
||||
# Global imports
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import re
|
||||
import textwrap
|
||||
#
|
||||
# import pprint
|
||||
import json
|
||||
|
||||
|
||||
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
|
||||
# 3D model generated by easyeda2kicad.py (https://github.com/uPesy/easyeda2kicad.py)
|
||||
"""
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class EasyedaApi:
|
||||
def __init__(self) -> None:
|
||||
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": "easyeda2kicad v0.6.2",
|
||||
}
|
||||
|
||||
def get_info_from_easyeda_api(self, lcsc_id: str) -> dict:
|
||||
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) -> dict:
|
||||
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) -> 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) -> 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: bool):
|
||||
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
|
||||
)
|
||||
|
||||
if model_3d_info := self.get_3d_model_info(ee_data=ee_data):
|
||||
model_3d: Ee3dModel = 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) -> dict:
|
||||
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) -> dict:
|
||||
|
||||
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) -> list:
|
||||
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 generate_wrl_model(model_3d: Ee3dModel) -> Ki3dModel:
|
||||
materials = get_materials(obj_data=model_3d.raw_obj)
|
||||
vertices = get_vertices(obj_data=model_3d.raw_obj)
|
||||
|
||||
raw_wrl = VRML_HEADER
|
||||
shapes = model_3d.raw_obj.split("usemtl")[1:]
|
||||
for shape in shapes:
|
||||
lines = shape.splitlines()
|
||||
material = materials[lines[0].replace(" ", "")]
|
||||
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{{
|
||||
appearance Appearance {{
|
||||
material Material {{
|
||||
diffuseColor {' '.join(material['diffuse_color'])}
|
||||
specularColor {' '.join(material['specular_color'])}
|
||||
ambientIntensity 0.2
|
||||
transparency {material['transparency']}
|
||||
shininess 0.5
|
||||
}}
|
||||
}}
|
||||
geometry IndexedFaceSet {{
|
||||
ccw TRUE
|
||||
solid FALSE
|
||||
coord DEF co Coordinate {{
|
||||
point [
|
||||
{(", ").join(points)}
|
||||
]
|
||||
}}
|
||||
coordIndex [
|
||||
{"".join(coord_index)}
|
||||
]
|
||||
}}
|
||||
}}"""
|
||||
)
|
||||
|
||||
raw_wrl += shape_str
|
||||
|
||||
logging.error('Aca')
|
||||
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) -> None:
|
||||
if self.output:
|
||||
logging.error('Aca 1')
|
||||
with open(
|
||||
file=f"{lib_path}.3dshapes/{self.output.name}.wrl",
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
) as my_lib:
|
||||
my_lib.write(self.output.raw_wrl)
|
||||
|
||||
|
||||
component_id = 'C181094'
|
||||
a = EasyedaApi()
|
||||
res = a.get_cad_data_of_component(component_id)
|
||||
# print(pprint.pformat(res))
|
||||
c = Easyeda3dModelImporter(res, True)
|
||||
# print(pprint.pformat(c.__dict__))
|
||||
exporter = Exporter3dModelKicad(model_3d=c.output)
|
||||
|
||||
os.makedirs('a.3dshapes', exist_ok=True)
|
||||
exporter.export('a')
|
||||
if exporter.output:
|
||||
filename = f"{exporter.output.name}.wrl"
|
||||
lib_path = "a.3dshapes"
|
||||
logging.error(f"Created 3D model for ID: {component_id}\n"
|
||||
f" 3D model name: {exporter.output.name}\n"
|
||||
f" 3D model path: {os.path.join(lib_path, filename)}")
|
||||
Loading…
Reference in New Issue