Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pip install easyeda2kicad
```bash
# For symbol + footprint + 3d model (with --full argument)
easyeda2kicad --full --lcsc_id=C2040
# For components not listed in public libraries (user-created)
easyeda2kicad --full --uuid=18f70c95488944a99ceedb9505986f4e
# For symbol + footprint + 3d model
easyeda2kicad --symbol --footprint --3d --lcsc_id=C2040
# For symbol + footprint
Expand Down Expand Up @@ -86,6 +88,10 @@ By default, easyeda2kicad will generate a symbol library for Kicad v6.x (.kicad_
easyeda2kicad --symbol --lcsc_id=C2040 --v5
```

If you created your own symbol, footprint or 3D model which is not listed in public libraries, you can retrieve the component by specifying the uuid argument. Find the uuid through the network inspector of your browser when you select a component in your workspace library(thus filed a request). The requested url should be something like this:

https://easyeda.com/api/components/{uuid}?version=6.5.42&uuid={uuid}&datastrid=blah

## 🔗 Add libraries in Kicad

**These are the instructions to add the default easyeda2kicad libraries in Kicad.**
Expand Down
146 changes: 75 additions & 71 deletions easyeda2kicad/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import sys
from textwrap import dedent
from typing import List
import pathlib

from easyeda2kicad import __version__
from easyeda2kicad.easyeda.easyeda_api import EasyedaApi
from easyeda2kicad.easyeda.easyeda_importer import (
Easyeda3dModelImporter,
EasyedaFootprintImporter,
EasyedaSymbolImporter,
selective_get_element_by_dictkey,
selective_choose_among_elements,
)
from easyeda2kicad.easyeda.parameters_easyeda import EeSymbol
from easyeda2kicad.helpers import (
Expand All @@ -25,8 +28,7 @@
from easyeda2kicad.kicad.export_kicad_3d_model import Exporter3dModelKicad
from easyeda2kicad.kicad.export_kicad_footprint import ExporterFootprintKicad
from easyeda2kicad.kicad.export_kicad_symbol import ExporterSymbolKicad
from easyeda2kicad.kicad.parameters_kicad_symbol import KicadVersion

from easyeda2kicad.kicad.parameters_kicad_symbol import KicadVersion, re_sub_pattern_slashes_in_filename, re_sub_replacement_for_slashes_in_filename

def get_parser() -> argparse.ArgumentParser:

Expand All @@ -37,7 +39,8 @@ def get_parser() -> argparse.ArgumentParser:
)
)

parser.add_argument("--lcsc_id", help="LCSC id", required=True, type=str)
parser.add_argument("--lcsc_id", help="LCSC id", required=False, type=str)
parser.add_argument("--uuid", help="Component uuid (particularly for private component)", required=False, type=str)

parser.add_argument(
"--symbol", help="Get symbol of this id", required=False, action="store_true"
Expand Down Expand Up @@ -109,9 +112,13 @@ def get_parser() -> argparse.ArgumentParser:

def valid_arguments(arguments: dict) -> bool:

if not arguments["lcsc_id"].startswith("C"):
if ("lcsc_id" in arguments) and (arguments["lcsc_id"] is not None) and (not arguments["lcsc_id"].startswith("C")):
logging.error("lcsc_id should start by C....")
return False

if (("lcsc_id" not in arguments) or (arguments["lcsc_id"] is None)) and (("uuid" not in arguments) or (arguments["uuid"] is None)):
logging.error("lcsc_id and uuid are both not provided.")
return False

if arguments["full"]:
arguments["symbol"], arguments["footprint"], arguments["3d"] = True, True, True
Expand All @@ -138,67 +145,53 @@ def valid_arguments(arguments: dict) -> bool:
return False

if arguments["output"]:
base_folder = "/".join(arguments["output"].replace("\\", "/").split("/")[:-1])
lib_name = (
arguments["output"]
.replace("\\", "/")
.split("/")[-1]
.split(".lib")[0]
.split(".kicad_sym")[0]
)
pathlibpath_output = pathlib.Path(arguments["output"])
base_folder = pathlibpath_output.parents[0]
lib_name = pathlibpath_output.name
lib_name = lib_name.split(".lib")[0].split(".kicad_sym")[0]

if not os.path.isdir(base_folder):
if not base_folder.is_dir():
logging.error(f"Can't find the folder : {base_folder}")
return False
else:
default_folder = os.path.join(
os.path.expanduser("~"),
"Documents",
"Kicad",
"easyeda2kicad",
)
if not os.path.isdir(default_folder):
os.makedirs(default_folder, exist_ok=True)
default_folder = pathlib.Path.home() / "Documents" / "Kicad" / "easyeda2kicad"
if not default_folder.is_dir():
default_folder.mkdir(parents=True, exist_ok=True)

base_folder = default_folder
lib_name = "easyeda2kicad"
arguments["use_default_folder"] = True

arguments["output"] = f"{base_folder}/{lib_name}"
arguments["output"] = base_folder / lib_name

# Create new footprint folder if it does not exist
if not os.path.isdir(f"{arguments['output']}.pretty"):
os.mkdir(f"{arguments['output']}.pretty")
logging.info(f"Create {lib_name}.pretty footprint folder in {base_folder}")
pathlibpath_footprint = arguments["output"].with_suffix(f".pretty")
if not pathlibpath_footprint.is_dir():
mkdir_res = pathlibpath_footprint.mkdir(parents=True, exist_ok=True)
logging.info(f"Create {lib_name}.pretty footprint folder in {base_folder}: {mkdir_res}")

# Create new 3d model folder if don't exist
if not os.path.isdir(f"{arguments['output']}.3dshapes"):
os.mkdir(f"{arguments['output']}.3dshapes")
logging.info(f"Create {lib_name}.3dshapes 3D model folder in {base_folder}")
pathlibpath_3dmodel = arguments["output"].with_suffix(f".3dshapes")
if not pathlibpath_3dmodel.is_dir():
mkdir_res = pathlibpath_3dmodel.mkdir(parents=True, exist_ok=True)
logging.info(f"Create {lib_name}.3dshapes 3D model folder in {base_folder}: {mkdir_res}")

lib_extension = "kicad_sym" if kicad_version == KicadVersion.v6 else "lib"
if not os.path.isfile(f"{arguments['output']}.{lib_extension}"):
with open(
file=f"{arguments['output']}.{lib_extension}", mode="w+", encoding="utf-8"
) as my_lib:
my_lib.write(
dedent(
"""\
pathlibpath_lib_extension = arguments["output"].with_suffix(f".{lib_extension}")
if not pathlibpath_lib_extension.is_file():
writecontent = dedent("""
(kicad_symbol_lib
(version 20211014)
(generator https://github.com/uPesy/easyeda2kicad.py)
)"""
)
if kicad_version == KicadVersion.v6
else "EESchema-LIBRARY Version 2.4\n#encoding utf-8\n"
)
logging.info(f"Create {lib_name}.{lib_extension} symbol lib in {base_folder}")
)""") if kicad_version == KicadVersion.v6 else "EESchema-LIBRARY Version 2.4\n#encoding utf-8\n"
write_res = pathlibpath_lib_extension.write_text(writecontent, encoding ="utf-8")
logging.info(f"Create {lib_name}.{lib_extension} symbol lib in {base_folder}: {write_res}")

return True


def delete_component_in_symbol_lib(
lib_path: str, component_id: str, component_name: str
lib_path: str|os.PathLike, component_id: str, component_name: str
) -> None:
with open(file=lib_path, encoding="utf-8") as f:
current_lib = f.read()
Expand All @@ -213,7 +206,7 @@ def delete_component_in_symbol_lib(
my_lib.write(new_data)


def fp_already_in_footprint_lib(lib_path: str, package_name: str) -> bool:
def fp_already_in_footprint_lib(lib_path: str|os.PathLike, package_name: str) -> bool:
if os.path.isfile(f"{lib_path}/{package_name}.kicad_mod"):
logging.warning(f"The footprint for this id is already in {lib_path}")
return True
Expand Down Expand Up @@ -243,25 +236,37 @@ def main(argv: List[str] = sys.argv[1:]) -> int:

component_id = arguments["lcsc_id"]
kicad_version = arguments["kicad_version"]
uuid = arguments["uuid"]
is_fetch_from_private_components = (component_id is None and uuid is not None)
sym_lib_ext = "kicad_sym" if kicad_version == KicadVersion.v6 else "lib"

# Get CAD data of the component using easyeda API
api = EasyedaApi()
cad_data = api.get_cad_data_of_component(lcsc_id=component_id)
cad_data = api.get_cad_data_of_component(lcsc_id=component_id, uuid=uuid)
partid = selective_get_element_by_dictkey(cad_data,["lcsc"],None)
partid = selective_get_element_by_dictkey(cad_data,["number"],None)
partids = [partid]
try:
partids.append(selective_get_element_by_dictkey(cad_data["dataStr"]["head"]["c_para"],["Supplier Part"],None))
except Exception as e:
logging.info(f"No private component Supplier Part found for reason {e}.")
partid = selective_choose_among_elements(partids)

# API returned no data
if not cad_data:
logging.error(f"Failed to fetch data from EasyEDA API for part {component_id}")
logging.error(f"Failed to fetch data from EasyEDA API for part {component_id} uuid {uuid}")
return 1

# ---------------- SYMBOL ----------------
pathlibpath_lib_path_symbol = arguments['output'].with_suffix(f".{sym_lib_ext}")
pathlibpath_lib_path_footprint = arguments['output'].with_suffix(f".pretty")
pathlibpath_lib_path_model_3d = arguments['output'].with_suffix(f".3dshapes")
if arguments["symbol"]:
importer = EasyedaSymbolImporter(easyeda_cp_cad_data=cad_data)
easyeda_symbol: EeSymbol = importer.get_symbol()
# print(easyeda_symbol)

is_id_already_in_symbol_lib = id_already_in_symbol_lib(
lib_path=f"{arguments['output']}.{sym_lib_ext}",
lib_path=pathlibpath_lib_path_symbol,
component_name=easyeda_symbol.info.name,
kicad_version=kicad_version,
)
Expand All @@ -273,29 +278,28 @@ def main(argv: List[str] = sys.argv[1:]) -> int:
exporter = ExporterSymbolKicad(
symbol=easyeda_symbol, kicad_version=kicad_version
)
# print(exporter.output)
kicad_symbol_lib = exporter.export(
footprint_lib_name=arguments["output"].split("/")[-1].split(".")[0],
footprint_lib_name=arguments["output"].stem
)

if is_id_already_in_symbol_lib:
update_component_in_symbol_lib_file(
lib_path=f"{arguments['output']}.{sym_lib_ext}",
lib_path=pathlibpath_lib_path_symbol,
component_name=easyeda_symbol.info.name,
component_content=kicad_symbol_lib,
kicad_version=kicad_version,
)
else:
add_component_in_symbol_lib_file(
lib_path=f"{arguments['output']}.{sym_lib_ext}",
lib_path=pathlibpath_lib_path_symbol,
component_content=kicad_symbol_lib,
kicad_version=kicad_version,
)

logging.info(
f"Created Kicad symbol for ID : {component_id}\n"
f"Created Kicad symbol for ID : {partid}\n"
f" Symbol name : {easyeda_symbol.info.name}\n"
f" Library path : {arguments['output']}.{sym_lib_ext}"
f" Library path : {pathlibpath_lib_path_symbol}"
)

# ---------------- FOOTPRINT ----------------
Expand All @@ -304,34 +308,33 @@ def main(argv: List[str] = sys.argv[1:]) -> int:
easyeda_footprint = importer.get_footprint()

is_id_already_in_footprint_lib = fp_already_in_footprint_lib(
lib_path=f"{arguments['output']}.pretty",
lib_path=pathlibpath_lib_path_footprint,
package_name=easyeda_footprint.info.name,
)
if not arguments["overwrite"] and is_id_already_in_footprint_lib:
logging.error("Use --overwrite to replace the older footprint lib")
return 1

ki_footprint = ExporterFootprintKicad(footprint=easyeda_footprint)
footprint_filename = f"{easyeda_footprint.info.name}.kicad_mod"
footprint_path = f"{arguments['output']}.pretty"
model_3d_path = f"{arguments['output']}.3dshapes".replace("\\", "/").replace(
"./", "/"
)
footprint_filename = re.sub(re_sub_pattern_slashes_in_filename, re_sub_replacement_for_slashes_in_filename, easyeda_footprint.info.name)
footprint_filename = f"{footprint_filename}.kicad_mod"
footprint_path = pathlibpath_lib_path_footprint
model_3d_path = pathlibpath_lib_path_model_3d

if arguments.get("use_default_folder"):
if arguments.get("use_default_folder") and os.getenv("EASYEDA2KICAD", default = None) is not None:
model_3d_path = "${EASYEDA2KICAD}/easyeda2kicad.3dshapes"
if arguments["project_relative"]:
model_3d_path = "${KIPRJMOD}" + model_3d_path
if arguments["project_relative"] and os.getenv("KIPRJMOD", default = None) is not None:
model_3d_path = "${KIPRJMOD}" + str(model_3d_path)

ki_footprint.export(
footprint_full_path=f"{footprint_path}/{footprint_filename}",
footprint_full_path=footprint_path/footprint_filename,
model_3d_path=model_3d_path,
)

logging.info(
f"Created Kicad footprint for ID: {component_id}\n"
f"Created Kicad footprint for ID: {partid}\n"
f" Footprint name: {easyeda_footprint.info.name}\n"
f" Footprint path: {os.path.join(footprint_path, footprint_filename)}"
f" Footprint path: {footprint_path/footprint_filename}"
)

# ---------------- 3D MODEL ----------------
Expand All @@ -343,22 +346,23 @@ def main(argv: List[str] = sys.argv[1:]) -> int:
)
exporter.export(lib_path=arguments["output"])
if exporter.output or exporter.output_step:
filename_wrl = f"{exporter.output.name}.wrl"
filename_step = f"{exporter.output.name}.step"
lib_path = f"{arguments['output']}.3dshapes"
cleaned_filename = re.sub(re_sub_pattern_slashes_in_filename, re_sub_replacement_for_slashes_in_filename, exporter.output.name)
filename_wrl = f"{cleaned_filename}.wrl"
filename_step = f"{cleaned_filename}.step"
lib_path = pathlibpath_lib_path_model_3d

logging.info(
f"Created 3D model for ID: {component_id}\n"
f"Created 3D model for ID: {partid}\n"
f" 3D model name: {exporter.output.name}\n"
+ (
" 3D model path (wrl):"
f" {os.path.join(lib_path, filename_wrl)}\n"
f" {lib_path/filename_wrl}\n"
if filename_wrl
else ""
)
+ (
" 3D model path (step):"
f" {os.path.join(lib_path, filename_step)}\n"
f" {lib_path/filename_step}\n"
if filename_step
else ""
)
Expand Down
14 changes: 9 additions & 5 deletions easyeda2kicad/easyeda/easyeda_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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}"
ENDPOINT_3D_MODEL_STEP = "https://modules.easyeda.com/qAxj6KHrDKw4blvCG8QJPs7Y/{uuid}"
API_PRIVATE_COMPONENTS = "https://easyeda.com/api/components/{uuid}?version=6.5.42&uuid={uuid}" #&datastrid=1d3f07aa4e674c7da0bf096e762290e2"
# ENDPOINT_3D_MODEL_STEP found in https://modules.lceda.cn/smt-gl-engine/0.8.22.6032922c/smt-gl-engine.js : points to the bucket containing the step files.

# ------------------------------------------------------------
Expand All @@ -22,8 +23,11 @@ def __init__(self) -> None:
"User-Agent": f"easyeda2kicad v{__version__}",
}

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)
def get_info_from_easyeda_api(self, lcsc_id: str|None, uuid: str|None=None) -> dict:
if lcsc_id is None and uuid is not None:
r = requests.get(url=API_PRIVATE_COMPONENTS.format(uuid=uuid), headers=self.headers)
else:
r = requests.get(url=API_ENDPOINT.format(lcsc_id=lcsc_id), headers=self.headers)
api_response = r.json()

if not api_response or (
Expand All @@ -34,8 +38,8 @@ def get_info_from_easyeda_api(self, lcsc_id: str) -> dict:

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)
def get_cad_data_of_component(self, lcsc_id: str|None, uuid: str|None=None) -> dict:
cp_cad_info = self.get_info_from_easyeda_api(lcsc_id=lcsc_id, uuid=uuid)
if cp_cad_info == {}:
return {}
return cp_cad_info["result"]
Expand All @@ -58,4 +62,4 @@ def get_step_3d_model(self, uuid: str) -> bytes:
if r.status_code != requests.codes.ok:
logging.error(f"No step 3D model data found for uuid:{uuid} on easyeda")
return None
return r.content
return r.content
Loading