1 #include "board.hpp"
2 #include "nlohmann/json.hpp"
3 #include "util.hpp"
4 #include "export_gerber/gerber_export.hpp"
5 #include "export_pdf/export_pdf_board.hpp"
6 #include "export_pnp/export_pnp.hpp"
7 #include "export_step/export_step.hpp"
8 #include "image_3d_exporter_wrapper.hpp"
9 #include "document/document_board.hpp"
10 #include "rules/cache.hpp"
11 #include <podofo/podofo.h>
12 #include "block/block.hpp"
13 #include "board/board.hpp"
14 #include "project/project.hpp"
15 #include "pool/project_pool.hpp"
16 #include "rules/rule_descr.hpp"
17 #include "3d_image_exporter.hpp"
18
19 class BoardWrapper : public horizon::DocumentBoard {
20 public:
21 BoardWrapper(const horizon::Project &prj);
22 horizon::ProjectPool pool;
23 horizon::Block block;
24 horizon::Board board;
get_board()25 horizon::Board *get_board() override
26 {
27 return &board;
28 }
get_block()29 horizon::Block *get_block() override
30 {
31 return █
32 }
get_pool()33 horizon::IPool &get_pool() override
34 {
35 return pool;
36 }
get_pool_caching()37 horizon::IPool &get_pool_caching() override
38 {
39 return pool;
40 }
get_rules()41 horizon::Rules *get_rules() override
42 {
43 return &board.rules;
44 }
get_layer_provider()45 horizon::LayerProvider &get_layer_provider() override
46 {
47 return board;
48 }
get_fab_output_settings()49 horizon::FabOutputSettings &get_fab_output_settings() override
50 {
51 return board.fab_output_settings;
52 }
get_pdf_export_settings()53 horizon::PDFExportSettings &get_pdf_export_settings() override
54 {
55 return board.pdf_export_settings;
56 }
get_step_export_settings()57 horizon::STEPExportSettings &get_step_export_settings() override
58 {
59 return board.step_export_settings;
60 }
get_pnp_export_settings()61 horizon::PnPExportSettings &get_pnp_export_settings() override
62 {
63 return board.pnp_export_settings;
64 }
get_colors()65 horizon::BoardColors &get_colors() override
66 {
67 return board.colors;
68 }
get_grid_settings()69 horizon::GridSettings *get_grid_settings() override
70 {
71 return &board.grid_settings;
72 }
get_version() const73 const horizon::FileVersion &get_version() const override
74 {
75 return board.version;
76 }
77 };
78
create_board_wrapper(const horizon::Project & prj)79 class BoardWrapper *create_board_wrapper(const horizon::Project &prj)
80 {
81 return new BoardWrapper(prj);
82 }
83
BoardWrapper(const horizon::Project & prj)84 BoardWrapper::BoardWrapper(const horizon::Project &prj)
85 : pool(prj.pool_directory, false), block(horizon::Block::new_from_file(prj.get_top_block().block_filename, pool)),
86 board(horizon::Board::new_from_file(prj.board_filename, block, pool))
87 {
88 board.expand();
89 board.update_planes();
90 }
91
92
PyBoard_new(PyTypeObject * type,PyObject * args,PyObject * kwds)93 static PyObject *PyBoard_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
94 {
95 PyBoard *self;
96 self = (PyBoard *)type->tp_alloc(type, 0);
97 if (self != NULL) {
98 self->board = nullptr;
99 }
100 return (PyObject *)self;
101 }
102
PyBoard_dealloc(PyObject * pself)103 static void PyBoard_dealloc(PyObject *pself)
104 {
105 auto self = reinterpret_cast<PyBoard *>(pself);
106 delete self->board;
107 Py_TYPE(self)->tp_free((PyObject *)self);
108 }
109
110
PyBoard_get_gerber_export_settings(PyObject * pself,PyObject * args)111 static PyObject *PyBoard_get_gerber_export_settings(PyObject *pself, PyObject *args)
112 {
113 auto self = reinterpret_cast<PyBoard *>(pself);
114 auto settings = self->board->board.fab_output_settings.serialize();
115 return py_from_json(settings);
116 }
117
PyBoard_export_gerber(PyObject * pself,PyObject * args)118 static PyObject *PyBoard_export_gerber(PyObject *pself, PyObject *args)
119 {
120 auto self = reinterpret_cast<PyBoard *>(pself);
121 PyObject *py_export_settings = nullptr;
122 if (!PyArg_ParseTuple(args, "O!", &PyDict_Type, &py_export_settings))
123 return NULL;
124 try {
125 auto settings_json = json_from_py(py_export_settings);
126 horizon::FabOutputSettings settings(settings_json);
127 horizon::GerberExporter ex(self->board->board, settings);
128 ex.generate();
129 }
130 catch (const std::exception &e) {
131 PyErr_SetString(PyExc_IOError, e.what());
132 return NULL;
133 }
134 catch (...) {
135 PyErr_SetString(PyExc_IOError, "unknown exception");
136 return NULL;
137 }
138 Py_RETURN_NONE;
139 }
140
PyBoard_get_pdf_export_settings(PyObject * pself,PyObject * args)141 static PyObject *PyBoard_get_pdf_export_settings(PyObject *pself, PyObject *args)
142 {
143 auto self = reinterpret_cast<PyBoard *>(pself);
144 auto settings = self->board->board.pdf_export_settings.serialize_board();
145 return py_from_json(settings);
146 }
147
PyBoard_export_pdf(PyObject * pself,PyObject * args)148 static PyObject *PyBoard_export_pdf(PyObject *pself, PyObject *args)
149 {
150 auto self = reinterpret_cast<PyBoard *>(pself);
151 PyObject *py_export_settings = nullptr;
152 if (!PyArg_ParseTuple(args, "O!", &PyDict_Type, &py_export_settings))
153 return NULL;
154 try {
155 auto settings_json = json_from_py(py_export_settings);
156 horizon::PDFExportSettings settings(settings_json);
157 horizon::export_pdf(self->board->board, settings, nullptr);
158 }
159 catch (const std::exception &e) {
160 PyErr_SetString(PyExc_IOError, e.what());
161 return NULL;
162 }
163 catch (const PoDoFo::PdfError &e) {
164 PyErr_SetString(PyExc_IOError, e.what());
165 return NULL;
166 }
167 catch (...) {
168 PyErr_SetString(PyExc_IOError, "unknown exception");
169 return NULL;
170 }
171 Py_RETURN_NONE;
172 }
173
PyBoard_get_pnp_export_settings(PyObject * pself,PyObject * args)174 static PyObject *PyBoard_get_pnp_export_settings(PyObject *pself, PyObject *args)
175 {
176 auto self = reinterpret_cast<PyBoard *>(pself);
177 auto settings = self->board->board.pnp_export_settings.serialize();
178 return py_from_json(settings);
179 }
180
PyBoard_export_pnp(PyObject * pself,PyObject * args)181 static PyObject *PyBoard_export_pnp(PyObject *pself, PyObject *args)
182 {
183 auto self = reinterpret_cast<PyBoard *>(pself);
184 PyObject *py_export_settings = nullptr;
185 if (!PyArg_ParseTuple(args, "O!", &PyDict_Type, &py_export_settings))
186 return NULL;
187 try {
188 auto settings_json = json_from_py(py_export_settings);
189 horizon::PnPExportSettings settings(settings_json);
190 horizon::export_PnP(self->board->board, settings);
191 }
192 catch (const std::exception &e) {
193 PyErr_SetString(PyExc_IOError, e.what());
194 return NULL;
195 }
196 catch (...) {
197 PyErr_SetString(PyExc_IOError, "unknown exception");
198 return NULL;
199 }
200 Py_RETURN_NONE;
201 }
202
PyBoard_get_step_export_settings(PyObject * pself,PyObject * args)203 static PyObject *PyBoard_get_step_export_settings(PyObject *pself, PyObject *args)
204 {
205 auto self = reinterpret_cast<PyBoard *>(pself);
206 auto settings = self->board->board.step_export_settings.serialize();
207 return py_from_json(settings);
208 }
209
callback_wrapper(PyObject * cb,const std::string & s)210 static void callback_wrapper(PyObject *cb, const std::string &s)
211 {
212 if (cb) {
213 PyObject *arglist = Py_BuildValue("(s)", s.c_str());
214 PyObject *result = PyObject_CallObject(cb, arglist);
215 Py_DECREF(arglist);
216 if (result == NULL) {
217 throw py_exception();
218 }
219 Py_DECREF(result);
220 }
221 }
222
PyBoard_export_step(PyObject * pself,PyObject * args)223 static PyObject *PyBoard_export_step(PyObject *pself, PyObject *args)
224 {
225 auto self = reinterpret_cast<PyBoard *>(pself);
226 PyObject *py_export_settings = nullptr;
227 PyObject *py_callback = nullptr;
228 if (!PyArg_ParseTuple(args, "O!|O", &PyDict_Type, &py_export_settings, &py_callback))
229 return NULL;
230 if (py_callback && !PyCallable_Check(py_callback)) {
231 PyErr_SetString(PyExc_TypeError, "callback must be callable");
232 return NULL;
233 }
234 try {
235 auto settings_json = json_from_py(py_export_settings);
236 horizon::STEPExportSettings settings(settings_json);
237 horizon::export_step(
238 settings.filename, self->board->board, self->board->pool, settings.include_3d_models,
239 [py_callback](const std::string &s) { callback_wrapper(py_callback, s); }, nullptr, settings.prefix);
240 }
241 catch (const py_exception &e) {
242 return NULL;
243 }
244 catch (const std::exception &e) {
245 PyErr_SetString(PyExc_IOError, e.what());
246 return NULL;
247 }
248 catch (...) {
249 PyErr_SetString(PyExc_IOError, "unknown exception");
250 return NULL;
251 }
252 Py_RETURN_NONE;
253 }
254
dummy_callback(const std::string & s)255 static void dummy_callback(const std::string &s)
256 {
257 }
258
PyBoard_run_checks(PyObject * pself,PyObject * args)259 static PyObject *PyBoard_run_checks(PyObject *pself, PyObject *args)
260 {
261 auto self = reinterpret_cast<PyBoard *>(pself);
262 PyObject *py_rules = nullptr;
263 PyObject *py_rule_ids = nullptr;
264 if (!PyArg_ParseTuple(args, "O!O", &PyDict_Type, &py_rules, &py_rule_ids))
265 return NULL;
266 PyObject *iter = nullptr;
267 if (!(iter = PyObject_GetIter(py_rule_ids))) {
268 PyErr_SetString(PyExc_TypeError, "rule ids must be iterable");
269 return NULL;
270 }
271
272 std::set<horizon::RuleID> ids;
273
274 while (PyObject *item = PyIter_Next(iter)) {
275 auto rule_name = PyUnicode_AsUTF8(item);
276 if (!rule_name) {
277 Py_DECREF(item);
278 Py_DECREF(iter);
279 return NULL;
280 }
281 Py_DECREF(item);
282 horizon::RuleID id;
283 try {
284 id = horizon::rule_id_lut.lookup(rule_name);
285 ids.emplace(id);
286 }
287 catch (...) {
288 PyErr_SetString(PyExc_IOError, "rule not found");
289 Py_DECREF(item);
290 Py_DECREF(iter);
291 return NULL;
292 }
293 }
294
295 Py_DECREF(iter);
296
297 if (PyErr_Occurred()) {
298 return NULL;
299 }
300 try {
301 auto rules_json = json_from_py(py_rules);
302 horizon::BoardRules rules;
303 rules.load_from_json(rules_json);
304
305 horizon::RulesCheckCache cache(self->board);
306 json j;
307 for (auto id : ids) {
308 auto r = self->board->board.rules.check(id, self->board->board, cache, &dummy_callback);
309 j[horizon::rule_id_lut.lookup_reverse(id)] = r.serialize();
310 }
311 return py_from_json(j);
312 }
313 catch (const py_exception &e) {
314 return NULL;
315 }
316 catch (const std::exception &e) {
317 PyErr_SetString(PyExc_IOError, e.what());
318 return NULL;
319 }
320 catch (...) {
321 PyErr_SetString(PyExc_IOError, "unknown exception");
322 return NULL;
323 }
324 Py_RETURN_NONE;
325 }
326
PyBoard_get_rules(PyObject * pself,PyObject * args)327 static PyObject *PyBoard_get_rules(PyObject *pself, PyObject *args)
328 {
329 auto self = reinterpret_cast<PyBoard *>(pself);
330 auto rules = self->board->board.rules.serialize();
331 return py_from_json(rules);
332 }
333
PyBoard_get_rule_ids(PyObject * pself,PyObject * args)334 static PyObject *PyBoard_get_rule_ids(PyObject *pself, PyObject *args)
335 {
336 auto self = reinterpret_cast<PyBoard *>(pself);
337 auto rules = self->board->board.rules.get_rule_ids();
338 auto r = PySet_New(NULL);
339 if (!r)
340 return NULL;
341 for (auto id : rules) {
342 if (horizon::rule_descriptions.at(id).can_check) {
343 auto u = PyUnicode_FromString(horizon::rule_id_lut.lookup_reverse(id).c_str());
344 if (PySet_Add(r, u) == -1) {
345 return NULL;
346 }
347 }
348 }
349 return r;
350 }
351
PyBoard_export_3d(PyObject * pself,PyObject * args)352 static PyObject *PyBoard_export_3d(PyObject *pself, PyObject *args)
353 {
354 auto self = reinterpret_cast<PyBoard *>(pself);
355 unsigned int w, h;
356 if (!PyArg_ParseTuple(args, "II", &w, &h))
357 return NULL;
358 class horizon::Image3DExporterWrapper *exporter = nullptr;
359 try {
360 exporter = new horizon::Image3DExporterWrapper(self->board->board, self->board->pool, w, h);
361 }
362 catch (const std::exception &e) {
363 PyErr_SetString(PyExc_IOError, e.what());
364 return NULL;
365 }
366 catch (...) {
367 PyErr_SetString(PyExc_IOError, "unknown exception");
368 return NULL;
369 }
370
371 PyImage3DExporter *ex = PyObject_New(PyImage3DExporter, &Image3DExporterType);
372 ex->exporter = exporter;
373 return reinterpret_cast<PyObject *>(ex);
374 }
375
376
377 static PyMethodDef PyBoard_methods[] = {
378 {"get_gerber_export_settings", PyBoard_get_gerber_export_settings, METH_NOARGS,
379 "Return gerber export settings"},
380 {"export_gerber", PyBoard_export_gerber, METH_VARARGS, "Export gerber"},
381 {"get_pdf_export_settings", PyBoard_get_pdf_export_settings, METH_NOARGS, "Return PDF export settings"},
382 {"get_pnp_export_settings", PyBoard_get_pnp_export_settings, METH_NOARGS, "Return PnP export settings"},
383 {"get_step_export_settings", PyBoard_get_step_export_settings, METH_NOARGS, "Return STEP export settings"},
384 {"get_rules", PyBoard_get_rules, METH_NOARGS, "Return rules"},
385 {"get_rule_ids", PyBoard_get_rule_ids, METH_NOARGS, "Return rule IDs"},
386 {"export_pdf", PyBoard_export_pdf, METH_VARARGS, "Export PDF"},
387 {"export_pnp", PyBoard_export_pnp, METH_VARARGS, "Export pick and place"},
388 {"export_step", PyBoard_export_step, METH_VARARGS, "Export STEP"},
389 {"run_checks", PyBoard_run_checks, METH_VARARGS, "Run checks"},
390 {"export_3d", PyBoard_export_3d, METH_VARARGS, "Export 3D image"},
391 {NULL} /* Sentinel */
392 };
393
__anon927b5c4a0202null394 PyTypeObject BoardType = [] {
395 PyTypeObject r = {PyVarObject_HEAD_INIT(NULL, 0)};
396 r.tp_name = "horizon.Board";
397 r.tp_basicsize = sizeof(PyBoard);
398
399 r.tp_itemsize = 0;
400 r.tp_dealloc = PyBoard_dealloc;
401 r.tp_flags = Py_TPFLAGS_DEFAULT;
402 r.tp_doc = "Board";
403
404 r.tp_methods = PyBoard_methods;
405 r.tp_new = PyBoard_new;
406 return r;
407 }();
408