1 #include "3d_image_exporter.hpp"
2 #include "image_3d_exporter_wrapper.hpp"
3 #include "nlohmann/json.hpp"
4 #include "util.hpp"
5 #define PYCAIRO_NO_IMPORT
6 #include <py3cairo.h>
7 
PyImage3DExporter_new(PyTypeObject * type,PyObject * args,PyObject * kwds)8 static PyObject *PyImage3DExporter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
9 {
10     PyImage3DExporter *self;
11     self = (PyImage3DExporter *)type->tp_alloc(type, 0);
12     if (self != NULL) {
13         self->exporter = nullptr;
14     }
15     return (PyObject *)self;
16 }
17 
PyImage3DExporter_dealloc(PyObject * pself)18 static void PyImage3DExporter_dealloc(PyObject *pself)
19 {
20     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
21     delete self->exporter;
22     Py_TYPE(self)->tp_free((PyObject *)self);
23 }
24 
PyImage3DExporter_render_to_png(PyObject * pself,PyObject * args)25 static PyObject *PyImage3DExporter_render_to_png(PyObject *pself, PyObject *args)
26 {
27     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
28     const char *filename = nullptr;
29     if (!PyArg_ParseTuple(args, "s", &filename))
30         return NULL;
31     try {
32         auto surf = self->exporter->render_to_surface();
33         surf->write_to_png(filename);
34     }
35     catch (const std::exception &e) {
36         PyErr_SetString(PyExc_IOError, e.what());
37         return NULL;
38     }
39     catch (...) {
40         PyErr_SetString(PyExc_IOError, "unknown exception");
41         return NULL;
42     }
43     Py_RETURN_NONE;
44 }
45 
PyImage3DExporter_render_to_surface(PyObject * pself,PyObject * args)46 static PyObject *PyImage3DExporter_render_to_surface(PyObject *pself, PyObject *args)
47 {
48     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
49     try {
50         cairo_surface_t *csurf = nullptr;
51         {
52             auto surf = self->exporter->render_to_surface();
53             csurf = surf->cobj();
54             cairo_surface_reference(csurf);
55         }
56         return PycairoSurface_FromSurface(csurf, NULL);
57     }
58     catch (const std::exception &e) {
59         PyErr_SetString(PyExc_IOError, e.what());
60         return NULL;
61     }
62     catch (...) {
63         PyErr_SetString(PyExc_IOError, "unknown exception");
64         return NULL;
65     }
66     Py_RETURN_NONE;
67 }
68 
PyImage3DExporter_view_all(PyObject * pself,PyObject * args)69 static PyObject *PyImage3DExporter_view_all(PyObject *pself, PyObject *args)
70 {
71     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
72     self->exporter->view_all();
73     Py_RETURN_NONE;
74 }
75 
PyImage3DExporter_load_3d_models(PyObject * pself,PyObject * args)76 static PyObject *PyImage3DExporter_load_3d_models(PyObject *pself, PyObject *args)
77 {
78     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
79     self->exporter->load_3d_models();
80     Py_RETURN_NONE;
81 }
82 
83 static PyMethodDef PyImage3DExporter_methods[] = {
84         {"render_to_png", (PyCFunction)PyImage3DExporter_render_to_png, METH_VARARGS, "Render to PNG"},
85         {"render_to_surface", (PyCFunction)PyImage3DExporter_render_to_surface, METH_NOARGS, "Render to cairo surface"},
86         {"view_all", (PyCFunction)PyImage3DExporter_view_all, METH_NOARGS, "View all"},
87         {"load_3d_models", (PyCFunction)PyImage3DExporter_load_3d_models, METH_NOARGS, "Load 3D models"},
88         {NULL} /* Sentinel */
89 };
90 
91 using Color = horizon::Color;
92 
ortho_get(PyObject * pself,void * pa)93 static PyObject *ortho_get(PyObject *pself, void *pa)
94 {
95     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
96     return PyBool_FromLong(self->exporter->get_projection() == horizon::Canvas3DBase::Projection::ORTHO);
97 }
98 
ortho_set(PyObject * pself,PyObject * pval,void * pa)99 static int ortho_set(PyObject *pself, PyObject *pval, void *pa)
100 {
101     if (!pval) {
102         PyErr_SetString(PyExc_AttributeError, "can't delete attr");
103         return -1;
104     }
105     if (!PyBool_Check(pval)) {
106         PyErr_SetString(PyExc_TypeError, "must be bool");
107         return -1;
108     }
109     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
110     if (pval == Py_True) {
111         self->exporter->set_projection(horizon::Canvas3DBase::Projection::ORTHO);
112     }
113     else {
114         self->exporter->set_projection(horizon::Canvas3DBase::Projection::PERSP);
115     }
116     return 0;
117 }
118 
119 
120 template <typename T> struct FnGetSet {
121     using Set = void (horizon::Image3DExporterWrapper::*)(const T &v);
122     using Get = const T &(horizon::Image3DExporterWrapper::*)() const;
123     Get get;
124     Set set;
125 };
126 
127 #define GET_SET(t_, x_)                                                                                                \
128     static FnGetSet<t_> get_set_##x_{                                                                                  \
129             &horizon::Image3DExporterWrapper::get_##x_,                                                                \
130             &horizon::Image3DExporterWrapper::set_##x_,                                                                \
131     };
132 
133 
134 // clang-format off
135 #define ATTRS                                                                                                          \
136     X(bool, render_background)                                                                                         \
137     X(bool, show_models)                                                                                               \
138     X(bool, show_dnp_models)                                                                                           \
139     X(bool, show_silkscreen)                                                                                           \
140     X(bool, show_solder_mask)                                                                                          \
141     X(bool, show_solder_paste)                                                                                         \
142     X(bool, show_substrate)                                                                                            \
143     X(bool, use_layer_colors)                                                                                          \
144     X(bool, show_copper)                                                                                               \
145     X(float, cam_azimuth)                                                                                              \
146     X(float, cam_elevation)                                                                                            \
147     X(float, cam_distance)                                                                                             \
148     X(float, cam_fov)                                                                                                  \
149     X(float, center_x)                                                                                                 \
150     X(float, center_y)                                                                                                 \
151     X(Color, background_top_color)                                                                                     \
152     X(Color, background_bottom_color)                                                                                  \
153     X(Color, solder_mask_color)                                                                                        \
154     X(Color, silkscreen_color)                                                                                         \
155     X(Color, substrate_color)
156 // clang-format on
157 
158 
159 #define X GET_SET
160 ATTRS
161 #undef X
162 
bool_attr_get(PyObject * pself,void * pa)163 static PyObject *bool_attr_get(PyObject *pself, void *pa)
164 {
165     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
166     auto getset = static_cast<FnGetSet<bool> *>(pa);
167     return PyBool_FromLong(std::invoke(getset->get, self->exporter));
168 }
169 
bool_attr_set(PyObject * pself,PyObject * pval,void * pa)170 static int bool_attr_set(PyObject *pself, PyObject *pval, void *pa)
171 {
172     if (!pval) {
173         PyErr_SetString(PyExc_AttributeError, "can't delete attr");
174         return -1;
175     }
176     if (!PyBool_Check(pval)) {
177         PyErr_SetString(PyExc_TypeError, "must be bool");
178         return -1;
179     }
180     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
181     auto getset = static_cast<FnGetSet<bool> *>(pa);
182     std::invoke(getset->set, self->exporter, pval == Py_True);
183     return 0;
184 }
185 
float_attr_get(PyObject * pself,void * pa)186 static PyObject *float_attr_get(PyObject *pself, void *pa)
187 {
188     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
189     auto getset = static_cast<FnGetSet<float> *>(pa);
190     return PyFloat_FromDouble(std::invoke(getset->get, self->exporter));
191 }
192 
float_attr_set(PyObject * pself,PyObject * pval,void * pa)193 static int float_attr_set(PyObject *pself, PyObject *pval, void *pa)
194 {
195     if (!pval) {
196         PyErr_SetString(PyExc_AttributeError, "can't delete attr");
197         return -1;
198     }
199     if (!PyNumber_Check(pval)) {
200         PyErr_SetString(PyExc_TypeError, "must be number");
201         return -1;
202     }
203     auto pfloat = PyNumber_Float(pval);
204     if (!pfloat)
205         return -1;
206     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
207     auto getset = static_cast<FnGetSet<float> *>(pa);
208     std::invoke(getset->set, self->exporter, PyFloat_AsDouble(pfloat));
209     Py_DecRef(pfloat);
210     return 0;
211 }
212 
213 
Color_attr_get(PyObject * pself,void * pa)214 static PyObject *Color_attr_get(PyObject *pself, void *pa)
215 {
216     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
217     auto getset = static_cast<FnGetSet<Color> *>(pa);
218     auto c = std::invoke(getset->get, self->exporter);
219     return Py_BuildValue("(fff)", c.r, c.g, c.b);
220 }
221 
Color_attr_set(PyObject * pself,PyObject * pval,void * pa)222 static int Color_attr_set(PyObject *pself, PyObject *pval, void *pa)
223 {
224     if (!pval) {
225         PyErr_SetString(PyExc_AttributeError, "can't delete attr");
226         return -1;
227     }
228     if (!PySequence_Check(pval)) {
229         PyErr_SetString(PyExc_TypeError, "must be sequence");
230         return -1;
231     }
232     if (PySequence_Length(pval) != 3) {
233         PyErr_SetString(PyExc_TypeError, "must be sequence of length 3");
234         return -1;
235     }
236     Color c;
237     for (size_t idx = 0; idx < 3; idx++) {
238         auto elem = PySequence_GetItem(pval, idx);
239         if (!elem)
240             return -1;
241         if (!PyNumber_Check(elem)) {
242             Py_DecRef(elem);
243             PyErr_SetString(PyExc_TypeError, "elem must be number");
244             return -1;
245         }
246         auto pfloat = PyNumber_Float(elem);
247         if (!pfloat) {
248             Py_DecRef(elem);
249             return -1;
250         }
251         float f = PyFloat_AsDouble(pfloat);
252         switch (idx) {
253         case 0:
254             c.r = f;
255             break;
256 
257         case 1:
258             c.g = f;
259             break;
260 
261         case 2:
262             c.b = f;
263             break;
264 
265         default:;
266         }
267         Py_DecRef(pfloat);
268         Py_DecRef(elem);
269     }
270     auto self = reinterpret_cast<PyImage3DExporter *>(pself);
271     auto getset = static_cast<FnGetSet<Color> *>(pa);
272     std::invoke(getset->set, self->exporter, c);
273     return 0;
274 }
275 
276 static PyGetSetDef PyImage3DExporter_getset[] = {
277 #define X(t_, x_) {#x_, &t_##_attr_get, &t_##_attr_set, NULL, static_cast<void *>(&get_set_##x_)},
278         ATTRS
279 #undef X
280         {"ortho", &ortho_get, &ortho_set, NULL, NULL},
281         {NULL},
282         /* Sentinel */};
283 
__anon4d20b6d30102null284 PyTypeObject Image3DExporterType = [] {
285     PyTypeObject r = {PyVarObject_HEAD_INIT(NULL, 0)};
286     r.tp_name = "horizon.Image3DExporter";
287     r.tp_basicsize = sizeof(PyImage3DExporter);
288 
289     r.tp_itemsize = 0;
290     r.tp_dealloc = PyImage3DExporter_dealloc;
291     r.tp_flags = Py_TPFLAGS_DEFAULT;
292     r.tp_doc = "Image3DExporer";
293 
294     r.tp_methods = PyImage3DExporter_methods;
295     r.tp_getset = PyImage3DExporter_getset;
296     r.tp_new = PyImage3DExporter_new;
297     return r;
298 }();
299