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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ global-exclude *.swp

recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-include easyeda2ato *.py

exclude .bumpversion.cfg
exclude .coveragerc
Expand Down
2 changes: 1 addition & 1 deletion easyeda2kicad/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = "0.7.0"
__version__ = "0.2.4"
__author__ = "uPesy"
__email__ = "[email protected]"
116 changes: 85 additions & 31 deletions easyeda2kicad/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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.atopile.export_ato import ExporterAto, sanitize_name


def get_parser() -> argparse.ArgumentParser:
Expand All @@ -39,6 +40,18 @@ def get_parser() -> argparse.ArgumentParser:

parser.add_argument("--lcsc_id", help="LCSC id", required=True, type=str)

parser.add_argument(
"--ato", help="Get atopile file definition of this id", required=False, action="store_true"
)

parser.add_argument(
"--ato_file_path",
required=False,
metavar="file.ato",
help="Output dir for .ato file",
type=str,
)

parser.add_argument(
"--symbol", help="Get symbol of this id", required=False, action="store_true"
)
Expand Down Expand Up @@ -114,9 +127,9 @@ def valid_arguments(arguments: dict) -> bool:
return False

if arguments["full"]:
arguments["symbol"], arguments["footprint"], arguments["3d"] = True, True, True
arguments["ato"], arguments["symbol"], arguments["footprint"], arguments["3d"] = True, True, True, True

if not any([arguments["symbol"], arguments["footprint"], arguments["3d"]]):
if not any([arguments["ato"], arguments["symbol"], arguments["footprint"], arguments["3d"]]):
logging.error(
"Missing action arguments\n"
" easyeda2kicad --lcsc_id=C2040 --footprint\n"
Expand Down Expand Up @@ -158,7 +171,7 @@ def valid_arguments(arguments: dict) -> bool:
"easyeda2kicad",
)
if not os.path.isdir(default_folder):
os.makedirs(default_folder, exist_ok=True)
os.mkdir(default_folder)

base_folder = default_folder
lib_name = "easyeda2kicad"
Expand Down Expand Up @@ -254,11 +267,49 @@ def main(argv: List[str] = sys.argv[1:]) -> int:
logging.error(f"Failed to fetch data from EasyEDA API for part {component_id}")
return 1


# ---------------- ATOPILE ----------------
if arguments["ato"]:
importer = EasyedaSymbolImporter(easyeda_cp_cad_data=cad_data)
easyeda_symbol: EeSymbol = importer.get_symbol()

component_name=sanitize_name(easyeda_symbol.info.name)
# ato file path should be the the base directory of output argument /elec/src
ato_full_path = f"{arguments['ato_file_path']}/{component_name}.ato"
is_ato_already_in_lib_folder = os.path.isfile(ato_full_path)

if not arguments["overwrite"] and is_ato_already_in_lib_folder:
logging.error("Use --overwrite to update the older ato file")
return 1

footprint_importer = EasyedaFootprintImporter(easyeda_cp_cad_data=cad_data)
easyeda_footprint = footprint_importer.get_footprint()
package_name=easyeda_footprint.info.name

exporter = ExporterAto(
symbol = easyeda_symbol,
component_id = component_id,
component_name = component_name,
footprint = package_name
)

exporter.export(
ato_full_path = ato_full_path
)


logging.info(
f"Created Atopile file for ID : {component_id}\n"
f" Symbol name : {easyeda_symbol.info.name}\n"
f" Library path : {ato_full_path}"
)


# ---------------- SYMBOL ----------------
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}",
Expand All @@ -273,7 +324,7 @@ 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],
)
Expand Down Expand Up @@ -336,33 +387,36 @@ def main(argv: List[str] = sys.argv[1:]) -> int:

# ---------------- 3D MODEL ----------------
if arguments["3d"]:
exporter = Exporter3dModelKicad(
model_3d=Easyeda3dModelImporter(
easyeda_cp_cad_data=cad_data, download_raw_3d_model=True
).output
)
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"

logging.info(
f"Created 3D model for ID: {component_id}\n"
f" 3D model name: {exporter.output.name}\n"
+ (
" 3D model path (wrl):"
f" {os.path.join(lib_path, filename_wrl)}\n"
if filename_wrl
else ""
)
+ (
" 3D model path (step):"
f" {os.path.join(lib_path, filename_step)}\n"
if filename_step
else ""
)
try:
exporter = Exporter3dModelKicad(
model_3d=Easyeda3dModelImporter(
easyeda_cp_cad_data=cad_data, download_raw_3d_model=True
).output
)
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"

logging.info(
f"Created 3D model for ID: {component_id}\n"
f" 3D model name: {exporter.output.name}\n"
+ (
" 3D model path (wrl):"
f" {os.path.join(lib_path, filename_wrl)}\n"
if filename_wrl
else ""
)
+ (
" 3D model path (step):"
f" {os.path.join(lib_path, filename_step)}\n"
if filename_step
else ""
)
)
except Exception as e:
logging.warning(f"Failed to create 3D model for ID: {component_id}\n{e}")

# logging.info(f"3D model: {os.path.join(lib_path, filename)}")

Expand Down
Empty file.
133 changes: 133 additions & 0 deletions easyeda2kicad/atopile/export_ato.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Global imports
import logging
from pathlib import Path
import re

log = logging.getLogger(__name__)

from easyeda2kicad.easyeda.parameters_easyeda import (
EasyedaPinType,
EeSymbol,
)

ee_pin_type_to_ato_pin_type = {
EasyedaPinType.unspecified: None,
EasyedaPinType._input: "input",
EasyedaPinType.output: "output",
EasyedaPinType.bidirectional: "bidirectional",
EasyedaPinType.power: "power",
}
ee_pin_rotation_to_vis_side = {0: "right", 90: "bottom", 180: "left", 270: "top"}


def add_pin_vis(name, pos):
return f"""
- name: {name}
index: 0
private: false
port: {pos}"""


def sanitize_name(name: str) -> str:
# Replace characters based on the replacement_dict
for symbol, replacement in replacement_dict.items():
name = name.replace(symbol, f"_{replacement}_")

# Replace all non-alphanumeric characters (except underscores) with a single underscore
sanitized = re.sub(r'[^a-zA-Z0-9_]+', '_', name)

# Remove any leading, trailing, or multiple consecutive underscores
sanitized = re.sub(r'^_+|_+$|(?<=_)_+', '', sanitized)

# Check if the first character is a digit, and if so, prepend an underscore
if sanitized and sanitized[0].isdigit():
sanitized = f"_{sanitized}"

return sanitized

replacement_dict = {
"+": "plus",
"-": "minus",
"/": "slash",
"*": "star",
"(": "lparen",
")": "rparen",
"[": "lbracket",
"]": "rbracket",
"{": "lbrace",
"}": "rbrace",
"<": "less",
">": "greater",
"=": "equal",
"~": "tilde",
"!": "exclamation",
"@": "at",
"#": "hash",
"$": "dollar",
"%": "percent",
"^": "caret",
"&": "ampersand",
"|": "pipe",
"\\": "backslash",
":": "colon",
";": "semicolon",
"'": "apostrophe",
'"': "quote",
"?": "question",
",": "comma",
".": "period",
" ": "space",
"\t": "tab",
}

def convert_to_ato(
ee_symbol: EeSymbol, component_id: str, component_name: str, footprint: str
) -> str:
ato_str = f"component {sanitize_name(component_name)}:\n"
ato_str += f" # component {component_name}\n"
ato_str += f' footprint = "{footprint}"\n'
ato_str += f' lcsc_id = "{component_id}"\n'
ato_str += " # pins\n"

defined_signals = set()
for ee_pin in ee_symbol.pins:
# clean the signal name from any non-alphanumeric symbols or leading digits
signal = sanitize_name(ee_pin.name.text)

pin = ee_pin.settings.spice_pin_number.replace(" ", "")
#check if the signal name has already been defined

if signal in defined_signals:
#if it has, append the pin number to the signal name
ato_str += f" {signal} ~ pin {pin}\n"
else:
defined_signals.add(signal)
ato_str += f" signal {signal} ~ pin {pin}\n"
ato_pin_type = ee_pin_type_to_ato_pin_type[ee_pin.settings.type]

return ato_str + "\n"


class ExporterAto:
def __init__(self, symbol, component_id: str, component_name: str, footprint: str):
self.input: EeSymbol = symbol
self.output = (
convert_to_ato(
ee_symbol=self.input,
component_id=component_id,
component_name=component_name,
footprint=footprint,
)
if isinstance(self.input, EeSymbol)
else logging.error("Unknown input symbol format")
)

def export(self, ato_full_path: str) -> str:
# Get the directory of the file
ato_dir = Path(ato_full_path).parent
ato_dir.mkdir(parents=True, exist_ok=True)
log.log(level=logging.INFO, msg=ato_full_path)
with open(file=ato_full_path, mode="w", encoding="utf-8") as my_lib:
my_lib.write(self.output)
log.log(level=logging.INFO, msg="ATO file written")

4 changes: 3 additions & 1 deletion easyeda2kicad/kicad/export_kicad_footprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,9 +511,11 @@ def export(self, footprint_full_path: str, model_3d_path: str) -> None:
for text in ki.texts:
ki_lib += KI_TEXT.format(**vars(text))

## models will be automatically copied to ato build directory ##
model_3d_path = "{build_dir}"
if ki.model_3d is not None:
ki_lib += KI_MODEL_3D.format(
file_3d=f"{model_3d_path}/{ki.model_3d.name}.wrl",
file_3d=f"{model_3d_path}/{ki.model_3d.name}.step",
pos_x=ki.model_3d.translation.x,
pos_y=ki.model_3d.translation.y,
pos_z=ki.model_3d.translation.z,
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pre-commit>=2.17.0
pydantic>=2.0.0
requests>2.0.0
pre-commit>=2.17.0
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.7.0
current_version = 0.2.1
commit = True
tag = True

Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
f.write("\n".join(production_dependencies + development_dependencies))

setup(
name="easyeda2kicad",
name="easyeda2ato",
description=(
"A Python script that convert any electronic components from LCSC or EasyEDA to"
" a Kicad library"
),
long_description=long_description,
long_description_content_type="text/markdown",
version="0.7.0",
version="0.2.6",
author="uPesy",
author_email="[email protected]",
url="https://github.com/uPesy/easyeda2kicad.py",
Expand All @@ -31,10 +31,11 @@
license="AGPL-3.0",
py_modules=["easyeda2kicad"],
platforms="any",
packages=find_packages(exclude=["tests", "utils"]),
packages=find_packages(),
package_dir={"easyeda2kicad": "easyeda2kicad"},
entry_points={"console_scripts": ["easyeda2kicad = easyeda2kicad.__main__:main"]},
python_requires=">=3.6",
include_package_data=True,
install_requires=production_dependencies,
extras_require={"dev": development_dependencies},
zip_safe=False,
Expand Down