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 &block;
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