Coverage for JLC2KiCadLib / footprint / footprint.py: 89%
73 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 10:00 +0100
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-04 10:00 +0100
1import json
2import logging
3import os
4from dataclasses import dataclass
6import requests
7from KicadModTree import Footprint, KicadFileHandler, Pad, Text, Translation
9from .footprint_handlers import handlers, mil2mm
12@dataclass
13class FootprintInfo:
14 max_X: float = -10000
15 max_Y: float = -10000
16 min_X: float = 10000
17 min_Y: float = 10000
18 footprint_name: str = ""
19 output_dir: str = ""
20 footprint_lib: str = ""
21 model_base_variable: str = ""
22 model_dir: str = ""
23 origin: tuple = (0, 0)
24 models: str = ""
27def create_footprint(
28 footprint_component_uuid,
29 component_id,
30 footprint_lib,
31 output_dir,
32 model_base_variable,
33 model_dir,
34 skip_existing,
35 models,
36):
37 logging.info("Creating footprint ...")
39 (
40 footprint_name,
41 datasheet_link,
42 footprint_shape,
43 translation,
44 ) = get_footprint_info(footprint_component_uuid)
46 if skip_existing and os.path.isfile(
47 os.path.join(output_dir, footprint_lib, footprint_name + ".kicad_mod")
48 ):
49 logging.info(f"Footprint {footprint_name} already exists, skipping.")
50 return f"{footprint_lib}:{footprint_name}", datasheet_link
52 # init kicad footprint
53 kicad_mod = Footprint(f'"{footprint_name}"')
54 kicad_mod.setDescription(f"{footprint_name} footprint") # TODO Set real description
55 kicad_mod.setTags(f"{footprint_name} footprint {component_id}")
57 footprint_info = FootprintInfo(
58 footprint_name=footprint_name,
59 output_dir=output_dir,
60 footprint_lib=footprint_lib,
61 model_base_variable=model_base_variable,
62 model_dir=model_dir,
63 origin=translation,
64 models=models,
65 )
67 # for each line in data : use the appropriate handler
68 for line in footprint_shape:
69 args = [i for i in line.split("~")] # split and remove empty string in list
70 model = args[0]
71 logging.debug(args)
72 if model not in handlers:
73 logging.warning(f"footprint : model not in handler : {model}")
74 else:
75 handlers.get(model)(args[1:], kicad_mod, footprint_info)
77 if any(
78 isinstance(child, Pad) and child.type == Pad.TYPE_THT
79 for child in kicad_mod.getAllChilds()
80 ):
81 kicad_mod.setAttribute("through_hole")
82 else:
83 kicad_mod.setAttribute("smd")
85 kicad_mod.insert(Translation(-mil2mm(translation[0]), -mil2mm(translation[1])))
87 # Translate the footprint max and min values to the origin
88 footprint_info.max_X -= mil2mm(translation[0])
89 footprint_info.max_Y -= mil2mm(translation[1])
90 footprint_info.min_X -= mil2mm(translation[0])
91 footprint_info.min_Y -= mil2mm(translation[1])
93 # set general values
94 kicad_mod.append(
95 Text(
96 type="reference",
97 text="REF**",
98 at=[
99 (footprint_info.min_X + footprint_info.max_X) / 2,
100 footprint_info.min_Y - 2,
101 ],
102 layer="F.SilkS",
103 )
104 )
105 kicad_mod.append(
106 Text(
107 type="user",
108 text="${REFERENCE}",
109 at=[
110 (footprint_info.min_X + footprint_info.max_X) / 2,
111 (footprint_info.min_Y + footprint_info.max_Y) / 2,
112 ],
113 layer="F.Fab",
114 )
115 )
116 kicad_mod.append(
117 Text(
118 type="value",
119 text=footprint_name,
120 at=[
121 (footprint_info.min_X + footprint_info.max_X) / 2,
122 footprint_info.max_Y + 2,
123 ],
124 layer="F.Fab",
125 )
126 )
128 if not os.path.exists(f"{output_dir}/{footprint_lib}"):
129 os.makedirs(f"{output_dir}/{footprint_lib}")
131 # output kicad model
132 file_handler = KicadFileHandler(kicad_mod)
133 file_handler.writeFile(f"{output_dir}/{footprint_lib}/{footprint_name}.kicad_mod")
134 logging.info(f"Created '{output_dir}/{footprint_lib}/{footprint_name}.kicad_mod'")
136 # return the datasheet link and footprint name to be linked with the symbol
137 return (f"{footprint_lib}:{footprint_name}", datasheet_link)
140def get_footprint_info(footprint_component_uuid):
141 # fetch the component data from easyeda library
142 response = requests.get(
143 f"https://easyeda.com/api/components/{footprint_component_uuid}"
144 )
146 if response.status_code == requests.codes.ok:
147 data = json.loads(response.content.decode())
148 else:
149 logging.error(
150 "create_footprint error. Requests returned with error code "
151 f"{response.status_code}"
152 )
153 return ("", None, "", (0, 0))
155 footprint_shape = data["result"]["dataStr"]["shape"]
156 x = data["result"]["dataStr"]["head"]["x"]
157 y = data["result"]["dataStr"]["head"]["y"]
158 try:
159 datasheet_link = data["result"]["dataStr"]["head"]["c_para"]["link"]
160 except KeyError:
161 datasheet_link = ""
162 logging.warning("Could not retrieve datasheet link from EASYEDA")
164 footprint_name = (
165 data["result"]["title"]
166 .replace(" ", "_")
167 .replace("/", "_")
168 .replace("(", "_")
169 .replace(")", "_")
170 )
172 if not footprint_name:
173 footprint_name = "NoName"
174 logging.warning(
175 "Could not retrieve components information from EASYEDA, default name "
176 "'NoName'."
177 )
179 return (footprint_name, datasheet_link, footprint_shape, (x, y))