1 // SPDX-FileCopyrightText: 2003 Dominique Devriese <devriese@kde.org>
2
3 // SPDX-License-Identifier: GPL-2.0-or-later
4
5 // mp: following a kind suggestion by David Faure the Python.h include has
6 // been moved before all qt includes in order to avoid a clash related to
7 // the "slots" identifier
8 // krazy:excludeall=includes
9 #undef _XOPEN_SOURCE // it will be defined inside Python
10 #include <Python.h>
11 #include "python_scripter.h"
12
13 #include <iostream>
14 #include <string>
15
16 #include <boost/python.hpp>
17 #include <boost/mpl/bool.hpp>
18
19 #include "../misc/common.h"
20 #include "../misc/coordinate.h"
21 #include "../misc/cubic-common.h"
22 #include "../misc/kigtransform.h"
23 #include "../objects/bogus_imp.h"
24 #include "../objects/common.h"
25 #include "../objects/circle_imp.h"
26 #include "../objects/cubic_imp.h"
27 #include "../objects/line_imp.h"
28 #include "../objects/other_imp.h"
29 #include "../objects/point_imp.h"
30 #include "../objects/text_imp.h"
31 #include "../objects/polygon_imp.h"
32
33 using namespace boost::python;
34
BOOST_PYTHON_MODULE_INIT(kig)35 BOOST_PYTHON_MODULE_INIT( kig )
36 {
37 class_<Coordinate>( "Coordinate" )
38 .def( init<double, double>() )
39 .def( init<const Coordinate&>() )
40 .def( "invalidCoord", &Coordinate::invalidCoord )
41 .staticmethod( "invalidCoord" )
42 .def( "valid", &Coordinate::valid )
43 .def( "distance", &Coordinate::distance )
44 .def( "length", &Coordinate::length )
45 .def( "squareLength", &Coordinate::squareLength )
46 .def( "orthogonal", &Coordinate::orthogonal )
47 .def( "round", &Coordinate::round )
48 .def( "normalize", &Coordinate::normalize )
49 .def( -self )
50 // .def( self = self )
51 .def( self += self )
52 .def( self -= self )
53 .def( self *= other<double>() )
54 .def( self *= other<int>() )
55 .def( self /= other<double>() )
56 .def( self / other<double>() )
57 .def( self + self )
58 .def( self - self )
59 .def( self * other<double>() )
60 .def( other<double>() * self )
61 .def( self * self )
62 .def_readwrite( "x", &Coordinate::x )
63 .def_readwrite( "y", &Coordinate::y )
64 ;
65
66 class_<LineData>( "LineData" )
67 .def( init<Coordinate, Coordinate>() )
68 .def( "dir", &LineData::dir )
69 .def( "length", &LineData::length )
70 .def( "isParallelTo", &LineData::isParallelTo )
71 .def_readwrite( "a", &LineData::a )
72 .def_readwrite( "b", &LineData::b )
73 ;
74
75 // we need this cause Transformation::apply is overloaded and
76 // otherwise using Transformation::apply would be ambiguous..
77 const Coordinate (Transformation::*transformapplyfunc)( const Coordinate& ) const = &Transformation::apply;
78 class_<Transformation>( "Transformation", no_init )
79 .def( "apply", transformapplyfunc )
80 .def( "isHomothetic", &Transformation::isHomothetic )
81 .def( "inverse", &Transformation::inverse )
82 .def( "identity", &Transformation::identity )
83 .def( "translation", &Transformation::translation )
84 .def( "rotation", &Transformation::rotation )
85 .def( "pointReflection", &Transformation::pointReflection )
86 .def( "lineReflection", &Transformation::lineReflection )
87 .def( "castShadow", &Transformation::castShadow )
88 .def( "projectiveRotation", &Transformation::projectiveRotation )
89 .def( "scalingOverPoint", &Transformation::scalingOverPoint )
90 .def( "scalingOverLine", &Transformation::scalingOverLine )
91 .def( self * self )
92 .def( self == self )
93 .staticmethod( "identity" )
94 .staticmethod( "translation" )
95 .staticmethod( "rotation" )
96 .staticmethod( "pointReflection" )
97 .staticmethod( "lineReflection" )
98 .staticmethod( "castShadow" )
99 .staticmethod( "projectiveRotation" )
100 .staticmethod( "scalingOverPoint" )
101 .staticmethod( "scalingOverLine" )
102 ;
103
104 class_<ObjectImpType, boost::noncopyable>( "ObjectType", no_init )
105 .def( "fromInternalName", &ObjectImpType::typeFromInternalName,
106 return_value_policy<reference_existing_object>() )
107 .staticmethod( "fromInternalName" )
108 .def( "inherits", &ObjectImpType::inherits )
109 .def( "internalName", &ObjectImpType::internalName )
110 .def( "translatedName", &ObjectImpType::translatedName )
111 .def( "selectStatement", &ObjectImpType::selectStatement )
112 .def( "removeAStatement", &ObjectImpType::removeAStatement )
113 .def( "addAStatement", &ObjectImpType::addAStatement )
114 .def( "moveAStatement", &ObjectImpType::moveAStatement )
115 .def( "attachToThisStatement", &ObjectImpType::attachToThisStatement )
116 ;
117
118 class_<ObjectImp, boost::noncopyable>( "Object", no_init )
119 .def( "stype", &ObjectImp::stype,
120 return_value_policy<reference_existing_object>() )
121 .staticmethod( "stype" )
122 .def( "inherits", &ObjectImp::inherits )
123 .def( "transform", &ObjectImp::transform,
124 return_value_policy<manage_new_object>() )
125 .def( "valid", &ObjectImp::valid )
126 .def( "copy", &ObjectImp::copy,
127 return_value_policy<manage_new_object>() )
128 .def( "equals", &ObjectImp::equals )
129 ;
130
131 class_<CurveImp, bases<ObjectImp>, boost::noncopyable>( "Curve", no_init )
132 .def( "stype", &CurveImp::stype,
133 return_value_policy<reference_existing_object>() )
134 .staticmethod( "stype" )
135 // .def( "getParam", &CurveImp::getParam )
136 // .def( "getPoint", &CurveImp::getPoint );
137 ;
138 class_<PointImp, bases<ObjectImp> >( "Point", init<Coordinate>() )
139 .def( "stype", &PointImp::stype,
140 return_value_policy<reference_existing_object>() )
141 .staticmethod( "stype" )
142 .def( "coordinate", &PointImp::coordinate,
143 return_internal_reference<1>() )
144 .def( "setCoordinate", &PointImp::setCoordinate )
145 ;
146
147 class_<AbstractLineImp, bases<CurveImp>, boost::noncopyable >( "AbstractLine", no_init )
148 .def( "stype", &AbstractLineImp::stype,
149 return_value_policy<reference_existing_object>() )
150 .staticmethod( "stype" )
151 .def( "slope", &AbstractLineImp::slope )
152 .def( "equationString", &AbstractLineImp::equationString )
153 .def( "data", &AbstractLineImp::data )
154 ;
155
156 class_<SegmentImp, bases<AbstractLineImp> >( "Segment", init<Coordinate, Coordinate>() )
157 .def( "stype", &SegmentImp::stype,
158 return_value_policy<reference_existing_object>() )
159 .staticmethod( "stype" )
160 .def( init<LineData>() )
161 .def( "length", &SegmentImp::length )
162 ;
163
164 class_<RayImp, bases<AbstractLineImp> >( "Ray", init<Coordinate, Coordinate>() )
165 .def( "stype", &RayImp::stype,
166 return_value_policy<reference_existing_object>() )
167 .staticmethod( "stype" )
168 .def( init<LineData>() )
169 ;
170
171 class_<LineImp, bases<AbstractLineImp> >( "Line", init<Coordinate, Coordinate>() )
172 .def( "stype", &LineImp::stype,
173 return_value_policy<reference_existing_object>() )
174 .staticmethod( "stype" )
175 .def( init<LineData>() )
176 ;
177
178 class_<ConicCartesianData>( "ConicCartesianData", init<double,double,double,double,double,double>() )
179 .def( init<ConicPolarData>() )
180 .def( "invalidData", &ConicCartesianData::invalidData )
181 .staticmethod( "invalidData" )
182 .def( "valid", &ConicCartesianData::valid )
183 // .def( init<double[6]>() )
184 // .def_readwrite( "coeffs", &ConicCartesianData::coeffs )
185 ;
186
187 class_<ConicPolarData>( "ConicPolarData", init<Coordinate, double, double, double>() )
188 .def( init<ConicCartesianData>() )
189 .def_readwrite( "focus1", &ConicPolarData::focus1 )
190 .def_readwrite( "pdimen", &ConicPolarData::pdimen )
191 .def_readwrite( "ecostheta0", &ConicPolarData::ecostheta0 )
192 .def_readwrite( "esintheta0", &ConicPolarData::esintheta0 )
193 ;
194
195 class_<ConicImp, bases<CurveImp>, boost::noncopyable >( "Conic", no_init )
196 .def( "stype", &ConicImp::stype,
197 return_value_policy<reference_existing_object>() )
198 .staticmethod( "stype" )
199 .def( "conicType", &ConicImp::conicType )
200 // .def( "conicTypeString", &ConicImp::conicTypeString )
201 // .def( "cartesianEquationString", &ConicImp::cartesianEquationString )
202 // .def( "polarEquationString", &ConicImp::polarEquationString )
203 .def( "cartesianData", &ConicImp::cartesianData )
204 .def( "polarData", &ConicImp::polarData )
205 .def( "focus1", &ConicImp::focus1 )
206 .def( "focus2", &ConicImp::focus2 )
207 ;
208
209 class_<ConicImpCart, bases<ConicImp> >( "CartesianConic", init<ConicCartesianData>() )
210 ;
211 class_<ConicImpPolar, bases<ConicImp> >( "PolarConic", init<ConicPolarData>() )
212 ;
213
214 class_<CircleImp, bases<ConicImp> >( "Circle", init<Coordinate, double>() )
215 .def( "stype", &CircleImp::stype,
216 return_value_policy<reference_existing_object>() )
217 .staticmethod( "stype" )
218 .def( "center", &CircleImp::center )
219 .def( "radius", &CircleImp::radius )
220 .def( "squareRadius", &CircleImp::squareRadius )
221 .def( "surface", &CircleImp::surface )
222 .def( "circumference", &CircleImp::circumference )
223 ;
224
225 class_<FilledPolygonImp, bases<ObjectImp>, boost::noncopyable>( "Polygon", no_init )
226 .def( "stype", &FilledPolygonImp::stype,
227 return_value_policy<reference_existing_object>() )
228 .staticmethod( "stype" )
229 .def( "npoints", &FilledPolygonImp::npoints )
230 .def( "perimeter", &FilledPolygonImp::cperimeter )
231 .def( "area", &FilledPolygonImp::area )
232 .def( "windingNumber", &FilledPolygonImp::windingNumber )
233 ;
234
235 class_<VectorImp, bases<CurveImp> >( "Vector", init<Coordinate, Coordinate>() )
236 .def( "stype", &VectorImp::stype,
237 return_value_policy<reference_existing_object>() )
238 .staticmethod( "stype" )
239 .def( "length", &VectorImp::length )
240 .def( "dir", &VectorImp::dir )
241 .def( "data", &VectorImp::data )
242 ;
243
244 //TODO find how to mark default
245 class_<AngleImp, bases<ObjectImp> >( "Angle", init<Coordinate, double, double, bool>() )
246 .def( "stype", &AngleImp::stype,
247 return_value_policy<reference_existing_object>() )
248 .staticmethod( "stype" )
249 .def( "size", &AngleImp::size )
250 .def( "point", &AngleImp::point )
251 .def( "startAngle", &AngleImp::startAngle )
252 .def( "angle", &AngleImp::angle )
253 ;
254
255 class_<ArcImp, bases<ObjectImp> >( "Arc", init<Coordinate, double, double, double>() )
256 .def( "stype", &ArcImp::stype,
257 return_value_policy<reference_existing_object>() )
258 .staticmethod( "stype" )
259 .def( "startAngle", &ArcImp::startAngle )
260 .def( "angle", &ArcImp::angle )
261 .def( "radius", &ArcImp::radius )
262 .def( "center", &ArcImp::center )
263 .def( "firstEndPoint", &ArcImp::firstEndPoint )
264 .def( "secondEndPoint", &ArcImp::secondEndPoint )
265 .def( "sectorSurface", &ArcImp::sectorSurface )
266 ;
267
268 class_<BogusImp, bases<ObjectImp>, boost::noncopyable >( "BogusObject", no_init )
269 .def( "stype", &BogusImp::stype,
270 return_value_policy<reference_existing_object>() )
271 .staticmethod( "stype" )
272 ;
273
274 class_<InvalidImp, bases<BogusImp> >( "InvalidObject", init<>() )
275 .def( "stype", &InvalidImp::stype,
276 return_value_policy<reference_existing_object>() )
277 .staticmethod( "stype" )
278 ;
279
280 class_<DoubleImp, bases<BogusImp> >( "DoubleObject", init<double>() )
281 .def( "stype", &DoubleImp::stype,
282 return_value_policy<reference_existing_object>() )
283 .staticmethod( "stype" )
284 .def( "data", &DoubleImp::data )
285 .def( "setData", &DoubleImp::setData )
286 ;
287
288 class_<IntImp, bases<BogusImp> >( "IntObject", init<int>() )
289 .def( "stype", &IntImp::stype,
290 return_value_policy<reference_existing_object>() )
291 .staticmethod( "stype" )
292 .def( "data", &IntImp::data )
293 .def( "setData", &IntImp::setData )
294 ;
295
296 class_<StringImp, bases<BogusImp> >( "StringObject", init<char*>() )
297 .def( "stype", &StringImp::stype,
298 return_value_policy<reference_existing_object>() )
299 .staticmethod( "stype" )
300 // .def( "data", &StringImp::data )
301 // .def( "setData", &StringImp::setData )
302 ;
303
304 class_<TestResultImp, bases<BogusImp> >( "TestResultObject", no_init )
305 .def( "stype", &TestResultImp::stype,
306 return_value_policy<reference_existing_object>() )
307 .staticmethod( "stype" )
308 // .def( "data", &TestResultImp::data )
309 ;
310
311 // class_<TextImp, bases<ObjectImp> >( "Text", init<string, Coordinate, bool>() )
312 // .def( "stype", &TextImp::stype,
313 // return_value_policy<reference_existing_object>() )
314 // .staticmethod( "stype" )
315 // .def( "text", &TextImp::text )
316 // .def( "coordinate", &TextImp::coordinate )
317 // .def( "hasFrame", &TextImp::hasFrame )
318 // ;
319
320 class_<NumericTextImp, bases<ObjectImp> >( "NumericObject", no_init )
321 .def( "stype", &NumericTextImp::stype,
322 return_value_policy<reference_existing_object>() )
323 .staticmethod( "stype" )
324 .def( "value", &NumericTextImp::getValue )
325 ;
326
327 class_<BoolTextImp, bases<ObjectImp> >( "BooleanObject", no_init )
328 .def( "stype", &BoolTextImp::stype,
329 return_value_policy<reference_existing_object>() )
330 .staticmethod( "stype" )
331 .def( "value", &BoolTextImp::getValue )
332 ;
333
334 class_<CubicCartesianData>( "CubicCartesianData", init<double,double,double,double,double,double,double,double,double,double>() )
335 .def( "invalidData", &CubicCartesianData::invalidData )
336 .staticmethod( "invalidData" )
337 .def( "valid", &CubicCartesianData::valid )
338 // .def( init<double[10]>() )
339 // .def_readwrite( "coeffs", &CubicCartesianData::coeffs )
340 ;
341
342 class_<CubicImp, bases<CurveImp> >( "Cubic", init<CubicCartesianData>() )
343 .def( "stype", &CubicImp::stype,
344 return_value_policy<reference_existing_object>() )
345 .staticmethod( "stype" )
346 .def( "data", &CubicImp::data )
347 ;
348
349 }
350
351 // helper class to initialize Python in a constructor;
352 // this way, PythonScripter::Private inherits from it and thus
353 // already has Python initialized before the class members
354 // (like 'mainnamespace') are initialized
355 class PythonInitializer
356 {
357 public:
358 PythonInitializer();
359 };
360
PythonInitializer()361 PythonInitializer::PythonInitializer()
362 {
363 // tell the python interpreter about our API..
364
365 PyImport_AppendInittab( "kig", PyInit_kig );
366
367 Py_Initialize();
368
369 PyRun_SimpleString( "import math; from math import *;" );
370 PyRun_SimpleString( "import kig; from kig import *;" );
371 PyRun_SimpleString( "import traceback;" );
372 }
373
instance()374 PythonScripter* PythonScripter::instance()
375 {
376 static PythonScripter t;
377 return &t;
378 }
379
380 class PythonScripter::Private : private PythonInitializer
381 {
382 public:
383 dict mainnamespace;
384 };
385
PythonScripter()386 PythonScripter::PythonScripter()
387 {
388 d = new Private;
389
390 // find the main namespace..
391
392 handle<> main_module( borrowed( PyImport_AddModule( "__main__" ) ) );
393
394 handle<> mnh(borrowed( PyModule_GetDict(main_module.get()) ));
395 d->mainnamespace = extract<dict>( mnh.get() );
396 }
397
~PythonScripter()398 PythonScripter::~PythonScripter()
399 {
400 PyErr_Clear();
401 delete d;
402 // Py_FinalizeEx();
403 Py_Finalize(); // maintained for compatibility reasons with python2
404 }
405
406 class CompiledPythonScript::Private
407 {
408 public:
409 int ref;
410 object calcfunc;
411 // TODO
412 // object movefunc;
413 };
414
calc(const Args & args,const KigDocument &)415 ObjectImp* CompiledPythonScript::calc( const Args& args, const KigDocument& )
416 {
417 return PythonScripter::instance()->calc( *this, args );
418 }
419
~CompiledPythonScript()420 CompiledPythonScript::~CompiledPythonScript()
421 {
422 --d->ref;
423 if ( d->ref == 0 )
424 delete d;
425 }
426
CompiledPythonScript(Private * ind)427 CompiledPythonScript::CompiledPythonScript( Private* ind )
428 : d( ind )
429 {
430 ++d->ref;
431 }
432
compile(const char * code)433 CompiledPythonScript PythonScripter::compile( const char* code )
434 {
435 clearErrors();
436 dict retdict;
437 bool error = false;
438 try
439 {
440 (void) PyRun_String( const_cast<char*>( code ), Py_file_input,
441 d->mainnamespace.ptr(), retdict.ptr() );
442 }
443 catch( ... )
444 {
445 error = true;
446 };
447 error |= static_cast<bool>( PyErr_Occurred() );
448 if ( error )
449 {
450 saveErrors();
451 retdict.clear();
452 }
453
454 // debugging stuff, removed.
455 // std::string dictstring = extract<std::string>( str( retdict ) );
456
457 CompiledPythonScript::Private* ret = new CompiledPythonScript::Private;
458 ret->ref = 0;
459 ret->calcfunc = retdict.get( "calc" );
460 return CompiledPythonScript( ret );
461 }
462
CompiledPythonScript(const CompiledPythonScript & s)463 CompiledPythonScript::CompiledPythonScript( const CompiledPythonScript& s )
464 : d( s.d )
465 {
466 ++d->ref;
467 }
468
lastErrorExceptionType() const469 std::string PythonScripter::lastErrorExceptionType() const
470 {
471 return lastexceptiontype;
472 }
473
lastErrorExceptionValue() const474 std::string PythonScripter::lastErrorExceptionValue() const
475 {
476 return lastexceptionvalue;
477 }
478
lastErrorExceptionTraceback() const479 std::string PythonScripter::lastErrorExceptionTraceback() const
480 {
481 return lastexceptiontraceback;
482 }
483
calc(CompiledPythonScript & script,const Args & args)484 ObjectImp* PythonScripter::calc( CompiledPythonScript& script, const Args& args )
485 {
486 clearErrors();
487 object calcfunc = script.d->calcfunc;
488 try
489 {
490 std::vector<object> objectvect;
491 objectvect.reserve( args.size() );
492
493 for ( int i = 0; i < (int) args.size(); ++i )
494 {
495 object o( boost::ref( *args[i] ) );
496 objectvect.push_back( o );
497 }
498
499 handle<> argstuph( PyTuple_New( args.size() ) );
500 for ( int i = 0; i < (int) objectvect.size(); ++i )
501 {
502 /*
503 * this fixes bug https://bugs.kde.org/show_bug.cgi?id=401512
504 *
505 * it isn't completely clear whether we need XINCREF (test for null pointer)
506 * instead of INCREF. However I think that the arguments should never be
507 * null pointers
508 * mp
509 */
510 Py_INCREF((objectvect.begin() +i)->ptr());
511 PyTuple_SetItem( argstuph.get(), i, (objectvect.begin() +i)->ptr() );
512 };
513 tuple argstup( argstuph );
514
515 handle<> reth( PyObject_CallObject( calcfunc.ptr(), argstup.ptr() ) );
516 // object resulto = calcfunc( argstup );
517 // handle<> reth( PyObject_CallObject( calcfunc.ptr(), args ) );
518 object resulto( reth );
519
520 extract<ObjectImp&> result( resulto );
521 if( ! result.check() ) return new InvalidImp;
522 else
523 {
524 ObjectImp& ret = result();
525 return ret.copy();
526 };
527 }
528 catch( ... )
529 {
530 saveErrors();
531
532 return new InvalidImp;
533 };
534 }
535
saveErrors()536 void PythonScripter::saveErrors()
537 {
538 erroroccurred = true;
539 PyObject* poexctype;
540 PyObject* poexcvalue;
541 PyObject* poexctraceback;
542 PyErr_Fetch( &poexctype, &poexcvalue, &poexctraceback );
543 PyErr_NormalizeException( &poexctype, &poexcvalue, &poexctraceback );
544 handle<> exctypeh( poexctype );
545 handle<> excvalueh( poexcvalue );
546
547 object exctype( exctypeh );
548 object excvalue( excvalueh );
549 object exctraceback;
550 if ( poexctraceback )
551 {
552 handle<> exctracebackh( poexctraceback );
553 exctraceback = object( exctracebackh );
554 }
555
556 lastexceptiontype = extract<std::string>( str( exctype ) )();
557 lastexceptionvalue = extract<std::string>( str( excvalue ) )();
558
559 object printexcfunc = d->mainnamespace[ "traceback" ].attr( "format_exception" );
560
561 list tracebacklist = extract<list>( printexcfunc( exctype, excvalue, exctraceback ) )();
562 str tracebackstr( "" );
563 while ( true )
564 {
565 try {
566 str s = extract<str>( tracebacklist.pop() );
567 tracebackstr += s;
568 }
569 catch( ... )
570 {
571 break;
572 }
573 }
574
575 lastexceptiontraceback = extract<std::string>( tracebackstr )();
576 PyErr_Clear();
577 }
578
clearErrors()579 void PythonScripter::clearErrors()
580 {
581 PyErr_Clear();
582 lastexceptiontype.clear();
583 lastexceptionvalue.clear();
584 lastexceptiontraceback.clear();
585 erroroccurred = false;
586 }
587
valid()588 bool CompiledPythonScript::valid()
589 {
590 return !!d->calcfunc;
591 }
592
errorOccurred() const593 bool PythonScripter::errorOccurred() const
594 {
595 return erroroccurred;
596 }
597
598