diff options
Diffstat (limited to 'src/cython')
27 files changed, 10984 insertions, 0 deletions
diff --git a/src/cython/CMakeLists.txt b/src/cython/CMakeLists.txt new file mode 100644 index 0000000..9187d35 --- /dev/null +++ b/src/cython/CMakeLists.txt @@ -0,0 +1,131 @@ +#TODO - rewrite to use ALLCAPS? + +OPTION(2GEOM_CYTHON_BINDINGS + "Build a python binding with Cython." + OFF) +OPTION(2GEOM_CYTHON_BUILD_SHARED + "Build cython shared libraries." + ON) +IF(2GEOM_CYTHON_BUILD_SHARED) + SET(LIB_TYPE SHARED) + SET (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -shared") +ELSE(2GEOM_CYTHON_BUILD_SHARED) + SET(LIB_TYPE STATIC) +ENDIF(2GEOM_CYTHON_BUILD_SHARED) + + +IF(2GEOM_CYTHON_BINDINGS) + + include( UseCython ) + + # With CMake, a clean separation can be made between the source tree and the + # build tree. When all source is compiled, as with pure C/C++, the source is + # no-longer needed in the build tree. However, with pure *.py source, the + # source is processed directly. To handle this, we reproduce the availability + # of the source files in the build tree. + #add_custom_target( ReplicatePythonSourceTree ALL ${CMAKE_COMMAND} -P + # ${CMAKE_CURRENT_SOURCE_DIR}/CMakeScripts/ReplicatePythonSourceTree.cmake + # ${CMAKE_CURRENT_BINARY_DIR} + # WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + + #include_directories( ${CYTHON_CMAKE_EXAMPLE_SOURCE_DIR}/include ) + + # Process the CMakeLists.txt in the 'src' and 'bin' directory. + + set_source_files_properties( + _common_decl.pxd + _common_decl.pyx + + _cy_primitives.pxd + _cy_primitives.pyx + + _cy_rectangle.pxd + _cy_rectangle.pyx + + _cy_affine.pxd + _cy_affine.pyx + + _cy_curves.pxd + _cy_curves.pyx + + _cy_path.pxd + _cy_path.pyx + + _cy_conicsection.pxd + _cy_conicsection.pyx + + cy2geom.pyx + + PROPERTIES CYTHON_IS_CXX 1) + + # Multi-file cython modules do not appear to be working at the moment. + cython_add_module( _common_decl _common_decl.pyx) + + + cython_add_module( _cy_primitives _cy_primitives.pyx) + + cython_add_module( _cy_rectangle _cy_rectangle.pyx) + + cython_add_module( _cy_affine _cy_affine.pyx) + + cython_add_module( _cy_curves _cy_curves.pyx) + + cython_add_module( _cy_path _cy_path.pyx) + + #not finished for now + #~ cython_add_module( _cy_shape _cy_shape.pyx) + + cython_add_module( _cy_conicsection _cy_conicsection.pyx) + + target_link_libraries(_cy_primitives + #TODO! linking to static lib2geom.a gives -fPIC error, to compile + #you have to enable building dynamic library in cmake . -i + gsl gslcblas 2geom + ) + target_link_libraries(_cy_rectangle + gsl gslcblas 2geom + ) + + target_link_libraries(_cy_affine + gsl gslcblas 2geom + ) + + target_link_libraries(_cy_curves + gsl gslcblas 2geom + ) + + target_link_libraries(_cy_path + gsl gslcblas 2geom + ) + + #~ target_link_libraries(_cy_shape + #~ gsl gslcblas 2geom + #~ ) + + target_link_libraries(_cy_conicsection + gsl gslcblas 2geom + ) + + cython_add_module( cy2geom cy2geom.pyx) + + add_test(cython-primitives python2 test-primitives.py) + add_test(cython-rectangle python2 test-rectangle.py) + add_test(cython-affine python2 test-affine.py) + add_test(cython-curves python2 test-curves.py) + add_test(cython-path python2 test-path.py) + add_test(cython-conicsection python2 test-conicsection.py) + + # stuff to install the cy2geom package in the Python site-packages directory + FIND_PACKAGE(PythonLibs) + IF (WIN32) + GET_FILENAME_COMPONENT(PYTHON_LIB_INSTALL "${PYTHON_LIBRARY}" PATH) + GET_FILENAME_COMPONENT(SITEPACKAGE "${PYTHON_LIB_INSTALL}/../Lib/site-packages" ABSOLUTE) + ELSE (WIN32) + SET(PYTHON_LIB_INSTALL "/usr/local/lib/python2.7/dist-packages" CACHE STRING "Where to install the cy2geom module?") + SET(SITEPACKAGE ${PYTHON_LIB_INSTALL}) + ENDIF(WIN32) + + INSTALL(TARGETS _common_decl _cy_primitives _cy_rectangle _cy_affine _cy_curves _cy_path _cy_conicsection cy2geom + DESTINATION "${SITEPACKAGE}/cy2geom") + +ENDIF(2GEOM_CYTHON_BINDINGS) diff --git a/src/cython/README.md b/src/cython/README.md new file mode 100644 index 0000000..a4a0847 --- /dev/null +++ b/src/cython/README.md @@ -0,0 +1,29 @@ +# Installing: + +In addition to 2geom dependencies, cython bindings need `cython >= 0.16`. + +You can turn them on using cmake. Please note that you need to enable +shared library option as well. + +Building on Windows is not tested yet, should be done shortly. Extrapolating +from other projects using cython, this should not be major problem. + +# Usage: + +Bindings are almost 1-1 to 2geom, so using them is pretty straightforward. +Classes and methods are documented shortly. It's generally good idea to +look at 2geom docs and, if problems persist, at their source. + +To look at simple use cases, I suggest looking at tests and utils.py, located +in cython-bindings directory. + +# Hacking: + +cython is pretty straightforward to pick up, but its docs usually cover only +the simplest example. Looking at source of other project can be helpful +(cython bindings for SFML 2 are good example). + +Don't hesitate to contact me or 2geom mailinglist with any requests concerning +design of bindings and bug reports. + +Jan Pulmann - jan.pulmann@gmail.com diff --git a/src/cython/_common_decl.pxd b/src/cython/_common_decl.pxd new file mode 100644 index 0000000..8877048 --- /dev/null +++ b/src/cython/_common_decl.pxd @@ -0,0 +1,14 @@ +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +ctypedef double Coord +ctypedef int IntCoord + +cdef extern from "2geom/coord.h" namespace "Geom": + cdef Coord EPSILON + cdef enum Dim2: + X = 0 + Y = 1 + +cdef object wrap_vector_double(vector[double] v) +cdef vector[double] make_vector_double(object l) diff --git a/src/cython/_common_decl.pyx b/src/cython/_common_decl.pyx new file mode 100644 index 0000000..1f1544e --- /dev/null +++ b/src/cython/_common_decl.pyx @@ -0,0 +1,12 @@ +cdef object wrap_vector_double(vector[double] v): + r = [] + cdef unsigned int i + for i in range(v.size()): + r.append(v[i]) + return r + +cdef vector[double] make_vector_double(object l): + cdef vector[double] ret + for i in l: + ret.push_back( float(i) ) + return ret diff --git a/src/cython/_cy_affine.pxd b/src/cython/_cy_affine.pxd new file mode 100644 index 0000000..91eb662 --- /dev/null +++ b/src/cython/_cy_affine.pxd @@ -0,0 +1,247 @@ +from _common_decl cimport * + +from _cy_rectangle cimport Rect, cy_Rect, wrap_Rect +from _cy_primitives cimport Point, cy_Point, wrap_Point + + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Affine: + + Affine(Affine &) + Affine() + Affine(Coord, Coord, Coord, Coord, Coord, Coord) + + Coord operator[](unsigned int) + + Affine & operator*(Affine &) + Affine & operator*(Translate &) + Affine & operator*(Scale &) + Affine & operator*(Rotate &) + Affine & operator*(HShear &) + Affine & operator*(VShear &) + Affine & operator*(Zoom &) + + bint operator==(Affine &) + bint operator!=(Affine &) + + Point xAxis() + Point yAxis() + Point translation() + Coord expansionX() + Coord expansionY() + Point expansion() + + void setXAxis(Point &) + void setYAxis(Point &) + void setTranslation(Point &) + void setExpansionX(Coord) + void setExpansionY(Coord) + void setIdentity() + + bint isIdentity(Coord) + bint isTranslation(Coord) + bint isScale(Coord) + bint isUniformScale(Coord) + bint isRotation(Coord) + bint isHShear(Coord) + bint isVShear(Coord) + bint isNonzeroTranslation(Coord) + bint isNonzeroScale(Coord) + bint isNonzeroUniformScale(Coord) + bint isNonzeroRotation(Coord) + bint isNonzeroHShear(Coord) + bint isNonzeroVShear(Coord) + bint isZoom(Coord) + bint preservesArea(Coord) + bint preservesAngles(Coord) + bint preservesDistances(Coord) + bint flips() + bint isSingular(Coord) + + Affine withoutTranslation() + Affine inverse() + + Coord det() + Coord descrim2() + Coord descrim() + + bint are_near(Affine &, Affine &, Coord) + Affine a_identity "Geom::Affine::identity" () + +cdef extern from "2geom/transforms.h" namespace "Geom": + Affine reflection(Point &, Point &) + #TODO find out how cython __pow__ works + Affine pow(Affine &, int) + Translate pow(Translate &, int) + Scale pow(Scale &, int) + Rotate pow(Rotate &, int) + HShear pow(HShear &, int) + VShear pow(VShear &, int) + Zoom pow(Zoom &, int) + +cdef class cy_Affine: + cdef Affine* thisptr + +cdef cy_Affine wrap_Affine(Affine) + +#helper functions +cdef Affine get_Affine(t) +cdef bint is_transform(t) + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Translate: + Translate(Translate &) + Translate() + Translate(Point &) + Translate(Coord, Coord) + Coord operator[](Dim2) + Coord operator[](unsigned int) + Translate & operator*(Translate &) + Affine & operator*(Affine &) + bint operator==(Translate &) + bint operator!=(Translate &) + + Affine operator() + + Point vector() + Translate inverse() + + Translate t_identity "Geom::Translate::identity" () + +cdef class cy_Translate: + cdef Translate* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Scale: + Scale(Scale &) + Scale() + Scale(Point &) + Scale(Coord, Coord) + Scale(Coord) + Coord operator[](Dim2) + Scale & operator*(Scale &) + Affine & operator*(Affine &) + bint operator==(Scale &) + bint operator!=(Scale &) + + Affine operator() + + Point vector() + Scale inverse() + Scale identity() + + Scale s_identity "Geom::Scale::identity" () + +cdef class cy_Scale: + cdef Scale* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Rotate: + Rotate(Rotate &) + Rotate() + Rotate(Coord) + Rotate(Point &) + Rotate(Coord, Coord) + Point vector() + + Coord operator[](Dim2) + Coord operator[](unsigned int) + Rotate & operator*(Rotate &) + Affine & operator*(Affine &) + bint operator==(Rotate &) + bint operator!=(Rotate &) + + Affine operator() + Rotate inverse() + + Rotate r_identity "Geom::Rotate::identity" () + +cdef extern from "2geom/transforms.h" namespace "Geom::Rotate": + Rotate from_degrees(Coord) + + +cdef class cy_Rotate: + cdef Rotate* thisptr + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass VShear: + VShear(VShear &) + VShear(Coord) + Coord factor() + void setFactor(Coord) + + VShear &operator*(VShear) + Affine & operator*(Affine &) + bint operator==(VShear &) + bint operator!=(VShear &) + Affine operator() + + VShear inverse() + + VShear vs_identity "Geom::VShear::identity"() + +cdef class cy_VShear: + cdef VShear* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass HShear: + HShear(HShear &) + HShear(Coord) + Coord factor() + void setFactor(Coord) + HShear &operator*(HShear) + Affine & operator*(Affine &) + bint operator==(HShear &) + bint operator!=(HShear &) + Affine operator() + + HShear inverse() + + HShear hs_identity "Geom::HShear::identity"() + +cdef class cy_HShear: + cdef HShear* thisptr + + +cdef extern from "2geom/transforms.h" namespace "Geom": + cdef cppclass Zoom: + Zoom(Zoom &) + Zoom(Coord) + Zoom(Translate &) + Zoom(Coord, Translate &) + + Zoom & operator*(Zoom &) + Affine & operator*(Affine &) + bint operator==(Zoom &) + bint operator!=(Zoom &) + + Affine operator() + + Coord scale() + void setScale(Coord) + Point translation() + void setTranslation(Point &) + + Zoom inverse() + + Zoom() + + Zoom z_identity "Geom::Zoom::identity" () + +cdef extern from "2geom/transforms.h" namespace "Geom::Zoom": + Zoom map_rect(Rect &, Rect &) + +cdef class cy_Zoom: + cdef Zoom* thisptr + + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Eigen: + Point *vectors + double *values + Eigen(Affine &) + Eigen(double[2][2]) diff --git a/src/cython/_cy_affine.pyx b/src/cython/_cy_affine.pyx new file mode 100644 index 0000000..19aa9ef --- /dev/null +++ b/src/cython/_cy_affine.pyx @@ -0,0 +1,736 @@ +from cython.operator cimport dereference as deref + +from numbers import Number + + +cdef class cy_Affine: + + """Class representing affine transform in 2D plane. + + Corresponds to Affine class in 2geom. + """ + + def __cinit__(self, c0=None, + Coord c1=0, + Coord c2=0, + Coord c3=1, + Coord c4=0, + Coord c5=0): + """Create Affine instance from either transform or from coefficients.""" + if c0 is None: + self.thisptr = new Affine() + elif is_transform(c0): + self.thisptr = new Affine( get_Affine(c0) ) + else: + self.thisptr = new Affine(<Coord> float(c0) ,c1 ,c2 ,c3 ,c4 ,c5) + + def __str__(self): + """str(self)""" + return "Affine({}, {}, {}, {}, {}, {})".format( self[0], + self[1], + self[2], + self[3], + self[4], + self[5], + ) + def __repr__(self): + """repr(self)""" + return str(self) + + def __dealloc__(self): + del self.thisptr + + def __getitem__(self, int i): + """Get coefficients.""" + if i >= 6: + raise IndexError("Affine has only 6 coefficients.") + return deref(self.thisptr) [i] + + def __mul__(cy_Affine self, other): + """Compose with another transformation.""" + if isinstance(other, cy_Affine): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Affine> other).thisptr ) ) + elif isinstance(other, cy_Translate): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Translate> other).thisptr ) ) + elif isinstance(other, cy_Scale): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Scale> other).thisptr ) ) + elif isinstance(other, cy_Rotate): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Rotate> other).thisptr ) ) + elif isinstance(other, cy_HShear): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_HShear> other).thisptr ) ) + elif isinstance(other, cy_VShear): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_VShear> other).thisptr ) ) + elif isinstance(other, cy_Zoom): + return wrap_Affine( deref(self.thisptr) * deref( (<cy_Zoom> other).thisptr ) ) + + def __pow__(cy_Affine self, int n, z): + """Compose with self n times.""" + return wrap_Affine(pow( deref(self.thisptr), n )) + + def __richcmp__(cy_Affine self, cy_Affine other, int op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + def x_axis(self): + """Transformation of unit x vector without translation.""" + return wrap_Point(self.thisptr.xAxis()) + + def y_axis(self): + """Transformation of unit y vector without translation.""" + return wrap_Point(self.thisptr.yAxis()) + + def translation(self): + """Translation of transformation.""" + return wrap_Point(self.thisptr.translation()) + + def expansion_X(self): + """Expansion of unit x vector.""" + return self.thisptr.expansionX() + + def expansion_Y(self): + """Expansion of unit y vector.""" + return self.thisptr.expansionY() + + def expansion(self): + """Point( self.expansion_X(), self.expansion_Y() )""" + return wrap_Point(self.thisptr.expansion()) + + def set_X_axis(self, cy_Point vec): + """Set transformation of x unit vector without translation.""" + self.thisptr.setXAxis(deref( vec.thisptr )) + + def set_Y_axis(self, cy_Point vec): + """Set transformation of y unit vector without translation.""" + self.thisptr.setYAxis(deref( vec.thisptr )) + + def set_translation(self, cy_Point loc): + """Set translation of origin.""" + self.thisptr.setTranslation(deref( loc.thisptr )) + + def set_expansion_X(self, Coord val): + """Set expansion of x unit vector.""" + self.thisptr.setExpansionX(val) + + def set_expansion_Y(self, Coord val): + """Set expansion of y unit vector.""" + self.thisptr.setExpansionY(val) + + def set_identity(self): + """Set self to identity transformation.""" + self.thisptr.setIdentity() + + def is_identity(self, Coord eps=EPSILON): + """Return true if self is close to identity transform. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isIdentity(eps) + + def is_translation(self, Coord eps=EPSILON): + """Return true if self is close to transformation. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isTranslation(eps) + + def is_scale(self, Coord eps=EPSILON): + """Return true if self is close to scale. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isScale(eps) + + def is_uniform_scale(self, Coord eps=EPSILON): + """Return true if self is close to uniform scale. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isUniformScale(eps) + + def is_rotation(self, Coord eps=EPSILON): + """Return true if self is close to rotation. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isRotation(eps) + + def is_HShear(self, Coord eps=EPSILON): + """Return true if self is close to horizontal shear. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isHShear(eps) + + def is_VShear(self, Coord eps=EPSILON): + """Return true if self is close to vertical shear. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isVShear(eps) + + def is_nonzero_translation(self, Coord eps=EPSILON): + """Return true if self is close to translation and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroTranslation(eps) + + def is_nonzero_scale(self, Coord eps=EPSILON): + """Return true if self is close to scale and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroScale(eps) + + def is_nonzero_uniform_scale(self, Coord eps=EPSILON): + """Return true if self is close to scale and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroUniformScale(eps) + + def is_nonzero_rotation(self, Coord eps=EPSILON): + """Return true if self is close to rotation and is identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroRotation(eps) + + def is_nonzero_HShear(self, Coord eps=EPSILON): + """Return true if self is close to horizontal shear and is not identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroHShear(eps) + + def is_nonzero_VShear(self, Coord eps=EPSILON): + """Return true if self is close to vertical shear and is not identity. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isNonzeroVShear(eps) + + def is_zoom(self, Coord eps=EPSILON): + """Return true if self is close to zoom. + + Use second argument eps to specify tolerance. + """ + return self.thisptr.isZoom(eps) + + def preserves_area(self, Coord eps=EPSILON): + """Return true if areas are preserved after transformation + + Use second argument eps to specify tolerance. + """ + return self.thisptr.preservesArea(eps) + + def preserves_angles(self, Coord eps=EPSILON): + """Return true if angles are preserved after transformation + + Use second argument eps to specify tolerance. + """ + return self.thisptr.preservesAngles(eps) + + def preserves_distances(self, Coord eps=EPSILON): + """Return true if distances are preserved after transformation + + Use second argument eps to specify tolerance. + """ + return self.thisptr.preservesDistances(eps) + + def flips(self): + """Return true if transformation flips - it has negative scaling.""" + return self.thisptr.flips() + + def is_singular(self, Coord eps=EPSILON): + """Check whether transformation matrix is singular.""" + return self.thisptr.isSingular(eps) + + def without_translation(self): + """Return transformation without translation part.""" + return wrap_Affine(self.thisptr.withoutTranslation()) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Affine(self.thisptr.inverse()) + + def det(self): + """Return determinant of transformation matrix.""" + return self.thisptr.det() + + def descrim2(self): + """Return absolute value of self.det()""" + return self.thisptr.descrim2() + + def descrim(self): + """Return square root of self.descrim2()""" + return self.thisptr.descrim() + + @classmethod + def identity(self): + """Create identity transformation.""" + return wrap_Affine(a_identity()) + + @classmethod + def are_near(cls, A, B, Coord eps=EPSILON): + """Test if two transforms are near.""" + if is_transform(A) & is_transform(B): + return are_near(get_Affine(A), get_Affine(B), eps) + + @classmethod + def reflection(cls, cy_Point vector, cy_Point origin): + """Create transformation reflecting along line specified by vector and origin.""" + return wrap_Affine( reflection( deref(vector.thisptr), deref(origin.thisptr) ) ) + +cdef cy_Affine wrap_Affine(Affine p): + cdef Affine * retp = new Affine() + retp[0] = p + cdef cy_Affine r = cy_Affine.__new__(cy_Affine) + r.thisptr = retp + return r + +cdef Affine get_Affine(t): + if isinstance(t, cy_Affine ): + return deref( (<cy_Affine> t).thisptr ) + elif isinstance(t, cy_Translate): + return <Affine> deref( (<cy_Translate> t).thisptr ) + elif isinstance(t, cy_Scale): + return <Affine> deref( (<cy_Scale> t).thisptr ) + elif isinstance(t, cy_Rotate): + return <Affine> deref( (<cy_Rotate> t).thisptr ) + elif isinstance(t, cy_HShear): + return <Affine> deref( (<cy_HShear> t).thisptr ) + elif isinstance(t, cy_VShear): + return <Affine> deref( (<cy_VShear> t).thisptr ) + elif isinstance(t, cy_Zoom): + return <Affine> deref( (<cy_Zoom> t).thisptr ) + +cdef bint is_transform(t): + return any([isinstance(t, cy_Affine), + isinstance(t, cy_Translate), + isinstance(t, cy_Scale), + isinstance(t, cy_Rotate), + isinstance(t, cy_HShear), + isinstance(t, cy_VShear), + isinstance(t, cy_Zoom) + ]) + + +cdef class cy_Translate: + + """Translation in 2D plane + + Corresponds to Translate class in 2geom. + """ + + def __cinit__(self, *args): + """Create Translate instance form point or it's two coordinates.""" + if len(args) == 0: + self.thisptr = new Translate() + elif len(args) == 1: + self.thisptr = new Translate( deref( (<cy_Point> args[0]).thisptr ) ) + elif len(args) == 2: + self.thisptr = new Translate(float(args[0]), float(args[1])) + + def __dealloc__(self): + del self.thisptr + + + def __getitem__(self, Dim2 dim): + """Get components of displacement vector.""" + return deref( self.thisptr ) [dim] + + def __mul__(cy_Translate self, o): + """Compose with another transformation.""" + if isinstance(o, cy_Translate): + return wrap_Translate(deref( self.thisptr ) * deref( (<cy_Translate>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_Translate self, int n, z): + """Compose with self n times.""" + return wrap_Translate(pow( deref(self.thisptr), n )) + + def vector(self): + """Get displacement vector.""" + return wrap_Point(self.thisptr.vector()) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Translate(self.thisptr.inverse()) + + @classmethod + def identity(self): + """Create identity translation.""" + return wrap_Translate(t_identity()) + + def __richcmp__(cy_Translate self, cy_Translate t, op): + if op == 2: + return deref(self.thisptr) == deref(t.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(t.thisptr) + +cdef cy_Translate wrap_Translate(Translate p): + cdef Translate * retp = new Translate() + retp[0] = p + cdef cy_Translate r = cy_Translate.__new__(cy_Translate) + r.thisptr = retp + return r + + +cdef class cy_Scale: + + """Scale in 2D plane. + + Corresponds to Scale in 2geom. + """ + + def __cinit__(self, *args): + """Create scale from number, point or it's two coordinates. + + One number creates uniform scale, point or two numbers create + scale with different x and y scale factor. + """ + if len(args) == 0: + self.thisptr = new Scale() + elif len(args) == 1: + if isinstance(args[0], Number): + self.thisptr = new Scale(<Coord> float(args[0])) + elif isinstance(args[0], cy_Point): + self.thisptr = new Scale( deref( (<cy_Point> args[0]).thisptr ) ) + elif len(args) == 2: + self.thisptr = new Scale(float(args[0]), float(args[1])) + + def __dealloc__(self): + del self.thisptr + + def __getitem__(self, Dim2 d): + """Get scale factors for each axis.""" + return deref( self.thisptr ) [d] + + def __mul__(cy_Scale self, o): + """Compose with another transformation.""" + if isinstance(o, cy_Scale): + return wrap_Scale(deref( self.thisptr ) * deref( (<cy_Scale>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_Scale self, int n, z): + """Compose with self n times.""" + return wrap_Scale(pow( deref(self.thisptr), n )) + + def vector(self): + """Get both scale factors as a point.""" + return wrap_Point(self.thisptr.vector()) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Scale(self.thisptr.inverse()) + + @classmethod + def identity(self): + """Create identity scale.""" + return wrap_Scale(s_identity()) + + def __richcmp__(cy_Scale self, cy_Scale s, op): + if op == 2: + return deref(self.thisptr) == deref(s.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(s.thisptr) + +cdef cy_Scale wrap_Scale(Scale p): + cdef Scale * retp = new Scale() + retp[0] = p + cdef cy_Scale r = cy_Scale.__new__(cy_Scale) + r.thisptr = retp + return r + + +cdef class cy_Rotate: + + """Rotation in 2D plane. + + Corresponds to Rotate in 2geom. + """ + + def __cinit__(self, *args): + """Create new Rotate instance, specifying angle. + + Use one number to set the angle, or point/its two coordinates, + using point's angle with x-axis as a rotation angle. + """ + if len(args) == 0: + self.thisptr = new Rotate() + elif len(args) == 1: + if isinstance(args[0], Number): + self.thisptr = new Rotate(<Coord> float(args[0])) + elif isinstance(args[0], cy_Point): + self.thisptr = new Rotate( deref( (<cy_Point> args[0]).thisptr ) ) + elif len(args) == 2: + self.thisptr = new Rotate(float(args[0]), float(args[1])) + + def __dealloc__(self): + del self.thisptr + + def vector(self): + """Return Point(1, 0)*self.""" + return wrap_Point(self.thisptr.vector()) + + def __getitem__(self, Dim2 dim): + """Get components of self.vector()""" + return deref( self.thisptr ) [dim] + + def __mul__(cy_Rotate self, o): + """Compose with another transformation.""" + if isinstance(o, cy_Rotate): + return wrap_Rotate(deref( self.thisptr ) * deref( (<cy_Rotate>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_Rotate self, int n, z): + """Compose with self n times.""" + return wrap_Rotate(pow( deref(self.thisptr), n )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Rotate(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity rotation.""" + return wrap_Rotate(r_identity()) + + @classmethod + def from_degrees(cls, Coord deg): + """Create rotation from angle in degrees.""" + return wrap_Rotate(from_degrees(deg)) + + def __richcmp__(cy_Rotate self, cy_Rotate r, op): + if op == 2: + return deref(self.thisptr) == deref(r.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(r.thisptr) + +cdef cy_Rotate wrap_Rotate(Rotate p): + cdef Rotate * retp = new Rotate() + retp[0] = p + cdef cy_Rotate r = cy_Rotate.__new__(cy_Rotate) + r.thisptr = retp + return r + + +cdef class cy_VShear: + + """Vertical shear in 2D plane + + Corresponds to VShear in 2geom. + """ + + def __cinit__(self, Coord h): + """Create VShear instance form shearing factor.""" + self.thisptr = new VShear(h) + + def __dealloc__(self): + del self.thisptr + + def factor(self): + """Get shearing factor.""" + return self.thisptr.factor() + + def set_factor(self, Coord nf): + """Set shearing factor.""" + self.thisptr.setFactor(nf) + + def __mul__(cy_VShear self, o): + """Compose with another transformation.""" + if isinstance(o, cy_VShear): + return wrap_VShear(deref( self.thisptr ) * deref( (<cy_VShear>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_VShear self, int n, z): + """Compose with self n times.""" + return wrap_VShear(pow( deref(self.thisptr), n )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_VShear(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity VShear.""" + return wrap_VShear( vs_identity() ) + + def __richcmp__(cy_VShear self, cy_VShear hs, op): + if op == 2: + return deref(self.thisptr) == deref(hs.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(hs.thisptr) + + + +cdef cy_VShear wrap_VShear(VShear p): + cdef VShear * retp = new VShear(0) + retp[0] = p + cdef cy_VShear r = cy_VShear.__new__(cy_VShear, 0) + r.thisptr = retp + return r + +cdef class cy_HShear: + + """Horizontal shear in 2D plane + + Corresponds to HShear in 2geom. + """ + + def __cinit__(self, Coord h): + """Create HShear instance form shearing factor.""" + self.thisptr = new HShear(h) + + def __dealloc__(self): + del self.thisptr + + def factor(self): + """Get shearing factor.""" + return self.thisptr.factor() + + def set_factor(self, Coord nf): + """Set shearing factor.""" + self.thisptr.setFactor(nf) + + def __mul__(cy_HShear self, o): + """Compose with another transformation.""" + if isinstance(o, cy_HShear): + return wrap_HShear(deref( self.thisptr ) * deref( (<cy_HShear>o).thisptr )) + elif is_transform(o): + return wrap_Affine(deref(self.thisptr) * get_Affine(o)) + + def __pow__(cy_HShear self, int n, z): + """Compose with self n times.""" + return wrap_HShear(pow( deref(self.thisptr), n )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_HShear(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity HShear.""" + return wrap_HShear( hs_identity() ) + + def __richcmp__(cy_HShear self, cy_HShear hs, op): + if op == 2: + return deref(self.thisptr) == deref(hs.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(hs.thisptr) + +cdef cy_HShear wrap_HShear(HShear p): + cdef HShear * retp = new HShear(0) + retp[0] = p + cdef cy_HShear r = cy_HShear.__new__(cy_HShear, 0) + r.thisptr = retp + return r + + +cdef class cy_Zoom: + + """Zoom in 2D plane, consisting of uniform scale and translation. + + Corresponds to Zoom in 2geom. + """ + + def __cinit__(self, Coord scale=1, cy_Translate translate=cy_Translate()): + """Create Zoom from scale factor and translation""" + self.thisptr = new Zoom( scale, deref( translate.thisptr ) ) + + def __dealloc__(self): + del self.thisptr + + def __mul__(cy_Zoom self, cy_Zoom z): + """Compose with another transformation.""" + return wrap_Zoom( deref(self.thisptr) * deref( z.thisptr )) + + def __pow__(cy_Zoom self, int n, z): + """Compose with self n times.""" + return wrap_Zoom(pow( deref(self.thisptr), n )) + + def __richcmp__(cy_Zoom self, cy_Zoom z, op): + if op == 2: + return deref(self.thisptr) == deref(z.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(z.thisptr) + + def scale(self): + """Get scale factor.""" + return self.thisptr.scale() + + def set_scale(self, Coord s): + """Set scale factor.""" + self.thisptr.setScale(s) + + def translation(self): + """Get translation as a point.""" + return wrap_Point(self.thisptr.translation()) + + def set_translation(self, cy_Point p): + """Set translation.""" + self.thisptr.setTranslation(deref( p.thisptr )) + + def inverse(self): + """Return inverse transformation.""" + return wrap_Zoom(self.thisptr.inverse()) + + @classmethod + def identity(cls): + """Create identity zoom.""" + return wrap_Zoom(z_identity()) + + @classmethod + def map_rect(self, cy_Rect old_r, cy_Rect new_r): + """Create zooming used to go from old rectangle to new.""" + return wrap_Zoom(map_rect(deref( old_r.thisptr ) ,deref( new_r.thisptr ))) + +cdef cy_Zoom wrap_Zoom(Zoom p): + cdef Zoom * retp = new Zoom(0) + retp[0] = p + cdef cy_Zoom r = cy_Zoom.__new__(cy_Zoom, 0) + r.thisptr = retp + return r + + +cdef class cy_Eigen: + + """Class computing eigenvalues and eigenvectors of 2x2 matrix. + + Corresponds to Eigen class in 2geom. + """ + + cdef Eigen* thisptr + + def __cinit__(self, a): + """Create Eigen form 2D transform or 2x2 list - matrix.""" + cdef Affine at + cdef double m[2][2] + if is_transform(a): + at = get_Affine(a) + self.thisptr = new Eigen(at) + else: + for i in range(2): + for j in range(2): + m[i][j] = a[i][j] + self.thisptr = new Eigen(m) + + def __dealloc__(self): + del self.thisptr + + @property + def vectors(self): + """Eigenvectors of matrix.""" + return (wrap_Point(self.thisptr.vectors[0]), wrap_Point(self.thisptr.vectors[1])) + + @property + def values(self): + """Eigenvalues of matrix.""" + return (self.thisptr.values[0], self.thisptr.values[1]) diff --git a/src/cython/_cy_conicsection.pxd b/src/cython/_cy_conicsection.pxd new file mode 100644 index 0000000..f330cd3 --- /dev/null +++ b/src/cython/_cy_conicsection.pxd @@ -0,0 +1,50 @@ +from _common_decl cimport * + +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect +from _cy_rectangle cimport cy_OptRect +from _cy_affine cimport is_transform, get_Affine, Affine +from _cy_curves cimport Curve, cy_Curve, wrap_Curve_p +from _cy_curves cimport SBasis, cy_SBasis +from _cy_curves cimport EllipticalArc, cy_EllipticalArc, wrap_EllipticalArc + +from _cy_path cimport Path, cy_Path + +from _cy_primitives cimport Point, cy_Point, wrap_Point, wrap_vector_point, make_vector_point + + +cdef extern from "2geom/circle.h" namespace "Geom": + cdef cppclass Circle: + Circle() + Circle(double, double, double) + Circle(Point, double) + Circle(double, double, double, double) + Circle(vector[Point] &) + void setCenter(Point &) + void setRadius(double) + void set(double, double, double, double) + void fit(vector[Point] &) + EllipticalArc * arc(Point &, Point &, Point &, bint) + Point center() + Coord center(Dim2) + Coord radius() + +cdef extern from "2geom/ellipse.h" namespace "Geom": + cdef cppclass Ellipse: + Ellipse() + Ellipse(double, double, double, double, double) + Ellipse(double, double, double, double, double, double) + Ellipse(vector[Point] &) + Ellipse(Circle &) + void set(double, double, double, double, double) + void set(double, double, double, double, double, double) + void set(vector[Point] &) + EllipticalArc * arc(Point &, Point &, Point &, bint) + Point center() + Coord center(Dim2) + Coord ray(Dim2) + Coord rot_angle() + vector[double] implicit_form_coefficients() + Ellipse transformed(Affine &) diff --git a/src/cython/_cy_conicsection.pyx b/src/cython/_cy_conicsection.pyx new file mode 100644 index 0000000..91bbd73 --- /dev/null +++ b/src/cython/_cy_conicsection.pyx @@ -0,0 +1,183 @@ +from cython.operator cimport dereference as deref +from numbers import Number +from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect +from _cy_rectangle cimport cy_Interval, wrap_Interval + +from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform + +from _cy_curves cimport is_Curve, get_Curve_p +from _cy_path cimport wrap_Path + + +cdef class cy_Circle: + + """Circle in 2D plane. + + Corresponds to Circle class in 2geom. + """ + + cdef Circle* thisptr + + def __cinit__(self, cy_Point center=None, double r=0): + """Create circle from center and radius.""" + if center is None: + self.thisptr = new Circle() + else: + self.thisptr = new Circle(deref( center.thisptr ), r) + + @classmethod + def from_coefficients(self, double A, double B, double C, double D): + """Create circle form implicit equation coefficients: + + Implicit equation is Ax**2 + Ay**2 + Bx + Cy + D = 0 + """ + return wrap_Circle( Circle(A, B, C, D) ) + + @classmethod + def from_points(self, points): + """Create best fitting circle from at least three points.""" + return wrap_Circle( Circle( make_vector_point(points) ) ) + + def set_center(self, cy_Point c): + """Set coordinates of center.""" + self.thisptr.setCenter(deref(c.thisptr)) + + def set_radius(self, double r): + """Set the circle's radius.""" + self.thisptr.setRadius(r) + + def set_coefficients(self, double A, double B, double C, double D): + """Set implicit equation coefficients: + + Implicit equation is Ax**2 + Ay**2 + Bx + Cy + D = 0 + """ + self.thisptr.set(A, B, C, D) + + def fit(self, points): + """Set circle to the best fit of at least three points.""" + self.thisptr.fit( make_vector_point(points) ) + + def arc(self, cy_Point initial, cy_Point inner, cy_Point final, bint _svg_compliant=True): + """Get (SVG)EllipticalArc. + + Args: + initial: Initial point of arc + inner: Inner point of arc. + final: Final point of arc. + """ + return wrap_EllipticalArc( deref(self.thisptr.arc(deref( initial.thisptr ), deref( inner.thisptr ), deref( final.thisptr ))) ) + + def center(self): + """Get center of circle in point.""" + return wrap_Point(self.thisptr.center()) + + def radius(self): + """Get radius of circle.""" + return self.thisptr.radius() + +cdef cy_Circle wrap_Circle(Circle p): + cdef Circle * retp = new Circle() + retp[0] = p + cdef cy_Circle r = cy_Circle.__new__(cy_Circle) + r.thisptr = retp + return r + +cdef class cy_Ellipse: + """Ellipse in 2D plane. + + Corresponds to Ellipse class in 2geom. + """ + + cdef Ellipse* thisptr + + def __cinit__(self, cy_Point center=None, rx=0, ry=0, double a=0): + """Create new ellipse: + + Args: + center: Center of ellipse (between foci) + rx, ry: major and minor semi-axis. + a: angle of major axis. + """ + if center is None: + self.thisptr = new Ellipse() + else: + self.thisptr = new Ellipse(center.x, center.y, rx, ry, a) + + @classmethod + def from_coefficients(cls, double A, double B, double C, double D, double E, double F): + """Create ellipse from coefficients of implicit equation. + + Implicit equation has form Ax**2 + Bxy + Cy**2 + Dx + Ey + F = 0 + """ + return wrap_Ellipse(Ellipse(A, B, C, D, E, F)) + + @classmethod + def from_circle(cls, cy_Circle c): + """Create ellipse identical to circle.""" + return wrap_Ellipse(Ellipse(deref( c.thisptr ))) + + @classmethod + def from_points(cls, points): + """Create ellipse fitting at least 5 points.""" + return wrap_Ellipse( Ellipse( make_vector_point(points) ) ) + + def set(self, cy_Point center, double rx, double ry, double a): + """Set center, rays and angle of ellipse. + + Args: + center: Center of ellipse. + rx, ry: Major and minor semi-axis. + a: angle of major axis. + self.thisptr.set(center.x, center.y, rx, ry, a) + """ + + def set_coefficients(self, double A, double B, double C, double D, double E, double F): + """Set coefficients of implicit equation. + + Implicit equation has form Ax**2 + Bxy + Cy**2 + Dx + Ey + F = 0 + """ + self.thisptr.set(A, B, C, D, E, F) + + def set_points(self, points): + """Set ellipse to the best fit of at least five points.""" + self.thisptr.set( make_vector_point(points) ) + + def arc(self, cy_Point initial, cy_Point inner, cy_Point final, bint svg_compliant=True): + """Get (SVG)EllipticalArc. + + Args: + initial: Initial point of arc + inner: Inner point of arc. + final: Final point of arc. + """ + return wrap_EllipticalArc( deref(self.thisptr.arc(deref( initial.thisptr ), deref( inner.thisptr ), deref( final.thisptr ))) ) + + def center(self): + """Get center of ellipse.""" + return wrap_Point(self.thisptr.center()) + + def ray(self, Dim2 d): + """Get major/minor semi-axis.""" + return self.thisptr.ray(d) + + def rot_angle(self): + """Get angle of major axis.""" + return self.thisptr.rot_angle() + + def implicit_form_coefficients(self): + """Get coefficients of implicit equation in list.""" + return wrap_vector_double(self.thisptr.implicit_form_coefficients()) + + def transformed(self, m): + """Return transformed ellipse.""" + cdef Affine at + if is_transform(m): + at = get_Affine(m) + return wrap_Ellipse(self.thisptr.transformed(at)) + +cdef cy_Ellipse wrap_Ellipse(Ellipse p): + cdef Ellipse * retp = new Ellipse() + retp[0] = p + cdef cy_Ellipse r = cy_Ellipse.__new__(cy_Ellipse) + r.thisptr = retp + return r diff --git a/src/cython/_cy_curves.pxd b/src/cython/_cy_curves.pxd new file mode 100644 index 0000000..dd13c06 --- /dev/null +++ b/src/cython/_cy_curves.pxd @@ -0,0 +1,421 @@ +from _common_decl cimport * + +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect +from _cy_affine cimport Affine +from _cy_primitives cimport Point, cy_Point, wrap_Point, wrap_vector_point, make_vector_point +from _cy_primitives cimport Angle, cy_Angle, wrap_Angle +from _cy_primitives cimport AngleInterval + + +cdef extern from "2geom/d2.h" namespace "Geom": + cdef cppclass D2[T]: + D2() + D2(T &, T &) + T& operator[](unsigned i) + +cdef extern from "2geom/curve.h" namespace "Geom": + cdef cppclass Curve: + Curve() + Point initialPoint() + Point finalPoint() + bint isDegenerate() + Point pointAt(Coord) + Coord valueAt(Coord, Dim2) + Point operator()(Coord) + vector[Point] pointAndDerivatives(Coord, unsigned int) + void setInitial(Point &) + void setFinal(Point &) + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + OptRect boundsLocal(OptInterval &) + Curve * duplicate() + Curve * transformed(Affine &) + Curve * portion(Coord, Coord) + Curve * portion(Interval &) + Curve * reverse() + Curve * derivative() + Coord nearestTime(Point &, Coord, Coord) + Coord nearestTime(Point &, Interval &) + vector[double] allNearestTimes(Point &, Coord, Coord) + vector[double] allNearestTimes(Point &, Interval &) + Coord length(Coord) + vector[double] roots(Coord, Dim2) + int winding(Point &) + Point unitTangentAt(Coord, unsigned int) + D2[SBasis] toSBasis() + int degreesOfFreedom() + bint operator==(Curve &) + +cdef class cy_Curve: + cdef Curve* thisptr + +#~ cdef cy_Curve wrap_Curve(Curve & p) +cdef cy_Curve wrap_Curve_p(Curve * p) + + +cdef extern from "2geom/linear.h" namespace "Geom": + cdef cppclass Linear: +#~ Linear(Linear &) + Linear() + Linear(double, double) + Linear(double) + double operator[](int const) + bint isZero(double) + bint isConstant(double) + bint isFinite() + double at0() + double at1() + double valueAt(double) + double operator()(double) + SBasis toSBasis() + OptInterval bounds_exact() + OptInterval bounds_fast() + OptInterval bounds_local(double, double) + double tri() + double hat() + + bint operator==(Linear &, Linear &) + bint operator!=(Linear &, Linear &) + Linear operator*(Linear &, double) + Linear operator+(Linear &, double) + Linear operator+(Linear &, Linear &) + #cython has trouble resolving these + Linear L_neg "operator-" (Linear &) + Linear L_sub_Ld "operator-"(Linear &, double) + Linear L_sub_LL "operator-"(Linear &, Linear &) + Linear operator/(Linear &, double) + + double lerp(double, double, double) + Linear reverse(Linear &) + + +cdef extern from "2geom/sbasis.h" namespace "Geom": + cdef cppclass SBasis: + + SBasis() + SBasis(double) + SBasis(double, double) + SBasis(SBasis &) + SBasis(vector[Linear] &) + SBasis(Linear &) + + size_t size() + Linear operator[](unsigned int) + bint empty() + Linear & back() + void pop_back() + void resize(unsigned int) + void resize(unsigned int, Linear &) + void reserve(unsigned int) + void clear() +#~ void insert(::__gnu_cxx::__normal_iterator<Geom::Linear*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >, ::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >, ::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > >) + Linear & at(unsigned int) + bint operator==(SBasis &) + bint operator!=(SBasis &) + + bint isZero(double) + bint isConstant(double) + bint isFinite() + double at0() + double at1() + int degreesOfFreedom() + double valueAt(double) + double operator()(double) + vector[double] valueAndDerivatives(double, unsigned int) + SBasis toSBasis() + double tailError(unsigned int) + SBasis operator()(SBasis &) + void normalize() + void truncate(unsigned int) + + SBasis operator*(SBasis &, SBasis &) + SBasis operator*(double, SBasis &) + SBasis operator*(SBasis &, double) + SBasis operator+(SBasis &, double) + SBasis operator+(SBasis &, SBasis &) + SBasis SB_sub_Sd "operator-" (SBasis &, double) + SBasis SB_sub_SS "operator-" (SBasis &, SBasis &) + SBasis SB_neg "operator-" (SBasis &) + SBasis operator/(SBasis &, double) + + SBasis sin(Linear, int) + SBasis cos(Linear, int) + SBasis sqrt(SBasis &, int) + SBasis reciprocal(Linear &, int) + SBasis shift(Linear &, int) + SBasis shift(SBasis &, int) + SBasis inverse(SBasis, int) + + SBasis portion(SBasis &, Interval) + SBasis portion(SBasis &, double, double) + SBasis compose(SBasis &, SBasis &, unsigned int) + SBasis compose(SBasis &, SBasis &) + SBasis truncate(SBasis &, unsigned int) + + unsigned int valuation(SBasis &, double) + + vector[ vector[double] ] multi_roots(SBasis &, vector[double] &, double, double, double, double) #TODO + vector[Interval] level_set(SBasis &, Interval &, double, double, double) + vector[Interval] level_set(SBasis &, double, double, double, double, double) + vector[double] roots(SBasis &, Interval const) + vector[double] roots(SBasis &) + vector[ vector[Interval] ] level_sets(SBasis &, vector[Interval] &, double, double, double) #TODO + vector[ vector[Interval] ] level_sets(SBasis &, vector[double] &, double, double, double, double) #TODO + + SBasis reverse(SBasis &) + SBasis derivative(SBasis &) + SBasis integral(SBasis &) + SBasis divide(SBasis &, SBasis &, int) + SBasis compose_inverse(SBasis &, SBasis &, unsigned int, double) + SBasis multiply(SBasis &, SBasis &) + SBasis multiply_add(SBasis &, SBasis &, SBasis) + + OptInterval bounds_exact(SBasis &) + OptInterval bounds_local(SBasis &, OptInterval &, int) + OptInterval bounds_fast(SBasis &, int) + +cdef class cy_SBasis: + cdef SBasis* thisptr + +cdef extern from "2geom/sbasis-curve.h" namespace "Geom": + cdef cppclass SBasisCurve: + SBasisCurve(D2[SBasis] &) + SBasisCurve(Curve &) + Curve * duplicate() + Point initialPoint() + Point finalPoint() + bint isDegenerate() + Point pointAt(Coord) + Point operator()(double) + vector[Point] pointAndDerivatives(Coord, unsigned int) + Coord valueAt(Coord, Dim2) + void setInitial(Point &) + void setFinal(Point &) + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + vector[double] roots(Coord, Dim2) + Coord nearestTime(Point &, Coord, Coord) + vector[double] allNearestTimes(Point &, Coord, Coord) + Coord length(Coord) + Curve * portion(Coord, Coord) + Curve * transformed(Affine &) + Curve * derivative() + D2[SBasis] toSBasis() + int degreesOfFreedom() + + +cdef extern from "2geom/bezier.h" namespace "Geom": + cdef cppclass Bezier: +#~ struct Order +#~ pass + unsigned int order() + unsigned int size() + Bezier() +#~ Bezier(Bezier.Order) + Bezier(Coord) + Bezier(Coord, Coord) + Bezier(Coord, Coord, Coord) + Bezier(Coord, Coord, Coord, Coord) + void resize(unsigned int, Coord) + void clear() + unsigned int degree() + bint isZero(double) + bint isConstant(double) + bint isFinite() + Coord at0() + Coord at1() + Coord valueAt(double) + Coord operator()(double) + SBasis toSBasis() + Coord & operator[](unsigned int) + void setPoint(unsigned int, double) + vector[double] valueAndDerivatives(Coord, unsigned int) + pair[Bezier, Bezier] subdivide(Coord) + vector[double] roots() + vector[double] roots(Interval const) + Bezier forward_difference(unsigned int) + Bezier elevate_degree() + Bezier reduce_degree() + Bezier elevate_to_degree(unsigned int) + Bezier deflate() + Bezier(Coord *, unsigned int) + + Bezier operator*(Bezier &, double) + Bezier operator+(Bezier &, double) + Bezier operator-(Bezier &, double) + Bezier operator/(Bezier &, double) + + Bezier portion(Bezier &, double, double) + OptInterval bounds_local(Bezier &, OptInterval) + Coord casteljau_subidivision(Coord, Coord *, Coord *, Coord *, unsigned int) + void bezier_to_sbasis(SBasis &, Bezier &) + Bezier integral(Bezier &) + Bezier derivative(Bezier &) + OptInterval bounds_exact(Bezier &) + double bernsteinValueAt(double, double *, unsigned int) + vector[Point] bezier_points(D2[Bezier] &) + OptInterval bounds_fast(Bezier &) + Bezier multiply(Bezier &, Bezier &) + Bezier reverse(Bezier &) + +#This is ugly workaround around cython's lack of support for integer template parameters +cdef extern from *: + ctypedef int n_0 "0" + ctypedef int n_1 "1" + +cdef extern from "2geom/bezier-curve.h" namespace "Geom": + cdef cppclass BezierCurve: + unsigned int order() + vector[Point] controlPoints() + void setPoint(unsigned int, Point) + void setPoints(vector[Point] &) + Point operator[](unsigned int) + Point initialPoint() + Point finalPoint() + bint isDegenerate() + void setInitial(Point &) + void setFinal(Point &) + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + Curve * duplicate() + Curve * portion(Coord, Coord) + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + int degreesOfFreedom() + vector[double] roots(Coord, Dim2) + Coord length(Coord) + Point pointAt(Coord) + vector[Point] pointAndDerivatives(Coord, unsigned int) + Coord valueAt(Coord, Dim2) + D2[SBasis] toSBasis() + #protected: +#~ BezierCurve() +#~ BezierCurve(D2[Bezier] &) +#~ BezierCurve(Bezier &, Bezier &) +#~ BezierCurve(vector[Point] &) + #~ Point middle_point(LineSegment &) + #~ Coord length(LineSegment &) + Coord bezier_length(Point, Point, Point, Point, Coord) + Coord bezier_length(Point, Point, Point, Coord) + Coord bezier_length(vector[Point] &, Coord) + + cdef cppclass LineSegment: + LineSegment() + LineSegment(D2[Bezier] &) + LineSegment(Bezier, Bezier) + LineSegment(vector[Point] &) + LineSegment(Point, Point) + pair[LineSegment, LineSegment] subdivide(Coord) + + Curve * duplicate() + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + + cdef cppclass QuadraticBezier: + QuadraticBezier() + QuadraticBezier(D2[Bezier] &) + QuadraticBezier(Bezier, Bezier) + QuadraticBezier(vector[Point] &) + QuadraticBezier(Point, Point, Point) + pair[QuadraticBezier, QuadraticBezier] subdivide(Coord) + + Curve * duplicate() + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + + + cdef cppclass CubicBezier: + CubicBezier() + CubicBezier(D2[Bezier] &) + CubicBezier(Bezier, Bezier) + CubicBezier(vector[Point] &) + CubicBezier(Point, Point, Point, Point) + pair[CubicBezier, CubicBezier] subdivide(Coord) + + Curve * duplicate() + Curve * reverse() + Curve * transformed(Affine &) + Curve * derivative() + +#~ cdef cppclass BezierCurveN[n_1]: +#~ BezierCurveN () +#~ BezierCurveN(Bezier &, Bezier &) +#~ #BezierCurveN(Point &, Point &) +#~ BezierCurveN(vector[Point] &) +#~ pair[BezierCurveN, BezierCurveN] subdivide(Coord) +#~ +#~ Curve * duplicate() +#~ Curve * reverse() +#~ Curve * transformed(Affine &) +#~ Curve * derivative() + +cdef class cy_BezierCurve: + cdef BezierCurve* thisptr + +cdef class cy_LineSegment(cy_BezierCurve): + pass + +cdef cy_LineSegment wrap_LineSegment(LineSegment p) + +cdef extern from "2geom/bezier-curve.h" namespace "Geom::BezierCurve": + BezierCurve * create(vector[Point] &) + +cdef extern from "2geom/elliptical-arc.h" namespace "Geom": + cdef cppclass EllipticalArc: + EllipticalArc(EllipticalArc &) + EllipticalArc() + EllipticalArc(Point, Coord, Coord, Coord, bint, bint, Point) + Interval angleInterval() + Angle rotationAngle() + Coord ray(Dim2) + Point rays() + bint largeArc() + bint sweep() + LineSegment chord() + void set(Point &, double, double, double, bint, bint, Point &) + void setExtremes(Point &, Point &) + Coord center(Dim2) + Point center() + Coord sweepAngle() + bint containsAngle(Coord) + Point pointAtAngle(Coord) + Coord valueAtAngle(Coord, Dim2) + Affine unitCircleTransform() + bint isSVGCompliant() + pair[EllipticalArc, EllipticalArc] subdivide(Coord) + Point initialPoint() + Point finalPoint() + Curve * duplicate() + void setInitial(Point &) + void setFinal(Point &) + bint isDegenerate() + Rect boundsFast() + Rect boundsExact() + OptRect boundsLocal(OptInterval &, unsigned int) + vector[double] roots(double, Dim2) + double nearestTime(Point &, double, double) + int degreesOfFreedom() + Curve * derivative() + Curve * transformed(Affine &) + vector[Point] pointAndDerivatives(Coord, unsigned int) + D2[SBasis] toSBasis() + double valueAt(Coord, Dim2) + Point pointAt(Coord) + Curve * portion(double, double) + Curve * reverse() + +cdef class cy_EllipticalArc: + cdef EllipticalArc* thisptr +cdef cy_EllipticalArc wrap_EllipticalArc(EllipticalArc p) + +cdef Curve * get_Curve_p(object c) +cdef bint is_Curve(object c) diff --git a/src/cython/_cy_curves.pyx b/src/cython/_cy_curves.pyx new file mode 100644 index 0000000..3584a87 --- /dev/null +++ b/src/cython/_cy_curves.pyx @@ -0,0 +1,1945 @@ +from numbers import Number + +from cython.operator cimport dereference as deref + +from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect +from _cy_rectangle cimport cy_Interval, wrap_Interval + +from _cy_affine cimport cy_Translate, cy_Rotate, cy_Scale +from _cy_affine cimport cy_VShear, cy_HShear, cy_Zoom +from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform + + +cdef class cy_Curve: + + """Class representing generic curve. + + Curve maps unit interval to real plane. All curves should implement + these methods. + + This class corresponds to Curve class in 2geom. It's children in cython + aren't actually derived from it, it would make code more unreadable. + """ + + def __cinit__(self): + """Create new Curve. + + You shouldn't create Curve this way, it usually wraps existing + curves (f. e. in Path). + """ + self.thisptr = <Curve *> new SBasisCurve(D2[SBasis]( SBasis(0), SBasis(0) )) + + def __call__(self, Coord t): + """Get point at time value t.""" + return wrap_Point( deref(self.thisptr)(t) ) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + def is_degenerate(self): + """Curve is degenerate if it's length is zero.""" + return self.thisptr.isDegenerate() + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def set_initial(self, cy_Point v): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( v.thisptr )) + + def set_final(self, cy_Point v): + """Set final point of curve.""" + self.thisptr.setFinal(deref( v.thisptr )) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(self, cy_OptInterval i, unsigned int deg=0): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + #TODO rewrite all duplicates to copy.""" + def duplicate(self): + """Duplicate the curve.""" + return wrap_Curve_p( self.thisptr.duplicate() ) + + def transformed(self, m): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(m): + at = get_Affine(m) + return wrap_Curve_p( self.thisptr.transformed(at) ) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_Curve_p( self.thisptr.portion(deref( interval.thisptr )) ) + else: + return wrap_Curve_p( self.thisptr.portion(fr, to) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_Curve_p( self.thisptr.reverse() ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_Curve_p( self.thisptr.derivative() ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return self.thisptr.nearestTime(deref( p.thisptr ), fr, to) + else: + return self.thisptr.nearestTime(deref( p.thisptr ), deref( interval.thisptr )) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), + deref( interval.thisptr ))) + + def length(self, Coord tolerance): + """Return length of curve, within give tolerance.""" + return self.thisptr.length(tolerance) + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return self.thisptr.winding(deref( p.thisptr )) + + def unit_tangent_at(self, Coord t, unsigned int n): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point(self.thisptr.unitTangentAt(t, n)) + + def to_SBasis(self): + """Return tuple of SBasis functions.""" + cdef D2[SBasis] ret = self.thisptr.toSBasis() + return ( wrap_SBasis(ret[0]), wrap_SBasis(ret[1]) ) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() +#~ def operator==(self, cy_Curve c): +#~ return deref( self.thisptr ) == deref( c.thisptr ) + +#~ cdef cy_Curve wrap_Curve(Curve & p): +#~ cdef Curve * retp = <Curve *> new SBasisCurve(D2[SBasis]( SBasis(), SBasis() )) +#~ retp[0] = p +#~ cdef cy_Curve r = cy_Curve.__new__(cy_Curve) +#~ r.thisptr = retp +#~ return r + +cdef cy_Curve wrap_Curve_p(Curve * p): + cdef cy_Curve r = cy_Curve.__new__(cy_Curve) + r.thisptr = p + return r + +cdef class cy_Linear: + """Function mapping linearly between two values. + + Corresponds to Linear class in 2geom. + """ + + cdef Linear* thisptr + + def __cinit__(self, aa = None, b = None): + """Create new Linear from two end values. + + No arguments create zero constant, one value creates constant. + """ + if aa is None: + self.thisptr = new Linear() + elif b is None: + self.thisptr = new Linear(float(aa)) + else: + self.thisptr = new Linear(float(aa), float(b)) + + def __dealloc__(self): + del self.thisptr + + def __call__(self, Coord t): + """Get value at time value t.""" + return deref(self.thisptr)(t) + + def __getitem__(self, i): + """Get end values.""" + return deref( self.thisptr ) [i] + + def __richcmp__(cy_Linear self, cy_Linear other, int op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + + def __neg__(cy_Linear self): + """Negate all values of self.""" + return wrap_Linear( L_neg(deref(self.thisptr)) ) + + + def __add__(cy_Linear self, other): + """Add number or other linear.""" + if isinstance(other, Number): + return wrap_Linear( deref(self.thisptr) + float(other) ) + elif isinstance(other, cy_Linear): + return wrap_Linear( deref(self.thisptr) + deref( (<cy_Linear> other).thisptr ) ) + + def __sub__(cy_Linear self, other): + """Substract number or other linear.""" + if isinstance(other, Number): + return wrap_Linear( L_sub_Ld(deref(self.thisptr), float(other)) ) + elif isinstance(other, cy_Linear): + return wrap_Linear( L_sub_LL(deref(self.thisptr), deref( (<cy_Linear> other).thisptr )) ) + + + def __mul__(cy_Linear self, double b): + """Multiply linear by number.""" + return wrap_Linear(deref( self.thisptr ) * b) + + def __div__(cy_Linear self, double b): + """Divide linear by value.""" + return wrap_Linear(deref( self.thisptr ) / b) + + def is_zero(self, double eps = EPSILON): + """Test whether linear is zero within given tolerance.""" + return self.thisptr.isZero(eps) + + def is_constant(self, double eps = EPSILON): + """Test whether linear is constant within given tolerance.""" + return self.thisptr.isConstant(eps) + + def is_finite(self): + """Test whether linear is finite.""" + return self.thisptr.isFinite() + + def at0(self): + """Equivalent to self(0).""" + return self.thisptr.at0() + + def at1(self): + """Equivalent to self(1).""" + return self.thisptr.at1() + + def value_at(self, double t): + """Equivalent to self(t).""" + return self.thisptr.valueAt(t) + + def to_SBasis(self): + """Convert to SBasis.""" + return wrap_SBasis(self.thisptr.toSBasis()) + + def bounds_exact(self): + """Return exact bounding interval + + This may take a while. + """ + return wrap_OptInterval(self.thisptr.bounds_exact()) + + def bounds_fast(self): + """Return bounding interval + + This method is fast, but does not guarantee to give smallest + interval. + """ + return wrap_OptInterval(self.thisptr.bounds_fast()) + + def bounds_local(self, double u, double v): + """Return bounding interval to the portion of Linear.""" + return wrap_OptInterval(self.thisptr.bounds_local(u, v)) + + def tri(self): + """Return difference between end values.""" + return self.thisptr.tri() + + def hat(self): + """Return value at (0.5).""" + return self.thisptr.hat() + + @classmethod + def sin(cls, cy_Linear bo, int k): + """Return sine of linear.""" + return wrap_SBasis(sin(deref( bo.thisptr ), k)) + + @classmethod + def cos(cls, cy_Linear bo, int k): + """Return cosine of linear.""" + return wrap_SBasis(cos(deref( bo.thisptr ), k)) + + @classmethod + def reciprocal(cls, cy_Linear a, int k): + """Return reciprocical of linear.""" + return wrap_SBasis(reciprocal(deref( a.thisptr ), k)) + + @classmethod + def shift(cls, cy_Linear a, int sh): + """Multiply by x**sh.""" + return wrap_SBasis(shift(deref( a.thisptr ), sh)) + +#leave these in cy2geom napespace? +def cy_lerp(double t, double a, double b): + return lerp(t, a, b) + +cdef cy_Linear wrap_Linear(Linear p): + cdef Linear * retp = new Linear() + retp[0] = p + cdef cy_Linear r = cy_Linear.__new__(cy_Linear) + r.thisptr = retp + return r + +cdef vector[Linear] make_vector_linear(object l): + cdef vector[Linear] ret + for i in l: + ret.push_back( deref( (<cy_Linear> i).thisptr ) ) + return ret + + +cdef class cy_SBasis: + + """Class representing SBasis polynomial. + + Corresponds to SBasis class in 2geom.""" + + def __cinit__(self, a=None, b=None): + """Create new SBasis. + + This constructor only creates linear SBasis, specifying endpoints. + """ + if a is None: + self.thisptr = new SBasis() + elif b is None: + self.thisptr = new SBasis( float(a) ) + else: + self.thisptr = new SBasis( float(a), float(b) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_linear(cls, cy_Linear l): + """Create SBasis from Linear.""" + return wrap_SBasis( SBasis(deref( l.thisptr )) ) + + @classmethod + def from_linears(cls, lst): + """Create SBasis from list of Linears.""" + return wrap_SBasis( SBasis( make_vector_linear(lst) ) ) + + def size(self): + """Return number of linears SBasis consists of.""" + return self.thisptr.size() + + def __call__(self, o): + """Get point at time value t.""" + if isinstance(o, Number): + return deref(self.thisptr)(float(o)) + elif isinstance(self, cy_SBasis): + return wrap_SBasis(deref(self.thisptr)( deref( (<cy_SBasis> o).thisptr ) )) + + def __getitem__(self, unsigned int i): + """Get Linear at i th position.""" + if i>=self.size(): + raise IndexError + else: + return wrap_Linear(deref( self.thisptr ) [i]) + + def __neg__(self): + """Return SBasis with negated values.""" + return wrap_SBasis( SB_neg(deref(self.thisptr)) ) + + #cython doesn't use __rmul__, it switches the arguments instead + def __add__(cy_SBasis self, other): + """Add number or other SBasis to SBasis.""" + if isinstance(other, Number): + return wrap_SBasis( deref(self.thisptr) + float(other) ) + elif isinstance(other, cy_SBasis): + return wrap_SBasis( deref(self.thisptr) + deref( (<cy_SBasis> other).thisptr ) ) + + def __sub__(cy_SBasis self, other): + """Substract number or other SBasis from SBasis.""" + if isinstance(other, Number): + return wrap_SBasis( SB_sub_Sd(deref(self.thisptr), float(other) ) ) + elif isinstance(other, cy_SBasis): + return wrap_SBasis( SB_sub_SS(deref(self.thisptr), deref( (<cy_SBasis> other).thisptr ) ) ) + + def __mul__(self, other): + """Multiply SBasis by number or other SBasis.""" + if isinstance(other, Number): + return wrap_SBasis( deref( (<cy_SBasis> self).thisptr ) * float(other) ) + elif isinstance(other, cy_SBasis): + if isinstance(self, cy_SBasis): + return wrap_SBasis( deref( (<cy_SBasis> self).thisptr ) * deref( (<cy_SBasis> other).thisptr ) ) + elif isinstance(self, Number): + return wrap_SBasis( float(self) * deref( (<cy_SBasis> other).thisptr ) ) + + def __div__(cy_SBasis self, double other): + """Divide SBasis by number.""" + return wrap_SBasis( deref(self.thisptr)/other ) + + + def empty(self): + """Test whether SBasis has no linears.""" + return self.thisptr.empty() + + def back(self): + """Return last linear in SBasis.""" + return wrap_Linear(self.thisptr.back()) + + def pop_back(self): + """Remove last linear in SBasis.""" + self.thisptr.pop_back() + + def resize(self, unsigned int n, cy_Linear l = None): + """Resize SBasis, optionally filling created slots with linear.""" + if l is None: + self.thisptr.resize(n) + else: + self.thisptr.resize(n, deref( l.thisptr )) + +#~ def reserve(self, unsigned int n): +#~ self.thisptr.reserve(n) + + def clear(self): + """Make SBasis empty.""" + self.thisptr.clear() +#~ def insert(self, cy_::__gnu_cxx::__normal_iterator<Geom::Linear*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > before, cy_::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > src_begin, cy_::__gnu_cxx::__normal_iterator<Geom::Linear const*, std::vector<Geom::Linear, std::allocator<Geom::Linear> > > src_end): +#~ self.thisptr.insert(deref( before.thisptr ), deref( src_begin.thisptr ), deref( src_end.thisptr )) + + def at(self, unsigned int i): + """Equivalent to self[i].""" + return wrap_Linear(self.thisptr.at(i)) + + def __richcmp__(cy_SBasis self, cy_SBasis B, int op): + if op == 2: + return deref( self.thisptr ) == deref( B.thisptr ) + elif op == 3: + return deref( self.thisptr ) != deref( B.thisptr ) + + def is_zero(self, double eps = EPSILON): + """Test whether linear is zero within given tolerance.""" + return self.thisptr.isZero(eps) + + def is_constant(self, double eps = EPSILON): + """Test whether linear is constant within given tolerance.""" + return self.thisptr.isConstant(eps) + + def is_finite(self): + """Test whether linear is finite.""" + return self.thisptr.isFinite() + + def at0(self): + """Equivalent to self(0).""" + return self.thisptr.at0() + + def at1(self): + """Equivalent to self(1).""" + return self.thisptr.at1() + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + + def value_at(self, double t): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t) + + def value_and_derivatives(self, double t, unsigned int n): + """Return value and at least n derivatives at time t.""" + return wrap_vector_double (self.thisptr.valueAndDerivatives(t, n)) + + def to_SBasis(self): + """Just return self.""" + return wrap_SBasis(self.thisptr.toSBasis()) + + def tail_error(self, unsigned int tail): + """Return largest error after truncating linears from tail.""" + return self.thisptr.tailError(tail) + + def normalize(self): + """Remove zero linears at the end.""" + self.thisptr.normalize() + + def truncate(self, unsigned int k): + """Truncate SBasis to have k elements.""" + self.thisptr.truncate(k) + + @classmethod + def sqrt(cls, cy_SBasis a, int k): + """Return square root of SBasis. + + Use k to specify degree of resulting SBasis. + """ + return wrap_SBasis(sqrt(deref( a.thisptr ), k)) + + @classmethod + def inverse(cls, cy_SBasis a, int k): + """Return inverse function to SBasis. + + Passed SBasis must be function [1-1] -> [1-1] bijection. + """ + return wrap_SBasis(inverse(deref( a.thisptr ), k)) + + @classmethod + def valuation(cls, cy_SBasis a, double tol = 0): + """Return the degree of the first non zero coefficient.""" + return valuation(deref( a.thisptr ), tol) + + #call with level_set(SBasis(1, 5), 2, a = 0.2, b = 0.4, tol = 0.02) + @classmethod + def level_set(cls, cy_SBasis f, level, a = 0, b = 1, tol = 1e-5, vtol = 1e-5): + """Return intervals where SBasis is in specified level. + + Specify range and tolerance in other arguments. + """ + if isinstance(level, cy_Interval): + return wrap_vector_interval(level_set(deref( f.thisptr ), deref( (<cy_Interval> level).thisptr ), a, b, tol)) #a, b, tol + else: + return wrap_vector_interval(level_set(deref( f.thisptr ), float(level), vtol, a, b, tol)) #vtol, a, b, tol + + @classmethod + def shift(cls, cy_SBasis a, int sh): + """Multiply by x**sh.""" + return wrap_SBasis(shift(deref( a.thisptr ), sh)) + + @classmethod + def compose(cls, cy_SBasis a, cy_SBasis b, k = None): + """Compose two SBasis. + + Specify order of resulting SBasis by parameter k. + """ + if k is None: + return wrap_SBasis(compose(deref( a.thisptr ), deref( b.thisptr ))) + else: + return wrap_SBasis(compose(deref( a.thisptr ), deref( b.thisptr ), int(k))) + + @classmethod + def roots(cls, cy_SBasis s, cy_Interval inside = None): + """Return time values where self equals 0. + + inside intervals specifies subset of domain. + """ + if inside is None: + return wrap_vector_double(roots(deref( s.thisptr ))) + else: + return wrap_vector_double(roots(deref( s.thisptr ), deref( inside.thisptr ))) + + @classmethod + def multi_roots(cls, cy_SBasis f, levels, double htol = 1e-7, double vtol = 1e-7, double a = 0, double b = 1): + """Return lists of roots for different levels.""" + cdef vector[double] l = make_vector_double(levels) + cdef vector[ vector[double] ] r = multi_roots(deref( f.thisptr ), l, htol, vtol, a, b) + lst = [] + for i in range(r.size()): + lst.append( wrap_vector_double(r[i]) ) + return lst + + @classmethod + def multiply_add(cls, cy_SBasis a, cy_SBasis b, cy_SBasis c): + """Return a*b+c.""" + return wrap_SBasis(multiply_add(deref( a.thisptr ), deref( b.thisptr ), deref( c.thisptr ))) + + @classmethod + def divide(cls, cy_SBasis a, cy_SBasis b, int k): + """Divide two SBasis functions. + + Use k to specify degree of resulting SBasis. + """ + return wrap_SBasis(divide(deref( a.thisptr ), deref( b.thisptr ), k)) + + @classmethod + def compose_inverse(cls, cy_SBasis f, cy_SBasis g, unsigned int order, double tol): + """Compose f with g's inverse. + + Requires g to be bijection g: [0, 1] -> [0, 1] + """ + return wrap_SBasis(compose_inverse(deref( f.thisptr ), deref( g.thisptr ), order, tol)) + + @classmethod + def multiply(cls, cy_SBasis a, cy_SBasis b): + """Multiply two SBasis functions.""" + return wrap_SBasis(multiply(deref( (<cy_SBasis> a).thisptr ), deref( (<cy_SBasis> b).thisptr ))) + + @classmethod + def derivative(cls, cy_SBasis a): + """Return derivative os SBasis.""" + return wrap_SBasis(derivative(deref( (<cy_SBasis> a).thisptr ))) + + @classmethod + def integral(cls, a): + """Return integral of SBasis.""" + return wrap_SBasis(integral(deref( (<cy_SBasis> a).thisptr ))) + + @classmethod + def portion(cls, cy_SBasis a, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of SBasis, specified by endpoints or interval.""" + if interval is None: + return wrap_SBasis( portion( deref( a.thisptr ), fr, to ) ) + else: + return wrap_SBasis( portion( deref( a.thisptr ), deref( interval.thisptr ) ) ) + + @classmethod + def bounds_fast(cls, cy_SBasis a, int order = 0): + """Return bounding interval + + This method is fast, but does not guarantee to give smallest + interval. + """ + return wrap_OptInterval(bounds_fast(deref( a.thisptr ), order)) + + @classmethod + def bounds_exact(cls, cy_SBasis a): + """Return exact bounding interval + + This may take a while. + """ + return wrap_OptInterval(bounds_exact(deref( a.thisptr ))) + + @classmethod + def bounds_local(cls, cy_SBasis a, cy_OptInterval t, int order = 0): + """Return bounding interval to the portion of SBasis.""" + return wrap_OptInterval(bounds_local(deref( a.thisptr ), deref( t.thisptr ), order)) + +#~ def cy_level_sets(cy_SBasis f, vector[Interval] levels, double a, double b, double tol): +#~ return wrap_::std::vector<std::vector<Geom::Interval, std::allocator<Geom::Interval> >,std::allocator<std::vector<Geom::Interval, std::allocator<Geom::Interval> > > >(level_sets(deref( f.thisptr ), deref( levels.thisptr ), a, b, tol)) +#~ def cy_level_sets(cy_SBasis f, vector[vector] levels, double a, double b, double vtol, double tol): +#~ return wrap_::std::vector<std::vector<Geom::Interval, std::allocator<Geom::Interval> >,std::allocator<std::vector<Geom::Interval, std::allocator<Geom::Interval> > > >(level_sets(deref( f.thisptr ), deref( levels.thisptr ), a, b, vtol, tol)) + +def cy_reverse(a): + if isinstance(a, cy_Linear): + return wrap_Linear( reverse(deref( (<cy_Linear> a).thisptr ))) + elif isinstance(a, cy_SBasis): + return wrap_SBasis( reverse(deref( (<cy_SBasis> a).thisptr ))) + elif isinstance(a, cy_Bezier): + return wrap_Bezier( reverse(deref( (<cy_Bezier> a).thisptr ))) + +#already implemented +#~ def cy_truncate(cy_SBasis a, unsigned int terms): +#~ return wrap_SBasis(truncate(deref( a.thisptr ), terms)) + +cdef cy_SBasis wrap_SBasis(SBasis p): + cdef SBasis * retp = new SBasis() + retp[0] = p + cdef cy_SBasis r = cy_SBasis.__new__(cy_SBasis, 0, 0) + r.thisptr = retp + return r + + +cdef class cy_SBasisCurve: + + """Curve mapping two SBasis functions to point (s1(t), s2(t)). + + Corresponds to SBasisCurve in 2geom. + """ + + cdef SBasisCurve* thisptr + +#~ def __init__(self, cy_Curve other): +#~ self.thisptr = self.thisptr.SBasisCurve(deref( other.thisptr )) + + def __cinit__(self, cy_SBasis s1, cy_SBasis s2): + """Create new SBasisCurve from two SBasis functions.""" + self.thisptr = new SBasisCurve( D2[SBasis]( + deref( s1.thisptr ), + deref( s2.thisptr ) ) ) + + def __dealloc__(self): + del self.thisptr + + def __call__(self, double t): + """Get point at time value t.""" + return wrap_Point(deref(self.thisptr)(t)) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.duplicate()) ) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + + def is_degenerate(self): + """Curve is degenerate if it's length is zero.""" + return self.thisptr.isDegenerate() + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def set_initial(self, cy_Point v): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( v.thisptr )) + + def set_final(self, cy_Point v): + """Set final point of curve.""" + self.thisptr.setFinal(deref( v.thisptr )) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(self, cy_OptInterval i, unsigned int deg): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double( self.thisptr.roots(v, d) ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return self.thisptr.nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), deref( interval.thisptr ) ) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), + deref( interval.thisptr ) )) + + def length(self, Coord tolerance = 0.01): + """Return length of curve, within give tolerance.""" + return self.thisptr.length(tolerance) + + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.portion( fr, to ) ) ) + else: + return wrap_SBasisCurve( <SBasisCurve> + deref( (<Curve *> self.thisptr).portion( deref( interval.thisptr ))) ) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.transformed( at ))) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_SBasisCurve( <SBasisCurve> deref( (<Curve *> self.thisptr).reverse() ) ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_SBasisCurve( <SBasisCurve> deref(self.thisptr.derivative()) ) + + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return (<Curve *> self.thisptr).winding(deref(p.thisptr)) + + def unit_tangent_at(self, Coord t, int n = 3): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n)) + + def to_SBasis(self): + """Return tuple containing it's SBasis functions.""" + return wrap_D2_SBasis(self.thisptr.toSBasis()) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + +cdef object wrap_D2_SBasis(D2[SBasis] p): + return ( wrap_SBasis(p[0]), wrap_SBasis(p[1]) ) + +cdef cy_SBasisCurve wrap_SBasisCurve(SBasisCurve p): + cdef SBasisCurve * retp = new SBasisCurve(D2[SBasis]( SBasis(), SBasis() )) + retp[0] = p + cdef cy_SBasisCurve r = cy_SBasisCurve.__new__(cy_SBasisCurve, cy_SBasis(), cy_SBasis()) + r.thisptr = retp + return r + + +cdef class cy_Bezier: + + """Bezier polynomial. + + Corresponds to Bezier class in 2geom. + """ + + cdef Bezier* thisptr + + def __cinit__(self, *args): + """Create Bezier polynomial specifying it's coeffincients + + This constructor takes up to four coefficients. + """ + if len(args) == 0: + #new Bezier() causes segfault + self.thisptr = new Bezier(0) + elif len(args) == 1: + self.thisptr = new Bezier( float(args[0]) ) + elif len(args) == 2: + self.thisptr = new Bezier( float(args[0]), float(args[1]) ) + elif len(args) == 3: + self.thisptr = new Bezier( float(args[0]), float(args[1]), float(args[2]) ) + elif len(args) == 4: + self.thisptr = new Bezier( float(args[0]), float(args[1]), float(args[2]), float(args[3]) ) + else: + raise ValueError("Passed list has too many points") + + def __dealloc__(self): + del self.thisptr + + def __call__(self, double t): + """Get point at time value t.""" + return deref( self.thisptr ) (t) + + + def __getitem__(self, unsigned int ix): + """Get coefficient by accessing list.""" + if ix >= self.size(): + raise IndexError + return deref( self.thisptr ) [ix] + + def order(self): + """Return order of Bezier.""" + return self.thisptr.order() + + def size(self): + """Return number of coefficients.""" + return self.thisptr.size() + + def __mul__( cy_Bezier self, double v): + """Multiply Bezier by number.""" + return wrap_Bezier(deref( self.thisptr ) * v) + + def __add__( cy_Bezier self, double v): + """Add number to Bezier.""" + return wrap_Bezier(deref( self.thisptr ) + v) + + def __sub__( cy_Bezier self, double v): + """Substract number from Bezier.""" + return wrap_Bezier(deref( self.thisptr ) - v) + + def __div__( cy_Bezier self, double v): + """Divide Bezier number.""" + return wrap_Bezier(deref( self.thisptr ) / v) + + + def resize(self, unsigned int n, Coord v): + """Change order of Bezier.""" + self.thisptr.resize(n, v) + + def clear(self): + """Create empty Bezier.""" + self.thisptr.clear() + + def degree(self): + """Return degree of Bezier polynomial.""" + return self.thisptr.degree() + + def is_zero(self, double eps = EPSILON): + """Test whether linear is zero within given tolerance.""" + return self.thisptr.isZero(eps) + + def is_constant(self, double eps = EPSILON): + """Test whether linear is constant within given tolerance.""" + return self.thisptr.isConstant(eps) + + def is_finite(self): + """Test whether linear is finite.""" + return self.thisptr.isFinite() + + def at0(self): + """Equivalent to self(0).""" + return self.thisptr.at0() + + def at1(self): + """Equivalent to self(1).""" + return self.thisptr.at1() + + def value_at(self, double t): + """Equivalent to self(t).""" + return self.thisptr.valueAt(t) + + def to_SBasis(self): + """Convert to SBasis.""" + return wrap_SBasis(self.thisptr.toSBasis()) + + def set_point(self, unsigned int ix, double val): + """Set self[ix] to val.""" + self.thisptr.setPoint(ix, val) + + def value_and_derivatives(self, Coord t, unsigned int n_derivs): + """Return value and at least n derivatives at time t.""" + return wrap_vector_double(self.thisptr.valueAndDerivatives(t, n_derivs)) + + def subdivide(self, Coord t): + """Get two beziers, from 0 to t and from t to 1.""" + cdef pair[Bezier, Bezier] p = self.thisptr.subdivide(t) + return ( wrap_Bezier(p.first), wrap_Bezier(p.second) ) + + def roots(self, cy_Interval ivl = None): + """Find time values where self(t)[d] == v.""" + if ivl is None: + return wrap_vector_double(self.thisptr.roots()) + else: + return wrap_vector_double(self.thisptr.roots(deref( ivl.thisptr ))) + + def forward_difference(self, unsigned int k): +#TODO: ask someone what this function does. +#~ """Compute forward difference of degree k. +#~ +#~ First forward difference of B is roughly function B'(t) = B(t+h)-B(t) +#~ for fixed step h""" + return wrap_Bezier(self.thisptr.forward_difference(k)) + + def elevate_degree(self): + """Increase degree of Bezier by 1.""" + return wrap_Bezier(self.thisptr.elevate_degree()) + + def reduce_degree(self): + """Decrease degree of Bezier by 1.""" + return wrap_Bezier(self.thisptr.reduce_degree()) + + def elevate_to_degree(self, unsigned int new_degree): + """Increase degree of Bezier to new_degree.""" + return wrap_Bezier(self.thisptr.elevate_to_degree(new_degree)) + + def deflate(self): +#TODO: ask someone what this function does. + #It looks like integral(self)*self.size() + return wrap_Bezier(self.thisptr.deflate()) + + @classmethod + def bezier_points(cls, cy_Bezier a, cy_Bezier b): + """Return control points of BezierCurve consisting of two beziers. + + Passed bezier must have same degree.""" + return wrap_vector_point(bezier_points( D2[Bezier]( deref(a.thisptr), deref(b.thisptr) ) )) + + @classmethod + def multiply(cls, cy_Bezier a, cy_Bezier b): + """Multiply two Bezier functions.""" + return wrap_Bezier(multiply(deref( (<cy_Bezier> a).thisptr ), + deref( (<cy_Bezier> b).thisptr ))) + + @classmethod + def portion(cls, cy_Bezier a, Coord fr=0, Coord to=1, interval=None): + """Return portion of bezier, specified by endpoints or interval.""" + if interval is None: + return wrap_Bezier(portion(deref( a.thisptr ), fr, to)) + else: + return wrap_Bezier(portion(deref( a.thisptr ), float(interval.min()), + float(interval.max()) )) + + @classmethod + def derivative(cls, cy_Bezier a): + """Return derivative of a bezier.""" + return wrap_Bezier(derivative(deref( a.thisptr ))) + + @classmethod + def integral(cls, cy_Bezier a): + """Return derivative of a bezier.""" + return wrap_Bezier(integral(deref( a.thisptr ))) + + @classmethod + def bounds_fast(cls, cy_Bezier a): + """Return bounding interval + + This method is fast, but does not guarantee to give smallest + interval. + """ + return wrap_OptInterval(bounds_fast(deref( a.thisptr ))) + + @classmethod + def bounds_exact(cls, cy_Bezier a): + """Return exact bounding interval + + This may take a while. + """ + return wrap_OptInterval(bounds_exact(deref( a.thisptr ))) + + @classmethod + def bounds_local(cls, cy_Bezier a, cy_OptInterval t): + """Return bounding interval to the portion of bezier.""" + return wrap_OptInterval(bounds_local(deref( a.thisptr ), deref( t.thisptr ))) + +#This is the same as bz.to_SBasis() +#~ def cy_bezier_to_sbasis(cy_SBasis sb, cy_Bezier bz): +#~ bezier_to_sbasis(deref( sb.thisptr ), deref( bz.thisptr )) + +#These are look like internal functions. +#~ def cy_casteljau_subdivision(Coord t, cy_Coord * v, cy_Coord * left, cy_Coord * right, unsigned int order): +#~ return subdivideArr(t, v.thisptr, left.thisptr, right.thisptr, order) +#~ def cy_bernsteinValueAt(double t, cy_double * c_, unsigned int n): +#~ return bernsteinValueAt(t, c_.thisptr, n) + +cdef cy_Bezier wrap_Bezier(Bezier p): + cdef Bezier * retp = new Bezier() + retp[0] = p + cdef cy_Bezier r = cy_Bezier.__new__(cy_Bezier) + r.thisptr = retp + return r + + +cdef class cy_BezierCurve: + + """Bezier curve, consisting of two Bezier functions. + + Corresponds to BezierCurve class in 2geom. + """ + + #This flag is due to this class children + def __cinit__(self, *args, **kwargs): + """Don't use this constructor, use create instead.""" + pass + + def __dealloc__(self): + del self.thisptr + + def __call__(self, Coord t): + """Get point at time value t.""" + return wrap_Point(deref( <Curve *> self.thisptr )(t)) + + def __getitem__(self, unsigned int ix): + """Get control point by list access.""" + return wrap_Point(deref( self.thisptr ) [ix]) + + @classmethod + def create(cls, pts): + """Create new BezierCurve from control points.""" + return wrap_BezierCurve( deref( create( make_vector_point(pts) ) ) ) + + def order(self): + """Get order of curve.""" + return self.thisptr.order() + + def control_points(self): + """Get control points.""" + return wrap_vector_point(self.thisptr.controlPoints()) + + def set_point(self, unsigned int ix, cy_Point v): + """Set control point.""" + self.thisptr.setPoint(ix, deref( v.thisptr )) + + def set_points(self, ps): + """Set control points""" + self.thisptr.setPoints( make_vector_point(ps) ) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + + def is_degenerate(self): + """Curve is degenerate if it's length is zero.""" + return self.thisptr.isDegenerate() + + def set_initial(self, cy_Point v): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( v.thisptr )) + + def set_final(self, cy_Point v): + """Set final point of curve.""" + self.thisptr.setFinal(deref( v.thisptr )) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(cy_BezierCurve self, cy_OptInterval i, unsigned int deg): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), deref( interval.thisptr ) ) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double((<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), + deref( interval.thisptr ) )) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_BezierCurve( <BezierCurve> deref(<BezierCurve *> + (<Curve *> self.thisptr).portion( fr, to ) + ) ) + else: + return wrap_BezierCurve( <BezierCurve> deref(<BezierCurve *> + (<Curve *> self.thisptr).portion(deref( interval.thisptr )) + ) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.duplicate())) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_BezierCurve( deref( <BezierCurve *> self.thisptr.derivative())) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def length(self, Coord tolerance = 0.01): + """Return length of curve, within give tolerance.""" + return self.thisptr.length(tolerance) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def to_SBasis(self): + """Convert self to pair of SBasis functions.""" + return wrap_D2_SBasis(self.thisptr.toSBasis()) + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return (<Curve *> self.thisptr).winding(deref(p.thisptr)) + + def unit_tangent_at(self, Coord t, int n = 3): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n)) + +cdef cy_BezierCurve wrap_BezierCurve(BezierCurve p): + cdef vector[Point] points = make_vector_point([cy_Point(), cy_Point()]) + cdef BezierCurve * retp = create(p.controlPoints()) + cdef cy_BezierCurve r = cy_BezierCurve.__new__(cy_BezierCurve, [cy_Point(), cy_Point()]) + r.thisptr = retp + return r + + +cdef class cy_LineSegment(cy_BezierCurve): + + """Bezier curve with fixed order 1. + + This class inherits from BezierCurve. + + Corresponds to LineSegment in 2geom. BezierCurveN is not wrapped. + """ + + def __cinit__(self, cy_Point p0=None, + cy_Point p1=cy_Point()): + """Create new LineSegment from it's endpoints.""" + if p0 is None: + self.thisptr = <BezierCurve *> new LineSegment() + else: + self.thisptr = <BezierCurve *> new LineSegment( deref(p0.thisptr), + deref(p1.thisptr)) + + @classmethod + def from_beziers(cls, cy_Bezier b0, cy_Bezier b1): + """Create LineSegment from two linear beziers.""" + return wrap_LineSegment( LineSegment(deref(b0.thisptr), deref(b1.thisptr)) ) + + def subdivide(self, Coord t): + """Get two LineSegments, from 0 to t and from t to 1.""" + cdef pair[LineSegment, LineSegment] p = (<LineSegment *> self.thisptr).subdivide(t) + return ( wrap_LineSegment(p.first), wrap_LineSegment(p.second) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.duplicate())) + + def portion(self, double fr=0, double to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_LineSegment( deref( <LineSegment *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr )) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.derivative())) + +cdef cy_LineSegment wrap_LineSegment(LineSegment p): + cdef LineSegment * retp = new LineSegment() + retp[0] = p + cdef cy_LineSegment r = cy_LineSegment.__new__(cy_LineSegment) + r.thisptr = <BezierCurve* > retp + return r + + +cdef class cy_QuadraticBezier(cy_BezierCurve): + + """Bezier curve with fixed order 2. + + This class inherits from BezierCurve. + + Corresponds to QuadraticBezier in 2geom. BezierCurveN is not wrapped. + """ + + def __cinit__(self, cy_Point p0=None, + cy_Point p1=cy_Point(), + cy_Point p2=cy_Point()): + """Create new QuadraticBezier from three control points.""" + if p0 is None: + self.thisptr = <BezierCurve *> new QuadraticBezier() + else: + self.thisptr = <BezierCurve *> new QuadraticBezier( deref( p0.thisptr ), + deref( p1.thisptr ), + deref( p2.thisptr ) ) + + @classmethod + def from_beziers(cls, cy_Bezier b0, cy_Bezier b1): + """Create QuadraticBezier from two quadratic bezier functions.""" + return wrap_QuadraticBezier( QuadraticBezier(deref(b0.thisptr), deref(b1.thisptr)) ) + + def subdivide(self, Coord t): + """Get two QuadraticBeziers, from 0 to t and from t to 1.""" + cdef pair[QuadraticBezier, QuadraticBezier] p = (<QuadraticBezier *> self.thisptr).subdivide(t) + return ( wrap_QuadraticBezier(p.first), wrap_QuadraticBezier(p.second) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.duplicate())) + + def portion(self, double fr=0, double to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_QuadraticBezier( deref( <QuadraticBezier *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr )) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_LineSegment( deref( <LineSegment *> self.thisptr.derivative())) + +cdef cy_QuadraticBezier wrap_QuadraticBezier(QuadraticBezier p): + cdef QuadraticBezier * retp = new QuadraticBezier() + retp[0] = p + cdef cy_QuadraticBezier r = cy_QuadraticBezier.__new__(cy_QuadraticBezier) + r.thisptr = <BezierCurve* > retp + return r + +cdef class cy_CubicBezier(cy_BezierCurve): + + """Bezier curve with fixed order 2. + + This class inherits from BezierCurve. + + Corresponds to QuadraticBezier in 2geom. BezierCurveN is not wrapped. + """ + + def __cinit__(self, cy_Point p0=None, + cy_Point p1=cy_Point(), + cy_Point p2=cy_Point(), + cy_Point p3=cy_Point()): + """Create new CubicBezier from four control points.""" + if p0 is None: + self.thisptr = <BezierCurve *> new CubicBezier() + else: + self.thisptr = <BezierCurve *> new CubicBezier( deref( p0.thisptr ), + deref( p1.thisptr ), + deref( p2.thisptr ), + deref( p3.thisptr ) ) + + @classmethod + def from_beziers(cls, cy_Bezier b0, cy_Bezier b1): + """Create CubicBezier from two cubic bezier functions.""" + return wrap_CubicBezier( CubicBezier(deref(b0.thisptr), deref(b1.thisptr)) ) + + def subdivide(self, Coord t): + """Get two CubicBeziers, from 0 to t and from t to 1.""" + cdef pair[CubicBezier, CubicBezier] p = (<CubicBezier *> self.thisptr).subdivide(t) + return ( wrap_CubicBezier(p.first), wrap_CubicBezier(p.second) ) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.duplicate())) + + def portion(self, double fr=0, double to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_CubicBezier( deref( <CubicBezier *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr )) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.reverse())) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_CubicBezier( deref( <CubicBezier *> self.thisptr.transformed( at ))) + + def derivative(self): + """Return curve's derivative.""" + return wrap_QuadraticBezier( deref( <QuadraticBezier *> self.thisptr.derivative())) + +cdef cy_CubicBezier wrap_CubicBezier(CubicBezier p): + cdef CubicBezier * retp = new CubicBezier() + retp[0] = p + cdef cy_CubicBezier r = cy_CubicBezier.__new__(cy_CubicBezier) + r.thisptr = <BezierCurve* > retp + return r + +#~ cdef class cy_BezierCurveN(cy_BezierCurve): + + +cdef class cy_HLineSegment(cy_LineSegment): + + """Horizontal line segment. + + This class corresponds to HLineSegment in 2geom. + """ + + def __cinit__(self, cy_Point p0=None, cy_Point p1=cy_Point()): + """Create HLineSegment from it's endpoints.""" + if p0 is None: + self.thisptr = <BezierCurve *> new HLineSegment() + else: + self.thisptr = <BezierCurve *> new HLineSegment( deref( p0.thisptr ), deref( p1.thisptr ) ) + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create HLineSegment from it's endpoints.""" + return wrap_HLineSegment( HLineSegment( deref(p0.thisptr), + deref(p1.thisptr)) ) + + @classmethod + def from_point_length(cls, cy_Point p, Coord length): + return wrap_HLineSegment( HLineSegment( deref( p.thisptr ), length ) ) + + def set_initial(self, cy_Point p): + """Set initial point of curve.""" + (<AxisLineSegment_X *> self.thisptr).setInitial( deref(p.thisptr) ) + + def set_final(self, cy_Point p): + """Set final point of curve.""" + (<AxisLineSegment_X *> self.thisptr).setFinal( deref(p.thisptr) ) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect( (<AxisLineSegment_X *> self.thisptr).boundsFast() ) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect( (<AxisLineSegment_X *> self.thisptr).boundsExact() ) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return (<AxisLineSegment_X *> self.thisptr).degreesOfFreedom() + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double( (<AxisLineSegment_X *> self.thisptr).roots(v, d) ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return (<AxisLineSegment_X *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), + deref( ( interval.thisptr ) ) ) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point((<AxisLineSegment_X *> self.thisptr).pointAt(t)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return (<AxisLineSegment_X *> self.thisptr).valueAt(t, d) + + def point_and_derivatives(self, Coord t, unsigned n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point( (<AxisLineSegment_X *> self.thisptr).pointAndDerivatives(t, n) ) + + def get_Y(self): + """Get distance of self from y-axis.""" + return (<HLineSegment *> self.thisptr).getY() + + def set_initial_X(self, Coord x): + """Set initial point's X coordinate.""" + (<HLineSegment *> self.thisptr).setInitialX(x) + + def set_final_X(self, Coord x): + """Set final point's X coordinate.""" + (<HLineSegment *> self.thisptr).setFinalX(x) + + def set_Y(self, Coord y): + """Set Y coordinate of points.""" + (<HLineSegment *> self.thisptr).setY(y) + + def subdivide(self, Coord t): + """Return two HLineSegments subdivided at t.""" + cdef pair[HLineSegment, HLineSegment] p = (<HLineSegment *> self.thisptr).subdivide(t) + return (wrap_HLineSegment(p.first), wrap_HLineSegment(p.second)) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.duplicate()) ) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_HLineSegment( deref( <HLineSegment *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_HLineSegment( deref( <HLineSegment *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr ) ) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.reverse()) ) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_LineSegment( deref(<LineSegment *> self.thisptr.transformed( at )) ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_HLineSegment( deref(<HLineSegment *> self.thisptr.derivative()) ) + +cdef cy_HLineSegment wrap_HLineSegment(HLineSegment p): + cdef HLineSegment * retp = new HLineSegment() + retp[0] = p + cdef cy_HLineSegment r = cy_HLineSegment.__new__(cy_HLineSegment) + r.thisptr = <BezierCurve *> retp + return r + +cdef class cy_VLineSegment(cy_LineSegment): + + """Vertical line segment. + + This class corresponds to HLineSegment in 2geom. + """ + + def __cinit__(self, cy_Point p0=None, cy_Point p1=cy_Point()): + """Create VLineSegment from it's endpoints.""" + if p0 is None: + self.thisptr = <BezierCurve *> new VLineSegment() + else: + self.thisptr = <BezierCurve *> new VLineSegment( deref( p0.thisptr ), deref( p1.thisptr ) ) + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create VLineSegment from it's endpoints.""" + return wrap_VLineSegment( VLineSegment( deref(p0.thisptr), + deref(p1.thisptr)) ) + + @classmethod + def from_point_length(cls, cy_Point p, Coord length): + return wrap_VLineSegment( VLineSegment( deref( p.thisptr ), length ) ) + + def set_initial(self, cy_Point p): + """Set initial point of curve.""" + (<AxisLineSegment_Y *> self.thisptr).setInitial( deref(p.thisptr) ) + + def set_final(self, cy_Point p): + """Set final point of curve.""" + (<AxisLineSegment_Y *> self.thisptr).setFinal( deref(p.thisptr) ) + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect( (<AxisLineSegment_Y *> self.thisptr).boundsFast() ) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect( (<AxisLineSegment_Y *> self.thisptr).boundsExact() ) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return (<AxisLineSegment_Y *> self.thisptr).degreesOfFreedom() + + def roots(self, Coord v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double( (<AxisLineSegment_Y *> self.thisptr).roots(v, d) ) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return (<AxisLineSegment_Y *> self.thisptr).nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), + deref( ( interval.thisptr ) ) ) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point((<AxisLineSegment_Y *> self.thisptr).pointAt(t)) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return (<AxisLineSegment_Y *> self.thisptr).valueAt(t, d) + + def point_and_derivatives(self, Coord t, unsigned n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point( (<AxisLineSegment_Y *> self.thisptr).pointAndDerivatives(t, n) ) + + def get_X(self): + return (<VLineSegment *> self.thisptr).getX() + + def set_initial_Y(self, Coord y): + (<VLineSegment *> self.thisptr).setInitialY(y) + + def set_final_Y(self, Coord y): + (<VLineSegment *> self.thisptr).setFinalY(y) + + def set_X(self, Coord x): + (<VLineSegment *> self.thisptr).setX(x) + + def subdivide(self, Coord t): + """Return two HLineSegments subdivided at t.""" + cdef pair[VLineSegment, VLineSegment] p = (<VLineSegment *> self.thisptr).subdivide(t) + return (wrap_VLineSegment(p.first), wrap_VLineSegment(p.second)) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.duplicate()) ) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_VLineSegment( deref( <VLineSegment *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_VLineSegment( deref( <VLineSegment *> + (<Curve *> self.thisptr).portion( deref( interval.thisptr ) ) + ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.reverse()) ) + + def transformed(self, t): + """Transform curve by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_LineSegment( deref(<LineSegment *> self.thisptr.transformed( at )) ) + + def derivative(self): + """Return curve's derivative.""" + return wrap_VLineSegment( deref(<VLineSegment *> self.thisptr.derivative()) ) + +cdef cy_VLineSegment wrap_VLineSegment(VLineSegment p): + cdef VLineSegment * retp = new VLineSegment() + retp[0] = p + cdef cy_VLineSegment r = cy_VLineSegment.__new__(cy_VLineSegment) + r.thisptr = <BezierCurve *> retp + return r + +cdef class cy_EllipticalArc: + + """Elliptical arc. + + Corresponds to EllipticalArc class in 2geom. + """ + + def __cinit__(self, cy_Point ip = cy_Point(0, 0), + Coord rx = 0, + Coord ry = 0, + Coord rot_angle = 0, + bint large_arc = True, + bint sweep = True, + cy_Point fp = cy_Point(0, 0)): + """Create Elliptical arc from it's major axis and rays.""" + self.thisptr = new EllipticalArc(deref( ip.thisptr ), rx, ry, rot_angle, large_arc, sweep, deref( fp.thisptr )) + + def __dealloc__(self): + del self.thisptr + + def __call__(self, Coord t): + """Get point at time value t.""" + return wrap_Point( deref(<Curve *> self.thisptr)(t) ) + #Curve methods + + def length(self, Coord tolerance = 0.01): + """Return length of curve, within give tolerance.""" + return (<Curve *> self.thisptr).length(tolerance) + + #AngleInterval methods + + def initial_angle(self): + """Get initial Angle of arc.""" + return wrap_Angle((<AngleInterval *> self.thisptr).initialAngle()) + + def final_angle(self): + """Get final Angle of arc.""" + return wrap_Angle((<AngleInterval *> self.thisptr).finalAngle()) + + def angle_at(self, Coord t): + """Get Angle from time value.""" + return wrap_Angle((<AngleInterval *> self.thisptr).angleAt(t)) + + def contains(self, cy_Angle a): + """Test whether arc contains angle.""" + return (<AngleInterval *> self.thisptr).contains(deref( a.thisptr )) + + def extent(self): + """Get extent of angle interval.""" + return (<AngleInterval *> self.thisptr).extent() + + def angle_interval(self): + """Get underlying angle Interval.""" + return wrap_Interval(self.thisptr.angleInterval()) + + def rotation_angle(self): + """Return rotation angle of major axis.""" + return wrap_Angle(self.thisptr.rotationAngle()) + + def ray(self, Dim2 d): + """Access rays with X or Y.""" + return self.thisptr.ray(d) + + def rays(self): + """Get rays as a point.""" + return wrap_Point(self.thisptr.rays()) + + def large_arc(self): + """Check if large arc flag is set.""" + return self.thisptr.largeArc() + + def sweep(self): + """Check if sweep flag is set.""" + return self.thisptr.sweep() + + def chord(self): + """Return chord of arc.""" + return wrap_LineSegment(self.thisptr.chord()) + + def set(self, cy_Point ip, double rx, double ry, double rot_angle, bint large_arc, bint sweep, cy_Point fp): + """Set arc's properties.""" + self.thisptr.set(deref( ip.thisptr ), rx, ry, rot_angle, large_arc, sweep, deref( fp.thisptr )) + + def set_extremes(self, cy_Point ip, cy_Point fp): + """Set endpoints of arc.""" + self.thisptr.setExtremes(deref( ip.thisptr ), deref( fp.thisptr )) + + def center(self, coordinate=None): + """Return center of ellipse, or it's coordinate.""" + if coordinate is None: + return wrap_Point(self.thisptr.center()) + else: + return self.thisptr.center(int(coordinate)) + + def sweep_angle(self): + """Equivalent to self.extent()""" + return self.thisptr.sweepAngle() + + def contains_angle(self, Coord angle): + """Test whether arc contains angle. + + Equivalent to self.contains(Angle(a)) + """ + return self.thisptr.containsAngle(angle) + + def point_at_angle(self, Coord a): + """Get point of arc at specified angle.""" + return wrap_Point(self.thisptr.pointAtAngle(a)) + + def value_at_angle(self, Coord a, Dim2 d): + """Equivalent to self.point_at_angle(a)[d]""" + return self.thisptr.valueAtAngle(a, d) + + def unit_circle_transform(self): + """Get Affine transform needed to transform unit circle to ellipse.""" + return wrap_Affine(self.thisptr.unitCircleTransform()) + + def is_SVG_compliant(self): + """Check whether arc is SVG compliant + + SVG has special specification for degenerated ellipse.""" + return self.thisptr.isSVGCompliant() + + def subdivide(self, Coord t): + """Return two arcs, subdivided at time t.""" + cdef pair[EllipticalArc, EllipticalArc] r = self.thisptr.subdivide(t) + return (wrap_EllipticalArc(r.first), wrap_EllipticalArc(r.second)) + + def initial_point(self): + """Get self(0).""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get self(1).""" + return wrap_Point(self.thisptr.finalPoint()) + + def duplicate(self): + """Duplicate the curve.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.duplicate()) ) + + def set_initial(self, cy_Point p): + """Set initial point of curve.""" + self.thisptr.setInitial(deref( p.thisptr )) + + def set_final(self, cy_Point p): + """Set final point of curve.""" + self.thisptr.setFinal(deref( p.thisptr )) + + def is_degenerate(self): + """Curve is degenerate if its length is zero.""" + return self.thisptr.isDegenerate() + + def bounds_fast(self): + """Return bounding rectangle for curve. + + This method is fast, but does not guarantee to give smallest + rectangle. + """ + return wrap_Rect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Return exact bounding rectangle for curve. + + This may take a while. + """ + return wrap_Rect(self.thisptr.boundsExact()) + + def bounds_local(self, cy_OptInterval i, unsigned int deg): + """Return bounding rectangle to portion of curve.""" + return wrap_OptRect(self.thisptr.boundsLocal(deref( i.thisptr ), deg)) + + def roots(self, double v, Dim2 d): + """Find time values where self(t)[d] == v.""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def nearest_time(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return such t that |self(t) - point| is minimized.""" + if interval is None: + return self.thisptr.nearestTime(deref( p.thisptr ), fr, to) + else: + return (<Curve *> self.thisptr).nearestTime(deref( p.thisptr ), + deref( interval.thisptr ) ) + + def all_nearest_times(self, cy_Point p, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return all values of t that |self(t) - point| is minimized.""" + if interval is None: + return wrap_vector_double( (<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), fr, to)) + else: + return wrap_vector_double( (<Curve *> self.thisptr).allNearestTimes(deref( p.thisptr ), deref( interval.thisptr ) )) + + def degrees_of_freedom(self): + """Return number of independent parameters needed to specify the curve.""" + return self.thisptr.degreesOfFreedom() + + def derivative(self): + """Return curve's derivative.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.derivative()) ) + + def transformed(self, cy_Affine m): + """Transform curve by affine transform.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.transformed(deref( m.thisptr ))) ) + + def point_and_derivatives(self, Coord t, unsigned int n): + """Return point and at least first n derivatives at point t in list.""" + return wrap_vector_point(self.thisptr.pointAndDerivatives(t, n)) + + def to_SBasis(self): + """Convert to pair of SBasis polynomials.""" + cdef D2[SBasis] r = self.thisptr.toSBasis() + return ( wrap_SBasis(r[0]), wrap_SBasis(r[1]) ) + + def value_at(self, Coord t, Dim2 d): + """Equivalent to self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def point_at(self, Coord t): + """Equivalent to self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve, specified by endpoints or interval.""" + if interval is None: + return wrap_EllipticalArc( deref( <EllipticalArc *> self.thisptr.portion( fr, to ) ) ) + else: + return wrap_EllipticalArc( deref( <EllipticalArc *> (<Curve *> self.thisptr).portion( deref( interval.thisptr ) ) ) ) + + def reverse(self): + """Return curve with reversed time.""" + return wrap_EllipticalArc( deref(<EllipticalArc *> self.thisptr.reverse()) ) + + def winding(self, cy_Point p): + """Return winding number around specified point.""" + return (<Curve *> self.thisptr).winding(deref(p.thisptr)) + + def unit_tangent_at(self, Coord t, int n = 3): + """Return tangent at self(t). + + Parameter n specifies how many derivatives to take into account.""" + return wrap_Point((<Curve *> self.thisptr).unitTangentAt(t, n)) + +cdef cy_EllipticalArc wrap_EllipticalArc(EllipticalArc p): + cdef EllipticalArc * retp = new EllipticalArc() + retp[0] = p + cdef cy_EllipticalArc r = cy_EllipticalArc.__new__(cy_EllipticalArc) + r.thisptr = retp + return r + +#TODO move somewhere else + +cdef object wrap_vector_interval(vector[Interval] v): + r = [] + cdef unsigned int i + for i in range(v.size()): + r.append( wrap_Interval(v[i])) + return r + + +cdef bint is_Curve(object c): + return any([ + isinstance(c, cy_Curve), + isinstance(c, cy_SBasisCurve), + isinstance(c, cy_BezierCurve), + isinstance(c, cy_EllipticalArc)]) + +cdef Curve * get_Curve_p(object c): + if isinstance(c, cy_Curve): + return (<cy_Curve> c).thisptr + elif isinstance(c, cy_SBasisCurve): + return <Curve *> (<cy_SBasisCurve> c).thisptr + elif isinstance(c, cy_BezierCurve): + return <Curve *> (<cy_BezierCurve> c).thisptr + elif isinstance(c, cy_EllipticalArc): + return <Curve *> (<cy_EllipticalArc> c).thisptr + return NULL + diff --git a/src/cython/_cy_path.pxd b/src/cython/_cy_path.pxd new file mode 100644 index 0000000..2d41581 --- /dev/null +++ b/src/cython/_cy_path.pxd @@ -0,0 +1,124 @@ +from _common_decl cimport * + +from libcpp.vector cimport vector +from libcpp.pair cimport pair + +from _cy_rectangle cimport Interval, OptInterval, Rect, OptRect +from _cy_affine cimport Affine +from _cy_curves cimport Curve, cy_Curve, wrap_Curve_p +from _cy_curves cimport SBasis, cy_SBasis + +from _cy_primitives cimport Point, cy_Point, wrap_Point + + +cdef extern from "2geom/d2.h" namespace "Geom": + cdef cppclass D2[T]: + D2() + D2(T &, T &) + T& operator[](unsigned i) + +#~ ctypedef int BaseIteratorConst "BaseIterator<ConstIterator, Path const>" + +cdef extern from "2geom/path.h" namespace "Geom::PathInternal": + cdef cppclass BaseIterator[C, P]: + Curve & c_item "operator*" () + C & c_next "operator++" () + C & c_next "operator++" (int) + bint operator==(BaseIterator[C, P]) + + + cdef cppclass ConstIterator: + ConstIterator() + cdef cppclass Iterator: + Iterator() + ConstIterator & operator() + + cdef cppclass BaseIteratorConst "Geom::PathInternal::BaseIterator<Geom::PathInternal::ConstIterator, Path const>": +#~ Curve & c_item "operator*" () + ConstIterator & c_next "operator++" () + ConstIterator & c_next "operator++" (int) +#~ bint operator==(BaseIterator[C, P]) + + +cdef extern from "2geom/path.h" namespace "Geom::Path": + cdef enum Stitching: + c_NO_STITCHING "Path::NO_STITCHING" = 0, + c_STITCH_DISCONTINUOUS "Path::STITCH_DISCONTINUOUS" + +cdef extern from "2geom/path.h" namespace "Geom": + cdef cppclass Path: + Path(Path &) + Path(Point) + Path(ConstIterator &, ConstIterator &, bint) + void swap(Path &) + Curve & operator[](unsigned int) +#~ Curve & at_index(unsigned int) +#~ ::boost::shared_ptr<Geom::Curve const> get_ref_at_index(unsigned int) + Curve & front() + Curve & back() + Curve & back_open() + Curve & back_closed() + Curve & back_default() + ConstIterator begin_const "begin" () + ConstIterator end() + Iterator begin() +#~ Iterator end() + ConstIterator end_open() + ConstIterator end_closed() + ConstIterator end_default() + size_t size_open() + size_t size_closed() + size_t size_default() + size_t size() + size_t max_size() + bint empty() + bint closed() + void close(bint) + OptRect boundsFast() + OptRect boundsExact() +#~ Piecewise<Geom::D2<Geom::SBasis> > toPwSb() + bint operator==(Path &) + bint operator!=(Path &) + Path operator*(Affine &) +#~ Path & operator*=(Affine &) + Point pointAt(double) + double valueAt(double, Dim2) + Point operator()(double) except + + vector[double] roots(double, Dim2) + vector[double] allNearestTimes(Point &, double, double) + vector[double] allNearestTimes(Point &) + vector[double] nearestTimePerCurve(Point &) + double nearestTime(Point &, double, double, double *) + double nearestTime(Point &, double *) + void appendPortionTo(Path &, double, double) + Path portion(double, double) + Path portion(Interval) + Path reversed() + void insert(Iterator &, Curve &, Stitching) except + + void insert(Iterator &, ConstIterator &, ConstIterator &, Stitching) + void clear() + void erase(Iterator &, Stitching) + void erase(Iterator &, Iterator &, Stitching) + void erase_last() + void replace(Iterator &, Curve &, Stitching) + void replace(Iterator &, Iterator &, Curve &, Stitching) + void replace(Iterator &, ConstIterator &, ConstIterator &, Stitching) + void replace(Iterator &, Iterator &, ConstIterator &, ConstIterator &, Stitching) + void start(Point) + Point initialPoint() + Point finalPoint() + void setInitial(Point &) + void setFinal(Point &) + void append(Curve &, Stitching) + void append(D2[SBasis] &, Stitching) + void append(Path &, Stitching) + void stitchTo(Point &) + +cdef class cy_Path: +#~ NO_STITCHING = c_NO_STITCHING +#~ STITCH_DISCONTINUOUS = c_STITCH_DISCONTINUOUS + cdef Path* thisptr + cdef ConstIterator _const_iterator_at_index(self, int i) + cdef Iterator _iterator_at_index(self, int i) + +cdef cy_Path wrap_Path(Path p) diff --git a/src/cython/_cy_path.pyx b/src/cython/_cy_path.pyx new file mode 100644 index 0000000..9f3f88e --- /dev/null +++ b/src/cython/_cy_path.pyx @@ -0,0 +1,457 @@ +from cython.operator cimport dereference as deref +from numbers import Number +from _cy_rectangle cimport cy_OptInterval, wrap_OptInterval, wrap_Rect, OptRect, wrap_OptRect +from _cy_rectangle cimport cy_Interval, wrap_Interval + +from _cy_affine cimport cy_Affine, wrap_Affine, get_Affine, is_transform + +from _cy_curves cimport is_Curve, get_Curve_p + + +cdef class cy_Iterator: + cdef Iterator* thisptr + cdef ConstIterator* startptr + cdef ConstIterator* endptr + + def __cinit__(self): + self.thisptr = new Iterator() + + def __dealloc__(self): + del self.thisptr + + def __iter__(self): + return self + + def __next__(self): + if deref(<BaseIterator[ConstIterator, Path] *> self.endptr) == deref(<BaseIterator[ConstIterator, Path] *> self.thisptr): + raise StopIteration + cdef Curve * r = <Curve *> &(<BaseIterator[Iterator, Path] * > self.thisptr).c_item() + (<BaseIterator[Iterator, Path] *> self.thisptr).c_next() + return wrap_Curve_p ( r ) + + + + +cdef cy_Iterator wrap_Iterator(Iterator i, ConstIterator starti, ConstIterator endi): + cdef Iterator * retp = new Iterator() + retp[0] = i + cdef cy_Iterator r = cy_Iterator.__new__(cy_Iterator) + r.thisptr = retp + + cdef ConstIterator * endp = new ConstIterator() + endp[0] = endi + r.endptr = endp + + cdef ConstIterator * startp = new ConstIterator() + startp[0] = starti + r.startptr = startp + + return r + +cdef class cy_Path: + + """Path is a ordered sequence of curves. + + You can iterate this class, accessing curves one at time, or access + them using indices. + + Two constants, NO_STITCHING and STITCH_DISCONTINUOUS are members of + Path namespace, and are used to specify type of stitching, if + necessary. + + Path is either open or closed, but in both cases carries closing + segment, connecting last and first point. + + Corresponds to Path class in 2geom. + """ + + NO_STITCHING = c_NO_STITCHING + STITCH_DISCONTINUOUS = c_STITCH_DISCONTINUOUS + + def __cinit__(self, cy_Point p=cy_Point()): + """Create Path containing only one point.""" + self.thisptr = new Path(deref( p.thisptr )) + + @classmethod + def fromList(cls, l, Stitching stitching=NO_STITCHING, closed=False): + """Create path from list of curves. + + Specify stithing and closed flag in additional arguments. + """ + p = cy_Path() + for curve in l: + p.append_curve(curve, stitching) + p.close(closed) + return p + + @classmethod + def fromPath(cls, cy_Path p, fr=-1, to = 1, bint closed=False): + """Create path copying another's path curves. + + Either copy all curves, or ones from + fr - index of first curve copied + to + to - index of first curve not copied. + + Also takes closed flag. + """ + if fr == -1: + return wrap_Path( Path(p.thisptr.begin_const(), p.thisptr.end_default(), closed) ) + else: + return wrap_Path( Path(p._const_iterator_at_index(fr), p._const_iterator_at_index(to), closed) ) + + def __dealloc__(self): + del self.thisptr + + def __getitem__(self, unsigned int i): + """Get curve with index i.""" + cdef Curve * r = <Curve *> & deref(self.thisptr)[i] + return wrap_Curve_p(r) + + def __call__(self, double t): + """Evaluate path at time t. + + Note: t can be greater than 1 here, it can go to self.size() + """ + return wrap_Point(deref( self.thisptr ) (t) ) + + def __richcmp__(cy_Path self, cy_Path other, int op): + if op == 2: + return deref( self.thisptr ) == deref( other.thisptr ) + elif op == 3: + return deref( self.thisptr ) != deref( other.thisptr ) + + def __mul__(cy_Path self, m): + """Transform path with a transform.""" + cdef Affine at + if is_transform(m): + at = get_Affine(m) + return wrap_Path( deref(self.thisptr) * at ) + + #This is not the fastest way, but it's pretty nice from python's perspective + #Anyway, I would expect that performance hit is minimal, since i is generally really small + cdef ConstIterator _const_iterator_at_index(self, int i): + cdef ConstIterator ci = self.thisptr.begin_const() + cdef ConstIterator * cip = &ci + for ii in range(i): + (<BaseIteratorConst *> cip).c_next() + return ci + + cdef Iterator _iterator_at_index(self, int i): + cdef Iterator ci = self.thisptr.begin() + cdef Iterator * cip = &ci + for ii in range(i): + (<BaseIterator[Iterator, Path] *> cip).c_next() + return ci + + def swap(self, cy_Path other): + """Swap curves with another path.""" + self.thisptr.swap(deref( other.thisptr )) + +#This is the same as __getitem__ +#~ def at_index(self, unsigned int i): +#~ return wrap_Curve(self.thisptr.at_index(i)) + + def front(self): + """Get first curve.""" + #this is AFAIK the shortest way to do this + cdef Curve * r = <Curve *> &self.thisptr.front() + return wrap_Curve_p(r) + + def back(self): + """Same as back_open.""" + cdef Curve * r = <Curve *> &self.thisptr.back() + return wrap_Curve_p(r) + + def back_open(self): + """Get last curve, treating self as open.""" + cdef Curve * r = <Curve *> &self.thisptr.back_open() + return wrap_Curve_p(r) + + def back_closed(self): + """Get last curve, treating self as closed.""" + cdef Curve * r = <Curve *> &self.thisptr.back_closed() + return wrap_Curve_p(r) + + def back_default(self): + """Get last curve.""" + cdef Curve * r = <Curve *> &self.thisptr.back_default() + return wrap_Curve_p(r) + + def curves(self): + """Same as curves_open""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end()) + + def curves_open(self): + """Return all curves as iterable, treating self as open.""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_open()) + + def curves_closed(self): + """Return all curves as iterable, treating self as closed.""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_closed()) + + def curves_default(self): + """Return all curves as iterable.""" + return wrap_Iterator(self.thisptr.begin(), self.thisptr.begin_const(), self.thisptr.end_default()) + + def __iter__(self): + return self.curves_default() + + def size_open(self): + """Get number of curves, treating self as open.""" + return self.thisptr.size_open() + + def size_closed(self): + """Get number of curves, treating self as closed.""" + return self.thisptr.size_closed() + + def size_default(self): + """Get number of curves.""" + return self.thisptr.size_default() + + def size(self): + """Same as size_open.""" + return self.thisptr.size() + +#Does the same as size_open, which doesn't correspond with name. +#~ def max_size(self): +#~ return self.thisptr.max_size() + + def empty(self): + """Test whether path contains no curves.""" + return self.thisptr.empty() + + def closed(self): + """Return state of closed flag.""" + return self.thisptr.closed() + + def close(self, bint closed): + """Set closed flag.""" + self.thisptr.close(closed) + + def bounds_fast(self): + """Return fast bounding rectangle for path. + + It's not guaranteed to give the tighest bound. + """ + return wrap_OptRect(self.thisptr.boundsFast()) + + def bounds_exact(self): + """Give the tighest bounding rectangle for path.""" + return wrap_OptRect(self.thisptr.boundsExact()) + +#~ def toPwSb(self): +#~ return wrap_Piecewise<Geom::D2<Geom::SBasis> >(self.thisptr.toPwSb()) + + def point_at(self, double t): + """Same as self(t).""" + return wrap_Point(self.thisptr.pointAt(t)) + + def value_at(self, double t, Dim2 d): + """Same as self(t)[d].""" + return self.thisptr.valueAt(t, d) + + def __call__(self, double t): + """Evaluate path at time t. + + Equivalent to self[floor(t)](t-floor(t)) + """ + return wrap_Point(deref( self.thisptr ) (t) ) + + def roots(self, double v, Dim2 d): + """Find time values where self(t)[d] == v""" + return wrap_vector_double(self.thisptr.roots(v, d)) + + def all_nearest_times(self, cy_Point _point, double fr=-1, double to=1): + """Return all values of t that |self(t) - point| is minimized.""" + if fr == -1: + return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr ))) + return wrap_vector_double(self.thisptr.allNearestTimes(deref( _point.thisptr ), fr, to)) + + + def nearest_time_per_curve(self, cy_Point _point): + """Find nearest points, return one time value per each curve.""" + return wrap_vector_double(self.thisptr.nearestTimePerCurve(deref( _point.thisptr ))) + + def nearest_time(self, cy_Point _point, double fr=-1, double to=1):#, cy_double * distance_squared): + """Return such t that |self(t) - point| is minimized.""" + if fr == -1: + return self.thisptr.nearestTime(deref( _point.thisptr ), NULL) + return self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, NULL) + + def nearest_time_and_dist_sq(self, cy_Point _point, double fr=-1, double to=1): + """Return such t that |self(t) - point| is minimized and square of that distance.""" + cdef double t, dist + if fr == -1: + t = self.thisptr.nearestTime(deref( _point.thisptr ), &dist ) + else: + t = self.thisptr.nearestTime(deref( _point.thisptr ), fr, to, &dist) + return (t, dist) + + def append_portion_to(self, cy_Path p, double f, double t): + """Append portion of path to self.""" + self.thisptr.appendPortionTo(deref( p.thisptr ), f, t) + + def portion(self, Coord fr=0, Coord to=1, cy_Interval interval=None): + """Return portion of curve between two time values. + + Alternatively use argument interval. + """ + if interval is None: + return wrap_Path(self.thisptr.portion(fr, to)) + else: + return wrap_Path(self.thisptr.portion(deref( interval.thisptr ))) + + def reversed(self): + """Return reversed curve.""" + return wrap_Path(self.thisptr.reversed()) + + def insert(self, int pos, curve, Stitching stitching=NO_STITCHING): + """Insert curve into position pos. + + Args: + pos: Position of inserted curve. + curve: Curve to insert. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.insert( self._iterator_at_index(pos), deref( cptr ), stitching ) + else: + raise TypeError("passed curve is not C++ Curve") + + def insert_slice(self, int pos, cy_Path p, int first, int last, Stitching stitching=NO_STITCHING): + """Insert curves to position pos. + + Args: + pos: Position of inserted slice. + p: Path from which slice is inserted. + first: First inserted curve position (in p). + last: Fist not inserted curve position (in p). + stiching=NO_STITCHING + """ + self.thisptr.insert(self._iterator_at_index(pos), p._const_iterator_at_index(first), p._const_iterator_at_index(last), stitching) + + def clear(self): + """Clear all curves.""" + self.thisptr.clear() + + def erase(self, int pos, Stitching stitching=NO_STITCHING): + """Erase curve at position pos. + + Args: + pos: Position of erased curve. + stitching=NO_STITCHING + """ + self.thisptr.erase(self._iterator_at_index(pos), stitching) + + def erase_slice(self, int start, int end, Stitching stitching=NO_STITCHING): + """Erase curves with indices [start, end). + + Args: + start, end: Curves with indices start...end-1 are erased + stitching=NO_STITCHING + """ + self.thisptr.erase(self._iterator_at_index(start), self._iterator_at_index(end), stitching) + + def erase_last(self): + """Erase last curve.""" + self.thisptr.erase_last() + + def replace(self, int replaced, curve, Stitching stitching=NO_STITCHING): + """Replace curve at position replaced with another curve. + + Args: + replaced: Position of replaced curve. + curve: New curve. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.replace(self._iterator_at_index(replaced), deref( cptr ), stitching) + else: + raise TypeError("passed curve is not C++ Curve") + + def replace_slice(self, int first_replaced, int last_replaced, curve, Stitching stitching=NO_STITCHING): + """Replace slice of curves by new curve. + + Args: + first_replaced, last_replace: Curves with indices + first_replaced ... last_replaced + curve: New curve. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.replace(self._iterator_at_index(first_replaced), self._iterator_at_index(last_replaced), deref( cptr ), stitching) + else: + raise TypeError("passed curve is not C++ Curve") + +#How to implement this nicely? +#~ def replaceByList(self, int replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching): +#~ self.thisptr.replace(deref( replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching) +#~ def replace(self, cy_Iterator first_replaced, cy_Iterator last_replaced, cy_ConstIterator first, cy_ConstIterator last, Stitching stitching): +#~ self.thisptr.replace(deref( first_replaced.thisptr ), deref( last_replaced.thisptr ), deref( first.thisptr ), deref( last.thisptr ), stitching) + + def start(self, cy_Point p): + """Erase all curves and set first point.""" + self.thisptr.start(deref( p.thisptr )) + + def initial_point(self): + """Get initial point.""" + return wrap_Point(self.thisptr.initialPoint()) + + def final_point(self): + """Get final point.""" + return wrap_Point(self.thisptr.finalPoint()) + + def set_initial(self, cy_Point p): + """Set initial point.""" + self.thisptr.setInitial(deref( p.thisptr )) + + def set_final(self, cy_Point p): + """Set final point.""" + self.thisptr.setFinal(deref( p.thisptr )) + + def append_curve(self, curve, Stitching stitching=NO_STITCHING): + """Append curve to path. + + Args: + curve: Curve to append. + stitching=NO_STITCHING + """ + cdef Curve * cptr = get_Curve_p(curve) + if cptr: + self.thisptr.append( deref( cptr ), stitching) + else: + raise TypeError("passed curve is not C++ Curve") + + def append_SBasis(self, cy_SBasis x, cy_SBasis y, Stitching stitching=NO_STITCHING): + """Append two SBasis functions to path. + + Args: + x, y: SBasis functions to append. + stitching=NO_STITCHING + """ + cdef D2[SBasis] sb = D2[SBasis]( deref(x.thisptr), deref(y.thisptr) ) + self.thisptr.append(sb, stitching) + + def append_path(self, cy_Path other, Stitching stitching=NO_STITCHING): + """Append another path to path. + + Args: + other: Path to append. + stitching=NO_STITCHING + """ + self.thisptr.append(deref( other.thisptr ), stitching) + + def stitch_to(self, cy_Point p): + """Set last point to p, creating stitching segment to it.""" + self.thisptr.stitchTo(deref( p.thisptr )) + +cdef cy_Path wrap_Path(Path p): + cdef Path * retp = new Path(Point()) + retp[0] = p + cdef cy_Path r = cy_Path.__new__(cy_Path) + r.thisptr = retp + return r diff --git a/src/cython/_cy_primitives.pxd b/src/cython/_cy_primitives.pxd new file mode 100644 index 0000000..87df1c6 --- /dev/null +++ b/src/cython/_cy_primitives.pxd @@ -0,0 +1,237 @@ +from _common_decl cimport * + + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Affine: + pass + cdef cppclass Translate + cdef cppclass Scale + cdef cppclass Rotate + cdef cppclass VShear + cdef cppclass HShear + cdef cppclass Zoom + + +cdef extern from "2geom/angle.h" namespace "Geom": + cdef cppclass Angle: + Angle() + Angle(Coord) + Angle(Point) + Coord radians() + Coord radians0() + Coord degrees() + Coord degreesClock() + + Coord operator() + Angle &operator+(Angle &) + Angle &operator-(Angle &) + bint operator==(Angle &) + bint operator!=(Angle &) + + Coord rad_from_deg(Coord) + Coord deg_from_rad(Coord) + +cdef extern from "2geom/angle.h" namespace "Geom::Angle": + Angle from_radians(Coord d) + Angle from_degrees(Coord d) + Angle from_degrees_clock(Coord d) + +cdef class cy_Angle: + cdef Angle* thisptr + +cdef cy_Angle wrap_Angle(Angle) + + +cdef extern from "2geom/angle.h" namespace "Geom": + cdef cppclass AngleInterval: + AngleInterval(AngleInterval &) + AngleInterval(Angle &, Angle &, bint) + AngleInterval(double, double, bint) + Angle & initialAngle() + Angle & finalAngle() + bint isDegenerate() + Angle angleAt(Coord) + Angle operator()(Coord) + bint contains(Angle &) + Coord extent() + + +cdef extern from "2geom/point.h" namespace "Geom": + cdef cppclass Point: + Point() + Point(Coord, Coord) + Coord length() + Point ccw() + Point cw() + Coord x() + Coord y() + IntPoint round() + IntPoint floor() + IntPoint ceil() + + bint isFinite() + bint isZero() + bint isNormalized(Coord) + + bint operator==(Point &) + bint operator!=(Point &) + bint operator<(Point &) + bint operator>(Point &) + bint operator<=(Point &) + bint operator>=(Point &) + + Coord &operator[](int) + Point operator-() + Point &operator+(Point &) + Point &operator-(Point &) + Point &operator*(Coord) + Point &operator/(Coord) + + Point &operator*(Affine &) + Point &operator*(Translate &) + Point &operator*(Scale &) + Point &operator*(Rotate &) + Point &operator*(HShear &) + Point &operator*(VShear &) + Point &operator*(Zoom &) + + Coord L2(Point &) + Coord L2sq(Point &) + + bint are_near(Point &, Point &, double) + + Point middle_point(Point &, Point &) + Point rot90(Point &) + Point lerp(double, Point &, Point &) + + Coord dot(Point &, Point &) + Coord cross(Point &, Point &) + Coord distance (Point &, Point &) + Coord distanceSq (Point &, Point &) + + Point unit_vector(Point &) + Coord L1(Point &) + Coord LInfty(Point &) + bint is_zero(Point &) + bint is_unit_vector(Point &) + double atan2(Point &) + double angle_between(Point &, Point &) + Point abs(Point &) + Point constrain_angle(Point &, Point &, unsigned int, Point &) + +cdef extern from "2geom/point.h" namespace "Geom::Point": + Point polar(Coord angle, Coord radius) + +cdef class cy_Point: + cdef Point* thisptr + +cdef cy_Point wrap_Point(Point p) +cdef object wrap_vector_point(vector[Point] v) +cdef vector[Point] make_vector_point(object l) + + +cdef extern from "2geom/int-point.h" namespace "Geom": + cdef cppclass IntPoint: + IntPoint() + IntPoint(IntCoord, IntCoord) + IntPoint(IntPoint &) + IntCoord operator[](unsigned int) + IntCoord x() + IntCoord y() + #why doesn't IntPoint have unary -? + IntPoint & operator+(IntPoint &) + IntPoint & operator-(IntPoint &) + bint operator==(IntPoint &) + bint operator!=(IntPoint &) + bint operator<=(IntPoint &) + bint operator>=(IntPoint &) + bint operator>(IntPoint &) + bint operator<(IntPoint &) + +cdef class cy_IntPoint: + cdef IntPoint* thisptr + +cdef cy_IntPoint wrap_IntPoint(IntPoint p) + + +cdef extern from "2geom/curve.h" namespace "Geom": + cdef cppclass Curve + +cdef extern from "2geom/bezier.h" namespace "Geom": + cdef cppclass LineSegment + +cdef extern from "2geom/line.h" namespace "Geom": + cdef cppclass Line: + Line() + Line(Point &, Coord) + Line(Point &, Point &) + + Line(LineSegment &) + Line(Ray &) + Line* duplicate() + + Point origin() + Point versor() + Coord angle() + void setOrigin(Point &) + void setVersor(Point &) + void setAngle(Coord) + void setPoints(Point &, Point &) + void setCoefficients(double, double, double) + bint isDegenerate() + Point pointAt(Coord) + Coord valueAt(Coord, Dim2) + Coord timeAt(Point &) + Coord timeAtProjection(Point &) + Coord nearestTime(Point &) + vector[Coord] roots(Coord, Dim2) + Line reverse() + Curve* portion(Coord, Coord) + LineSegment segment(Coord, Coord) + Ray ray(Coord) + Line derivative() + Line transformed(Affine &) + Point normal() + Point normalAndDist(double &) + + double distance(Point &, Line &) + bint are_near(Point &, Line &, double) + bint are_parallel(Line &, Line &, double) + bint are_same(Line &, Line &, double) + bint are_orthogonal(Line &, Line &, double) + bint are_collinear(Point &, Point &, Point &, double) + + double angle_between(Line &, Line &) + double distance(Point &, LineSegment &) + +cdef extern from "2geom/line.h" namespace "Geom::Line": + Line from_origin_and_versor(Point, Point) + Line from_normal_distance(Point, double) + + +cdef extern from "2geom/ray.h" namespace "Geom": + cdef cppclass Ray: + Ray() + Ray(Point &, Coord) + Ray(Point&, Point &) + Point origin() + Point versor() + void setOrigin(Point &) + void setVersor(Point &) + void setAngle(Coord) + Coord angle() + void setPoints(Point &, Point &) + bint isDegenerate() + Point pointAt(Coord) + Coord valueAt(Coord, Dim2) + vector[Coord] roots(Coord, Dim2) + Coord nearestTime(Point &) + Ray reverse() + Curve *portion(Coord, Coord) + LineSegment segment(Coord, Coord) + Ray transformed(Affine &) + double distance(Point &, Ray &) + bint are_near(Point &, Ray &, double) + bint are_same(Ray&, Ray &, double) + double angle_between(Ray &, Ray &, bint) + Ray make_angle_bisector_ray(Ray &, Ray&) diff --git a/src/cython/_cy_primitives.pyx b/src/cython/_cy_primitives.pyx new file mode 100644 index 0000000..62f7f41 --- /dev/null +++ b/src/cython/_cy_primitives.pyx @@ -0,0 +1,846 @@ +from numbers import Number + +from _common_decl cimport * +from cython.operator cimport dereference as deref + +from _cy_affine cimport cy_Translate, cy_Rotate, cy_Scale +from _cy_affine cimport cy_VShear, cy_HShear, cy_Zoom +from _cy_affine cimport cy_Affine, get_Affine, is_transform + +from _cy_curves cimport cy_Curve, wrap_Curve_p +from _cy_curves cimport cy_LineSegment, wrap_LineSegment + +cdef class cy_Angle: + + """Class representig angle. + + Angles can be tested for equality, but they are not ordered. + + Corresponds to Angle class in 2geom. Most members are direct + calls to Angle methods, otherwise C++ call is specified. + """ + + def __cinit__(self, double x): + """Create new angle from value in radians.""" + self.thisptr = new Angle(x) + + def __repr__(self): + """repr(self)""" + return "Angle({0:2.f})".format(self.radians()) + + def __str__(self): + """str(self)""" + return "{0:.2f} radians".format(self.radians()) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_radians(cls, rad): + """Construnct angle from radians.""" + return wrap_Angle(from_radians(rad)) + + @classmethod + def from_degrees(cls, d): + """Construnct angle from degrees.""" + return wrap_Angle(from_degrees(d)) + + @classmethod + def from_degrees_clock(cls, d): + """Construnct angle from degrees in clock convention.""" + return wrap_Angle(from_degrees_clock(d)) + + @classmethod + def from_Point(cls, cy_Point p): + """Construct angle from Point. Calls Angle(Point) in 2geom.""" + cdef Point * pp = p.thisptr + return wrap_Angle( Angle( deref(p.thisptr) )) + + def __float__(self): + """float(self)""" + return <Coord> deref(self.thisptr) + + def __add__(cy_Angle self, cy_Angle other): + """alpha + beta""" + return wrap_Angle(deref(other.thisptr) + deref(self.thisptr)) + + def __sub__(cy_Angle self, cy_Angle other): + """alpha - beta""" + return wrap_Angle(deref(other.thisptr) - deref(self.thisptr)) + + def __richcmp__(cy_Angle self, cy_Angle other, int op): + """Test equality of angles. Note: angles are not ordered.""" + if op == 2: + return deref(other.thisptr) == deref(self.thisptr) + elif op == 3: + return deref(other.thisptr) != deref(self.thisptr) + return NotImplemented + + def radians(self): + """Return the angle in radians.""" + return self.thisptr.radians() + + def radians0(self): + """Return the angle in positive radians.""" + return self.thisptr.radians0() + + def degrees(self): + """Return the angle in degrees.""" + return self.thisptr.degrees() + + def degrees_clock(self): + """Return the angle in clock convention. Calls degreesClock() in 2geom.""" + return self.thisptr.degreesClock() + + @classmethod + def rad_from_deg(cls, deg): + """Convert degrees to radians.""" + return rad_from_deg(deg) + + @classmethod + def deg_from_rad(cls, rad): + """Convert radians to degrees.""" + return deg_from_rad(rad) + +cdef cy_Angle wrap_Angle(Angle p): + cdef Angle * retp = new Angle() + retp[0] = p + cdef cy_Angle r = cy_Angle.__new__(cy_Angle, 0) + r.thisptr = retp + return r + + +cdef class cy_AngleInterval: + + """ Class representing interval of angles. + + Corresponds to AngleInterval class in 2geom. Most members are direct + calls to AngleInterval methods, otherwise C++ call is specified. + """ + + cdef AngleInterval* thisptr + + def __cinit__(self, start, end, bint cw=False): + """Create AngleInterval from starting and ending value. + + Optional argument cw specifies direction - counter-clockwise + is default. + """ + self.thisptr = new AngleInterval(float(start), float(end), cw) + + def __call__(self, Coord t): + """A(t), maps unit interval to Angle.""" + return wrap_Angle(deref( self.thisptr ) (t)) + + def initial_angle(self): + """Return initial angle as Angle instance.""" + return wrap_Angle(self.thisptr.initialAngle()) + + def final_angle(self): + """Return final angle as Angle instance.""" + return wrap_Angle(self.thisptr.finalAngle()) + + def is_degenerate(self): + """Test for empty interval.""" + return self.thisptr.isDegenerate() + + def angle_at(self, Coord t): + """A.angle_at(t) <==> A(t)""" + return wrap_Angle(self.thisptr.angleAt(t)) + + def contains(self, cy_Angle a): + """Test whether interval contains Angle.""" + return self.thisptr.contains(deref( a.thisptr )) + + def extent(self): + """Calculate interval's extent.""" + return self.thisptr.extent() + +cdef cy_AngleInterval wrap_AngleInterval(AngleInterval p): + cdef AngleInterval * retp = new AngleInterval(0, 0, 0) + retp[0] = p + cdef cy_AngleInterval r = cy_AngleInterval.__new__(cy_AngleInterval) + r.thisptr = retp + return r + + +cdef class cy_Point: + + """Represents point or vector in 2D plane. + + Points are ordered lexicographically, with y coordinate being + more significant. + + Corresponds to Point class in 2geom. Most members are direct + calls to Point methods, otherwise C++ call is specified. + """ + + def __cinit__(self, double x=0.0, double y=0.0): + """Create Point from it's cartesian coordinates.""" + self.thisptr = new Point(x, y) + + def __repr__(self): + """repr(self)""" + return "Point ({0:.3f}, {1:.3f})".format(self[0], self[1]) + + def __str__(self): + """str(self)""" + return "[{0:.3f}, {1:.3f}]".format(self[0], self[1]) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def polar(cls, Coord angle, Coord radius = 1.0): + """Create Point from polar coordinates.""" + return wrap_Point(polar(angle, radius)) + + def length(self): + """Return distance from origin or length of vector.""" + return self.thisptr.length() + + def ccw(self): + """Return point rotated counter-clockwise.""" + return wrap_Point(self.thisptr.ccw()) + + def cw(self): + """Return point rotated clockwise.""" + return wrap_Point(self.thisptr.cw()) + + def __getitem__(self, key): + """Access coordinates of point.""" + return deref(self.thisptr)[key] + + @property + def x(self): + """First coordinate of point.""" + return self.thisptr.x() + + @property + def y(self): + """Second coordinate of point.""" + return self.thisptr.y() + + def round(self): + """Create IntPoint rounding coordinates.""" + return wrap_IntPoint(self.thisptr.round()) + + def floor(self): + """Create IntPoint flooring coordinates.""" + return wrap_IntPoint(self.thisptr.floor()) + + def ceil(self): + """Create IntPoint ceiling coordinates.""" + return wrap_IntPoint(self.thisptr.ceil()) + + def __neg__(self): + """-P""" + return(wrap_Point(-deref(self.thisptr))) + + def __abs__(self): + """abs(P)""" + return self.length() + + def __add__(cy_Point self, cy_Point other): + """P + Q""" + return wrap_Point(deref(self.thisptr) + deref(other.thisptr)) + + def __sub__(cy_Point self, cy_Point other): + """P - Q""" + return wrap_Point(deref(self.thisptr) - deref(other.thisptr)) + + #TODO exceptions + def __mul__(cy_Point self, s): + """Multiply point by number or Affine transform.""" + if isinstance(s, Number): + return wrap_Point(deref(self.thisptr)* (<Coord> float(s))) + elif isinstance(s, cy_Affine): + return wrap_Point( deref(self.thisptr) * <Affine &> deref( (<cy_Affine> s).thisptr ) ) + elif isinstance(s, cy_Translate): + return wrap_Point( deref(self.thisptr) * <Translate &> deref( (<cy_Translate> s).thisptr ) ) + elif isinstance(s, cy_Scale): + return wrap_Point( deref(self.thisptr) * <Scale &> deref( (<cy_Scale> s).thisptr ) ) + elif isinstance(s, cy_Rotate): + return wrap_Point( deref(self.thisptr) * <Rotate &> deref( (<cy_Rotate> s).thisptr ) ) + elif isinstance(s, cy_HShear): + return wrap_Point( deref(self.thisptr) * <HShear &> deref( (<cy_HShear> s).thisptr ) ) + elif isinstance(s, cy_VShear): + return wrap_Point( deref(self.thisptr) * <VShear &> deref( (<cy_VShear> s).thisptr ) ) + elif isinstance(s, cy_Zoom): + return wrap_Point( deref(self.thisptr) * <Zoom &> deref( (<cy_Zoom> s).thisptr ) ) + return NotImplemented + + def __div__(cy_Point self, Coord s): + """P / s""" + return wrap_Point(deref(self.thisptr)/s) + + def __richcmp__(cy_Point self, cy_Point other, int op): + if op == 0: + return deref(self.thisptr) < deref(other.thisptr) + if op == 1: + return deref(self.thisptr) <= deref(other.thisptr) + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + if op == 3: + return deref(self.thisptr) != deref(other.thisptr) + if op == 4: + return deref(self.thisptr) > deref(other.thisptr) + if op == 5: + return deref(self.thisptr) >= deref(other.thisptr) + + def isFinite(self): + """Test whether point is finite.""" + return self.thisptr.isFinite() + + def isZero(self): + """Test whether point is origin""" + return self.thisptr.isZero() + + def isNormalized(self, eps=EPSILON): + """Test whether point's norm is close to 1.""" + return self.thisptr.isNormalized(eps) + + @classmethod + def L2(cls, cy_Point p): + """Compute L2 (Euclidean) norm of point. + + L2(P) = sqrt( P.x**2 + P.y**2 ) + """ + return L2(deref(p.thisptr)) + + @classmethod + def L2sq(cls, cy_Point p): + """Compute square of L2 (Euclidean) norm.""" + return L2sq(deref(p.thisptr)) + + @classmethod + def are_near(cls, cy_Point a, cy_Point b, double eps=EPSILON): + """Test if two points are close.""" + return are_near(deref(a.thisptr), deref(b.thisptr), eps) + + @classmethod + def middle_point(cls, cy_Point a, cy_Point b): + """Return point between two points.""" + return wrap_Point(middle_point(deref(a.thisptr), deref(b.thisptr))) + + @classmethod + def rot90(cls, cy_Point a): + """Rotate point by 90 degrees.""" + return wrap_Point(rot90(deref(a.thisptr))) + + @classmethod + def lerp(cls, double t, cy_Point a, cy_Point b): + """Linearly interpolate between too points.""" + return wrap_Point(lerp(t, deref(a.thisptr), deref(b.thisptr))) + + @classmethod + def dot(cls, cy_Point a, cy_Point b): + """Calculate dot product of two points.""" + return dot(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def cross(cls, cy_Point a, cy_Point b): + """Calculate (z-coordinate of) cross product of two points.""" + return cross(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def distance(cls, cy_Point a, cy_Point b): + """Compute distance between two points.""" + return distance(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def distanceSq(cls, cy_Point a, cy_Point b): + """Compute square of distance between two points.""" + return distanceSq(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def unit_vector(cls, cy_Point p): + """Normalise point.""" + return wrap_Point(unit_vector(deref(p.thisptr))) + + @classmethod + def L1(cls, cy_Point p): + """Compute L1 (Manhattan) norm of a point. + + L1(P) = |P.x| + |P.y| + """ + return L1(deref(p.thisptr)) + + @classmethod + def LInfty(cls, cy_Point p): + """Compute Infinity norm of a point. + + LInfty(P) = max(|P.x|, |P.y|) + """ + return LInfty(deref(p.thisptr)) + + @classmethod + def is_zero(cls, cy_Point p): + """Test whether point is origin.""" + return is_zero(deref(p.thisptr)) + + @classmethod + def is_unit_vector(cls, cy_Point p): + """Test whether point's length equal 1.""" + return is_unit_vector(deref(p.thisptr)) + + @classmethod + def atan2(cls, cy_Point p): + """Return angle between point and x-axis.""" + return atan2(deref(p.thisptr)) + + @classmethod + def angle_between(cls, cy_Point a, cy_Point b): + """Return angle between two point.""" + return angle_between(deref(a.thisptr), deref(b.thisptr)) + + @classmethod + def abs(cls, cy_Point p): + """Return length of a point.""" + return wrap_Point(abs(deref(p.thisptr))) + + @classmethod + def constrain_angle(cls, cy_Point a, cy_Point b, unsigned int n, cy_Point direction): + """Rotate B around A to have specified angle wrt. direction.""" + return wrap_Point(constrain_angle(deref(a.thisptr), deref(b.thisptr), n, deref(direction.thisptr))) + +cdef cy_Point wrap_Point(Point p): + cdef Point * retp = new Point() + retp[0] = p + cdef cy_Point r = cy_Point.__new__(cy_Point) + r.thisptr = retp + return r + +cdef object wrap_vector_point(vector[Point] v): + r = [] + cdef unsigned int i + for i in range(v.size()): + r.append( wrap_Point(v[i]) ) + return r + +cdef vector[Point] make_vector_point(object l): + cdef vector[Point] ret + for i in l: + ret.push_back( deref( (<cy_Point> i).thisptr ) ) + return ret + + +cdef class cy_IntPoint: + + """Represents point with integer coordinates + + IntPoints are ordered lexicographically, with y coordinate being + more significant. + + Corresponds to IntPoint class in 2geom. Most members are direct + calls to IntPoint methods, otherwise C++ call is specified. + """ + + def __init__(self, IntCoord x = 0, IntCoord y = 0): + """Create new IntPoint from it's cartesian coordinates.""" + self.thisptr = new IntPoint(x ,y) + + def __getitem__(self, key): + """Get coordinates of IntPoint.""" + return deref(self.thisptr)[key] + + def __repr__(self): + """repr(self)""" + return "IntPoint ({0}, {1})".format(self[0], self[1]) + + def __str__(self): + """str(self)""" + return "[{0}, {1}]".format(self[0], self[1]) + + def __dealloc__(self): + del self.thisptr + + @property + def x(self): + """First coordinate of IntPoint.""" + return self.thisptr.x() + + @property + def y(self): + """Second coordinate of IntPoint.""" + return self.thisptr.y() + + def __add__(cy_IntPoint self, cy_IntPoint o): + """P + Q""" + return wrap_IntPoint(deref(self.thisptr)+deref( o.thisptr )) + + def __sub__(cy_IntPoint self, cy_IntPoint o): + """P - Q""" + return wrap_IntPoint(deref(self.thisptr)-deref( o.thisptr )) + + def __richcmp__(cy_IntPoint self, cy_IntPoint other, int op): + if op == 0: + return deref(self.thisptr) < deref(other.thisptr) + if op == 1: + return deref(self.thisptr) <= deref(other.thisptr) + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + if op == 3: + return deref(self.thisptr) != deref(other.thisptr) + if op == 4: + return deref(self.thisptr) > deref(other.thisptr) + if op == 5: + return deref(self.thisptr) >= deref(other.thisptr) + +cdef cy_IntPoint wrap_IntPoint(IntPoint p): + cdef IntPoint * retp = new IntPoint() + retp[0] = p + cdef cy_IntPoint r = cy_IntPoint.__new__(cy_IntPoint) + r.thisptr = retp + return r + + +cdef class cy_Line: + + """Class representing line in plane. + + Corresponds to Line class in 2geom. Most members are direct + calls to Line methods, otherwise C++ call is specified. + """ + + cdef Line* thisptr + + def __cinit__(self, cy_Point cp = None, double x = 0): + """Create Line from point and angle to x-axis. + + Constructor with no arguments calls Line() in 2geom, otherwise + Line(Point &, double) is called. + """ + if cp is None: + self.thisptr = new Line() + else: + self.thisptr = new Line( deref(cp.thisptr), x) + + def __repr__(self): + """repr(self).""" + return "Line({0}, {1:3f})".format(repr(self.origin()), self.angle()) + + def __str__(self): + """str(self)""" + return repr(self) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point cp, cy_Point cq): + """Create Line passing through two points. + + Calls Line(Point &, Point &) in 2geom. + """ + return wrap_Line( Line( deref(cp.thisptr), deref(cq.thisptr) ) ) + + @classmethod + def from_origin_and_versor(cls, cy_Point o, cy_Point v): + """Create Line passing through point with specified versor.""" + return wrap_Line( from_origin_and_versor(deref(o.thisptr), deref(v.thisptr)) ) + + @classmethod + def from_normal_distance(cls, cy_Point normal, double dist): + """Create Line from it's normal and distance from origin.""" + return wrap_Line( from_normal_distance( deref(normal.thisptr), dist ) ) + + @classmethod + def from_LineSegment(cls, cy_LineSegment LS): + """Create Line from LineSegment. + + Calls Line(LineSegment &) in 2geom. + """ + return wrap_Line( Line( deref(<LineSegment *> LS.thisptr) ) ) + + @classmethod + def from_Ray(cls, cy_Ray R): + """Create Line from Ray. + + Calls Line(Ray &) in 2geom. + """ + return wrap_Line( Line( deref(R.thisptr) ) ) + + #maybe implement as properties. + + def origin(self): + """Return origin of line.""" + return wrap_Point(self.thisptr.origin()) + + def versor(self): + """Return versor of line.""" + return wrap_Point(self.thisptr.versor()) + + def angle(self): + """Return angle between line and x-axis.""" + return self.thisptr.angle() + + def set_origin(self, cy_Point origin): + """Set origin.""" + self.thisptr.setOrigin( deref(origin.thisptr) ) + + def set_versor(self, cy_Point versor): + """Set versor.""" + self.thisptr.setVersor( deref(versor.thisptr) ) + + def set_angle(self, Coord a): + """Set angle.""" + self.thisptr.setAngle(a) + + def set_points(self, cy_Point cp, cy_Point cq): + """Set two points line passes through.""" + self.thisptr.setPoints( deref(cp.thisptr), deref(cq.thisptr) ) + + def set_coefficients(self, a, b, c): + """Set coefficients in parametric equation of line.""" + self.thisptr.setCoefficients(a, b, c) + + def is_degenerate(self): + """Test whether line's versor is zero vector.""" + return self.thisptr.isDegenerate() + + def point_at(self, t): + """origin + t*versor""" + return wrap_Point(self.thisptr.pointAt(t)) + + def value_at(self, t, Dim2 d): + """Coordinates of point_at(t).""" + return self.thisptr.valueAt(t, d) + + def time_at(self, cy_Point cp): + """Find time value corresponding to point on line.""" + return self.thisptr.timeAt( deref(cp.thisptr) ) + + def time_at_projection(self, cy_Point cp): + """Find time value corresponding to orthogonal projection of point.""" + return self.thisptr.timeAtProjection( deref(cp.thisptr) ) + + def nearest_time(self, cy_Point cp): + """Alias for time_at_projection.""" + return self.thisptr.nearestTime( deref(cp.thisptr) ) + + def roots(self, Coord v, Dim2 d): + """Return time values where self.value_at(t, dim) == v.""" + return wrap_vector_double( self.thisptr.roots(v, d) ) + + def reverse(self): + """Reverse line.""" + return wrap_Line( self.thisptr.reverse() ) + + def derivative(self): + """Take line's derivative.""" + return wrap_Line( self.thisptr.derivative() ) + + def normal(self): + """Return line's normal.""" + return wrap_Point( self.thisptr.normal() ) + + def normal_and_dist(self): + """return tuple containing normal vector and distance from origin. + + Calls normal_and_dist(x) and return it's result and x as a tuple. + """ + cdef double x = 0 + cdef Point p = self.thisptr.normalAndDist(x) + return (wrap_Point(p), x) + + def portion(self, Coord f, Coord t): + """Return Curve corresponding to portion of line.""" + return wrap_Curve_p( self.thisptr.portion(f, t) ) + + def ray(self, Coord t): + """Return Ray continuing from time value t.""" + return wrap_Ray( self.thisptr.ray(t) ) + + def segment(self, Coord f, Coord t): + """Return LineSegment corresponding to portion of line.""" + return wrap_LineSegment( self.thisptr.segment(f, t) ) + + def transformed(self, t): + """Return line transformed by transform.""" + #doing this because transformed(t) takes reference + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_Line(self.thisptr.transformed( at )) + + @classmethod + def distance(cls, cy_Point cp, cy_Line cl): + """Calculate distance between point and line.""" + return distance( deref(cp.thisptr), deref(cl.thisptr)) + + @classmethod + def are_near(cls, cy_Point cp, cy_Line cl, double eps=EPSILON): + """Test if point is near line.""" + return are_near( deref(cp.thisptr), deref(cl.thisptr), eps) + + @classmethod + def are_parallel(cls, cy_Line cl, cy_Line ck, eps=EPSILON): + """Test if lines are almost parallel.""" + return are_parallel( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def are_same(cls, cy_Line cl, cy_Line ck, double eps=EPSILON): + """Test if lines represent the same line.""" + return are_same( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def are_orthogonal(cls, cy_Line cl, cy_Line ck, eps=EPSILON): + """Test two lines for orthogonality.""" + return are_orthogonal( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def are_collinear(cls, cy_Point cp, cy_Point cq, cy_Point cr, eps=EPSILON): + """Test for collinearity of vectors (cq-cp) and (cr-cp)""" + return are_collinear( deref(cp.thisptr), deref(cq.thisptr), deref(cr.thisptr), eps) + + @classmethod + def angle_between(cls, cy_Line cl, cy_Line ck): + """Calculate angle between two lines""" + return angle_between( deref(cl.thisptr), deref(ck.thisptr) ) + +cdef cy_Line wrap_Line(Line p): + cdef Line * retp = new Line() + retp[0] = p + cdef cy_Line r = cy_Line.__new__(cy_Line) + r.thisptr = retp + return r + +#-- Ray -- + +cdef class cy_Ray: + + """Ray represents half of line, starting at origin and going to + infinity. + + Corresponds to Ray class in 2geom. Most members are direct + calls to Ray methods, otherwise C++ call is specified. + """ + + cdef Ray* thisptr + + def __cinit__(self, cy_Point cp = None, double x = 0): + """Create Ray from origin and angle with x-axis. + + Empty constructor calls Ray() in 2geom. + """ + if cp is None: + self.thisptr = new Ray() + else: + self.thisptr = new Ray( deref(cp.thisptr), x) + + def __repr__(self): + """repr(self).""" + return "Ray({0}, {1:3f})".format(repr(self.origin()), self.angle()) + + def __str__(self): + """str(self)""" + return repr(self) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point cp, cy_Point cq): + """Create ray passing through two points, starting at first one.""" + return wrap_Ray( (Ray( deref(cp.thisptr), deref(cq.thisptr) )) ) + + def origin(self): + """Return origin.""" + return wrap_Point(self.thisptr.origin()) + + def versor(self): + """Return versor.""" + return wrap_Point(self.thisptr.versor()) + + def angle(self): + """Return angle between ray and x-axis.""" + return self.thisptr.angle() + + def set_origin(self, cy_Point cp): + """Set origin.""" + self.thisptr.setOrigin( deref(cp.thisptr) ) + + def set_versor(self, cy_Point cp): + """Set versor.""" + self.thisptr.setVersor( deref(cp.thisptr) ) + + def set_angle(self, Coord a): + """Set angle.""" + self.thisptr.setAngle(a) + + def set_points(self, cy_Point cp, cy_Point cq): + """Set origin and second point of ray.""" + self.thisptr.setPoints( deref(cp.thisptr), deref(cq.thisptr) ) + + def is_degenerate(self): + """Check for zero versor.""" + return self.thisptr.isDegenerate() + + def point_at(self, t): + """origin + t * versor""" + return wrap_Point(self.thisptr.pointAt(t)) + def value_at(self, t, Dim2 d): + """Access coordinates of point_at(t).""" + return self.thisptr.valueAt(t, d) + + def nearest_time(self, cy_Point cp): + """Get time value of nearest point of ray.""" + return self.thisptr.nearestTime( deref(cp.thisptr) ) + def reverse(self): + """Reverse the ray.""" + return wrap_Ray( self.thisptr.reverse() ) + + def roots(self, Coord v, Dim2 d): + """Return time values for which self.value_at(t, d) == v.""" + return wrap_vector_double( self.thisptr.roots(v, d) ) + + def transformed(self, t): + """Return ray transformed by affine transform.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_Ray(self.thisptr.transformed( at )) + + def portion(self, Coord f, Coord t): + """Return Curve corresponding to portion of ray.""" + return wrap_Curve_p( self.thisptr.portion(f, t) ) + + def segment(self, Coord f, Coord t): + """Return LineSegment corresponding to portion of ray.""" + return wrap_LineSegment( self.thisptr.segment(f, t) ) + + @classmethod + def distance(cls, cy_Point cp, cy_Ray cl): + """Compute distance between point and ray.""" + return distance( deref(cp.thisptr), deref(cl.thisptr)) + + @classmethod + def are_near(cls, cy_Point cp, cy_Ray cl, double eps=EPSILON): + """Check if distance between point and ray is small.""" + return are_near( deref(cp.thisptr), deref(cl.thisptr), eps) + + @classmethod + def are_same(cls, cy_Ray cl, cy_Ray ck, double eps=EPSILON): + """Check if two ray are same.""" + return are_same( deref(cl.thisptr), deref(ck.thisptr), eps) + + @classmethod + def angle_between(cls, cy_Ray cl, cy_Ray ck, bint cw=True): + """Compute angle between two rays. + + Can specify direction using parameter cw. + """ + return angle_between( deref(cl.thisptr), deref(ck.thisptr), cw) + + @classmethod + def make_angle_bisector_ray(cls, cy_Ray cl, cy_Ray ck): + """Make ray bisecting smaller angle formed by two rays.""" + return wrap_Ray( make_angle_bisector_ray(deref(cl.thisptr), deref(ck.thisptr) )) + +cdef cy_Ray wrap_Ray(Ray p): + cdef Ray * retp = new Ray() + retp[0] = p + cdef cy_Ray r = cy_Ray.__new__(cy_Ray) + r.thisptr = retp + return r diff --git a/src/cython/_cy_rectangle.pxd b/src/cython/_cy_rectangle.pxd new file mode 100644 index 0000000..ac18169 --- /dev/null +++ b/src/cython/_cy_rectangle.pxd @@ -0,0 +1,442 @@ +from _common_decl cimport * +from cpython.ref cimport PyObject + +from _cy_primitives cimport Point, cy_Point, wrap_Point +from _cy_primitives cimport IntPoint, cy_IntPoint, wrap_IntPoint + +cdef extern from "2geom/affine.h" namespace "Geom": + cdef cppclass Affine: + pass + +cdef extern from "2geom/cython-bindings/wrapped-pyobject.h" namespace "Geom": + + cdef cppclass WrappedPyObject: + WrappedPyObject() + WrappedPyObject(object) + object getObj() + + cdef cppclass PyPoint: + PyPoint() + PyPoint(WrappedPyObject, WrappedPyObject) + WrappedPyObject operator[](int) + + +cdef extern from "2geom/generic-interval.h" namespace "Geom": + + cdef cppclass GenericInterval[C]: + + GenericInterval() + GenericInterval(C) + GenericInterval(C, C) + + C min() + C max() + C extent() + C middle() + + bint isSingular() + bint contains(C) + bint contains(GenericInterval[C] &) + bint intersects(GenericInterval[C] &) + + void setMin(C) + void setMax(C) + void expandTo(C) + void expandBy(C) + void unionWith(GenericInterval[C] &) + + GenericInterval[C] &operator+(C) + GenericInterval[C] &operator-(C) + GenericInterval[C] &operator-() + GenericInterval[C] &operator+(GenericInterval[C] &) + GenericInterval[C] &operator-(GenericInterval[C] &) + GenericInterval[C] &operator|(GenericInterval[C] &) + bint operator==(GenericInterval[C] &) + bint operator!=(GenericInterval[C] &) + + + cdef cppclass GenericOptInterval[C]: + + GenericOptInterval() + GenericOptInterval(C) + GenericOptInterval(C, C) + + GenericInterval get() + + C min() + C max() + C extent() + C middle() + + bint isEmpty() + bint isSingular() + bint contains(C) + bint contains(GenericOptInterval[C] &) + bint intersects(GenericOptInterval[C] &) + + void setMin(C) + void setMax(C) + void expandTo(C) + void expandBy(C) + void unionWith(GenericOptInterval[C] &) + void intersectWith(GenericOptInterval &) + + + GenericOptInterval[C] &operator+(C) + GenericOptInterval[C] &operator-(C) + GenericOptInterval[C] &operator-() + GenericOptInterval[C] &operator+(GenericOptInterval[C] &) + GenericOptInterval[C] &operator-(GenericOptInterval[C] &) + GenericOptInterval[C] &operator|(GenericOptInterval[C] &) + GenericOptInterval[C] &operator&(GenericOptInterval[C] &) + bint operator==(GenericOptInterval[C] &) + bint operator!=(GenericOptInterval[C] &) + + +cdef extern from "2geom/int-interval.h" namespace "Geom": + ctypedef GenericInterval[IntCoord] IntInterval + ctypedef GenericOptInterval[IntCoord] OptIntInterval + + +#redeclaring inherited methods, other option is to cast all +#pointers from Interval to GenericInterval[Coord] in extension class +cdef extern from "2geom/interval.h" namespace "Geom": + + cdef cppclass Interval: + + Interval() + Interval(Coord) + Interval(Coord, Coord) + + Coord min() + Coord max() + Coord extent() + Coord middle() + + bint isSingular() + bint contains(Coord) + bint contains(Interval &) + bint intersects(Interval &) + + void setMin(Coord) + void setMax(Coord) + void expandTo(Coord) + void expandBy(Coord) + void unionWith(Interval &) + + bint isFinite() + bint interiorContains(Coord) + bint interiorContains(Interval &) + bint interiorIntersects(Interval &) + + Interval & operator*(Coord) + Interval & operator/(Coord) + Interval & operator*(Interval &) + bint operator==(IntInterval &) + bint operator==(Interval &) + bint operator!=(Interval &) + bint operator!=(IntInterval &) + Interval &operator+(Coord) + Interval &operator-(Coord) + Interval &operator-() + Interval &operator+(Interval &) + Interval &operator-(Interval &) + Interval &operator|(Interval &) + + IntInterval roundOutwards() + OptIntInterval roundInwards() + + + cdef cppclass OptInterval: + + OptInterval(OptInterval &) + OptInterval() + OptInterval(Interval &) + OptInterval(Coord) + OptInterval(Coord, Coord) + OptInterval(GenericOptInterval[double] &) + OptInterval(IntInterval &) + OptInterval(OptIntInterval &) + + Interval get() + bint isEmpty() + void unionWith(OptInterval &) + void intersectWith(OptInterval &) + + OptInterval &operator|(OptInterval &) + OptInterval &operator&(OptInterval &) + +cdef class cy_Interval: + cdef Interval* thisptr + +cdef cy_Interval wrap_Interval(Interval p) + +cdef class cy_OptInterval: + cdef OptInterval* thisptr + +cdef cy_OptInterval wrap_OptInterval(OptInterval p) + + +cdef extern from "2geom/generic-rect.h" namespace "Geom": + cdef cppclass GenericRect[C]: + GenericRect() + GenericRect(GenericInterval[C] &, GenericInterval[C] &) + GenericRect(Point &, Point &) + GenericRect(PyPoint &, PyPoint &) + GenericRect(IntPoint &, IntPoint &) + GenericRect(C, C, C, C) + + GenericInterval[C] & operator[](Dim2) + + PyPoint min() + PyPoint max() + PyPoint corner(unsigned int) + + IntPoint i_min "min" () + IntPoint i_max "max" () + IntPoint i_corner "corner" (unsigned int) + + C top() + C bottom() + C left() + C right() + C width() + C height() + Coord aspectRatio() + + PyPoint dimensions() + PyPoint midpoint() + + IntPoint i_dimensions "dimensions" () + IntPoint i_midpoint "midpoint" () + + C area() + bint hasZeroArea() + C maxExtent() + C minExtent() + bint intersects(GenericRect[C] &) + bint contains(GenericRect[C] &) + + bint contains(PyPoint &) + bint contains(IntPoint &) + + void setLeft(C) + void setRight(C) + void setTop(C) + void setBottom(C) + + void setMin(PyPoint &) + void setMax(PyPoint &) + void expandTo(PyPoint &) + void setMin(IntPoint &) + void setMax(IntPoint &) + void expandTo(IntPoint &) + + void unionWith(GenericRect[C] &) + void expandBy(C) + void expandBy(C, C) + + void expandBy(PyPoint &) + void expandBy(IntPoint &) + + GenericRect[C] & operator+(PyPoint &) + GenericRect[C] & operator+(IntPoint &) + + GenericRect[C] & operator-(PyPoint &) + GenericRect[C] & operator-(IntPoint &) + + GenericRect[C] & operator|(GenericRect[C] &) + bint operator==(GenericRect[C] &) + bint operator!=(GenericRect[C] &) + + + cdef cppclass GenericOptRect[C]: + GenericOptRect() + GenericOptRect(GenericRect[C] &) + GenericOptRect(C, C, C, C) + GenericOptRect(Point &, Point &) + GenericOptRect(GenericOptInterval[C] &, GenericOptInterval[C] &) + + GenericRect[C] get() + + bint isEmpty() + bint intersects(GenericRect[C] &) + bint contains(GenericRect[C] &) + bint intersects(GenericOptRect[C] &) + bint contains(GenericOptRect[C] &) + + bint contains(Point &) + bint contains(IntPoint &) + + void unionWith(GenericRect[C] &) + void unionWith(GenericOptRect[C] &) + void intersectWith(GenericRect[C] &) + void intersectWith(GenericOptRect[C] &) + + void expandTo(Point &) + + GenericOptRect[C] &operator|(GenericOptRect[C] &) + GenericOptRect[C] &operator&(GenericRect[C] &) + GenericOptRect[C] &operator&(GenericOptRect[C] &) + + bint operator==(GenericOptRect[C] &) + bint operator==(GenericRect[C] &) + + bint operator!=(GenericOptRect[C] &) + bint operator!=(GenericRect[C] &) + +cdef extern from "2geom/generic-rect.h" namespace "Geom::GenericRect<Geom::WrappedPyObject>": + GenericRect[WrappedPyObject] from_xywh(WrappedPyObject, WrappedPyObject, WrappedPyObject, WrappedPyObject) + GenericRect[WrappedPyObject] from_xywh(PyPoint &, PyPoint &) + +cdef extern from "2geom/int-rect.h" namespace "Geom": + ctypedef GenericRect[IntCoord] IntRect + +cdef extern from "2geom/rect.h" namespace "Geom": + cdef cppclass Rect: + Rect() + Rect(Interval &, Interval &) + Rect(Point &, Point &) + Rect(Coord, Coord, Coord, Coord) + + Interval & operator[](Dim2) + Point min() + Point max() + Point corner(unsigned int) + Coord top() + Coord bottom() + Coord left() + Coord right() + Coord width() + Coord height() + Coord aspectRatio() + Point dimensions() + Point midpoint() + Coord area() + bint hasZeroArea() + Coord maxExtent() + Coord minExtent() + + bint intersects(Rect &) + bint contains(Rect &) + bint contains(Point &) + + void setLeft(Coord) + void setRight(Coord) + void setTop(Coord) + void setBottom(Coord) + void setMin(Point &) + void setMax(Point &) + void expandTo(Point &) + void unionWith(Rect &) + void expandBy(Coord) + void expandBy(Coord, Coord) + void expandBy(Point &) + + Rect & operator+(Point &) + Rect & operator-(Point &) + Rect & operator|(Rect &) + bint operator==(Rect &) + bint operator!=(Rect &) + bint operator==(IntRect &) + bint operator!=(IntRect &) + + bint hasZeroArea(Coord) + bint interiorIntersects(Rect &) + bint interiorContains(Point &) + bint interiorContains(Rect &) + bint interiorContains(OptRect &) + + IntRect roundOutwards() + OptIntRect roundInwards() + Rect &operator*(Affine &) + + Coord distanceSq(Point &, Rect &) + Coord distance(Point &, Rect &) + + + cdef cppclass OptRect: + OptRect() + OptRect(Rect &) + OptRect(Coord, Coord, Coord, Coord) + OptRect(Point &, Point &) + OptRect(OptInterval &, OptInterval &) + + Rect get() + + bint isEmpty() + bint intersects(Rect &) + bint contains(Rect &) + bint intersects(OptRect &) + bint contains(OptRect &) + bint contains(Point &) + + void unionWith(Rect &) + void unionWith(OptRect &) + void intersectWith(Rect &) + void intersectWith(OptRect &) + void expandTo(Point &) + + OptRect &operator|(OptRect &) + OptRect &operator&(Rect &) + OptRect &operator&(OptRect &) + + bint operator==(OptRect &) + bint operator==(Rect &) + + bint operator!=(OptRect &) + bint operator!=(Rect &) + +cdef extern from "2geom/rect.h" namespace "Geom::Rect": + Rect from_xywh(Coord, Coord, Coord, Coord) + Rect from_xywh(Point &, Point &) + Rect infinite() + +cdef class cy_Rect: + cdef Rect* thisptr + +cdef cy_Rect wrap_Rect(Rect p) + +cdef class cy_OptRect: + cdef OptRect* thisptr + +cdef cy_OptRect wrap_OptRect(OptRect p) + + +cdef extern from "2geom/int-rect.h" namespace "Geom": + #redeclaring because cython complains about ambiguous overloading otherwise + cdef cppclass OptIntRect: + OptIntRect() + OptIntRect(IntRect &) + OptIntRect(IntCoord, IntCoord, IntCoord, IntCoord) + OptIntRect(IntPoint &, IntPoint &) + OptIntRect(OptIntInterval &, OptIntInterval &) + + IntRect get() + + bint isEmpty() + bint intersects(IntRect &) + bint contains(IntRect &) + bint intersects(OptIntRect &) + bint contains(OptIntRect &) + + bint contains(Point &) + bint contains(IntPoint &) + + void unionWith(IntRect &) + void unionWith(OptIntRect &) + void intersectWith(IntRect &) + void intersectWith(OptIntRect &) + + void expandTo(IntPoint &) + + OptIntRect &operator|(OptIntRect &) + OptIntRect &operator&(IntRect &) + OptIntRect &operator&(OptIntRect &) + + bint operator==(OptIntRect &) + bint operator==(IntRect &) + + bint operator!=(OptIntRect &) + bint operator!=(IntRect &) + diff --git a/src/cython/_cy_rectangle.pyx b/src/cython/_cy_rectangle.pyx new file mode 100644 index 0000000..1d1b687 --- /dev/null +++ b/src/cython/_cy_rectangle.pyx @@ -0,0 +1,2202 @@ +from numbers import Number + +from _common_decl cimport * +from cython.operator cimport dereference as deref + +from _cy_affine cimport cy_Affine, get_Affine, is_transform + + +cdef class cy_GenericInterval: + """ + Represents all numbers between min and max. + + Corresponds to GenericInterval in 2geom. min and max can be arbitrary + python object, they just have to implement arithmetic operations and + comparison - fractions.Fraction works. This is a bit experimental, + it leak memory right now. + """ + + cdef GenericInterval[WrappedPyObject]* thisptr + + def __cinit__(self, u = 0, v = None): + """Create GenericInterval from either one or two values.""" + if v is None: + self.thisptr = new GenericInterval[WrappedPyObject]( WrappedPyObject(u) ) + else: + self.thisptr = new GenericInterval[WrappedPyObject]( WrappedPyObject(u), WrappedPyObject(v) ) + + def __str__(self): + """str(self)""" + return "[{}, {}]".format(self.min(), self.max()) + + def __repr__(self): + """repr(self)""" + if self.is_singular(): + return "GenericInterval({})".format( str(self.min()) ) + return "GenericInterval({}, {})".format( str(self.min()) , str(self.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(self, i): + """Create GenericInterval with same minimum and maximum as argument.""" + return cy_GenericInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create GenericInterval containing all values in list.""" + if len(lst) == 0: + return cy_GenericInterval() + ret = cy_GenericInterval(lst[0]) + for i in lst[1:]: + ret.expand_to(i) + return ret + + def min(self): + """Return minimal value of interval.""" + return self.thisptr.min().getObj() + + def max(self): + """Return maximal value of interval.""" + return self.thisptr.max().getObj() + def extent(self): + """Return difference between maximal and minimal value.""" + return self.thisptr.extent().getObj() + + def middle(self): + """Return midpoint of interval.""" + return self.thisptr.middle().getObj() + + def is_singular(self): + """Test for one-valued interval.""" + return self.thisptr.isSingular() + + def set_min(self, val): + """Set minimal value.""" + self.thisptr.setMin( WrappedPyObject(val) ) + + def set_max(self, val): + """Set maximal value.""" + self.thisptr.setMax( WrappedPyObject(val) ) + + def expand_to(self, val): + """Create smallest superset of self containing value.""" + self.thisptr.expandTo( WrappedPyObject(val) ) + + def expand_by(self, val): + """Push both boundaries by value.""" + self.thisptr.expandBy( WrappedPyObject(val) ) + def union_with(self, cy_GenericInterval interval): + """self = self | other""" + self.thisptr.unionWith( deref(interval.thisptr) ) + + def contains(self, other): + """Check if interval contains value.""" + return self.thisptr.contains( WrappedPyObject(other) ) + + def contains_interval(self, cy_GenericInterval other): + """Check if interval contains every point of interval.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def intersects(self, cy_GenericInterval other): + """Check for intersecting intervals.""" + return self.thisptr.intersects(deref( other.thisptr )) + + def __neg__(self): + """Return interval with negated boundaries.""" + return wrap_GenericInterval(-deref(self.thisptr)) + + def _add_pyobj(self, X): + return wrap_GenericInterval(deref(self.thisptr) + WrappedPyObject(X) ) + def _sub_pyobj(self, X): + return wrap_GenericInterval(deref(self.thisptr) - WrappedPyObject(X) ) + + def _add_interval(self, cy_GenericInterval I): + return wrap_GenericInterval(deref(self.thisptr)+deref(I.thisptr)) + def _sub_interval(self, cy_GenericInterval I): + return wrap_GenericInterval(deref(self.thisptr)-deref(I.thisptr)) + + def __add__(cy_GenericInterval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + if isinstance(other, cy_GenericInterval): + return self._add_interval(other) + else: + return self._add_pyobj(other) + + def __sub__(cy_GenericInterval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + if isinstance(other, cy_GenericInterval): + return self._sub_interval(other) + else: + return self._sub_pyobj(other) + + def __or__(cy_GenericInterval self, cy_GenericInterval I): + """Return a union of two intervals""" + return wrap_GenericInterval(deref(self.thisptr)|deref(I.thisptr)) + + def _eq(self, cy_GenericInterval other): + return deref(self.thisptr)==deref(other.thisptr) + + def _neq(self, cy_GenericInterval other): + return deref(self.thisptr)!=deref(other.thisptr) + + def __richcmp__(cy_GenericInterval self, other, op): + """Intervals are not ordered.""" + if op == 2: + return self._eq(other) + elif op == 3: + return self._neq(other) + +cdef cy_GenericInterval wrap_GenericInterval(GenericInterval[WrappedPyObject] p): + cdef GenericInterval[WrappedPyObject] * retp = new GenericInterval[WrappedPyObject](WrappedPyObject(0)) + retp[0] = p + cdef cy_GenericInterval r = cy_GenericInterval.__new__( + cy_GenericInterval, 0, 0) + r.thisptr = retp + return r + + +cdef class cy_GenericOptInterval: + + """Class representing optionally empty interval. + + Empty interval has False bool value, and using methods that require + non-empty interval will result in ValueError. This is supposed to be + used this way: + + >>> C = A & B + >>> if C: + >>> print C.min() + + This class represents GenericOptInterval with python object boundaries. + It tries to model behaviour of std::optional. + """ + + cdef GenericOptInterval[WrappedPyObject]* thisptr + + def __cinit__(self, u = None, v = None): + """Create interval from boundaries. + + Using no arguments, you will end up with empty interval.""" + if u is None: + self.thisptr = new GenericOptInterval[WrappedPyObject]() + elif v is None: + self.thisptr = new GenericOptInterval[WrappedPyObject](WrappedPyObject(u)) + else: + self.thisptr = new GenericOptInterval[WrappedPyObject](WrappedPyObject(u), WrappedPyObject(v) ) + + def __bool__(self): + """Logical value of interval, False only for empty interval.""" + return not self.thisptr.isEmpty() + + def __str__(self): + """str(self)""" + if not self: + return "[]" + return "[{}, {}]".format(self.Interval.min(), self.Interval.max()) + + def __repr__(self): + """repr(self)""" + if not self: + return "GenericOptInterval()" + if self.Interval.isSingular(): + return "GenericOptInterval({})".format( str(self.Interval.min()) ) + return "GenericOptInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(self, i): + """Create interval from existing interval.""" + if hasattr(i, "isEmpty"): + if i.isEmpty(): + return cy_GenericOptInterval() + else: + return cy_GenericOptInterval.from_Interval(i.Interval) + return cy_GenericOptInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create interval containing all values in list. + + Empty list will result in empty interval.""" + if len(lst) == 0: + return cy_GenericOptInterval() + ret = cy_GenericOptInterval(lst[0]) + for i in lst[1:]: + ret.Interval.expandTo(i) + return ret + + property Interval: + """Get underlying GenericInterval.""" + def __get__(self): + if self.is_empty(): + raise ValueError("Interval is empty.") + else: + return wrap_GenericInterval(self.thisptr.get()) + + def is_empty(self): + """Check whether interval is empty set.""" + return self.thisptr.isEmpty() + + def union_with(self, cy_GenericOptInterval o): + """self = self | other""" + self.thisptr.unionWith( deref(o.thisptr) ) + + def intersect_with(cy_GenericOptInterval self, cy_GenericOptInterval o): + """self = self & other""" + self.thisptr.intersectWith( deref(o.thisptr) ) + + def __or__(cy_GenericOptInterval self, cy_GenericOptInterval o): + """Return a union of two intervals.""" + return wrap_GenericOptInterval(deref(self.thisptr) | deref(o.thisptr)) + + def __and__(cy_GenericOptInterval self, cy_GenericOptInterval o): + """Return an intersection of two intervals.""" + return wrap_GenericOptInterval(deref(self.thisptr) & deref(o.thisptr)) + + def __richcmp__(cy_GenericOptInterval self, cy_GenericOptInterval o, int op): + """Intervals are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(o.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(o.thisptr) + return NotImplemented + + + def _get_Interval_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("GenericOptInterval is empty.") + else: + return self.Interval.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Interval_methods = set(['contains', 'contains_interval', + 'expand_by', 'expand_to', 'extent', 'from_Interval', 'from_list', + 'intersects', 'is_singular', 'max', 'middle', 'min', 'set_max', + 'set_min', 'union_with']) + + if name in Interval_methods: + return self._get_Interval_method(name) + else: + raise AttributeError("GenericOptInterval instance has no attribute \"{}\"".format(name)) + + def _wrap_Interval_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("GenericOptInterval is empty.") + else: + return self.Interval.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __neg__(self): + """Return interval with negated boundaries.""" + return self._wrap_Interval_method("__sub__") + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + return self._wrap_Interval_method("__add__", other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + return self._wrap_Interval_method("__sub__", other) + +cdef cy_GenericOptInterval wrap_GenericOptInterval(GenericOptInterval[WrappedPyObject] p): + cdef GenericOptInterval[WrappedPyObject] * retp = new GenericOptInterval[WrappedPyObject]() + retp[0] = p + cdef cy_GenericOptInterval r = cy_GenericOptInterval.__new__(cy_GenericOptInterval) + r.thisptr = retp + return r + + +cdef class cy_Interval: + + """Class representing interval on real line. + + Corresponds to Interval class in 2geom. + """ + + def __cinit__(self, u = None, v = None): + """Create interval from it's boundaries. + + One argument will create interval consisting that value, no + arguments create Interval(0). + """ + if u is None: + self.thisptr = new Interval() + elif v is None: + self.thisptr = new Interval(<Coord>float(u)) + else: + self.thisptr = new Interval(<Coord>float(u), <Coord>float(v)) + + def __str__(self): + """str(self)""" + return "[{}, {}]".format(self.min(), self.max()) + + def __repr__(self): + """repr(self)""" + if self.is_singular(): + return "Interval({})".format( str(self.min()) ) + return "Interval({}, {})".format( str(self.min()) , str(self.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(c, i): + """Create Interval with same boundaries as argument.""" + return cy_Interval( i.min(), i.max() ) + + @classmethod + def from_list(cls, lst): + """Create interval containing all values in a list.""" + if len(lst) == 0: + return cy_Interval() + ret = cy_Interval(lst[0]) + for i in lst[1:]: + ret.expand_to(i) + return ret + + def min(self): + """Return minimal boundary.""" + return self.thisptr.min() + + def max(self): + """Return maximal boundary.""" + return self.thisptr.max() + + def extent(self): + """Return length of interval.""" + return self.thisptr.extent() + + def middle(self): + """Return middle value.""" + return self.thisptr.middle() + + def set_min(self, Coord val): + """Set minimal value.""" + self.thisptr.setMin(val) + + def set_max(self, Coord val): + """Set maximal value.""" + self.thisptr.setMax(val) + + def expand_to(self, Coord val): + """Set self to smallest superset of set containing value.""" + self.thisptr.expandTo(val) + + def expand_by(self, Coord amount): + """Move both boundaries by amount.""" + self.thisptr.expandBy(amount) + + def union_with(self, cy_Interval a): + """self = self | other""" + self.thisptr.unionWith(deref( a.thisptr )) + +# Not exposing this - deprecated +# def __getitem__(self, unsigned int i): +# return deref(self.thisptr)[i] + + def is_singular(self): + """Test if interval contains only one value.""" + return self.thisptr.isSingular() + + def isFinite(self): + """Test for finiteness of interval's extent.""" + return self.thisptr.isFinite() + + def contains(cy_Interval self, other): + """Test if interval contains number.""" + return self.thisptr.contains(float(other)) + + def contains_interval(cy_Interval self, cy_Interval other): + """Test if interval contains another interval.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def intersects(self, cy_Interval val): + """Test for intersection of intervals.""" + return self.thisptr.intersects(deref( val.thisptr )) + + def interior_contains(cy_Interval self, other): + """Test if interior of iterval contains number.""" + return self.thisptr.interiorContains(float(other)) + + def interior_contains_interval(cy_Interval self, cy_Interval other): + """Test if interior of interval contains another interval.""" + return self.thisptr.interiorContains( <Interval &> deref(other.thisptr) ) + + + def interior_intersects(self, cy_Interval val): + """Test for intersection of interiors of two points.""" + return self.thisptr.interiorIntersects(deref( val.thisptr )) + + def _cmp_Interval(cy_Interval self, cy_Interval other, op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + def _cmp_IntInterval(cy_Interval self, cy_IntInterval other, op): + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + def __richcmp__(cy_Interval self, other, op): + """Intervals are not ordered.""" + if isinstance(other, cy_Interval): + return self._cmp_Interval(other, op) + elif isinstance(other, cy_IntInterval): + return self._cmp_IntInterval(other, op) + + def __neg__(self): + """Return interval with negated boundaries.""" + return wrap_Interval(-deref(self.thisptr)) + + def _add_number(self, Coord X): + return wrap_Interval(deref(self.thisptr)+X) + def _sub_number(self, Coord X): + return wrap_Interval(deref(self.thisptr)-X) + def _mul_number(self, Coord X): + return wrap_Interval(deref(self.thisptr)*X) + + def _add_interval(self, cy_Interval I): + return wrap_Interval(deref(self.thisptr)+deref(I.thisptr)) + def _sub_interval(self, cy_Interval I): + return wrap_Interval(deref(self.thisptr)-deref(I.thisptr)) + def _mul_interval(self, cy_Interval I): + return wrap_Interval(deref(self.thisptr)*deref(I.thisptr)) + + def __mul__(cy_Interval self, other): + """Multiply interval by interval or number. + + Multiplying by number simply multiplies boundaries, + multiplying intervals creates all values that can be written as + product i*j of i in I and j in J. + """ + if isinstance(other, Number): + return self._mul_number(float(other)) + else: + return self._mul_interval(other) + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + if isinstance(other, Number): + return self._add_number(float(other)) + else: + return self._add_interval(other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + if isinstance(other, Number): + return self._sub_number(float(other)) + else: + return self._sub_interval(other) + + def __div__(cy_Interval self, Coord s): + """Divide boundaries by number.""" + return wrap_Interval(deref(self.thisptr)/s) + + def __or__(cy_Interval self, cy_Interval I): + """Return union of two intervals.""" + return wrap_Interval(deref(self.thisptr)|deref(I.thisptr)) + + def round_outwards(self): + """Create the smallest IntIterval that is superset.""" + return wrap_IntInterval(self.thisptr.roundOutwards()) + + def round_inwards(self): + """Create the largest IntInterval that is subset.""" + return wrap_OptIntInterval(self.thisptr.roundInwards()) + +cdef cy_Interval wrap_Interval(Interval p): + cdef Interval * retp = new Interval() + retp[0] = p + cdef cy_Interval r = cy_Interval.__new__(cy_Interval) + r.thisptr = retp + return r + + +cdef class cy_OptInterval: + + """Class representing optionally empty interval on real line. + + Empty interval has False bool value, and using methods that require + non-empty interval will result in ValueError. This is supposed to be + used this way: + + >>> C = A & B + >>> if C: + >>> print C.min() + + This class represents OptInterval. It tries to model behaviour of + std::optional. + """ + + def __cinit__(self, u = None, v = None): + """Create optionally empty interval form it's endpoints. + + No arguments will result in empty interval. + """ + if u is None: + self.thisptr = new OptInterval() + elif v is None: + self.thisptr = new OptInterval(<Coord>float(u)) + else: + self.thisptr = new OptInterval(<Coord>float(u), <Coord>float(v)) + + def __bool__(self): + """Only empty interval is False.""" + return not self.thisptr.isEmpty() + + def __str__(self): + """str(self)""" + if not self: + return "[]" + return "[{}, {}]".format(self.Interval.min(), self.Interval.max()) + + def __repr__(self): + """repr(self)""" + if not self: + return "OptInterval()" + if self.Interval.isSingular(): + return "OptInterval({})".format( str(self.Interval.min()) ) + return "OptInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(cls, i): + """Create interval from other (possibly empty) interval.""" + if hasattr(i, "isEmpty"): + if i.isEmpty(): + return cy_OptInterval() + else: + return cy_OptInterval.from_Interval(i.Interval) + return cy_OptInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create interval containing all values in list. + + Empty list will result in empty interval.""" + if len(lst) == 0: + return cy_OptInterval() + ret = cy_OptInterval(lst[0]) + for i in lst[1:]: + ret.Interval.expandTo(i) + return ret + + property Interval: + """Get underlying Interval.""" + def __get__(self): + if self.is_empty(): + raise ValueError("Interval is empty.") + else: + return wrap_Interval(self.thisptr.get()) + + def is_empty(self): + """Test for empty interval.""" + return self.thisptr.isEmpty() + + def union_with(self, cy_OptInterval o): + """self = self | other""" + self.thisptr.unionWith( deref(o.thisptr) ) + + def intersect_with(cy_OptInterval self, cy_OptInterval o): + """self = self & other""" + self.thisptr.intersectWith( deref(o.thisptr) ) + + def __or__(cy_OptInterval self, cy_OptInterval o): + """Return union of intervals.""" + return wrap_OptInterval(deref(self.thisptr) | deref(o.thisptr)) + + def __and__(cy_OptInterval self, cy_OptInterval o): + """Return intersection of intervals.""" + return wrap_OptInterval(deref(self.thisptr) & deref(o.thisptr)) + + def _get_Interval_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptInterval is empty.") + else: + return self.Interval.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Interval_methods = set(['contains', 'contains_interval', 'expand_by', + 'expand_to', 'extent', 'from_Interval', 'from_list', + 'interior_contains', 'interior_contains_interval', + 'interior_intersects', 'intersects', 'isFinite', 'is_singular', + 'max', 'middle', 'min', 'round_inwards', 'round_outwards', + 'set_max', 'set_min', 'union_with']) + + if name in Interval_methods: + return self._get_Interval_method(name) + else: + raise AttributeError("OptInterval instance has no attribute \"{}\"".format(name)) + + def _wrap_Interval_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptInterval is empty.") + else: + return self.Interval.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __neg__(self): + """Return interval with negated boundaries.""" + return self._wrap_Interval_method("__sub__") + + def __mul__(cy_Interval self, other): + """Multiply interval by interval or number. + + Multiplying by number simply multiplies boundaries, + multiplying intervals creates all values that can be written as + product i*j of i in I and j in J. + """ + return self._wrap_Interval_method("__mul__", other) + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + return self._wrap_Interval_method("__add__", other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + return self._wrap_Interval_method("__sub__", other) + + def __div__(cy_Interval self, other): + """Divide boundaries by number.""" + return self._wrap_Interval_method("__div__", other) + +cdef cy_OptInterval wrap_OptInterval(OptInterval p): + cdef OptInterval * retp = new OptInterval() + retp[0] = p + cdef cy_OptInterval r = cy_OptInterval.__new__(cy_OptInterval) + r.thisptr = retp + return r + + +cdef class cy_IntInterval: + + """Class representing interval of integers. + + Corresponds to IntInterval class in 2geom. + """ + + cdef IntInterval* thisptr + + def __cinit__(self, u = None, v = None): + """Create interval from it's boundaries. + + One argument will create interval consisting that value, no + arguments create IntInterval(0). + """ + if u is None: + self.thisptr = new IntInterval() + elif v is None: + self.thisptr = new IntInterval(<IntCoord>int(u)) + else: + self.thisptr = new IntInterval(<IntCoord>int(u), <IntCoord>int(v)) + + def __str__(self): + """str(self)""" + return "[{}, {}]".format(self.min(), self.max()) + + def __repr__(self): + """repr(self)""" + if self.is_singular(): + return "IntInterval({})".format( str(self.min()) ) + return "IntInterval({}, {})".format( str(self.min()) , str(self.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(cls, i): + return cy_IntInterval( int(i.min()), int(i.max()) ) + + @classmethod + def from_list(cls, lst): + if len(lst) == 0: + return cy_IntInterval() + ret = cy_IntInterval(lst[0]) + for i in lst[1:]: + ret.expand_to(i) + return ret + + def min(self): + """Return minimal boundary.""" + return self.thisptr.min() + + def max(self): + """Return maximal boundary.""" + return self.thisptr.max() + + def extent(self): + """Return length of interval.""" + return self.thisptr.extent() + + def middle(self): + """Return middle value.""" + return self.thisptr.middle() + + def set_min(self, IntCoord val): + """Set minimal value.""" + self.thisptr.setMin(val) + + def set_max(self, IntCoord val): + """Set maximal value.""" + self.thisptr.setMax(val) + + def expand_to(self, IntCoord val): + """Set self to smallest superset of set containing value.""" + self.thisptr.expandTo(val) + + def expand_by(self, IntCoord amount): + """Move both boundaries by amount.""" + self.thisptr.expandBy(amount) + + def union_with(self, cy_IntInterval a): + """self = self | other""" + self.thisptr.unionWith(deref( a.thisptr )) + +# Not exposing this - deprecated +# def __getitem__(self, unsigned int i): +# return deref(self.thisptr)[i] + + def is_singular(self): + """Test if interval contains only one value.""" + return self.thisptr.isSingular() + + def contains(cy_IntInterval self, other): + """Test if interval contains number.""" + return self.thisptr.contains(<IntCoord> int(other)) + + def contains_interval(cy_IntInterval self, cy_IntInterval other): + """Test if interval contains another interval.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def intersects(self, cy_IntInterval val): + """Test for intersection with other interval.""" + return self.thisptr.intersects(deref( val.thisptr )) + + def __richcmp__(cy_IntInterval self, cy_IntInterval other, op): + """Intervals are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(other.thisptr) + elif op == 3: + return deref(self.thisptr) != deref(other.thisptr) + + def __neg__(self): + """Negate interval's endpoints.""" + return wrap_IntInterval(-deref(self.thisptr)) + + def _add_number(self, IntCoord X): + return wrap_IntInterval(deref(self.thisptr)+X) + def _sub_number(self, IntCoord X): + return wrap_IntInterval(deref(self.thisptr)-X) + + def _add_interval(self, cy_IntInterval I): + return wrap_IntInterval(deref(self.thisptr)+deref(I.thisptr)) + def _sub_interval(self, cy_IntInterval I): + return wrap_IntInterval(deref(self.thisptr)-deref(I.thisptr)) + + def __add__(cy_IntInterval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + if isinstance(other, Number): + return self._add_number(int(other)) + else: + return self._add_interval(other) + + def __sub__(cy_IntInterval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + if isinstance(other, Number): + return self._sub_number(int(other)) + else: + return self._sub_interval(other) + + def __or__(cy_IntInterval self, cy_IntInterval I): + """Return union of two intervals.""" + return wrap_IntInterval(deref(self.thisptr)|deref(I.thisptr)) + +cdef cy_IntInterval wrap_IntInterval(IntInterval p): + cdef IntInterval * retp = new IntInterval() + retp[0] = p + cdef cy_IntInterval r = cy_IntInterval.__new__(cy_IntInterval) + r.thisptr = retp + return r + +cdef class cy_OptIntInterval: + + """Class representing optionally empty interval of integers. + + Empty interval has False bool value, and using methods that require + non-empty interval will result in ValueError. This is supposed to be + used this way: + + >>> C = A & B + >>> if C: + >>> print C.min() + + This class represents OptIntInterval. It tries to model behaviour of + std::optional. + """ + + cdef OptIntInterval* thisptr + + def __cinit__(self, u = None, v = None): + """Create optionally empty interval form it's endpoints. + + No arguments will result in empty interval. + """ + if u is None: + self.thisptr = new OptIntInterval() + elif v is None: + self.thisptr = new OptIntInterval(<IntCoord>int(u)) + else: + self.thisptr = new OptIntInterval(<IntCoord>int(u), <IntCoord>int(v)) + + def __bool__(self): + """Only empty interval is False.""" + return not self.thisptr.isEmpty() + + def __str__(self): + """str(self)""" + if not self: + return "[]" + return "[{}, {}]".format(self.Interval.min(), self.Interval.max()) + + def __repr__(self): + """repr(self)""" + if not self: + return "OptIntInterval()" + if self.Interval.isSingular(): + return "OptIntInterval({})".format( str(self.Interval.min()) ) + return "OptIntInterval({}, {})".format( str(self.Interval.min()) , str(self.Interval.max()) ) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_Interval(self, i): + """Create interval from other (possibly empty) interval.""" + if hasattr(i, "isEmpty"): + if i.isEmpty(): + return cy_OptIntInterval() + else: + return cy_OptIntInterval.from_Interval(i.Interval) + return cy_OptIntInterval( i.min(), i.max() ) + + @classmethod + def from_list(self, lst): + """Create interval containing all values in list. + + Empty list will result in empty interval.""" + if len(lst) == 0: + return cy_OptIntInterval() + ret = cy_OptIntInterval(lst[0]) + for i in lst[1:]: + ret.Interval.expandTo(i) + return ret + + property Interval: + """Get underlying interval.""" + def __get__(self): + return wrap_IntInterval(self.thisptr.get()) + + def is_empty(self): + """Test for empty interval.""" + return self.thisptr.isEmpty() + + def union_with(self, cy_OptIntInterval o): + """self = self | other""" + self.thisptr.unionWith( deref(o.thisptr) ) + + def intersect_with(cy_OptIntInterval self, cy_OptIntInterval o): + """self = self & other""" + self.thisptr.intersectWith( deref(o.thisptr) ) + + def __or__(cy_OptIntInterval self, cy_OptIntInterval o): + """Return a union of two intervals.""" + return wrap_OptIntInterval(deref(self.thisptr) | deref(o.thisptr)) + + def __and__(cy_OptIntInterval self, cy_OptIntInterval o): + """Return an intersection of two intervals.""" + return wrap_OptIntInterval(deref(self.thisptr) & deref(o.thisptr)) + + #TODO decide how to implement various combinations of comparisons! + + def _get_Interval_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptInterval is empty.") + else: + return self.Interval.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Interval_methods = set(['contains', 'contains_interval', + 'expand_by', 'expand_to', 'extent', 'from_Interval', 'from_list', + 'intersects', 'is_singular', 'max', 'middle', 'min', 'set_max', + 'set_min', 'union_with']) + + if name in Interval_methods: + return self._get_Interval_method(name) + else: + raise AttributeError("OptIntInterval instance has no attribute \"{}\"".format(name)) + + def _wrap_Interval_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptIntInterval is empty.") + else: + return self.Interval.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __neg__(self): + """Negate interval's endpoints.""" + return self._wrap_Interval_method("__sub__") + + + def __add__(cy_Interval self, other): + """Add interval or value to self. + + Interval I+J consists of all values i+j such that i is in I and + j is in J + + Interval I+x consists of all values i+x such that i is in I. + """ + return self._wrap_Interval_method("__add__", other) + + def __sub__(cy_Interval self, other): + """Substract interval or value. + + Interval I-J consists of all values i-j such that i is in I and + j is in J + + Interval I-x consists of all values i-x such that i is in I. + """ + return self._wrap_Interval_method("__sub__", other) + +cdef cy_OptIntInterval wrap_OptIntInterval(OptIntInterval p): + cdef OptIntInterval * retp = new OptIntInterval() + retp[0] = p + cdef cy_OptIntInterval r = cy_OptIntInterval.__new__(cy_OptIntInterval) + r.thisptr = retp + return r + + +cdef class cy_GenericRect: + + """Class representing axis aligned rectangle, with arbitrary corners. + + Plane in which the rectangle lies can have any object as a coordinates, + as long as they implement arithmetic operations and comparison. + + This is a bit experimental, corresponds to GenericRect[C] templated + with (wrapped) python object. + """ + + cdef GenericRect[WrappedPyObject]* thisptr + + def __cinit__(self, x0=0, y0=0, x1=0, y1=0): + """Create rectangle from it's top-left and bottom-right corners.""" + self.thisptr = new GenericRect[WrappedPyObject](WrappedPyObject(x0), + WrappedPyObject(y0), + WrappedPyObject(x1), + WrappedPyObject(y1)) + + def __str__(self): + """str(self)""" + return "Rectangle with dimensions {}, topleft point {}".format( + str(self.dimensions()), + str(self.min())) + + def __repr__(self): + """repr(self)""" + return "Rect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom()) ) + + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_intervals(self, I, J): + """Create rectangle from two intervals. + + First interval corresponds to side parallel with x-axis, + second one with side parallel with y-axis.""" + return cy_GenericRect(I.min(), I.max(), J.min(), J.max()) + + @classmethod + def from_list(cls, lst): + """Create rectangle containing all points in list. + + These points are represented simply by 2-tuples. + """ + ret = cy_GenericRect() + for a in lst: + ret.expand_to(a) + return ret + + @classmethod + def from_xywh(cls, x, y, w, h): + """Create rectangle from topleft point and dimensions.""" + return wrap_GenericRect( from_xywh(WrappedPyObject(x), + WrappedPyObject(y), + WrappedPyObject(w), + WrappedPyObject(h))) + + def __getitem__(self, Dim2 d): + """self[i]""" + return wrap_GenericInterval( deref(self.thisptr)[d] ) + + def min(self): + """Get top-left point.""" + return wrap_PyPoint( self.thisptr.min() ) + + def max(self): + """Get bottom-right point.""" + return wrap_PyPoint( self.thisptr.max() ) + + def corner(self, unsigned int i): + """Get corners (modulo) indexed from 0 to 3.""" + return wrap_PyPoint( self.thisptr.corner(i) ) + + def top(self): + """Get top coordinate.""" + return self.thisptr.top().getObj() + + def bottom(self): + """Get bottom coordinate.""" + return self.thisptr.bottom().getObj() + + def left(self): + """Get left coordinate.""" + return self.thisptr.left().getObj() + + def right(self): + """Get right coordinate.""" + return self.thisptr.right().getObj() + + def width(self): + """Get width.""" + return self.thisptr.width().getObj() + + def height(self): + """Get height.""" + return self.thisptr.height().getObj() + + #For some reason, Cpp aspectRatio returns Coord. + def aspectRatio(self): + """Get ratio between width and height.""" + return float(self.width())/float(self.height()) + + def dimensions(self): + """Get dimensions as tuple.""" + return wrap_PyPoint( self.thisptr.dimensions() ) + + def midpoint(self): + """Get midpoint as tuple.""" + return wrap_PyPoint( self.thisptr.midpoint() ) + + def area(self): + """Get area.""" + return self.thisptr.area().getObj() + + def has_zero_area(self): + """Test for area being zero.""" + return self.thisptr.hasZeroArea() + + def max_extent(self): + """Get bigger value from width, height.""" + return self.thisptr.maxExtent().getObj() + + def min_extent(self): + """Get smaller value from width, height.""" + return self.thisptr.minExtent().getObj() + + def intersects(self, cy_GenericRect r): + """Check if rectangle intersects another rectangle.""" + return self.thisptr.intersects(deref( r.thisptr )) + + def contains(self, r): + """Check if rectangle contains point represented as tuple.""" + if not isinstance(r, tuple): + raise TypeError("Tuple required to create point.") + return self.thisptr.contains( make_PyPoint(r) ) + + def contains_rect(self, cy_GenericRect r): + """Check if rectangle contains another rect.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def set_left(self, val): + """Set left coordinate.""" + self.thisptr.setLeft( WrappedPyObject(val) ) + + def set_right(self, val): + """Set right coordinate.""" + self.thisptr.setRight( WrappedPyObject(val) ) + + def set_top(self, val): + """Set top coordinate.""" + self.thisptr.setTop( WrappedPyObject(val) ) + + def set_bottom(self, val): + """Set bottom coordinate.""" + self.thisptr.setBottom( WrappedPyObject(val) ) + + def set_min(self, p): + """Set top-left point.""" + self.thisptr.setMin(make_PyPoint(p)) + + def set_max(self, p): + """Set bottom-right point.""" + self.thisptr.setMax(make_PyPoint(p)) + + def expand_to(self, p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo(make_PyPoint(p)) + + def union_with(self, cy_GenericRect b): + """self = self | other.""" + self.thisptr.unionWith(deref( b.thisptr )) + + def expand_by(self, x, y = None): + """Expand both intervals. + + Either expand them both by one value, or each by different value. + """ + if y is None: + self.thisptr.expandBy(WrappedPyObject(x)) + else: + self.thisptr.expandBy(WrappedPyObject(x), + WrappedPyObject(y)) + + + def __add__(cy_GenericRect self, p): + """Offset rectangle by point.""" + return wrap_GenericRect( deref(self.thisptr) + make_PyPoint(p) ) + + def __sub__(cy_GenericRect self, p): + """Offset rectangle by -point.""" + return wrap_GenericRect( deref(self.thisptr) - make_PyPoint(p) ) + + def __or__(cy_GenericRect self, cy_GenericRect o): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_GenericRect( deref(self.thisptr) | deref( o.thisptr )) + + def __richcmp__(cy_GenericRect self, cy_GenericRect o, int op): + """Rectangles are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(o.thisptr) + if op == 3: + return deref(self.thisptr) != deref(o.thisptr) + +cdef PyPoint make_PyPoint(p): + return PyPoint( WrappedPyObject(p[0]), WrappedPyObject(p[1]) ) + +#D2[WrappedPyObject] is converted to tuple +cdef wrap_PyPoint(PyPoint p): + return (p[0].getObj(), p[1].getObj()) + +cdef cy_GenericRect wrap_GenericRect(GenericRect[WrappedPyObject] p): + cdef WrappedPyObject zero = WrappedPyObject(0) + cdef GenericRect[WrappedPyObject] * retp = new GenericRect[WrappedPyObject](zero, zero, zero, zero) + retp[0] = p + cdef cy_GenericRect r = cy_GenericRect.__new__(cy_GenericRect) + r.thisptr = retp + return r + + +cdef class cy_Rect: + + """Class representing axis-aligned rectangle in 2D real plane. + + Corresponds to Rect class in 2geom.""" + + def __cinit__(self, Coord x0=0, Coord y0=0, Coord x1=0, Coord y1=0): + """Create Rect from coordinates of its top-left and bottom-right corners.""" + self.thisptr = new Rect(x0, y0, x1, y1) + + def __str__(self): + """str(self)""" + return "Rectangle with dimensions {}, topleft point {}".format(str(self.dimensions()), str(self.min())) + + def __repr__(self): + """repr(self)""" + return "Rect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_Rect( Rect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + return wrap_Rect( Rect( float(I.min()), + float(J.min()), + float(I.max()), + float(J.max()) ) ) + + @classmethod + def from_list(cls, lst): + """Create rectangle containing all points in list.""" + if lst == []: + return cy_Rect() + if len(lst) == 1: + return cy_Rect.from_points(lst[0], lst[0]) + ret = cy_Rect.from_points(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + @classmethod + def from_xywh(cls, x, y, w, h): + """Create rectangle from it's topleft point and dimensions.""" + return wrap_Rect( from_xywh(<Coord> x, + <Coord> y, + <Coord> w, + <Coord> h) ) + + @classmethod + def infinite(self): + """Create infinite rectangle.""" + return wrap_Rect(infinite()) + + def __getitem__(self, Dim2 d): + """self[d]""" + return wrap_Interval( deref(self.thisptr)[d] ) + + def min(self): + """Get top-left point.""" + return wrap_Point( self.thisptr.min() ) + + def max(self): + """Get bottom-right point.""" + return wrap_Point( self.thisptr.max() ) + + def corner(self, unsigned int i): + """Get corners (modulo) indexed from 0 to 3.""" + return wrap_Point( self.thisptr.corner(i) ) + + def top(self): + """Get top coordinate.""" + return self.thisptr.top() + + def bottom(self): + """Get bottom coordinate.""" + return self.thisptr.bottom() + + def left(self): + """Get left coordinate.""" + return self.thisptr.left() + + def right(self): + """Get right coordinate.""" + return self.thisptr.right() + + def width(self): + """Get width.""" + return self.thisptr.width() + + def height(self): + """Get height.""" + return self.thisptr.height() + + def aspect_ratio(self): + """Get ratio between width and height.""" + return self.thisptr.aspectRatio() + + def dimensions(self): + """Get dimensions as point.""" + return wrap_Point( self.thisptr.dimensions() ) + + def midpoint(self): + """Get midpoint.""" + return wrap_Point( self.thisptr.midpoint() ) + + def area(self): + """Get area.""" + return self.thisptr.area() + + def has_zero_area(self, Coord eps = EPSILON): + """Test for area being zero.""" + return self.thisptr.hasZeroArea(eps) + + def max_extent(self): + """Get bigger value from width, height.""" + return self.thisptr.maxExtent() + + def min_extent(self): + """Get smaller value from width, height.""" + return self.thisptr.minExtent() + + def intersects(self, cy_Rect r): + """Check if rectangle intersects another rectangle.""" + return self.thisptr.intersects(deref( r.thisptr )) + + def contains(self, cy_Point r): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def contains_rect(self, cy_Rect r): + """Check if rectangle contains another rect.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def interior_intersects(self, cy_Rect r): + """Check if interior of self intersects another rectangle.""" + return self.thisptr.interiorIntersects(deref( r.thisptr )) + + def interior_contains(self, cy_Point other): + """Check if interior of self contains point.""" + return self.thisptr.interiorContains( deref( (<cy_Point> other).thisptr ) ) + + def interior_contains_rect(self, other): + """Check if interior of self contains another rectangle.""" + if isinstance(other, cy_Rect): + return self.thisptr.interiorContains( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + return self.thisptr.interiorContains( deref( (<cy_OptRect> other).thisptr ) ) + + def set_left(self, Coord val): + """Set left coordinate.""" + self.thisptr.setLeft(val) + + def set_right(self, Coord val): + """Set right coordinate.""" + self.thisptr.setRight(val) + + def set_top(self, Coord val): + """Set top coordinate.""" + self.thisptr.setTop(val) + + def set_bottom(self, Coord val): + """Set bottom coordinate.""" + self.thisptr.setBottom(val) + + def set_min(self, cy_Point p): + """Set top-left point.""" + self.thisptr.setMin( deref( p.thisptr ) ) + + def set_max(self, cy_Point p): + """Set bottom-right point.""" + self.thisptr.setMax( deref( p.thisptr )) + + def expand_to(self, cy_Point p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo( deref( p.thisptr ) ) + + def union_with(self, cy_Rect b): + """self = self | other.""" + self.thisptr.unionWith(deref( b.thisptr )) + + def expand_by(cy_Rect self, x, y = None): + """Expand both intervals. + + Either expand them both by one value, or each by different value. + """ + if y is None: + if isinstance(x, cy_Point): + self.thisptr.expandBy( deref( (<cy_Point> x).thisptr ) ) + else: + self.thisptr.expandBy( <Coord> x) + else: + self.thisptr.expandBy( <Coord> x, + <Coord> y) + + def __add__(cy_Rect self, cy_Point p): + """Offset rectangle by point.""" + return wrap_Rect( deref(self.thisptr) + deref( p.thisptr ) ) + + def __sub__(cy_Rect self, cy_Point p): + """Offset rectangle by -point.""" + return wrap_Rect( deref(self.thisptr) - deref( p.thisptr ) ) + + def __mul__(cy_Rect self, t): + """Apply transform to rectangle.""" + cdef Affine at + if is_transform(t): + at = get_Affine(t) + return wrap_Rect( deref(self.thisptr) * at ) + + def __or__(cy_Rect self, cy_Rect o): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_Rect( deref(self.thisptr) | deref( o.thisptr )) + + def __richcmp__(cy_Rect self, o, int op): + """Rectangles are not ordered.""" + if op == 2: + if isinstance(o, cy_Rect): + return deref(self.thisptr) == deref( (<cy_Rect> o).thisptr) + elif isinstance(o, cy_IntRect): + return deref(self.thisptr) == deref( (<cy_IntRect> o).thisptr) + if op == 3: + if isinstance(o, cy_Rect): + return deref(self.thisptr) != deref( (<cy_Rect> o).thisptr) + elif isinstance(o, cy_IntRect): + return deref(self.thisptr) != deref( (<cy_IntRect> o).thisptr) + + def round_inwards(self): + """Create OptIntRect rounding inwards.""" + return wrap_OptIntRect(self.thisptr.roundInwards()) + + def round_outwards(self): + """Create IntRect rounding outwards.""" + return wrap_IntRect(self.thisptr.roundOutwards()) + + @classmethod + def distanceSq(cls, cy_Point p, cy_Rect rect): + """Compute square of distance between point and rectangle.""" + return distanceSq( deref(p.thisptr), deref(rect.thisptr) ) + + @classmethod + def distance(cls, cy_Point p, cy_Rect rect): + """Compute distance between point and rectangle.""" + return distance( deref(p.thisptr), deref(rect.thisptr) ) + +cdef cy_Rect wrap_Rect(Rect p): + cdef Rect* retp = new Rect() + retp[0] = p + cdef cy_Rect r = cy_Rect.__new__(cy_Rect) + r.thisptr = retp + return r + + +cdef class cy_OptRect: + + """Class representing optionally empty rect in real plane. + + This class corresponds to OptRect in 2geom, and it tries to mimic + the behaviour of std::optional. In addition to OptRect methods, + this class passes calls for Rect methods to underlying Rect class, + or throws ValueError when it's empty. + """ + + def __cinit__(self, x0=None, y0=None, x1=None, y1=None): + """Create OptRect from coordinates of top-left and bottom-right corners. + + No arguments will result in empty rectangle. + """ + if x0 is None: + self.thisptr = new OptRect() + else: + self.thisptr = new OptRect( float(x0), + float(y0), + float(x1), + float(y1) ) + + def __str__(self): + """str(self)""" + if self.is_empty(): + return "Empty OptRect." + return "OptRect with dimensions {}, topleft point {}".format(str(self.dimensions()), str(self.min())) + + def __repr__(self): + """repr(self)""" + if self.is_empty(): + return "OptRect()" + return "OptRect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_Point p0, cy_Point p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_OptRect( OptRect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + if hasattr(I, "isEmpty"): + if I.isEmpty(): + return cy_OptRect() + + if hasattr(J, "isEmpty"): + if J.isEmpty(): + return cy_OptRect() + + return wrap_OptRect( OptRect( float(I.min()), + float(J.min()), + float(I.max()), + float(J.max()) ) ) + + @classmethod + def from_rect(cls, r): + """Create OptRect from other, possibly empty, rectangle.""" + if hasattr(r, "isEmpty"): + if r.isEmpty(): + return cy_OptRect() + + return cy_OptRect( r.min().x, + r.min().y, + r.max().x, + r.max().y ) + + @classmethod + def from_list(cls, lst): + """Create OptRect containing all points in the list. + + Empty list will result in empty OptRect. + """ + if lst == []: + return cy_OptRect() + if len(lst) == 1: + return cy_OptRect.from_points(lst[0], lst[0]) + ret = cy_OptRect.from_points(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + property Rect: + """Get underlying Rect.""" + def __get__(self): + if self.is_empty(): + raise ValueError("Rect is empty.") + else: + return wrap_Rect(self.thisptr.get()) + + def __bool__(self): + """OptRect is False only when it's empty.""" + return not self.thisptr.isEmpty() + + def is_empty(self): + """Check for OptRect containing no points.""" + return self.thisptr.isEmpty() + + def intersects(self, other): + """Check if rectangle intersects another rectangle.""" + if isinstance(other, cy_Rect): + return self.thisptr.intersects( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + return self.thisptr.intersects( deref( (<cy_OptRect> other).thisptr ) ) + + def contains(self, cy_Point r): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def contains_rect(self, other): + """Check if rectangle contains another rect.""" + if isinstance(other, cy_Rect): + return self.thisptr.contains( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + return self.thisptr.contains( deref( (<cy_OptRect> other).thisptr ) ) + + def union_with(self, other): + """self = self | other.""" + if isinstance(other, cy_Rect): + self.thisptr.unionWith( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + self.thisptr.unionWith( deref( (<cy_OptRect> other).thisptr ) ) + + def intersect_with(self, other): + """self = self & other.""" + if isinstance(other, cy_Rect): + self.thisptr.intersectWith( deref( (<cy_Rect> other).thisptr ) ) + elif isinstance(other, cy_OptRect): + self.thisptr.intersectWith( deref( (<cy_OptRect> other).thisptr ) ) + + def expand_to(self, cy_Point p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo( deref(p.thisptr) ) + + def __or__(cy_OptRect self, cy_OptRect other): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_OptRect( deref(self.thisptr) | deref(other.thisptr) ) + + def __and__(cy_OptRect self, other): + """Return intersection of two rectangles.""" + if isinstance(other, cy_Rect): + return wrap_OptRect( deref(self.thisptr) & deref( (<cy_Rect> other).thisptr) ) + elif isinstance(other, cy_OptRect): + return wrap_OptRect( deref(self.thisptr) & deref( (<cy_OptRect> other).thisptr) ) + + def __richcmp__(cy_OptRect self, other, op): + """Rectangles are not ordered.""" + if op == 2: + if isinstance(other, cy_Rect): + return deref(self.thisptr) == deref( (<cy_Rect> other).thisptr ) + elif isinstance(other, cy_OptRect): + return deref(self.thisptr) == deref( (<cy_OptRect> other).thisptr ) + elif op == 3: + if isinstance(other, cy_Rect): + return deref(self.thisptr) != deref( (<cy_Rect> other).thisptr ) + elif isinstance(other, cy_OptRect): + return deref(self.thisptr) != deref( (<cy_OptRect> other).thisptr ) + return NotImplemented + + def _get_Rect_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptRect is empty.") + else: + return self.Rect.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + Rect_methods = set(['area', 'aspectRatio', 'bottom', 'contains', + 'contains_rect', 'corner', 'dimensions', 'distance', 'distanceSq', + 'expand_by', 'expand_to', 'has_zero_area', 'height', 'infinite', + 'interior_contains', 'interior_contains_rect', + 'interior_intersects', 'intersects', 'left', 'max', 'max_extent', + 'midpoint', 'min', 'min_extent', 'right', 'round_inwards', + 'round_outwards', 'set_bottom', 'set_left', 'set_max', 'set_min', + 'set_right', 'set_top', 'top', 'union_with', 'width']) + + if name in Rect_methods: + return self._get_Rect_method(name) + else: + raise AttributeError("OptRect instance has no attribute \"{}\"".format(name)) + + def _wrap_Rect_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptRect is empty.") + else: + return self.Rect.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __getitem__(self, i): + """self[d]""" + return self._wrap_Rect_method("__getitem__", i) + + def __add__(self, other): + """Offset rectangle by point.""" + return self._wrap_Rect_method("__add__", other) + + def __mul__(self, other): + """Apply transform to rectangle.""" + return self._wrap_Rect_method("__mul__", other) + + def __sub__(self, other): + """Offset rectangle by -point.""" + return self._wrap_Rect_method("__sub__", other) + + +cdef cy_OptRect wrap_OptRect(OptRect p): + cdef OptRect* retp = new OptRect() + retp[0] = p + cdef cy_OptRect r = cy_OptRect.__new__(cy_OptRect) + r.thisptr = retp + return r + + + +cdef class cy_IntRect: + + """Class representing axis-aligned rectangle in 2D with integer coordinates. + + Corresponds to IntRect class (typedef) in 2geom.""" + + cdef IntRect* thisptr + + def __cinit__(self, IntCoord x0=0, IntCoord y0=0, IntCoord x1=0, IntCoord y1=0): + """Create IntRect from coordinates of its top-left and bottom-right corners.""" + self.thisptr = new IntRect(x0, y0, x1, y1) + + def __str__(self): + """str(self)""" + return "IntRect with dimensions {}, topleft point {}".format( + str(self.dimensions()), + str(self.min())) + + def __repr__(self): + """repr(self)""" + return "IntRect({}, {}, {}, {})".format( str(self.left()), + str(self.top()), + str(self.right()), + str(self.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_IntPoint p0, cy_IntPoint p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_IntRect( IntRect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + return cy_IntRect( int(I.min()), + int(J.min()), + int(I.max()), + int(J.max()) ) + + @classmethod + def from_list(cls, lst): + """Create rectangle containing all points in list.""" + if lst == []: + return cy_IntRect() + if len(lst) == 1: + return cy_IntRect(lst[0], lst[0]) + ret = cy_IntRect(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + #didn't manage to declare from_xywh for IntRect + @classmethod + def from_xywh(cls, x, y, w, h): + """Create rectangle from it's topleft point and dimensions.""" + return cy_IntRect(int(x), + int(y), + int(x) + int(w), + int(y) + int(h) ) + + def __getitem__(self, Dim2 d): + """self[d]""" + return wrap_IntInterval( deref(self.thisptr)[d] ) + + def min(self): + """Get top-left point.""" + return wrap_IntPoint( self.thisptr.i_min() ) + + def max(self): + """Get bottom-right point.""" + return wrap_IntPoint( self.thisptr.i_max() ) + + def corner(self, unsigned int i): + """Get corners (modulo) indexed from 0 to 3.""" + return wrap_IntPoint( self.thisptr.i_corner(i) ) + + def top(self): + """Get top coordinate.""" + return self.thisptr.top() + + def bottom(self): + """Get bottom coordinate.""" + return self.thisptr.bottom() + + def left(self): + """Get left coordinate.""" + return self.thisptr.left() + + def right(self): + """Get right coordinate.""" + return self.thisptr.right() + + def width(self): + """Get width.""" + return self.thisptr.width() + + def height(self): + """Get height.""" + return self.thisptr.height() + + def aspect_ratio(self): + """Get ratio between width and height.""" + return self.thisptr.aspectRatio() + + def dimensions(self): + """Get dimensions as IntPoint.""" + return wrap_IntPoint( self.thisptr.i_dimensions() ) + + def midpoint(self): + """Get midpoint.""" + return wrap_IntPoint( self.thisptr.i_midpoint() ) + + def area(self): + """Get area.""" + return self.thisptr.area() + + def has_zero_area(self): + """Test for area being zero.""" + return self.thisptr.hasZeroArea() + + def max_extent(self): + """Get bigger value from width, height.""" + return self.thisptr.maxExtent() + + def min_extent(self): + """Get smaller value from width, height.""" + return self.thisptr.minExtent() + + def intersects(self, cy_IntRect r): + """Check if rectangle intersects another rectangle.""" + return self.thisptr.intersects(deref( r.thisptr )) + + def contains(self, cy_IntPoint r): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def contains_rect(self, cy_IntRect r): + """Check if rectangle contains another rect.""" + return self.thisptr.contains( deref(r.thisptr) ) + + def set_left(self, IntCoord val): + """Set left coordinate.""" + self.thisptr.setLeft(val) + + def set_right(self, IntCoord val): + """Set right coordinate.""" + self.thisptr.setRight(val) + + def set_top(self, IntCoord val): + """Set top coordinate.""" + self.thisptr.setTop(val) + + def set_bottom(self, IntCoord val): + """Set bottom coordinate.""" + self.thisptr.setBottom(val) + + def set_min(self, cy_IntPoint p): + """Set top-left point.""" + self.thisptr.setMin( deref( p.thisptr ) ) + + def set_max(self, cy_IntPoint p): + """Set bottom-right point.""" + self.thisptr.setMax( deref( p.thisptr )) + + def expand_to(self, cy_IntPoint p): + """Expand rectangle to contain point represented as tuple.""" + self.thisptr.expandTo( deref( p.thisptr ) ) + + def union_with(self, cy_IntRect b): + """self = self | other.""" + self.thisptr.unionWith(deref( b.thisptr )) + + def expand_by(cy_IntRect self, x, y = None): + """Expand both intervals. + + Either expand them both by one value, or each by different value. + """ + if y is None: + if isinstance(x, cy_IntPoint): + self.thisptr.expandBy( deref( (<cy_IntPoint> x).thisptr ) ) + else: + self.thisptr.expandBy( <IntCoord> x) + else: + self.thisptr.expandBy( <IntCoord> x, + <IntCoord> y) + + def __add__(cy_IntRect self, cy_IntPoint p): + """Offset rectangle by point.""" + return wrap_IntRect( deref(self.thisptr) + deref( p.thisptr ) ) + + def __sub__(cy_IntRect self, cy_IntPoint p): + """Offset rectangle by -point.""" + return wrap_IntRect( deref(self.thisptr) - deref( p.thisptr ) ) + + def __or__(cy_IntRect self, cy_IntRect o): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_IntRect( deref(self.thisptr) | deref( o.thisptr )) + + def __richcmp__(cy_IntRect self, cy_IntRect o, int op): + """Rectangles are not ordered.""" + if op == 2: + return deref(self.thisptr) == deref(o.thisptr) + if op == 3: + return deref(self.thisptr) != deref(o.thisptr) + +cdef cy_IntRect wrap_IntRect(IntRect p): + cdef IntRect* retp = new IntRect() + retp[0] = p + cdef cy_IntRect r = cy_IntRect.__new__(cy_IntRect) + r.thisptr = retp + return r + + + +cdef class cy_OptIntRect: + + """Class representing optionally empty rect in with integer coordinates. + + This class corresponds to OptIntRect in 2geom, and it tries to mimic + the behaviour of std::optional. In addition to OptIntRect methods, + this class passes calls for IntRect methods to underlying IntRect class, + or throws ValueError when it's empty. + """ + + cdef OptIntRect* thisptr + + def __cinit__(self, x0=None, y0=None, x1=None, y1=None): + """Create OptIntRect from coordinates of top-left and bottom-right corners. + + No arguments will result in empty rectangle. + """ + if x0 is None: + self.thisptr = new OptIntRect() + else: + self.thisptr = new OptIntRect( int(x0), + int(y0), + int(x1), + int(y1) ) + + def __str__(self): + """str(self)""" + if self.isEmpty(): + return "Empty OptIntRect" + return "OptIntRect with dimensions {}, topleft point {}".format( + str(self.Rect.dimensions()), + str(self.Rect.min())) + + def __repr__(self): + """repr(self)""" + if self.isEmpty(): + return "OptIntRect()" + return "OptIntRect({}, {}, {}, {})".format( str(self.Rect.left()), + str(self.Rect.top()), + str(self.Rect.right()), + str(self.Rect.bottom())) + + def __dealloc__(self): + del self.thisptr + + @classmethod + def from_points(cls, cy_IntPoint p0, cy_IntPoint p1): + """Create rectangle from it's top-left and bottom-right corners.""" + return wrap_OptIntRect( OptIntRect(deref(p0.thisptr), deref(p1.thisptr)) ) + + @classmethod + def from_intervals(cls, I, J): + """Create rectangle from two intervals representing its sides.""" + if hasattr(I, "isEmpty"): + if I.isEmpty(): + return cy_OptIntRect() + + if hasattr(J, "isEmpty"): + if J.isEmpty(): + return cy_OptIntRect() + + return wrap_OptIntRect( OptIntRect( int(I.min()), + int(J.min()), + int(I.max()), + int(J.max()) ) ) + + @classmethod + def from_rect(cls, r): + """Create OptIntRect from other, possibly empty, rectangle.""" + if hasattr(r, "isEmpty"): + if r.isEmpty(): + return cy_OptIntRect() + return cy_OptIntRect( r.min().x, + r.min().y, + r.max().x, + r.max().y ) + + @classmethod + def from_list(cls, lst): + """Create OptIntRect containing all points in the list. + + Empty list will result in empty OptIntRect. + """ + if lst == []: + return cy_OptIntRect() + if len(lst) == 1: + return cy_OptIntRect.from_points(lst[0], lst[0]) + ret = cy_OptIntRect.from_points(lst[0], lst[1]) + for a in lst: + ret.expand_to(a) + return ret + + property Rect: + """Get underlying IntRect.""" + def __get__(self): + return wrap_IntRect(self.thisptr.get()) + + def __bool__(self): + """OptIntRect is False only when it's empty.""" + return not self.thisptr.isEmpty() + + def is_empty(self): + """Check for OptIntRect containing no points.""" + return self.thisptr.isEmpty() + + def intersects(cy_OptIntRect self, other): + """Check if rectangle intersects another rectangle.""" + if isinstance(other, cy_IntRect): + return self.thisptr.intersects( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + return self.thisptr.intersects( deref( (<cy_OptIntRect> other).thisptr ) ) + + def contains(self, cy_IntPoint other): + """Check if rectangle contains point.""" + return self.thisptr.contains( deref(other.thisptr) ) + + def contains_rect(cy_OptIntRect self, other): + """Check if rectangle contains another rectangle.""" + if isinstance(other, cy_IntRect): + return self.thisptr.contains( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + return self.thisptr.contains( deref( (<cy_OptIntRect> other).thisptr ) ) + + + def union_with(cy_OptIntRect self, other): + """self = self | other.""" + if isinstance(other, cy_IntRect): + self.thisptr.unionWith( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + self.thisptr.unionWith( deref( (<cy_OptIntRect> other).thisptr ) ) + + def intersect_with(cy_OptIntRect self, other): + """self = self & other.""" + if isinstance(other, cy_IntRect): + self.thisptr.intersectWith( deref( (<cy_IntRect> other).thisptr ) ) + elif isinstance(other, cy_OptIntRect): + self.thisptr.intersectWith( deref( (<cy_OptIntRect> other).thisptr ) ) + + def expand_to(self, cy_IntPoint p): + """Expand rectangle to contain point.""" + self.thisptr.expandTo( deref(p.thisptr) ) + + def __or__(cy_OptIntRect self, cy_OptIntRect other): + """Return union of two rects - it's actually bounding rect of union.""" + return wrap_OptIntRect( deref(self.thisptr) | deref(other.thisptr) ) + + def __and__(cy_OptIntRect self, other): + """Return intersection of two rectangles.""" + if isinstance(other, cy_IntRect): + return wrap_OptIntRect( deref(self.thisptr) & deref( (<cy_IntRect> other).thisptr) ) + elif isinstance(other, cy_OptIntRect): + return wrap_OptIntRect( deref(self.thisptr) & deref( (<cy_OptIntRect> other).thisptr) ) + + def __richcmp__(cy_OptIntRect self, other, op): + """Rectangles are not ordered.""" + if op == 2: + if isinstance(other, cy_IntRect): + return deref(self.thisptr) == deref( (<cy_IntRect> other).thisptr ) + elif isinstance(other, cy_OptIntRect): + return deref(self.thisptr) == deref( (<cy_OptIntRect> other).thisptr ) + elif op == 3: + if isinstance(other, cy_IntRect): + return deref(self.thisptr) != deref( (<cy_IntRect> other).thisptr ) + elif isinstance(other, cy_OptIntRect): + return deref(self.thisptr) != deref( (<cy_OptIntRect> other).thisptr ) + + def _get_Rect_method(self, name): + def f(*args, **kwargs): + if self.is_empty(): + raise ValueError("OptIntRect is empty.") + else: + return self.Rect.__getattribute__(name)(*args, **kwargs) + return f + + def __getattr__(self, name): + + Rect_methods = set(['area', 'aspect_ratio', 'bottom', 'contains', + 'contains_rect', 'corner', 'dimensions', 'expand_by', 'expand_to', + 'from_intervals', 'from_list', 'from_points', 'from_xywh', + 'has_zero_area', 'height', 'intersects', 'left', 'max', + 'max_extent', 'midpoint', 'min', 'min_extent', 'right', + 'set_bottom', 'set_left', 'set_max', 'set_min', 'set_right', + 'set_top', 'top', 'union_with', 'width']) + + if name in Rect_methods: + return self._get_Rect_method(name) + else: + raise AttributeError("OptIntRect instance has no attribute \"{}\"".format(name)) + + def _wrap_Rect_method(self, name, *args, **kwargs): + if self.isEmpty(): + raise ValueError("OptIntRect is empty.") + else: + return self.Rect.__getattr__(name)(*args, **kwargs) + + #declaring these by hand, because they take fixed number of arguments, + #which is enforced by cython + + def __getitem__(self, i): + """self[d]""" + return self._wrap_Rect_method("__getitem__", i) + + def __add__(self, other): + """Offset rectangle by point.""" + return self._wrap_Rect_method("__add__", other) + + def __sub__(self, other): + """Offset rectangle by -point.""" + return self._wrap_Rect_method("__sub__", other) + +cdef cy_OptIntRect wrap_OptIntRect(OptIntRect p): + cdef OptIntRect* retp = new OptIntRect() + retp[0] = p + cdef cy_OptIntRect r = cy_OptIntRect.__new__(cy_OptIntRect) + r.thisptr = retp + return r diff --git a/src/cython/cy2geom.pyx b/src/cython/cy2geom.pyx new file mode 100644 index 0000000..f1ba7a6 --- /dev/null +++ b/src/cython/cy2geom.pyx @@ -0,0 +1,71 @@ +#Axis specifiers for Dim2 +X = 0 +Y = 1 + +from _cy_primitives import cy_Angle as Angle +from _cy_primitives import cy_AngleInterval as AngleInterval +from _cy_primitives import cy_Point as Point +from _cy_primitives import cy_Line as Line +from _cy_primitives import cy_Ray as Ray +from _cy_primitives import cy_IntPoint as IntPoint + + +from _cy_rectangle import cy_Interval as Interval +from _cy_rectangle import cy_OptInterval as OptInterval +from _cy_rectangle import cy_IntInterval as IntInterval +from _cy_rectangle import cy_OptIntInterval as OptIntInterval + +from _cy_rectangle import cy_GenericInterval as GenericInterval +from _cy_rectangle import cy_GenericOptInterval as GenericOptInterval + +from _cy_rectangle import cy_GenericRect as GenericRect + +from _cy_rectangle import cy_Rect as Rect +from _cy_rectangle import cy_OptRect as OptRect +from _cy_rectangle import cy_IntRect as IntRect +from _cy_rectangle import cy_OptIntRect as OptIntRect + + +from _cy_affine import cy_Affine as Affine +from _cy_affine import cy_Translate as Translate +from _cy_affine import cy_Rotate as Rotate +from _cy_affine import cy_VShear as VShear +from _cy_affine import cy_HShear as HShear +from _cy_affine import cy_Scale as Scale +from _cy_affine import cy_Zoom as Zoom + +from _cy_affine import cy_Eigen as Eigen + +from _cy_curves import cy_Curve as Curve + +from _cy_curves import cy_Linear as Linear +from _cy_curves import cy_SBasis as SBasis +from _cy_curves import cy_SBasisCurve as SBasisCurve + +from _cy_curves import cy_Bezier as Bezier +from _cy_curves import cy_BezierCurve as BezierCurve +from _cy_curves import cy_LineSegment as LineSegment +from _cy_curves import cy_QuadraticBezier as QuadraticBezier +from _cy_curves import cy_CubicBezier as CubicBezier + +from _cy_curves import cy_HLineSegment as HLineSegment +from _cy_curves import cy_VLineSegment as VLineSegment + +from _cy_curves import cy_EllipticalArc as EllipticalArc +#Wrap this? It doesn't fit into python's dynamic nature and +#BezierCurve covers most of it's functionality +#Maybe implement constructors for BezierCurve similar to those +#seen in BezierCurveN +#TODO +#from _cy_curves import cy_BezierCurveN as BezierCurveN + +from _cy_curves import cy_lerp as lerp +from _cy_curves import cy_reverse as reverse +#~ from _cy_curves import cy_level_sets as level_sets + + +from _cy_path import cy_Path as Path + + +from _cy_conicsection import cy_Circle as Circle +from _cy_conicsection import cy_Ellipse as Ellipse diff --git a/src/cython/report.md b/src/cython/report.md new file mode 100644 index 0000000..e1f8353 --- /dev/null +++ b/src/cython/report.md @@ -0,0 +1,237 @@ +# State of cython Bindings + +This report summarizes work done on [lib2geom][2g] cython bindings during +GSoC 2012 mostly from technical standpoint. It should serve for anyone +doing any further work on these bindings, and for me to help my memory. + +Prior to the project, there were bindings using boost.python. These +bindigs, however, uncovered only part of the lib2geom functionality and +were not actively developed. I decided to go for a fresh start using +[cython][cy]. Cython is programming language capable of calling C and C++ +methods and making so-called "extension types", which act as python +classes. Reasons for choosing python include bit of experience I had with +it, its cleanliness and activity in development. + +## Overview of Bindings + +All work was done in [trunk][tr], since it mostly added new functionality +without altering existing code. Whole bindings are located in directory + + 2geom_dir/src/2geom/cython-bindings + +Code is located in files ending with `.pxd` and `.pyx`, roughly cython's +equivalent of header and implementation files. Classes in cython are +divided into logical groups, which are wrapped in corresponding classes. +cy2geom.pyx imports all classes and methods, and it's what one sees after + + >>> import cy2geom + +In addition to this, there are some C++ files, necessary to wrap some of +the code for cython, unit-tests covering most of the functionality and +wrapper.py script, used to skip tedious part of creating extension types. + +Building of bindings was integrated with cmake, which is used to build +the bindings, using [thewext's code][cm]. + +I will now describe files/logical groups of them, their significance and +state. I will try to write down most of the technical non-trivialities that +I encountered. + +Pattern used for wrapping all classes is taken from cython docs and various +other cython bindings. In .pxd file C++ classes, methods and functions are +declared. In corresponding .pyx file there is an extension type which holds +pointer (called `thisptr`) to it's underlying C++ class. Instance of this +class is created with cython's `__cinit__`, and deleted with `__dealloc__`. +Methods of class are called with `self.thisptr.methodname`, which cython +translates to `thisptr->methodname`. Argument are stripped of their python +wrapping using `deref(arg.thisptr)`, or they are translated automatically +(these are basic numerical types). + +There is a function for each class, named `wrap_classname()`, which takes +C++ instance and creates new python object around it. + +## `_common_decl` +These files (.pxd/.pyx) include basic declarations, common to all files. +Functions for creating and wrapping `vector[double]` should become redundant +in next cython release (0.17), since it should do this conversion +automatically. + +All extension types (cdef classes) have prefix `_cy_`, to avoid clash with +C++ classes. This is removed when importing to `_common_decl.pyx`, but is still +somewhat visible to the user of bindings, for example in traces. Other +option would be renaming the C++ classes. + +## `_cy_primitives` +Geometric primitives are wrapped here. These are relatively simple, few +things that are worth mentioning are: + +* Some of the methods for Line and Ray could be rewritten using properties. +* General points about exceptions, docstrings and classmethod apply here. + +## `_cy_rectangle` +These files include intervals and rectangles, which all inherit from +`GenericInterval[C]` or `GenericRect[C]`, respectively. + +For intervals, classes Interval and IntInterval are wrapped, with added +methods for interval too. Opt variants are supported by rewriting +`__getattr__` method. When calling the method that Interval provides, +OptInterval checks whether it's not empty and passes arguments to Interval's +method. If it is empty, it will raise ValueError. Due to this, Interval's +method do not appear in OptInterval's namespace, which might be not ideal. + +GenericInterval is supported by setting the template type to WrappedPyObject, +defined in `wrapped-pyobject.h`. This thin wrapper of `PyObject *` overloads +operators to support arithmetic operations, comparisons and similar. When +adding two WrappedPyObjects, these objects call corresponding addition using +Python's C-API. This is a bit unstable feature, because it still leaks these +python objects, and propagating error from Python, through C-API to cython +back to Python is not done yet correctly. However, constraints on +parameter of GenericInterval's type are pretty tight - it has to support +arithmetic operations within itself, multiplication by reals and has to be +ordered (ordered linear space with multiplication and division between its +elements), so having GenericInterval only with reals and integers covers most +cases. + +For rectangles, situations is almost identical as for intervals. GenericRect, +return 2-tuples as a points, which is a bit of disadvantage, because adding +tuples doesn't add element-wise, but append them one after another. It might +be convenient to implement PyPoint overloading this functionality. + +A bit problematic region is in comparing all the intervals and rectangles. +Should every interval be comparable to every other type, Interval with +OptIntInterval? This would probably require writing the logic in cython. + +GenericOptRect is not there yet, because I got GenericRect only working +only recently. It should be, however, quick to add. + +## `_cy_affine` +Affine and specialised transforms. This is in pretty good state. Eigenvalues +seem to be a bit off sometimes. + +## `_cy_curves` +All of the curves are wrapped here, together with functions like Linear, +SBasis and Bezier. + +Curve implements method every curve should implement, but other curve +classes do not inherit from Curve. This is mainly because of technical +details, this inheritance would only add complexity to code. However, I +might consider rewriting curves, so they would actually inherit from +Curve, if this appears to be needed. + +State of functions and classmethods is a bit strange. I tried to copy +2geom's methods, but there are differences for Beziers and SBasis +polynomials - to name a few, SBasis implements multiplication with SBasis +as a operator, but Bezier only as a function, or bounds are implemented +as instance methods for Linear, but as a module level functions for Bezier +and SBasis. In case I wasn't sure what to do with a method, I put it, as +a classmethod, to corresponding class. + +BezierCurveN is not implemented. It would be major hurdle, as cython has +problems with templated classes - one would have to declare each type +separately. First three orders are wrapped, and BezierCurve, with variable +degree, is also wrapped. + +`hacks.h` is used to go around cython's problems with integer template +parameters. + +## `_cy_path` +Path is wrapped here. Biggest problem with path was it's heavy use of +iterators, which are not the same as python's iterators. In most of the +cases, I replaced them with integers marking the position of iterator, +and go to the position step-by-step. This makes potentially O(1) operations +actually O(n) where n is size of Path. Best way to deal with this would be +to allow iterators to be shifted by arbitrary number. + +Not everything is provided, but there should be enough functionality to +have total control over Path's curves. + +## `_cy_conicsection` +Circle and ellipse classes. ratQuad and xAx from conic-section should also +go there. + +## `utils.py` +Only a simple function to draw curve/path to Tk windows and regular +N-agon creating function reside there, useful mainly for debugging + +## `wrapper.py` +Script used to cut the most trivial part of creating bindings, writing +.pxd declarations, extension type and other functions. It's pretty tailored +into 2geom, but I guess after some work one could make it more generic. + + +## General Issues +Exceptions coming from C++ code are not handled yet, they actually crash +the program. Simplest solution is adding `except +` after every method that +possibly raises exceptions, but traces won't look that nice. + +cython has some kind of problem with docstrings, it doesn't write function's +argument to them, just ellipsis. This can be addressed by specifying all +arguments in the docstring. + +Design decision has been made to put most of function not belonging to any +class to extension type's namespace using classmethods. Most apt extension +type is chosen, distance(Point, Point) goes to Point class. Other options +are making them check for all possible types (this is ugly and error-prone) +or differentiating using different names, which is more or less the same as +classmethods, just not that systematic + +## What's missing +Piecewise missing and all of crossings/boolops stuff are biggest things +that didn't make. Concerning Piecewise, I think best option is to +implement only `Piecewise<SBasis>` and `Piecewise<D2<SBasis>>` as those are +only ones actually useful (SBasis, Bezier and their D2 versions are AFAIK +only classes implementing required concepts). + +Crossings are working, boolops not so much. Work on Shape and Region has +been started. + +# Personal Notes +I am also summarizing a bit on my personal experience with GSoC. + +## What Did I Learn +I learned a lot, not only new technology, but also gained new perspective +on long-time work. + +Speaking of technology, I am much more confident with cython, and I am not +really scared of python C-API. I got a grasp of bazaar, CMake, python's +unittest module and various (mainly template) C++ features, heavily used +in 2geom. + +I got a grasp of what a polished product looks like, and I realize how +hard is to get your product to that state. I can now appreciate testing, +at first seemingly just a boring and not trivial chore, turned out to be +very helpful both for my understanding of code and for making changes more +quickly. I also read a lot about design of python API, what's idiomatic +and what does various norms (PEPs) say about python. + +I think I never did something of this size before (not that monolithic), +so I learned lot about my work habits and importance of both work ethic +and taking a rest. + +## What I Liked & Disliked +I liked mainly the process of learning new things. I disliked the really +technical scope of this project - I can imagine more exciting things to +work on than doing bindings, but I guess it doesn't hurt to have this kind +of experience. I certainly won't spend my whole life doing only exciting +things :) + +## What Would I Do Differently +I would have planned a bit more. I like to write it on account of my +inexperience, I felt many times that knowing how should final product +look like would save me tons of work - this way, I approached state +with which I was more-or-less content at the end of doing most of the tasks. +Luckily, I got time to go through the code at the end, so I ended up +correcting the largest mistakes. + +I would also discuss design of API bit more, since mapping the C++ sometimes +turned out to be cumbersome from python's perspective. Often, I would spend +lots of time thinking through some detail (Iterator for Path, std::optional, +most of the templated classes), only doing ad-hoc solution at the end in +order to I to keep progressing. + +Jan Pulmann - jan.pulmann@gmail.com + +[2g]: http://lib2geom.sourceforge.net/ +[cy]: http://www.cython.org/ +[cm]: https://github.com/thewtex/cython-cmake-example +[tr]: https://code.launchpad.net/~lib2geom-hackers/lib2geom/trunk diff --git a/src/cython/test-affine.py b/src/cython/test-affine.py new file mode 100644 index 0000000..a020d73 --- /dev/null +++ b/src/cython/test-affine.py @@ -0,0 +1,249 @@ +import unittest +from math import pi, sqrt, sin, cos +from random import randint, uniform + +import cy2geom + +from cy2geom import Point, IntPoint +from cy2geom import Line, Ray, Rect + +from cy2geom import Rect + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +class TestPrimitives(unittest.TestCase): + def affine(self, A, B): + c0, c1, c2, c3, c4, c5 = A[0], A[1], A[2], A[3], A[4], A[5] + C = Affine(c0, c1, c2, c3, c4, c5) + self.assertEqual(C, A) + E = Affine.identity() + self.assertEqual(C, C*E) + self.assertEqual(E*B, B) + self.assertEqual(E.det(), 1) + + self.assertAlmostEqual(A.det(), c0*c3-c1*c2) + self.assertAlmostEqual(abs(A.det()), A.descrim2()) + self.assertAlmostEqual(abs(A.det())**0.5, A.descrim()) + #xor + self.assertFalse( A.flips() ^ (A.det() < 0) ) + + if A.is_singular(): + self.assertAlmostEqual(A.det(), 0) + else: + self.assertTrue( Affine.are_near (A*A.inverse(), E) ) + self.assertAlmostEqual(A.det(), 1/A.inverse().det()) + self.assertEqual( A.x_axis(), Point(c0, c1) ) + self.assertEqual( A.y_axis(), Point(c2, c3) ) + self.assertEqual( A.translation(), Point(c4, c5) ) + + self.assertAlmostEqual(A.expansion_X(), A.x_axis().length()) + self.assertAlmostEqual(A.expansion_Y(), A.y_axis().length()) + + if abs(A.expansion_X()) > 1e-7 and abs(A.expansion_Y()) > 1e-7: + A.set_expansion_X(2) + A.set_expansion_Y(3) + self.assertAlmostEqual(A.expansion_X(), 2) + self.assertAlmostEqual(A.expansion_Y(), 3) + + A.set_identity() + + self.assertTrue(A.is_identity()) + self.assertTrue(A.is_translation()) + self.assertFalse(A.is_nonzero_translation()) + self.assertTrue(A.is_scale()) + self.assertTrue(A.is_uniform_scale()) + self.assertFalse(A.is_nonzero_scale()) + self.assertFalse(A.is_nonzero_uniform_scale()) + self.assertTrue(A.is_rotation()) + self.assertFalse(A.is_nonzero_rotation()) + self.assertTrue(A.is_HShear()) + self.assertTrue(A.is_VShear()) + self.assertFalse(A.is_nonzero_HShear()) + self.assertFalse(A.is_nonzero_VShear()) + self.assertTrue(A.is_zoom()) + + self.assertTrue(A.preserves_area() and A.preserves_angles() and A.preserves_distances()) + + self.assertFalse( A.flips() ) + self.assertFalse( A.is_singular() ) + + A.set_X_axis(Point(c0, c1)) + A.set_Y_axis(Point(c2, c3)) + + self.assertEqual(A.without_translation(), A) + + A.set_translation(Point(c4, c5)) + self.assertEqual(C, A) + + self.assertAlmostEqual( (A*B).det(), A.det()*B.det() ) + + self.assertEqual( A.translation(), Point()*A ) + self.assertEqual( Point(1, 1)*A, Point( c0+c2+c4, c1+c3+c5 )) + + l = Line(Point(1, 1), 2) + self.assertEqual( (l.transformed(A)).origin(), l.origin()*A ) + self.assertTrue( Line.are_near( l.point_at(3)*A, l.transformed(A) ) ) + + r = Ray(Point(2, 3), 4) + self.assertEqual( (r.transformed(A)).origin(), r.origin()*A ) + self.assertTrue( Ray.are_near( r.point_at(3)*A, r.transformed(A) ) ) + + + + def test_affine(self): + al = [] + for i in range(10): + al.append(Affine( uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10))) + for A in al: + for B in al: + self.affine(A, B) + + o = Point(2, 4) + v = Point(-1, 1)/sqrt(2) + l = Line.from_origin_and_versor(o, v) + + R = Affine.reflection(v, o) + for i in range(100): + p = Point(randint(0, 100), randint(0, 100)) + self.assertAlmostEqual(Line.distance(p, l), Line.distance(p*R, l)) + self.assertTrue( Affine.are_near( R, R.inverse() ) ) + + self.affine(R, R.inverse()) + + def test_translate(self): + T = Translate() + U = Translate(Point(2, 4)) + V = Translate(1, -9) + + self.assertTrue(Affine(T).is_translation()) + self.assertTrue(Affine(U).is_nonzero_translation()) + + self.assertEqual( (U*V).vector(), U.vector()+V.vector() ) + self.assertEqual( U.inverse().vector(), -U.vector() ) + self.assertEqual(T, Translate.identity()) + self.assertEqual( U.vector(), Point(U[0], U[1]) ) + + self.affine(Affine(V), Affine(U)) + self.affine(Affine(U), Affine(V)) + + r = Rect.from_points( Point(0, 2), Point(4, 8) ) + + self.assertEqual( ( r*(U*V) ).min(), r.min()+U.vector()+V.vector()) + + def test_scale(self): + S = Scale() + T = Scale( Point (3, 8) ) + U = Scale( -3, 1) + V = Scale(sqrt(2)) + + self.assertTrue( Affine(T).is_scale() ) + self.assertTrue( Affine(T).is_nonzero_scale() ) + self.assertTrue( Affine(V).is_nonzero_uniform_scale()) + + self.assertEqual( (T*V).vector(), T.vector()*sqrt(2) ) + self.assertEqual( (T*U)[0], T[0]*U[0] ) + self.assertAlmostEqual( 1/U.inverse()[1], U[1] ) + + r = Rect.from_points( Point(0, 2), Point(4, 8) ) + self.assertAlmostEqual((r*V).area(), 2*r.area()) + self.assertFalse(Affine(U).preserves_area()) + self.assertTrue(Affine(V).preserves_angles()) + + self.affine(Affine(T), Affine(U)) + self.affine(Affine(U), Affine(V)) + self.affine(Affine(V), Affine(T)) + + def test_rotate(self): + R = Rotate() + S = Rotate(pi/3) + T = Rotate(Point( 1, 1 )) + U = Rotate( -1, 1 ) + + self.assertTrue(S.vector(), Point(cos(pi/3), sin(pi/3)) ) + self.assertEqual( Point(T[0], T[1]), T.vector() ) + self.assertTrue( Affine.are_near( Rotate.from_degrees(60), S ) ) + self.assertEqual(R, Rotate.identity()) + self.assertTrue( Point.are_near( ( S * T ).vector(), + Point( cos( pi/3 + pi/4 ), sin( pi/3 + pi/4 ) ) ) ) + + self.affine( Affine(R), Affine(S)) + self.affine( Affine(S), Affine(T)) + self.affine( Affine(T), Affine(U)) + self.affine( Affine(U), Affine(R)) + + def test_shear(self): + H = HShear(2.98) + V = VShear(-sqrt(2)) + + self.assertAlmostEqual(H.factor(), 2.98) + self.assertAlmostEqual(V.inverse().factor(), sqrt(2)) + + G = HShear.identity() + H.set_factor(0) + self.assertEqual(G, H) + + G.set_factor(2) + H.set_factor(4) + self.assertAlmostEqual((G*H).factor(), G.factor()+H.factor()) + + W = VShear.identity() + V.set_factor(0) + self.assertEqual(W, V) + + W.set_factor(-2) + V.set_factor(3) + self.assertAlmostEqual((W*V).factor(), W.factor()+V.factor()) + + def test_zoom(self): + Z = Zoom(3) + Y = Zoom(translate=Translate(3,2)) + X = Zoom(sqrt(3), Translate(-1, 3)) + + self.assertEqual( + Zoom(Z.scale(), Translate(Y.translation())), + Y*Z ) + + Z.set_translation(Y.translation()) + Y.set_scale(Z.scale()) + self.assertEqual(Z, Y) + + self.assertEqual(Y.inverse().scale(), 1/Y.scale()) + + r = Rect.from_xywh( 1, 1, 3, 6) + q = Rect.from_xywh( 0, -1, 1, 2) + W = Zoom.map_rect(r, q) + + self.assertAlmostEqual(W.scale()*r.width(), q.width()) + self.assertTrue(Point.are_near( + r.min()+W.translation(), + q.min())) + def test_eigen(self): + #TODO looks like bug in eigen - (1, 0) should be eigenvector too + #~ S = Scale(1, 2) + #~ E_S = Eigen(S) + #~ print E_S.vectors, E_S.values + #~ print Affine(S) + #~ for i in E_S.vectors: + #~ print i, i*S, Point(1, 0) * S + + B = Affine(-2, 2, 2, 1, 0, 0) + G1 = Eigen(B) + G2 = Eigen( [[-2, 2], [2, 1]] ) + + self.assertAlmostEqual(min(G1.values), min(G2.values)) + self.assertAlmostEqual(max(G1.values), max(G2.values)) + + if Point.are_near( G1.vectors[0]*G1.values[0], G1.vectors[0]*B ): + self.assertTrue( Point.are_near( G1.vectors[1]*G1.values[1], G1.vectors[1]*B ) ) + else: + self.assertTrue( Point.are_near( G1.vectors[1]*G1.values[0], G1.vectors[1]*B ) ) + self.assertTrue( Point.are_near( G1.vectors[0]*G1.values[1], G1.vectors[0]*B ) ) + +unittest.main() diff --git a/src/cython/test-conicsection.py b/src/cython/test-conicsection.py new file mode 100644 index 0000000..9fc1c20 --- /dev/null +++ b/src/cython/test-conicsection.py @@ -0,0 +1,137 @@ +import unittest +import math +from random import randint, uniform + +import cy2geom + +from cy2geom import Point, IntPoint +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +from cy2geom import Curve +from cy2geom import Linear +from cy2geom import SBasis, SBasisCurve +from cy2geom import Bezier, BezierCurve + +from cy2geom import LineSegment, QuadraticBezier, CubicBezier +from cy2geom import HLineSegment, VLineSegment + +from cy2geom import EllipticalArc + +from cy2geom import Path + +from cy2geom import Circle, Ellipse + + +class TestPrimitives(unittest.TestCase): + def test_circle(self): + C = Circle() + self.assertEqual(C.center(), Point()) + + D = Circle(Point(2, 4), 2) + Dp = D.getPath() + self.assertEqual(D.center(), Point(2, 4)) + self.assertEqual(D.ray(), 2) + + for i in range(11): + t = i/10.0 + #Circle approximated by SBasis is not perfect + self.assertAlmostEqual( abs(D.center()-Dp(t)), D.ray(), delta=0.1 ) + + half_circle = D.arc(Dp(0), Dp(0.3), Dp(0.5)) + + self.assertTrue(half_circle.is_SVG_compliant()) + + self.assertAlmostEqual(Dp(0.25), half_circle(0.5), delta=0.1) + + points = [Point(2, 5), Point(1, 4), Point(9, 0)] + D.set_points(points) + for p in points: + self.assertAlmostEqual( abs(p-D.center()), D.ray() ) + Dc = Circle.from_points(points) + self.assertAlmostEqual(Dc.center(), D.center()) + self.assertAlmostEqual(Dc.ray(), D.ray()) + + coeffs = (2, 4, 1, -4) + E = Circle.from_coefficients(*coeffs) + def param(x, y): + A, B, C, D = coeffs + return A*x**2 + A*y**2 + B*x + C*y + D + Ec = E.arc(E.center()+Point(E.ray(), 0), E.center()-Point(E.ray(), 0), E.center()+Point(E.ray(), 0) ) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(param(Ec.value_at(t, 0), Ec.value_at(t, 1)), 0) + + E.set(3, 5, 9) + self.assertAlmostEqual(E.center(), Point(3, 5)) + self.assertAlmostEqual(E.ray(), 9) + + E.set_coefficients(*coeffs) + #radius and center from parametric equation + ca = float(coeffs[1])/coeffs[0] + cb = float(coeffs[2])/coeffs[0] + cc = float(coeffs[3])/coeffs[0] + self.assertAlmostEqual( 4*E.ray()**2 , ca**2 + cb**2 -4*cc ) + self.assertAlmostEqual( E.center(), -Point(ca, cb)/2) + + def test_ellipse(self): + #TODO: maybe a bug in arc? get_curve(F) returns different ellipse than F + def get_curve(ellipse): + p = Point(ellipse.ray(0), 0)*Rotate(ellipse.rot_angle()) + return ellipse.arc(ellipse.center()+p, ellipse.center()-p, ellipse.center()+p*(1-1e-7)) + E = Ellipse() + self.assertAlmostEqual(E.center(), Point()) + self.assertAlmostEqual(E.ray(0), 0) + self.assertAlmostEqual(E.ray(1), 0) + + F = Ellipse(Point(), 3, 2, 0) + self.assertAlmostEqual(F.center(), Point()) + self.assertAlmostEqual(F.ray(0), 3) + self.assertAlmostEqual(F.ray(1), 2) + self.assertAlmostEqual(F.rot_angle(), 0) + # x**2/9 + y**2/4 = 1 + self.assertAlmostEqual(F.implicit_form_coefficients()[0], 1/9.0) + self.assertAlmostEqual(F.implicit_form_coefficients()[2], 1/4.0) + self.assertAlmostEqual(F.implicit_form_coefficients()[5], -1) + + coeffs = (1/3.0, 0, 1/16.0, 1, 0, -1/4.0) + G = Ellipse.from_coefficients(*coeffs) + self.assertAlmostEqual(G.center(), Point(-3/2.0, 0)) + self.assertAlmostEqual(G.ray(0), math.sqrt(3)) + self.assertAlmostEqual(G.ray(1), 4) + self.assertAlmostEqual(G.rot_angle(), 0) + + points = [Point(1, 2), Point(2 ,9), Point(0, 3), Point(-3, 8), Point(5, 8)] + G.set_points(points) + coeffs_G = tuple(G.implicit_form_coefficients()) + def paramG(x, y): + A, B, C, D, E, F = coeffs_G + return A*x**2 + B*x*y + C*y**2 + D*x + E*y + F + for p in points: + self.assertAlmostEqual(paramG(p.x, p.y), 0) + + G2 = Ellipse.from_points(points) + coeffs_G2 = tuple(G.implicit_form_coefficients()) + def paramG2(x, y): + A, B, C, D, E, F = coeffs_G2 + return A*x**2 + B*x*y + C*y**2 + D*x + E*y + F + for p in points: + self.assertAlmostEqual(paramG2(p.x, p.y), 0) + + E.set_coefficients(*coeffs_G2) + for a1, a2 in zip(E.implicit_form_coefficients(), G2.implicit_form_coefficients()): + self.assertAlmostEqual(a1, a2) + + H = Ellipse.from_circle(Circle(Point(2, 8), 5)) + self.assertAlmostEqual(H.center(), Point(2, 8)) + self.assertAlmostEqual(H.ray(0), 5) + self.assertAlmostEqual(H.ray(1), 5) + + Ft = F.transformed( Rotate(math.pi/2) ) + self.assertAlmostEqual(F.ray(0), Ft.ray(1)) + self.assertAlmostEqual(F.ray(1), Ft.ray(0)) + +unittest.main() diff --git a/src/cython/test-curves.py b/src/cython/test-curves.py new file mode 100644 index 0000000..8f9b870 --- /dev/null +++ b/src/cython/test-curves.py @@ -0,0 +1,458 @@ +import unittest +import math +from random import randint, uniform + +import cy2geom + +from cy2geom import Angle +from cy2geom import Point, IntPoint +from cy2geom import Line, Ray, Rect +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval + + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +from cy2geom import Linear +from cy2geom import SBasis, SBasisCurve +from cy2geom import Bezier, BezierCurve +from cy2geom import lerp + +from cy2geom import LineSegment, QuadraticBezier, CubicBezier +from cy2geom import HLineSegment, VLineSegment + +from cy2geom import EllipticalArc + +class TestPrimitives(unittest.TestCase): + def test_linear(self): + L = Linear(0, 1) + M = Linear(2) + N = Linear() + self.assertEqual( (L+M), L+2 ) + self.assertEqual( (L-M), L-2 ) + self.assertAlmostEqual(L(0.5), lerp(.5, 0, 1)) + #~ self.assertTrue(N.is_zero()) + self.assertTrue(M.is_constant()) + self.assertTrue(L.is_finite()) + self.assertAlmostEqual(L(0), L.at0()) + self.assertAlmostEqual(L(1), L.at1()) + self.assertAlmostEqual(L.value_at(0.3), L(0.3)) + self.assertTrue( isinstance(M.to_SBasis(), SBasis )) + + self.assertAlmostEqual(L.tri(), L(1) - L(0)) + self.assertAlmostEqual(L.hat(), (L(1) + L(0))/2) + + for i in range(11): + t = i/10.0 + self.assertTrue(L.bounds_exact().Interval.contains(L(t))) + self.assertTrue(L.bounds_fast().Interval.contains(L(t))) + self.assertTrue(L.bounds_local(t-0.05, t+0.05).Interval.contains(L(t))) + self.assertAlmostEqual(lerp(t, 0, 4), t*4) + self.assertAlmostEqual(L(t), cy2geom.reverse(L)(1-t)) + self.assertAlmostEqual( L(t)*t, (L*t)(t) ) + self.assertAlmostEqual( L(t)+t, (L+t)(t) ) + self.assertAlmostEqual( L(t)-t, (L-t)(t) ) + self.assertAlmostEqual( -( L(t) ), (-L)(t) ) + self.assertAlmostEqual( (L/2)(t), L(t)/2 ) + + def test_sBasis(self): + S = SBasis() + T = SBasis(2) + U = SBasis(1, 7) + V = SBasis.from_linear( Linear(2, 8) ) + + self.assertEqual(V[0], Linear(2, 8)) + self.assertEqual(V.back(), Linear(2, 8)) + + #~ self.assertTrue(S.empty()) + self.assertFalse(T.empty()) + + T.pop_back() + self.assertTrue(T.empty()) + + self.assertEqual(S.size(), 0) + self.assertEqual(U.size(), 1) + self.assertEqual((U*V).size(), 2) + + T.resize(1, Linear(2, 3)) + self.assertEqual(T[0], Linear(2, 3)) + T.clear() + self.assertTrue(T.empty()) + #TODO + #~ T.reserve(5) + #~ print T.size() + self.assertEqual(V.at(0), V[0]) + self.assertEqual(V, U+1) + self.assertNotEqual(V, U) + self.assertTrue(T.is_zero()) + self.assertTrue(SBasis(1).is_constant()) + def f(A, B): + return (-A)*(A+B*2.2)*(A*B-B*B/3) + W = f(U, V) + self.assertAlmostEqual(W(0), W.at0()) + self.assertAlmostEqual(W(1), W.at1()) + + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(W(t), W.value_at(t)) + self.assertAlmostEqual(W(t), f(U(t), V(t))) + + vd_UV = (U*V).value_and_derivatives(t, 1) + vd_U = U.value_and_derivatives(t, 1) + vd_V = V.value_and_derivatives(t, 1) + self.assertAlmostEqual( vd_UV[1], vd_U[1]*V(t)+U(t)*vd_V[1] ) + + self.assertAlmostEqual( U(V)(t), U(V(t)) ) + self.assertEqual(T.degrees_of_freedom(), 0) + self.assertEqual(U.degrees_of_freedom(), 2) + + self.assertEqual(T, T.to_SBasis()) + + U2 = SBasis(U(0), U(1)) + U2.resize(10) + self.assertNotEqual(U2, U) + U2.truncate(U.size()) + self.assertEqual(U2, U) + #TODO: normalize() + sL = Linear.sin(Linear(0, 1), 3) + cL = Linear.cos(Linear(0, 1), 3) + sqrtU = SBasis.sqrt( U, 3 ) + rL = Linear.reciprocal(Linear(1,2), 3) + # cy2geom.inverse seems to return nans for degrees > 1 + #~ asin = cy2geom.inverse( cy2geom.sqrt( SBasis(Linear(0, 1)), 3 ), 1) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(sL(t), math.sin(t)) + self.assertAlmostEqual(cL(t), math.cos(t)) + #cy2geom.sqrt is not that precise + self.assertAlmostEqual(sqrtU(t), math.sqrt(U(t)), places = 1) + self.assertAlmostEqual(rL(t), 1/(1+t), places = 1 ) + #~ self.assertAlmostEqual( asin(t), math.asin(t) ) + self.assertAlmostEqual( SBasis.compose(U, V)(t), U(V)(t) ) + self.assertAlmostEqual( SBasis.divide(U, V, 3)(t), U(t)/V(t), places = 1) + + self.assertAlmostEqual( SBasis.derivative(SBasis.integral(W))(t), W(t)) + self.assertAlmostEqual( cy2geom.reverse(W)(t), W(1-t) ) + self.assertAlmostEqual( SBasis.multiply(U, V)(t), (U*V)(t)) + #TODO looks like bug in 2geom + #~ print cy2geom.multiply_add(U, V, W)(t), (U*V+W)(t) + self.assertAlmostEqual( SBasis.multiply_add(U, W, V)(t), (U*W+V)(t)) + + self.assertTrue( SBasis.bounds_exact(U).Interval.contains(U(t)) ) + self.assertTrue( SBasis.bounds_fast(U).Interval.contains(U(t)) ) + self.assertTrue( SBasis.bounds_local(U, OptInterval(t-0.05, t+0.05)).Interval.contains(U(t)) ) + + + for r in SBasis.roots(W): + self.assertAlmostEqual(W(r), 0) + for r in SBasis.roots(W, Interval(0, 0.7)): + self.assertAlmostEqual(W(r), 0) + self.assertTrue(Interval(0, 0.7).contains(r)) + + levels = [0, 3, 22, -21] + for i, roots in enumerate( SBasis.multi_roots(W, levels) ): + level = levels[i] + for r in roots: + self.assertAlmostEqual(W(r), level) + + self.assertEqual(SBasis.valuation(W), 0) + #TODO: why is this still 0? + #~ print cy2geom.valuation(cy2geom.shift(W, 6)) + self.assertEqual( U[0], SBasis.shift(U, 2)[2] ) + + for I in SBasis.level_set(W, 2, tol = 1e-7): + self.assertAlmostEqual( W(I.mid()), 2 ) + for I in SBasis.level_set(W, Interval(0, 1), tol = 1e-7, vtol = 1e-7): + self.assertTrue( 0 <= W(I.begin()) <= 1 ) + self.assertTrue( 0 <= W(I.mid()) <= 1 ) + self.assertTrue( 0 <= W(I.end()) <= 1 ) + + def test_bezier(self): + B = Bezier() + C = Bezier(2) + D = Bezier(2, 4) + E = Bezier(1, 3, 9) + F = Bezier(-2, 5, -1, 2) + self.assertTrue( B.is_zero() ) + self.assertTrue( C.is_constant() ) + self.assertTrue( D.is_finite() ) + C.clear() + self.assertEqual(D.degree(), 1) + self.assertEqual(E.at0(), 1) + self.assertEqual(E.at1(), 9) + self.assertEqual(E[2], 9) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual( D(t), lerp(t, 2, 4) ) + self.assertAlmostEqual( D(t), D.value_at(t)) + self.assertAlmostEqual( D.value_and_derivatives(t, 0)[0], D(t) ) + self.assertAlmostEqual( D.value_and_derivatives(t, 1)[1], Bezier.derivative(D)(t) ) + self.assertAlmostEqual( Bezier.integral(D).value_and_derivatives(t, 1)[1], D(t) ) + #~ self.assertAlmostEqual( D.elevate_degree().reduce_degree()(t), D(t) ) + self.assertAlmostEqual( (D+2)(t), D(t)+2 ) + self.assertAlmostEqual( (D-1)(t), D(t)-1 ) + self.assertAlmostEqual( (D*2)(t), D(t)*2 ) + self.assertAlmostEqual( (D/4)(t), D(t)/4 ) + self.assertTrue( Bezier.bounds_fast(F).Interval.contains(F(t)) ) + self.assertTrue( Bezier.bounds_exact(F).Interval.contains(F(t)) ) + self.assertTrue( Bezier.bounds_local(F, OptInterval(t-0.05, t+0.05)).Interval.contains(F(t)) ) + for r in F.roots(): + self.assertAlmostEqual(F(r), 0) + #TODO: bug in 2geom? + #~ for r in F.roots(Interval(0.1, 0.8)): + #~ self.assertAlmostEqual(F(r), 0) + #~ self.assertTrue( 0.1 <= r <= 0.8 ) + self.assertIsInstance(F.forward_difference(1), Bezier) + self.assertIsInstance(F.elevate_degree(), Bezier) + self.assertIsInstance(E.reduce_degree(), Bezier) + #F.reduce_degree() fails with + # *** glibc detected *** python2: malloc(): memory corruption: + self.assertIsInstance(F.elevate_to_degree(4), Bezier) + self.assertIsInstance(F.deflate(), Bezier) + S = F.to_SBasis() + self.assertIsInstance(S, SBasis) + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(S(t), F(t)) + + def curve(self, C): + self.assertAlmostEqual(C.initial_point(), C(0)) + self.assertAlmostEqual(C.final_point(), C.point_at(1)) + #Doesn't have to be true + #~ if C.length() > 0.01: + #~ self.assertFalse(C.is_degenerate()) + + if C.is_degenerate(): + #trivial special case + return + + for i in range(11): + t = i/10.0 + self.assertAlmostEqual(C(t).x, C.point_at(t).x) + self.assertAlmostEqual(C(t).y, C.value_at(t, 1)) + self.assertEqual( C(t), C.point_and_derivatives(t, 1)[0] ) + self.assertTrue( C.bounds_exact().contains(C(t)) ) + self.assertTrue( C.bounds_fast().contains(C(t)) ) + #TODO why this works only with degree = 0? + if C.bounds_local(OptInterval(t-0.05, t+0.05), 0 + ) and ( + C.bounds_local(OptInterval(t-0.05, t+0.05), 0).Rect.area() > 1e-10): + #ruling out too small rectangles, they have problems with precision + self.assertTrue( C.bounds_local( OptInterval(t-0.05, t+0.05), 0 ).Rect.contains(C(t))) + D = C.duplicate() + + D.set_initial(Point()) + self.assertAlmostEqual(D.initial_point(), Point()) + + D.set_final(Point(1, 1)) + self.assertAlmostEqual(D.final_point(), Point(1, 1)) + + A = Affine( uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10), + uniform(-10, 10)) + E = C.transformed(A) + for i in range(11): + t = i/10.0 + # self.assertAlmostEqual( E(t), C(t)*A ) + G1 = C.portion(0.2, 0.8) + G2 = C.portion( interval=Interval(2, 8)/10 ) + self.assertAlmostEqual( G1(0), C(0.2) ) + self.assertAlmostEqual( G2(0.5), C( lerp(0.5, 0.2, 0.8) )) + self.assertAlmostEqual( G1(1), G2(1) ) + + for i in range(11): + t = i/10.0 + self.assertAlmostEqual( C.reverse()(t), C(1-t) ) + self.assertAlmostEqual( C.point_and_derivatives(0.3, 1)[1], C.derivative()(0.3) ) + + self.assertAlmostEqual( C.nearest_time(C(0)), 0 ) + self.assertAlmostEqual( C( C.nearest_time(C(0.5), interval=Interval(0.2, 0.5)) ), C(0.5) ) + self.assertAlmostEqual( C( C.nearest_time(C(0.5), 0.2, 0.5) ), C(0.5) ) + for p in C.all_nearest_times( C(0), 0, 1): + self.assertEqual(C(p), C(0)) + for p in C.all_nearest_times( C(1), interval=Interval(0, 1)): + self.assertEqual(C(p), C(1)) + for r in C.roots(0, 0): + self.assertAlmostEqual(C.value_at(r, 0), 0) + + self.assertGreaterEqual(C.length(), abs(C(1) - C(0))) + self.assertEqual(C.winding(Point()), int(C.winding(Point())) ) + self.assertAlmostEqual( C.unit_tangent_at(0.5), + Point.unit_vector(C.derivative()(0.5)) ) + self.assertTrue(isinstance(C.to_SBasis()[0], SBasis)) + + def test_sBasisCurve(self): + S = SBasisCurve(SBasis(0, 2), SBasis(3, 7)*SBasis(1, 8)) + a = SBasis(3, 9)*SBasis(4, 6) + b = SBasis(2, 0) + c = a(b) + self.curve(S) + self.curve(S.derivative()) + self.curve(S.reverse()) + self.curve(S.transformed( Scale(4) )) + self.curve(S.transformed( Zoom(9, Translate(3, 6)) )) + self.curve(SBasisCurve(a*b*c, a+b+c)) + self.curve(S.derivative().derivative()) + + def test_bezierCurve(self): + B = BezierCurve.create( [ Point(0, 5), Point(3, 65), Point(-3, 2), Point(1, 9) ] ) + C = BezierCurve.create( [ Point(0,1), Point(1, 0) ] ) + self.curve(B) + self.curve(C) + self.curve(C.reverse()) + self.curve(B.portion(0, 2)) + self.curve(B.transformed(Zoom(9, Translate(3, 6)))) + self.curve(B.derivative()) + + def ntest_lineSegment(self): + L = LineSegment(Point(2, 8), Point(1, 9)) + K = LineSegment.from_beziers(Bezier(2, 8), Bezier(-1, 9)) + self.curve(L) + self.curve(K) + self.curve(L.reverse()) + self.curve(L.portion(Interval(0.2, 0.4))) + self.curve(L.subdivide(0.3)[0]) + self.curve(L.subdivide(0.3)[1]) + self.curve(L.derivative()) + self.curve(L.transformed(Scale(30)*Translate(3, 9))) + + self.curve(LineSegment()) + + def test_quadraticBezier(self): + Q = QuadraticBezier(Point(2, 8), Point(1, 9), Point(-2, 3)) + R = QuadraticBezier.from_beziers(Bezier(2, 8, 4), Bezier(-1, 9, 9)) + self.curve(Q) + self.curve(R) + self.curve(Q.reverse()) + self.curve(Q.portion(interval=Interval(0.1, 0.9))) + self.curve(Q.subdivide(0.8)[0]) + self.curve(Q.subdivide(0.8)[1]) + self.curve(Q.derivative()) + self.curve(Q.transformed(Scale(-3)*Translate(4, 8))) + + self.curve(QuadraticBezier()) + + def test_cubicBezier(self): + C = CubicBezier(Point(2, 0), Point(-1, 2.9), Point(-2, 3), Point(3, 1)) + D = CubicBezier.from_beziers(Bezier(2, 8, 4, 7), Bezier(-1, 9, 9, 8)) + print 343 + self.curve(C) + self.curve(D) + self.curve(C.reverse()) + #Some kind of numerical instability imo + #~ self.curve(C.portion(Interval(0.1, 0.9))) + self.curve(C.subdivide(0.8)[0]) + self.curve(C.subdivide(0.8)[1]) + self.curve(C.derivative()) + self.curve(C.transformed(Scale(-3)*Translate(4, 8))) + + self.curve(CubicBezier()) + + def test_hLineSegment(self): + H = HLineSegment(Point(3, 9), Point(9, 9)) + I = HLineSegment(Point(1, 3), Point(92, 3)) + J = HLineSegment.from_point_length( Point(2, 4), 1) + self.curve( H ) + self.curve( I ) + self.curve( J ) + self.curve( H.portion(0, .25) ) + self.curve( H.derivative() ) + self.curve( H.transformed(Rotate(20)) ) + self.curve( HLineSegment() ) + self.curve( I.reverse() ) + map(self.curve, I.subdivide(0.8)) + + self.assertAlmostEqual(I.get_Y(), 3) + J.set_Y(2) + J.set_initial_X(0) + J.set_final_X(1) + self.assertAlmostEqual( J(0), Point(0, 2) ) + self.assertAlmostEqual( J(1), Point(1, 2) ) + + def test_vLineSegment(self): + V = VLineSegment(Point(2, 9), Point(2, 6)) + W = VLineSegment(Point(1, 2), Point(1, 8)) + X = VLineSegment.from_point_length( Point(2, 4), 1) + #~ self.curve( V ) + #~ self.curve( W ) + #~ self.curve( X ) + #~ self.curve( V.portion(0, .25) ) + #~ self.curve( V.derivative() ) + #~ self.curve( V.transformed(Rotate(20)) ) + #~ self.curve( VLineSegment() ) + #~ self.curve( W.reverse() ) + #~ map(self.curve, W.subdivide(0.8)) + #~ + #~ self.assertAlmostEqual(I.get_Y(), 3) + #~ X.set_Y(2) + #~ X.set_initialX(0) + #~ X.set_finalX(1) + #~ self.assertAlmostEqual( X(0), Point(0, 2) ) + #~ self.assertAlmostEqual( X(1), Point(1, 2) ) + #~ print V(0.5) + #~ print V.nearest_time(V(0.5), 0.1, 0.4 ) + #~ print V.nearest_time(V(0.5), Interval(0.2, 0.5)) + #~ print V(0.5), V(0.2) + #TODO: + #this is likely a bug in 2geom, following code + + #~ VLineSegment V(Point(0, 0), 2); + #~ printf("%f\n", V.nearest_time(V(0.5), 0.2, 0.5)); + + #prints + #0.2 + + def test_ellipticalArc(self): + E = EllipticalArc() + self.curve(E) + F = EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)) + + self.assertTrue(F.sweep()) + self.assertTrue(F.large_arc()) + self.assertAlmostEqual(F.chord()(0), Point()) + self.assertAlmostEqual(F.chord()(1), Point(1, 1)) + + F.set_extremes(Point(1, 1), Point(-1, 1)) + self.assertAlmostEqual(F.initial_point(), Point(1, 1)) + self.assertAlmostEqual(F.final_point(), Point(-1, 1)) + self.assertEqual(F.initial_angle(), F.angle_at(0)) + self.assertEqual(F.final_angle(), F.angle_at(1)) + self.assertTrue(F.contains(F.angle_at(0.5))) + + G = EllipticalArc(Point(), 1, 1, 0, True, True, Point(2, 0)) + for i in range(11): + t = i/10.0 + print G(t) + self.assertAlmostEqual(G.extent(), math.pi) + self.assertAlmostEqual(G.extent(), G.sweep_angle()) + self.assertAlmostEqual(float(G.angle_at(0.5)), -math.pi/2) + + self.assertAlmostEqual(Point(1, 1), G.rays()) + self.assertAlmostEqual(1, G.ray(1)) + self.assertAlmostEqual(0, float(G.rotation_angle())) + + self.assertAlmostEqual(G.extent(), G.angle_interval().extent()) + + self.assertAlmostEqual(G.center(), Point(1, 0)) + #unit half-circle + U = EllipticalArc(Point(1, 0), 1, 1, 0, True, True, Point(-1, 0)) + + G.set(Point(), 1, 1, 0, True, False, Point(1, 0)) + + A = G.unit_circle_transform() + + self.assertAlmostEqual( G(0.5), U.transformed(A)(0.5) ) + self.assertAlmostEqual( G.value_at_angle(G.angle_at(0.32), 0), G.value_at(0.32, 0) ) + + self.assertTrue(G.contains_angle(Angle(math.pi/4))) + self.assertFalse(G.is_SVG_compliant()) + #~ self.curve(F) + #TODO: + #F.point_and_derivatives(t, 1)[0] differs from F(0) and F.bounds_exact, + #F.bounds_fast doesn't contain F(1) + +unittest.main() diff --git a/src/cython/test-path.py b/src/cython/test-path.py new file mode 100644 index 0000000..6ad6af4 --- /dev/null +++ b/src/cython/test-path.py @@ -0,0 +1,218 @@ +import unittest +import math +from random import randint, uniform + +import cy2geom + +from cy2geom import Point, IntPoint +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval + +from cy2geom import Affine +from cy2geom import Translate, Scale, Rotate, VShear, HShear, Zoom +from cy2geom import Eigen + +from cy2geom import Curve +from cy2geom import Linear +from cy2geom import SBasis, SBasisCurve +from cy2geom import Bezier, BezierCurve + +from cy2geom import LineSegment, QuadraticBezier, CubicBezier +from cy2geom import HLineSegment, VLineSegment + +from cy2geom import EllipticalArc + +from cy2geom import Path + +#TODO! move drawing elsewhere, it nice to see paths, but is not very suitable for automatic testing +draw = False + +try: + import utils +except ImportError: + print "No drawing with Tk" + draw = False + +class TestPrimitives(unittest.TestCase): + def curves_equal(self, C1, C2): + for i in range(101): + t = i/100.0 + self.assertAlmostEqual(C1(t), C2(t)) + def path(self, P): + for curve in P: + self.assertIsInstance(curve, Curve) + + self.assertAlmostEqual(P(0), P.front()(0)) + self.curves_equal(P.front(), P[0]) + + self.curves_equal(P.back_default(), P[P.size_default()-1]) + self.curves_equal(P.back_open(), P.back()) + self.assertEqual(P.size_open(), P.size()) + + self.assertFalse(P.empty() ^ (P.size()==0)) + + exact = P.bounds_exact().Rect + exact.expand_by(1e-5) + + fast = P.bounds_fast().Rect + fast.expand_by(1e-5) + A1 = Affine(3, 1, 8, 3, 9, 9) + A2 = Rotate(0.231) + + for i in range(100 * P.size_open() + 1): + t = i/100.0 + self.assertTrue(exact.contains(P(t))) + self.assertTrue(fast.contains(P(t))) + self.assertAlmostEqual( (P*A1)(t) , P(t)*A1 ) + self.assertAlmostEqual( (P*A2)(t) , P(t)*A2 ) + + self.assertAlmostEqual(P(t), P.point_at(t)) + self.assertAlmostEqual(P(t).x, P.value_at(t, 0)) + self.assertAlmostEqual(P(t).y, P.value_at(t, 1)) + + if P.closed(): + self.curves_equal(P.back_default(), P.back_closed()) + self.assertEqual(P.size_default(), P.size_closed()) + else: + self.curves_equal(P.back_default(), P.back_open()) + self.assertEqual(P.size_default(), P.size_open()) + + for i in range(10): + for root in P.roots(i, 0): + if root < P.size_default(): + self.assertAlmostEqual(P.value_at(root, 0), i) + for root in P.roots(i, 1): + if root < P.size_default(): + self.assertAlmostEqual(P.value_at(root, 1), i) + + for t in P.all_nearest_times(P(0)): + self.assertAlmostEqual(P(t), P(0)) + self.assertAlmostEqual(min(P.all_nearest_times( P(0) )), 0) + self.assertAlmostEqual(P.nearest_time(P(0), 0, 0.2), 0) + self.assertEqual( len(P.nearest_time_per_curve(Point())), P.size_default() ) + + t, distSq = P.nearest_time_and_dist_sq(Point(-1, -1), 0, P.size()) + self.assertAlmostEqual(distSq**0.5, abs(P(t)-Point(-1, -1)) ) + + self.assertAlmostEqual(P.portion(0.3, 0.4)(0), P(0.3)) + self.assertAlmostEqual( P.portion( interval=Interval(P.size(), P.size() * 2) / 3 )(0), + P(P.size()/3.0)) + + self.assertAlmostEqual(P(0.23), P.reverse()(P.size()-0.23)) + + self.assertAlmostEqual(P.initial_point(), P(0)) + self.assertAlmostEqual(P.final_point(), P(P.size())) + def test_path(self): + a = Path() + a.append_curve( CubicBezier( Point(-7, -3), Point(2, 8), Point(2, 1), Point(-2, 0) ) ) + + self.assertEqual(a.size(), 1) + self.assertFalse(a.closed()) + self.path(a) + + a.close(True) + self.assertTrue(a.closed()) + self.path(a) + + a.close(False) + a.append_curve( LineSegment(a.final_point(), Point(3, 5)) ) + self.assertEqual(a.size(), 2) + self.path(a) + + a.append_SBasis( SBasis(3, 6)*SBasis(1, 0), SBasis(5, 2)) + self.path(a) + + a.append_curve(EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)), Path.STITCH_DISCONTINUOUS) + #Stitching adds new segment + self.assertEqual(a.size(), 5) + + b = Path() + for c in a: + b.append_curve(c) + + #TODO: This fails with STITCH_DISCONTINUOUS, but also does so in C++, so + #it's either correct behaviour or bug in 2geom + #~ self.path(b) + + b.insert(2, LineSegment(b[2-1](1), b[2](0))) #, Path.STITCH_DISCONTINUOUS) + self.curves_equal(LineSegment(b[2-1](1), b[2](0)), b[2]) + #TODO! fails on root finding + #self.path(b) + + b.set_initial(a[2](1)) + b.set_final(a[3](0)) + + a.insert_slice(3, b, 0, b.size()) + self.assertEqual(a.size(), b.size()*2-1) + + for i in range(b.size()): + self.curves_equal(a[3+i], b[i]) + + #Looks like bug: +# A = Path() +# A.append_curve( CubicBezier( Point(-7, -3), Point(2, 8), Point(2, 1), Point(-2, 0) ) ) +# A.append_curve(EllipticalArc(Point(), 1, 2, math.pi/6, True, True, Point(1, 1)), Path.STITCH_DISCONTINUOUS) +# print A.roots(0, 1) + + #Roots are [1.0, 2.768305708350847, 3.25], Point at second root is + #Point (2.32, -0.48) + #and third root is > 3 - it corresponds to root on closing segment, but A is open, + #and computing A(3.25) results in RangeError - this might be bug or feature. + + self.path(a.portion(0.232, 3.12)) + self.path(a.portion( interval=Interval(0.1, 4.7) )) + self.path(a.portion(0.232, 3.12).reverse()) + + b.clear() + self.assertTrue(b.empty()) + + aa = Path() + for c in a: + aa.append_curve(c) + + a.erase(0) + self.assertEqual(a.size(), aa.size() - 1) + self.assertAlmostEqual(a(0), aa(1)) + + a.erase_last() + self.assertEqual(a.size(), aa.size() - 2) + self.assertAlmostEqual(a.final_point(), aa[aa.size()-2](1)) + + a.replace(3, QuadraticBezier(a(3), Point(), a(4))) + self.assertEqual(a.size(), aa.size() - 2) + + cs = [LineSegment(Point(-0.5, 0), Point(0.5, 0)).transformed( Rotate(-math.pi/3 * i)*Translate(Point(0, math.sqrt(3)/2)*Rotate(-math.pi/3 * i)) ) for i in range(6)] + + hexagon = Path.fromList(cs, stitching = Path.STITCH_DISCONTINUOUS, closed = True) + + if draw: + utils.draw(hexagon, scale = 100) + + #to = 5 because each corner contains one stitching segment + half_hexagon = Path.fromPath(hexagon, fr = 0, to = 5) + if draw: + utils.draw(half_hexagon, scale = 100) + + half_hexagon.replace_slice(1, 5, LineSegment(half_hexagon(1), half_hexagon(5))) + self.assertEqual(half_hexagon.size(), 2) + self.assertAlmostEqual(half_hexagon(1.5), Point(0.5, 0)) + + half_hexagon.stitch_to(half_hexagon(0)) + self.assertAlmostEqual(half_hexagon(2.5), Point()) + + a.start(Point(2, 2)) + a.append_SBasis( SBasis(2, 6), SBasis(1, 5)*SBasis(2, 9) ) + self.assertAlmostEqual(a(1), Point(6, 5*9)) + + l = Path.fromList([QuadraticBezier(Point(6, 5*9), Point(1, 2), Point(-2, .21))]) + a.append_path(l) + self.assertAlmostEqual(a.final_point(), l.final_point()) + + k = Path.fromList([QuadraticBezier(Point(), Point(2, 1), Point(-2, .21)).reverse()]) + k.append_portion_to(l, 0, 0.3) + self.assertAlmostEqual(l.final_point(), k(0.3)) + + def test_read_svgd(self): + p = Path.read_svgd("../toys/spiral.svgd") + if draw: + utils.draw(p[0], scale=0.4) +unittest.main() diff --git a/src/cython/test-primitives.py b/src/cython/test-primitives.py new file mode 100644 index 0000000..0edf218 --- /dev/null +++ b/src/cython/test-primitives.py @@ -0,0 +1,288 @@ +import unittest +from math import pi, sqrt + +import cy2geom +from cy2geom import Angle, AngleInterval, Point, IntPoint, Line, Ray +from cy2geom import LineSegment, Curve + +class TestPrimitives(unittest.TestCase): + + def test_angle(self): + self.assertAlmostEqual(Angle.rad_from_deg(45), pi/4) + self.assertAlmostEqual(Angle.deg_from_rad(pi/6), 30) + + p = Point(1, sqrt(3)) + alpha = Angle.from_Point(p) + self.assertAlmostEqual(alpha.degrees(), 60) + + beta = Angle.from_radians(pi/5) + gamma = Angle.from_degrees(36) + self.assertAlmostEqual(beta.radians0(), gamma.radians0()) + self.assertTrue(beta==gamma) + omega = Angle.from_degrees_clock(0) + self.assertAlmostEqual(omega.radians(), pi/2) + + delta = Angle(-pi * 0.5) + self.assertAlmostEqual(delta.degrees(), -90) + self.assertAlmostEqual(delta.radians0(), 1.5*pi) + #degreesClock roughly means [ 90 - Angle.degrees() ] mod 360 + self.assertAlmostEqual(delta.degrees_clock(), 180) + + self.assertAlmostEqual( + (beta + gamma).radians(), + beta.radians()+gamma.radians() ) + self.assertAlmostEqual( (beta - gamma).degrees(), 0) + + def test_angleInterval(self): + A = AngleInterval(Angle(pi/6), Angle(pi/4)) + B = AngleInterval( 0, pi/4, cw = True ) + self.assertEqual(A(0), Angle(pi/6)) + self.assertEqual(A(0.5), A.angle_at(0.5)) + self.assertEqual(A(0), A.initial_angle()) + self.assertEqual(B(1), B.final_angle()) + self.assertFalse(B.is_degenerate()) + self.assertTrue(B.contains(Angle(pi/6))) + self.assertTrue(A.contains(Angle(pi))) + + self.assertAlmostEqual( B.extent(), pi/4 ) + def test_point(self): + p = Point(3, 4) + q = Point(8, 16) + p_inf = Point(float('inf'), 1) + #y axis points downwards + p_ccw = Point(4, -3) + + self.assertAlmostEqual(p.length(), 5) + self.assertAlmostEqual(p.ccw(), p_ccw) + self.assertAlmostEqual(p_ccw.cw(), p) + self.assertAlmostEqual(p[0], 3) + self.assertAlmostEqual(p[1], 4) + + self.assertFalse(p_inf.isFinite()) + self.assertTrue(p.isFinite()) + self.assertFalse(p.isNormalized()) + self.assertTrue((p/p.length()).isNormalized()) + self.assertFalse(p.isZero()) + self.assertTrue((p*0).isZero()) + + self.assertTrue( (p + p.ccw().ccw()).isZero) + self.assertAlmostEqual( (q-p).length(), 13) + + self.assertGreater(q, p) + self.assertGreaterEqual(q, p) + self.assertEqual(p, p) + self.assertNotEqual(p, q) + self.assertLess( Point(1, 1), Point(1, 2) ) + self.assertLessEqual(p, p) + + self.assertTrue( Point.are_near( + Point.polar(pi/4, sqrt(2)), + Point(1, 1) )) + self.assertAlmostEqual(sqrt(2), Point.L2(Point(1, 1))) + self.assertAlmostEqual(2, Point.L2sq(Point(1, 1))) + self.assertAlmostEqual( Point.middle_point(Point(), q), q/2 ) + self.assertAlmostEqual( Point.rot90(p), p.cw() ) + self.assertAlmostEqual( + Point.lerp(0.2, Point(), Point(3,4)).length(), + 1) + self.assertAlmostEqual(Point.dot(p, p_ccw), 0) + self.assertAlmostEqual(Point.dot(p, p_inf), float('inf')) + self.assertAlmostEqual(Point.dot(p, q), 88) + #TODO this might be implemented incorrectly in lib2geom! + self.assertAlmostEqual(Point.cross(p, q), -16) + + self.assertAlmostEqual(Point.distance(p, q), 13) + self.assertAlmostEqual(Point.distanceSq(p, p_ccw), 50) + self.assertAlmostEqual(Point.unit_vector(p), p/5) + + self.assertAlmostEqual(Point.L1(p), 7) + self.assertAlmostEqual(Point.L1(p_inf), float('inf')) + self.assertAlmostEqual(Point.LInfty(q), 16) + self.assertAlmostEqual(Point.LInfty(p_inf), float('inf')) + + self.assertTrue(Point.is_zero(Point())) + self.assertFalse(Point.is_zero(p)) + self.assertTrue(Point.is_unit_vector(p/5)) + self.assertFalse(Point.is_unit_vector(q)) + + self.assertAlmostEqual(Point.atan2(Point(1, 1)), pi/4) + self.assertAlmostEqual(Point.angle_between(p, p_ccw), -pi/2) + self.assertAlmostEqual(Point.abs(-p), p) + #TODO I have no idea what should this function do + # self.assertAlmostEqual( + # Point.constrain_angle(Point(1, 0), Point(0, 1), 1, Point(sqrt(2)/2, sqrt(2)/2)), + # + # )) + + def test_intPoint(self): + p = Point(4.89, 3.21) + self.assertEqual(p.round(), IntPoint(5, 3)) + self.assertEqual(p.floor(), IntPoint(4, 3)) + self.assertEqual(p.ceil(), IntPoint(5, 4)) + + self.assertEqual(p.ceil().x, 5) + self.assertEqual(p.floor().y, 3) + self.assertEqual(IntPoint(), p.floor()-p.floor()) + + a = IntPoint(2, -5) + b = IntPoint(5, 3) + self.assertEqual(IntPoint(7, -2), a+b) + self.assertEqual(IntPoint(3, 8), b-a) + self.assertGreater(b, a) + self.assertGreaterEqual(b, b) + self.assertNotEqual(a, b) + + def test_line(self): + + l = Line(Point(), pi/4) + self.assertAlmostEqual( l.origin(), Point() ) + self.assertAlmostEqual( l.versor(), Point(1, 1)/sqrt(2) ) + self.assertAlmostEqual( l.angle(), pi/4 ) + + k = Line.from_points(Point(), Point(2, 1)) + self.assertFalse(k.is_degenerate()) + self.assertFalse(Line().is_degenerate()) + self.assertAlmostEqual( l.point_at(sqrt(2)), Point(1,1) ) + self.assertAlmostEqual( + k.point_at(43), + Point(k.value_at(43, 0), k.value_at(43, 1))) + self.assertAlmostEqual(k.time_at(Point(4, 2)), sqrt(20)) + self.assertAlmostEqual( + k.time_at_projection(Point(4, 2) + Point(2, -4)), + sqrt(20)) + self.assertAlmostEqual( + k.point_at(k.nearest_time(Point(4, 2) + Point(2, -4))), + Point(4,2)) + self.assertAlmostEqual( + k.time_at_projection(Point(3, 3)), + -k.reverse().time_at_projection(Point(3, 3))) + self.assertAlmostEqual( k.derivative().origin(), k.versor()) + self.assertAlmostEqual(k.normal(), k.versor().cw()) + + roots = k.roots( 3, 0 ) + for root in roots: + self.assertAlmostEqual( k.value_at(root, 0), 3) + + self.assertAlmostEqual(l.normal(), l.normal_and_dist()[0]) + self.assertAlmostEqual(Line.distance(Point(), l), l.normal_and_dist()[1]) + + self.assertAlmostEqual(Line.distance(Point(-1, 1), l), sqrt(2)) + self.assertTrue(Line.are_near(Point(0), l)) + self.assertFalse(Line.are_near(Point(1, 1), k)) + self.assertTrue(Line.are_near(Point(1, 1), k, 2)) + + p = Line(Point(1, 1)) + p_orto = Line(Point(2, 3), pi/2) + p_para = Line(Point(2, 3)) + p_same = Line.from_points(Point(1, 1), Point(5, 1)) + + self.assertTrue(Line.are_orthogonal(p, p_orto)) + self.assertFalse(Line.are_orthogonal(p, p_para)) + self.assertTrue(Line.are_parallel(p, p_para)) + self.assertFalse(Line.are_parallel(p, p_orto)) + self.assertTrue(Line.are_same(p, p_same)) + self.assertFalse(Line.are_same(p, p_para)) + + self.assertTrue(Line.are_collinear( + Point(1,1), + Point(2, 3), + Point(4, 7))) + self.assertAlmostEqual(Line.angle_between(p, p_orto), pi/2) + + m = Line.from_normal_distance(Point(1, -1), 1) + self.assertAlmostEqual(m.angle(), pi/4) + + m = Line.from_LineSegment( LineSegment( Point(2, 2), Point(4, 4) ) ) + self.assertAlmostEqual(m.angle(), pi/4) + + m = Line.from_Ray( Ray(Point(2, 3), 0.2) ) + self.assertAlmostEqual(m.angle(), 0.2) + self.assertAlmostEqual(m.origin(), Point(2, 3)) + + self.assertIsInstance(m.portion(2, 4), Curve) + self.assertAlmostEqual(m.portion(2, 4)(0), m.point_at(2)) + + self.assertIsInstance(m.segment(1, 5), LineSegment) + self.assertAlmostEqual(m.segment(1, 5)(1), m.point_at(5)) + + self.assertAlmostEqual(m.ray(4).origin(), m.point_at(4)) + + m.set_origin(Point()) + self.assertAlmostEqual(m.origin(), Point()) + + m.set_angle(0.2) + self.assertAlmostEqual(m.angle(), 0.2) + + m.set_versor(Point()) + self.assertTrue(m.is_degenerate()) + + m.set_points(Point(2, 9), Point(1, 8)) + self.assertAlmostEqual(m.versor(), Point.unit_vector(Point(1, 8) - Point(2, 9))) + + def test_ray(self): + r = Ray(Point(1,1), pi/4) + self.assertAlmostEqual(r.origin(), Point(1, 1)) + self.assertAlmostEqual(r.versor(), Point(1, 1)/sqrt(2)) + self.assertAlmostEqual(r.angle(), pi/4) + + r.set_origin(Point(4, 3)) + #TODO this should maybe normalize the versor! + r.set_versor(Point(1, -1)/sqrt(2)) + self.assertAlmostEqual(r.origin(), Point(4, 3)) + self.assertAlmostEqual(r.versor(), Point(1, -1)/sqrt(2)) + self.assertAlmostEqual(r.angle(), -pi/4) + + r.set_points(Point(1, 1), Point(1, 3)) + self.assertFalse(r.is_degenerate()) + self.assertFalse(Ray().is_degenerate()) + self.assertAlmostEqual(r.point_at(4), Point(1, 5)) + + #TODO I think this should be expected behaviour +# self.assertAlmostEqual( +# r.pointAt(-3), +# Point(1, 1))) + self.assertAlmostEqual(r.value_at(4, 0), 1) + self.assertAlmostEqual(r.value_at(4, 1), 5) + + roots = r.roots( 3, 1 ) + for root in roots: + self.assertAlmostEqual( r.value_at(root, 1), 3) + + self.assertAlmostEqual( + r.point_at(3) - r.origin(), + r.origin()-r.reverse().point_at(3)) + + self.assertAlmostEqual(Ray.distance(Point(), r), sqrt(2)) + self.assertAlmostEqual(Ray.distance(Point()+r.versor(), r), 1) + + self.assertTrue(Ray.are_near(Point(), r, 2)) + self.assertFalse(Ray.are_near(Point(), r)) + self.assertTrue(Ray.are_same(r, r)) + + q = Ray(r.origin(), r.angle()) + self.assertTrue(Ray.are_same(r, q)) + + q.set_origin(r.origin()+Point(0, 1)) + self.assertFalse(Ray.are_same(r, q)) + #TODO shouldn't this really be 0? + self.assertAlmostEqual(Ray.angle_between(r, q), 2*pi) + + q.set_versor(Point(1, 0)) + q.set_origin(r.origin()) + self.assertAlmostEqual( + Point(1, 1)/sqrt(2), + Ray.make_angle_bisector_ray(q, r).versor()) + + q.set_angle(pi/7) + self.assertAlmostEqual(q.angle(), pi/7) + + self.assertIsInstance(q.portion(2, 4), Curve) + self.assertAlmostEqual(q.portion(2, 4)(0), q.point_at(2)) + + self.assertIsInstance(q.segment(1, 5), LineSegment) + + self.assertAlmostEqual(q.segment(1, 5)(1), q.point_at(5)) + + +unittest.main() + diff --git a/src/cython/test-rectangle.py b/src/cython/test-rectangle.py new file mode 100644 index 0000000..51f3c50 --- /dev/null +++ b/src/cython/test-rectangle.py @@ -0,0 +1,601 @@ +import unittest +from math import pi, sqrt +from random import randint + +import cy2geom + +from cy2geom import Point, IntPoint + +from cy2geom import Interval, IntInterval, OptInterval, OptIntInterval +from cy2geom import GenericInterval, GenericOptInterval + +from cy2geom import Rect, OptRect, IntRect, OptIntRect +from cy2geom import GenericRect + +from fractions import Fraction + + +class TestPrimitives(unittest.TestCase): + def interval_basic(self, I, J): + #for simplicity + self.assertTrue(I.min() >= 0) + self.assertTrue(J.min() >= 0) + a = I.min() + b = I.max(); + self.assertAlmostEqual(I.middle(), (a+b)/2); + self.assertAlmostEqual(I.extent(), (b-a)); + if a != b: + self.assertFalse(I.is_singular()) + else: + self.assertTrue(I.is_singular()) + + I.expand_by(a) + self.assertAlmostEqual(I.min(), 0); + self.assertAlmostEqual(I.max(), a+b) + I.set_min(a) + I.set_max(b) + + self.assertTrue(I.contains(a+ (b-a)/3 )) + self.assertTrue(I.contains(a)) + self.assertTrue(I.contains_interval(I)) + + if (not I.is_singular()) or I.min() != 0 : + pass + self.assertFalse(I.contains_interval(I+I)) + self.assertFalse(I.contains(a-1)) + + c = J.min() + d = J.max() + self.assertAlmostEqual( (I+J).min(), a+c ) + self.assertAlmostEqual((I|J).min(), min(a, c)) + J.set_min(a+2) + J.set_max(b+2) + self.assertEqual(I+2, J) + I.expand_to(2*b) + self.assertAlmostEqual(I.max(), 2*b) + + def test_interval(self): + I = Interval(1.2, 5) + J = Interval(0, 0.3) + self.interval_basic(I, J) + + self.assertTrue(I.interior_contains(I.middle())) + self.assertFalse(I.interior_contains(I.min())) + self.assertFalse(I.interior_contains_interval(I)) + self.assertTrue(I.interior_contains_interval(Interval(I.min()+1, I.max()-1))) + + self.assertTrue(I.interior_intersects(I)) + self.assertFalse(I.interior_intersects(-I)) + p = [1, 2, 3.442, 3] + K = Interval.from_list(p) + self.assertAlmostEqual(K.max(), max(p)) + self.assertAlmostEqual((K+Interval(1.0)).min(), min(p)+1) + L = Interval(10/3.0) + for i in range(3): + K+=L + self.assertAlmostEqual(K.max(), max(p)+10) + + #TODO This 2geom behaviour is a bit strange + self.assertEqual(Interval(3.0)|Interval(5.0), + Interval(3.0, 5.0)) + self.assertAlmostEqual((K-L).max(), (K-10/3.0).max()) + + self.assertAlmostEqual((K*3.4).max(), 3.4*K.max()) + self.assertAlmostEqual((K/3).extent(), K.extent()/3) + + def test_optInterval(self): + I = OptInterval(2.2, 9.3) + J = Interval(3, 13) + K = OptInterval.from_Interval(J) + self.assertEqual(K.Interval, J) + self.interval_basic(K.Interval, I.Interval) + + L = OptInterval() + + self.assertFalse(L) + self.assertTrue( (L&I).is_empty() ) + L.intersect_with(I) + self.assertFalse(L) + + L |= I + + self.assertEqual(L.Interval, I.Interval) + + self.assertEqual((I & K).Interval, Interval(3, 9.3)) + + def test_intInterval(self): + I = IntInterval(2, 6) + J = IntInterval(0, 1) + self.interval_basic(I, J) + p = [3, 2.3, 65.3, 43] + K = IntInterval.from_list(p) + self.assertAlmostEqual(K.max(), int(max(p))) + self.assertAlmostEqual(int((K+IntInterval(1.0)).min()), int(min(p)+1)) + L = IntInterval(3) + for i in range(3): + K+=L + self.assertAlmostEqual(K.max(), int(max(p))+9) + + self.assertEqual(Interval(3)|Interval(5), + Interval(3, 5)) + self.assertAlmostEqual((K-L).max(), (K-3).max()) + + def test_optIntInterval(self): + I = OptIntInterval(2, 9) + J = IntInterval(3, 13) + K = OptIntInterval.from_Interval(J) + self.assertEqual(K.Interval, J) + self.interval_basic(K.Interval, I.Interval) + + L = OptIntInterval() + + self.assertFalse(L) + self.assertTrue( (L&I).is_empty() ) + L.intersect_with(I) + self.assertFalse(L) + + L |= I + + self.assertEqual(L.Interval, I.Interval) + + self.assertEqual((I & K).Interval, IntInterval(3, 9)) + + def test_genericInterval(self): + maxv = 100000 + test_vars = [ + ( (randint(0, maxv), randint(0, maxv)), (randint(0, maxv), randint(0, maxv)) ), + ( (3,), (2, 0) ), + ( (0.0, 9), (4, 1.3)), + ((2.98, sqrt(2)), (sqrt(7),)), + ( (Fraction(1,2), Fraction(3, 7)), ( Fraction(2, 1), ) ) + ] + for a,b in test_vars: + self.interval_basic( GenericInterval(*a), GenericInterval(*b) ) + + def test_genericOptInterval(self): + test_vars = [ + ( (3,), (2, 0) ), + ( (0.0, 9), (4, 1.3)), + ((2.98, sqrt(2)), (sqrt(7),)), + ( (Fraction(1,2), Fraction(3, 7)), ( Fraction(2, 1), ) ) + ] + + for a, b in test_vars: + I = GenericOptInterval(*a) + J = GenericInterval(*b) + K = GenericOptInterval.from_Interval(J) + + self.assertEqual(I, GenericOptInterval.from_Interval(I)) + + self.assertEqual(K.Interval, J) + self.interval_basic(K.Interval, I.Interval) + + L = GenericOptInterval() + + self.assertFalse(L) + self.assertTrue( (L&I).is_empty() ) + L.intersect_with(I) + self.assertFalse(L) + + L |= I + + self.assertEqual(L.Interval, I.Interval) + + if I.intersect_with(K): + if I.Interval.min() <= K.Interval.min(): + if I.Interval.max() >= K.Interval.max(): + self.assertEqual( I & K, K) + else: + self.assertEqual( I & K, GenericInterval(K.min(), I.max())) + else: + if I.Interval.max() >= K.Interval.max(): + self.assertEqual( I & K, GenericInterval(I.min(), K.max())) + else: + self.assertEqual( I & K, I) + + def test_genericRect(self): + A = GenericRect(1, 1, 4, 4) + self.assertEqual( A.min(), (1, 1) ) + B = GenericRect(Fraction(1,4), Fraction(9, 94), Fraction(2, 3), Fraction(23, 37)) + + amin = A.min() + amax = A.max() + + self.assertAlmostEqual(amin[0], A[0].min()) + self.assertAlmostEqual(amax[1], A[1].max()) + self.assertEqual(amin, A.corner(0)) + + self.assertEqual(amin, (A.left(), A.top())) + self.assertEqual(amax, (A.right(), A.bottom())) + + self.assertAlmostEqual( A.width(), A[0].extent() ) + self.assertAlmostEqual( A.height(), A[1].extent() ) + + self.assertEqual( A.dimensions(), ( A.width(), A.height() ) ) + #~ self.assertEqual( A.midpoint(), (A.min() + A.max())/2 ) + self.assertAlmostEqual(A.area(), A.width()*A.height()) + #TODO export EPSILON from 2geom + if A.area() > 0: + self.assertFalse(A.has_zero_area()) + else: + self.assertTrue(A.has_zero_area()) + self.assertAlmostEqual(A.max_extent(), max(A.width(), A.height())) + self.assertGreaterEqual(A.max_extent(), A.min_extent()) + + bmin = B.min() + bmax = B.max() + + pdiag = sqrt((amax[0]-amin[0])**2+(amax[1]-amin[1])**2) + + B.set_min(A.midpoint()) + B.set_max(A.midpoint()) + + self.assertTrue(B.has_zero_area()) + + B.expand_by(A.min_extent()/3.0) + + self.assertTrue(A.contains_rect(B)) + self.assertTrue(A.intersects(B)) + self.assertTrue(B.intersects(A)) + self.assertFalse(B.contains_rect(A)) + + self.assertTrue(A.contains(A.midpoint())) + self.assertFalse(A.contains( (A.midpoint()[0]*3, A.midpoint()[1]*3) )) + + A.union_with(B) + + self.assertEqual( A.min(), amin ) + + B.set_left(bmin[0]) + B.set_top(bmin[1]) + B.set_right(bmax[0]) + B.set_bottom(bmax[1]) + + self.assertEqual(B.min(), bmin) + self.assertEqual(B.max(), bmax) + + B.expand_to( (0, 0) ) + self.assertEqual((0, 0), B.min()) + + B.expand_by(*bmax) + self.assertEqual(bmax, (- (B.min()[0]), - (B.min()[1])) ) + + B.expand_by(-bmax[0], -bmax[1]) + self.assertEqual(B.max(), bmax) + + self.assertEqual( (A+B.min()).max()[0], A.max()[0] + B.min()[0] ) + + #~ self.assertEqual( (A-B.max()).min(), A.min() - B.max() ) + + self.assertEqual( A|A, A ) + + self.assertFalse( A != A ) + + B.set_left(bmin[0]) + B.set_top(bmin[1]) + B.set_right(bmax[0]) + B.set_bottom(bmax[1]) + + #~ self.assertAlmostEqual(Rect.distance(Point(), A), A.min().length()) + #~ self.assertAlmostEqual(Rect.distanceSq(B.min(), A), Rect.distance(B.min(), A)**2 ) + + + + def rect_basic(self, P, Q): + pmin = P.min() + pmax = P.max() + #for simplicity + self.assertTrue(pmin.x > 0) + self.assertTrue(pmin.y > 0) + + self.assertAlmostEqual(pmin.x, P[0].min()) + self.assertAlmostEqual(pmax.y, P[1].max()) + self.assertEqual(pmin, P.corner(0)) + + self.assertEqual(pmin, Point(P.left(), P.top())) + self.assertEqual(pmax, Point(P.right(), P.bottom())) + + self.assertAlmostEqual( P.width(), P[0].extent() ) + self.assertAlmostEqual( P.height(), P[1].extent() ) + + self.assertAlmostEqual( P.aspect_ratio(), P.width()/P.height() ) + self.assertEqual( P.dimensions(), Point( P.width(), P.height() ) ) + self.assertEqual( P.midpoint(), (P.min() + P.max())/2 ) + self.assertAlmostEqual(P.area(), P.width()*P.height()) + #TODO export EPSILON from 2geom + if P.area() > 1e-7: + self.assertFalse(P.has_zero_area()) + self.assertTrue(P.has_zero_area(P.area())) + else: + self.assertTrue(P.has_zero_area()) + self.assertAlmostEqual(P.max_extent(), max(P.width(), P.height())) + self.assertGreaterEqual(P.max_extent(), P.min_extent()) + + qmin = Q.min() + qmax = Q.max() + + pdiag = (pmax-pmin).length() + + Q.set_min(P.midpoint()) + Q.set_max(P.midpoint()) + self.assertTrue(Q.has_zero_area()) + + #print P,Q + Q.expand_by(P.min_extent()/3.0) + + #print P, Q + + self.assertTrue(P.contains_rect(Q)) + self.assertTrue(P.intersects(Q)) + self.assertTrue(Q.intersects(P)) + self.assertFalse(Q.contains_rect(P)) + + self.assertTrue(P.interior_contains_rect(Q)) + self.assertFalse(P.interior_contains_rect(P)) + self.assertTrue(P.interior_intersects(Q)) + self.assertTrue(P.interior_intersects(P)) + + self.assertTrue(P.contains(P.midpoint())) + self.assertFalse(P.contains(P.midpoint()*3)) + + P.union_with(Q) + + self.assertEqual( P.min(), pmin ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + self.assertEqual(Q.min(), qmin) + self.assertEqual(Q.max(), qmax) + + Q.expand_to( Point() ) + self.assertEqual(Point(), Q.min()) + Q.expand_by(qmax) + self.assertEqual(qmax, -Q.min()) + + Q.expand_by(-qmax.x, -qmax.y) + self.assertEqual(Q.max(), qmax) + + self.assertEqual( (P+Q.min()).max(), P.max() + Q.min() ) + + self.assertEqual( (P-Q.max()).min(), P.min() - Q.max() ) + + self.assertEqual( P|P, P ) + + self.assertFalse( P != P ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + self.assertAlmostEqual(Rect.distance(Point(), P), P.min().length()) + self.assertAlmostEqual(Rect.distanceSq(Q.min(), P), Rect.distance(Q.min(), P)**2 ) + + self.assertEqual(P.round_outwards()[0], P[0].round_outwards()) + if P.round_inwards(): + self.assertEqual(P.round_inwards().Rect[1], P[1].round_inwards().Interval) + + + def intrect_basic(self, P, Q): + pmin = P.min() + pmax = P.max() + #for simplicity + self.assertTrue(pmin.x > 0) + self.assertTrue(pmin.y > 0) + + self.assertAlmostEqual(pmin.x, P[0].min()) + self.assertAlmostEqual(pmax.y, P[1].max()) + self.assertEqual(pmin, P.corner(0)) + + self.assertEqual(pmin, IntPoint(P.left(), P.top())) + self.assertEqual(pmax, IntPoint(P.right(), P.bottom())) + + self.assertAlmostEqual( P.width(), P[0].extent() ) + self.assertAlmostEqual( P.height(), P[1].extent() ) + + self.assertAlmostEqual( P.aspect_ratio(), float(P.width())/float(P.height()) ) + self.assertEqual( P.dimensions(), IntPoint( P.width(), P.height() ) ) + self.assertEqual( P.midpoint().x, (P.min() + P.max()).x/2 ) + self.assertAlmostEqual(P.area(), P.width()*P.height()) + + if P.area() > 0: + self.assertFalse(P.has_zero_area()) + else: + self.assertTrue(P.has_zero_area()) + self.assertAlmostEqual(P.max_extent(), max(P.width(), P.height())) + self.assertGreaterEqual(P.max_extent(), P.min_extent()) + + qmin = Q.min() + qmax = Q.max() + + Q.set_min(P.midpoint()) + Q.set_max(P.midpoint()) + self.assertTrue(Q.has_zero_area()) + + Q.expand_by(P.min_extent()/3.0) + + self.assertTrue(P.contains_rect(Q)) + self.assertTrue(P.intersects(Q)) + self.assertTrue(Q.intersects(P)) + self.assertFalse(Q.contains_rect(P)) + + self.assertTrue(P.contains(P.midpoint())) + self.assertFalse(P.contains(P.midpoint()+P.midpoint()+P.midpoint())) + + P.union_with(Q) + + self.assertEqual( P.min(), pmin ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + self.assertEqual(Q.min(), qmin) + self.assertEqual(Q.max(), qmax) + + Q.expand_to( IntPoint() ) + self.assertEqual(IntPoint(), Q.min()) + Q.expand_by(qmax) + self.assertEqual(qmax, IntPoint()-Q.min()) + + Q.expand_by(-qmax.x, -qmax.y) + self.assertEqual(Q.max(), qmax) + + self.assertEqual( (P+Q.min()).max(), P.max() + Q.min() ) + + self.assertEqual( (P-Q.max()).min(), P.min() - Q.max() ) + + self.assertEqual( P|P, P ) + + self.assertFalse( P != P ) + + Q.set_left(qmin.x) + Q.set_top(qmin.y) + Q.set_right(qmax.x) + Q.set_bottom(qmax.y) + + + + def test_rect(self): + + P = Rect(0.298, 2, 4, 5) + + self.interval_basic(P[0], P[1]) + G = Rect(sqrt(2), sqrt(2), sqrt(3), sqrt(3)) + H = Rect.from_xywh(3.43232, 9.23214, 21.523, -0.31232) + + self.rect_basic(P, G) + self.rect_basic(G, H) + + lst = [Point(randint(-100, 100), randint(-100, 100)) for i in range(10)] + + R = Rect.from_list(lst) + + for p in lst: + self.assertTrue(R.contains(p)) + + self.assertAlmostEqual(min(lst).y, R.min().y) + self.assertAlmostEqual(max(lst).y, R.max().y) + + + + def test_optRect(self): + + P = OptRect(0.298, 2, 4, 5) + self.interval_basic(P.Rect[0], P.Rect[1]) + + + G = Rect(sqrt(2), sqrt(2), sqrt(3), sqrt(3)) + H = OptRect.from_rect(G) + + self.rect_basic(P.Rect, G) + + lst = [Point(randint(-100, 100), randint(-100, 100)) for i in range(10)] + + R = OptRect.from_list(lst) + + for p in lst: + self.assertTrue(R.Rect.contains(p)) + + self.assertAlmostEqual(min(lst).y, R.Rect.min().y) + self.assertAlmostEqual(max(lst).y, R.Rect.max().y) + + Q = OptRect() + self.assertFalse(Q) + self.assertTrue(P) + + self.assertTrue(Q.is_empty()) + self.assertFalse(P.is_empty()) + + self.assertTrue(P.contains_rect( P )) + self.assertTrue(P.contains_rect(Q)) + self.assertFalse(Q.contains_rect(P)) + self.assertFalse(P.intersects(Q)) + self.assertTrue(P.contains_rect(P.Rect)) + self.assertTrue(P.contains(P.Rect.midpoint())) + + self.assertEqual(P, OptRect.from_rect(P)) + + P.union_with(G) + P.union_with(H) + self.assertTrue(P.contains_rect(H)) + + P.intersect_with(G) + self.assertEqual(P, G) + + self.assertEqual( P|H, G ) + self.assertEqual( (P|R).Rect.min().x , min( P.Rect.min().x, R.Rect.min().x )) + + self.assertFalse(P & Q) + self.assertEqual(P, P&P) + + self.assertEqual( P & (R | H), (P & R) | (P & H) ) + + def test_intRect(self): + A = IntRect(2, 6, 9, 23) + B = IntRect.from_intervals(IntInterval(1, 5), IntInterval(8, 9)) + C = IntRect.from_points(IntPoint(1, 8), IntPoint(5, 9)) + + self.assertEqual(B, C) + + self.intrect_basic(A, B) + self.intrect_basic(B, C) + self.intrect_basic(C, A) + + def test_optIntRect(self): + P = OptIntRect(1, 2, 4, 5) + self.interval_basic(P.Rect[0], P.Rect[1]) + + + G = IntRect(2, 2, 3, 3) + H = OptIntRect.from_rect(G) + + self.intrect_basic(P.Rect, G) + + lst = [IntPoint(randint(-100, 100), randint(-100, 100)) for i in range(10)] + + R = OptIntRect.from_list(lst) + + for p in lst: + self.assertTrue(R.Rect.contains(p)) + + self.assertAlmostEqual(min(lst).y, R.Rect.min().y) + self.assertAlmostEqual(max(lst).y, R.Rect.max().y) + + Q = OptIntRect() + self.assertFalse(Q) + self.assertTrue(P) + + self.assertTrue(Q.is_empty()) + self.assertFalse(P.is_empty()) + + self.assertTrue(P.contains_rect( P )) + self.assertTrue(P.contains_rect( P.Rect )) + self.assertTrue(P.contains_rect(Q)) + self.assertFalse(Q.contains_rect(P)) + self.assertFalse(P.intersects(Q)) + self.assertTrue(P.contains_rect(P.Rect)) + self.assertTrue(P.contains(P.Rect.midpoint())) + + self.assertEqual(P, OptIntRect.from_rect(P)) + + P.union_with(G) + P.union_with(H) + self.assertTrue(P.contains_rect(H)) + + P.intersect_with(G) + self.assertEqual(P, G) + + self.assertEqual( P|H, G ) + self.assertEqual( (P|R).Rect.min().x , min( P.Rect.min().x, R.Rect.min().x )) + + self.assertFalse(P & Q) + self.assertEqual(P, P&P) + + self.assertEqual( P & (R | H), (P & R) | (P & H) ) + +unittest.main() diff --git a/src/cython/utils.py b/src/cython/utils.py new file mode 100644 index 0000000..1115d32 --- /dev/null +++ b/src/cython/utils.py @@ -0,0 +1,52 @@ +from Tkinter import * + +import math + +import cy2geom +from cy2geom import Point, Path + + +def Nagon(N): + """Return N-agon with side of length 1.""" + side = cy2geom.LineSegment(Point(-0.5, 0), Point(0.5, 0)) + angle = 2*math.pi/N + distance_to_center = 0.5 / math.tan(math.pi/N) + return Path.fromList( + [ side.transformed( + cy2geom.Translate(Point(0, -distance_to_center))* + cy2geom.Rotate(angle*i)) + for i in range(N) + ], + stitching = Path.STITCH_DISCONTINUOUS, + closed = True ) + +def draw(c, dt=0.001, batch=10, scale=20, x_offset = 400, y_offset = 300): + """Draw curve or path.""" + master = Tk() + w = Canvas(master, width=800, height=600) + w.pack() + n = 0 + t = 0 + points = [] + + if isinstance(c, Path): + maxt = c.size_default() + else: + maxt = 1 + + while (t < maxt): + t = n*dt + n+=1 + p = c(t) + points.extend( [ p.x * scale + x_offset, p.y * scale + y_offset] ) + + while points: + draw_points = tuple(points[:batch*2]) + if len(points) == 2: + break + del points[:batch*2] + + l = w.create_line(*draw_points) + w.grid() + + master.mainloop() diff --git a/src/cython/wrapped-pyobject.h b/src/cython/wrapped-pyobject.h new file mode 100644 index 0000000..bd316fb --- /dev/null +++ b/src/cython/wrapped-pyobject.h @@ -0,0 +1,237 @@ +#include "Python.h" +#include "2geom/generic-interval.h" +#include "2geom/generic-rect.h" +#include "2geom/d2.h" + + +namespace Geom{ + +//TODO! looks like memory leak +class WrappedPyObject{ +private: + PyObject* self; +public: + //~ int code; + + WrappedPyObject(){ + //~ printf("Created empty WPO at address %p\n", this); + self = NULL; + //~ code = 0; + } + + WrappedPyObject(WrappedPyObject const &other){ + self = other.getObj(); + //~ code = other.code; + //~ printf("COPY-CONSTRUCTOR %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_XINCREF(self); + } + + WrappedPyObject(PyObject* arg){ + if (arg == NULL){ + //PyErr_Print(); + //~ code = c; + } + else{ + //~ printf("CONSTRUCTOR %p\n", arg); + Py_INCREF(arg); + self = arg; + //~ code = c; + } + } + + WrappedPyObject(int c){ + self = Py_BuildValue("i", c); + //~ printf("INT-OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_INCREF(self); + } + + + ~WrappedPyObject(){ + //TODO Leaking memory + //~ printf("DECREF %p\n", self); + //Py_DECREF(self); + } + + PyObject* getObj() const { + return self; + } + + WrappedPyObject operator=(WrappedPyObject other){ + if (this != &other){ + self = other.getObj(); + //~ printf("OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_XINCREF(self); + } + return *this; + } + + WrappedPyObject operator=(int other){ + + self = Py_BuildValue("i", other); + //~ printf("INT-OPERATOR= %p, this WPO %p, other WPO %p\n", self, this, &other); + Py_INCREF(self); + + return *this; + } + + WrappedPyObject operator-() const { + PyObject * ret; + ret = PyObject_CallMethodObjArgs(self, Py_BuildValue("s", "__neg__"), NULL); + if (ret == NULL){ + Py_INCREF(Py_None); + return WrappedPyObject(Py_None); + } + //Py_INCREF(ret); + WrappedPyObject * retw = new WrappedPyObject(ret); + //Py_DECREF(ret); + return *retw; + } + + WrappedPyObject arithmetic(PyObject* const other, std::string method, std::string rmethod) const { + PyObject * ret; + //printf("%p %p\n", self, other); + ret = PyObject_CallMethodObjArgs(self, Py_BuildValue("s", method.c_str()), other, NULL); + if (ret == NULL){ + Py_INCREF(Py_None); + return WrappedPyObject(Py_None); + } + PyObject * isNI = PyObject_RichCompare(ret, Py_NotImplemented, Py_EQ); + if ( PyInt_AsLong(isNI) ){ + ret = PyObject_CallMethodObjArgs(other, Py_BuildValue("s", rmethod.c_str()), self, NULL); + } + if (ret == NULL){ + Py_INCREF(Py_None); + return WrappedPyObject(Py_None); + } + WrappedPyObject * retw = new WrappedPyObject(ret); + return *retw; + + } + + WrappedPyObject operator+(WrappedPyObject const other) const { + return arithmetic(other.getObj(), "__add__", "__radd__"); + } + + WrappedPyObject operator-(WrappedPyObject const other){ + return arithmetic(other.getObj(), "__sub__", "__rsub__"); + } + + WrappedPyObject operator*(WrappedPyObject const other){ + return arithmetic(other.getObj(), "__mul__", "__rmul__"); + } + + WrappedPyObject operator*(int other){ + PyObject* other_obj = Py_BuildValue("i", other); + //Py_INCREF(other_obj); + WrappedPyObject ret = arithmetic(other_obj, "__mul__", "__rmul__"); + //Py_DECREF(other_obj); + return ret; + } + + WrappedPyObject operator/(int other){ + PyObject* other_obj = Py_BuildValue("i", other); + Py_INCREF(other_obj); + WrappedPyObject ret = arithmetic(other_obj, "__div__", "__rdiv__"); + Py_DECREF(other_obj); + return ret; + } + + bool richcmp(WrappedPyObject const &other, int op) const { + PyObject * ret; + long retv; + ret = PyObject_RichCompare(self, other.getObj(), op); + retv = PyInt_AsLong(ret); + return retv; + } + + bool operator<(WrappedPyObject const &other) const { + return richcmp(other, Py_LT); + } + + bool operator<=(WrappedPyObject const &other) const { + return richcmp(other, Py_LE); + } + + bool operator>=(WrappedPyObject const &other) const{ + return richcmp(other, Py_GE); + } + + bool operator>(WrappedPyObject const &other) const { + return richcmp(other, Py_GT); + } + + bool operator==(WrappedPyObject const &other) const { + return richcmp(other, Py_EQ); + } + + + bool operator<(int const c) const { + return richcmp(WrappedPyObject(c), Py_LT); + } + + bool operator<=(int const c) const { + return richcmp(WrappedPyObject(c), Py_LE); + } + + bool operator>=(int const c) const{ + return richcmp(WrappedPyObject(c), Py_GE); + } + + bool operator>(int const c) const { + return richcmp(WrappedPyObject(c), Py_GT); + } + + bool operator==(int const c) const { + return richcmp(WrappedPyObject(c), Py_EQ); + } + + + WrappedPyObject operator+=(WrappedPyObject other){ + *this = *this + other; + return *this; + + } + + WrappedPyObject operator-=(WrappedPyObject other){ + *this = *this - other; + return *this; + } +}; + +typedef GenericInterval<WrappedPyObject> PyInterval; +typedef GenericOptInterval<WrappedPyObject> PyOptInterval; + +typedef GenericRect<WrappedPyObject> PyRect; +typedef GenericOptRect<WrappedPyObject> PyOptRect; + +typedef D2<WrappedPyObject> PyPoint; + +template<> +struct CoordTraits<WrappedPyObject> { + typedef PyPoint PointType; + typedef PyInterval IntervalType; + typedef PyOptInterval OptIntervalType; + typedef PyRect RectType; + typedef PyOptRect OptRectType; + + typedef + boost::equality_comparable< PyInterval + , boost::additive< PyInterval + , boost::multipliable< PyInterval + , boost::orable< PyInterval + , boost::arithmetic< PyInterval, WrappedPyObject + > > > > > + IntervalOps; + + typedef + boost::equality_comparable< PyRect + //, boost::equality_comparable< PyRect, IntRect + , boost::orable< PyRect + , boost::orable< PyRect, PyOptRect + , boost::additive< PyRect, PyPoint + //, boost::multipliable< Rect, Affine + > > > > //> > + RectOps; +}; + +} diff --git a/src/cython/wrapper.py b/src/cython/wrapper.py new file mode 100644 index 0000000..241782a --- /dev/null +++ b/src/cython/wrapper.py @@ -0,0 +1,360 @@ +import os
+import sys
+import argparse
+
+from pygccxml import parser, declarations
+
+class wType:
+ def __init__(self, c_str):
+ self.c_str = c_str
+ self.wrapped_types = ["bint", "double", "int", "unsigned int",
+ "Coord", "IntCoord", "Dim2", "size_t"]
+
+ def get_C(self):
+ """returns plain c string"""
+ return self.c_str
+
+ def get_Cython_type(self):
+ """Returns corresponding Cython type string"""
+ typestr= self.get_C()
+ typestr= typestr.replace(" const ", " ")
+ if typestr.find("::Geom::")==0:
+ typestr = typestr[len("::Geom::"):]
+ if typestr == "bool":
+ typestr = "bint"
+ return typestr
+
+ def get_Python_function_argument(self):
+ """Returns argument type to Python function call"""
+
+ typestr = self.get_Cython_type()
+ if typestr[-1]=="&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return typestr+" "
+ return "cy_"+typestr+" "
+ def get_Python_return(self):
+ """ Returns a string used to wrap a function returning this type
+ returns a string to be called with format( 'function call to c function ')
+ """
+ typestr = self.get_Cython_type()
+
+ if typestr[-1] == "&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return "return {}"
+ if typestr == "void":
+ return "{}"
+ if typestr[-1] == "*":
+ typestr = typestr[:-2]+"_p"
+ return "return wrap_"+typestr+"({})"
+
+ def get_wrap_method(self, delim):
+ """Returns wrap method in a form of a list of lines"""
+ ret = []
+ typestr = self.get_Cython_type()
+
+ if typestr[-1] == "&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return []
+ if typestr == "void":
+ return []
+ if typestr[-1] == "*":
+ typestr = typestr[:-2]+"_p"
+ ret.append("cdef cy_{} wrap_{}({} p):".format(typestr, typestr, typestr))
+ ret.append(delim+"cdef {} * retp = new {}()".format(typestr, typestr))
+ ret.append(delim+"retp[0] = p")
+ ret.append(delim+"cdef cy_{} r = cy_{}.__new__(cy_{})".format(typestr, typestr, typestr))
+ ret.append(delim+"r.thisptr = retp")
+ ret.append(delim+"return r")
+ return ret
+
+ def get_Python_pass_argument(self):
+ """ Returns argument to be passed to C function, to be called with .format(arg_name)"""
+ typestr = self.get_Cython_type()
+ if typestr[-1]=="&":
+ typestr = typestr[:-2]
+ if typestr in self.wrapped_types:
+ return "{}"
+ if typestr[-1] == "*":
+ return "{}.thisptr"
+ return "deref( {}.thisptr )"
+
+class CythonWrapper:
+ def __init__(self, gn):
+ self.global_namespace = gn
+ self.delim = ' '
+
+ def wrap_constructor(self, constructor, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in constructor.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+ pxd_function_str = constructor.name+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ #python function
+ arguments_python = ["self"]
+ for argument in constructor.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format("__init__")+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in constructor.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "new "+constructor.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = "self.thisptr = {}".format(function_call)
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_operator(self, operator, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in operator.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+ boost_wrapped = ["operator{}=".format(i) for i in ["+", "-", "*", "/", "|", "&"]]
+ pxd_function_str = types_dict[operator.return_type.decl_string].get_Cython_type()+\
+ " "+operator.name+pxd_argument_str
+
+ if not (operator.name in boost_wrapped):
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ else:
+ pxd_lines.append(self.delim*2+"#"+pxd_function_str)
+
+ pxd_function_str = types_dict[operator.return_type.decl_string].get_Cython_type()+\
+ " "+operator.name[:-1]+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+
+ special_methods = {
+ "+" : "__add__",
+ "-" : "__sub__",
+ "*" : "__mul__",
+ "/" : "__div__",
+ "|" : "__or__",
+ "&" : "__and__",
+ "[]": "__getitem__",
+ "()": "__call__",
+ "==": "__richcmp__",
+ "!=": "__richcmp__"
+ }
+
+ if operator.name in boost_wrapped:
+ operation = operator.name[-2]
+ else:
+ operation = operator.name[ len("operator"): ]
+
+ if operation in special_methods:
+ python_name = special_methods[operation]
+ else:
+ python_name = operator.name
+
+ arguments_python = ["self"]
+ for argument in operator.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format(python_name)+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in operator.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "deref( self.thisptr ) "+operation+" {}".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[operator.return_type.decl_string].get_Python_return().format(function_call)
+
+ if (operator.arguments == []) and (operation == '-'):
+ declaration = "def __neg__(self):"
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_method(self, method, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+
+ pxd_arguments = []
+ for argument_type in method.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+
+ pxd_function_str = types_dict[method.return_type.decl_string].get_Cython_type()+\
+ " "+method.name+pxd_argument_str
+ pxd_lines.append(self.delim*2+pxd_function_str)
+
+ #python function
+ arguments_python = ["self"]
+ for argument in method.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def {}".format(method.name)+"({}):".format(", ".join(arguments_python))
+
+ arguments_cython = []
+ for argument in method.arguments:
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = "self.thisptr."+method.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[method.return_type.decl_string].get_Python_return().format(function_call)
+ pyx_lines.append(self.delim+declaration)
+ pyx_lines.append(self.delim*2 + return_statement)
+
+ return (pxd_lines, pyx_lines)
+
+ def wrap_free_function(self, function, types_dict):
+ pxd_lines = []
+ pyx_lines = []
+ decl_lines = []
+
+ pxd_arguments = []
+ for argument_type in function.argument_types:
+ pxd_arguments.append( types_dict[argument_type.decl_string].get_Cython_type() )
+ pxd_argument_str = "({})".format(", ".join(pxd_arguments))
+
+
+ pxd_function_str = types_dict[function.return_type.decl_string].get_Cython_type()+\
+ " "+function.name+pxd_argument_str
+ pxd_lines.append(self.delim+pxd_function_str)
+
+ #python function
+ arguments_python = []
+ for argument in function.arguments:
+ arguments_python.append(types_dict[argument.type.decl_string].get_Python_function_argument()+argument.name)
+ declaration = "def cy_{}".format(function.name)+"({}):".format(", ".join(arguments_python))
+
+ decl_lines.append("from _cy_??? import cy_{0} as {0}".format(function.name))
+
+ arguments_cython = []
+ for argument in function.arguments:
+
+ arguments_cython.append(types_dict[argument.type.decl_string].get_Python_pass_argument().format(argument.name))
+ function_call = function.name+"({})".format(", ".join(arguments_cython))
+
+ return_statement = types_dict[function.return_type.decl_string].get_Python_return().format(function_call)
+ pyx_lines.append(declaration)
+ pyx_lines.append(self.delim + return_statement)
+
+ return (pxd_lines, pyx_lines, decl_lines)
+
+
+ def wrap_class(self, class_name, class_file):
+ pxd_lines = []
+ pyx_lines = []
+ decl_lines = []
+ other_lines = []
+
+ pxd_lines.append("cdef extern from \"2geom/{}\" namespace \"Geom\":".format(class_file))
+ pxd_lines.append(self.delim+"cdef cppclass {}:".format(class_name))
+ pyx_lines.append("cdef class cy_{}:".format(class_name))
+ pyx_lines.append(self.delim+"cdef {}* thisptr".format(class_name))
+ #get_types
+ types_dict = self.collect_types(class_name)
+ #parse
+ c_class = self.global_namespace.namespace(name = "Geom").class_(name=class_name)
+
+ for member in c_class.get_members():
+ if isinstance(member, declarations.calldef.constructor_t):
+ pxd, pyx = self.wrap_constructor(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+ elif isinstance(member, declarations.calldef.member_operator_t):
+ pxd, pyx = self.wrap_operator(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+ elif isinstance(member, declarations.calldef.member_function_t):
+ pxd, pyx = self.wrap_method(member, types_dict)
+ pxd_lines += pxd
+ pyx_lines += pyx
+
+ pyx_lines.append("#free functions:")
+ for ff in self.global_namespace.namespace(name = "Geom").free_funs():
+ if os.path.basename(ff.location.file_name) == class_file:
+ pxd, pyx, decl = self.wrap_free_function(ff, types_dict)
+ pxd_lines += map(lambda x: x[4:], pxd)
+ pyx_lines += pyx
+ decl_lines += decl
+
+ for ff in self.global_namespace.namespace(name = "Geom").free_operators():
+ if os.path.basename(ff.location.file_name) == class_file:
+ pxd, pyx = self.wrap_operator(ff, types_dict)
+ pxd_lines += map(lambda x: x[4:], pxd)
+ pyx_lines += pyx
+
+ pyx_lines.append("")
+ for ctype in types_dict:
+ wtype = types_dict[ctype]
+ for line in wtype.get_wrap_method(self.delim):
+ pyx_lines.append(line)
+ print "------"
+ for i in pxd_lines:
+ print i
+ print "------"
+ for i in pyx_lines:
+ print i
+ print "------"
+ for i in decl_lines:
+ print i
+
+
+ def collect_types(self, class_name):
+ types_set = set()
+ c_class = self.global_namespace.namespace(name = "Geom").class_(name=class_name)
+ for member in c_class.get_members():
+ if any([isinstance(member, declarations.calldef.constructor_t),
+ isinstance(member, declarations.calldef.member_operator_t),
+ isinstance(member, declarations.calldef.member_function_t)]):
+ for a_type in member.argument_types:
+ types_set.add(a_type.decl_string)
+ if isinstance(member, declarations.calldef.member_operator_t) or isinstance(member, declarations.calldef.member_function_t):
+ types_set.add(member.return_type.decl_string)
+ for ff in list(self.global_namespace.namespace(name = "Geom").free_funs()) + list(self.global_namespace.namespace(name = "Geom").free_operators()):
+ for a_type in ff.argument_types:
+ types_set.add(a_type.decl_string)
+ types_set.add(ff.return_type.decl_string)
+ types_dict = dict()
+
+ for f_type in types_set:
+
+ types_dict[f_type] = wType(f_type)
+ return types_dict
+
+def main():
+
+ #set up argument parser
+ cmd_parser = argparse.ArgumentParser()
+ cmd_parser.add_argument('--lib2geom_dir', action = 'store')
+ cmd_parser.add_argument('--file_name', action = 'store')
+ cmd_parser.add_argument('--class_name', action = 'store')
+ cmd_args = cmd_parser.parse_args()
+
+ #set up pygccxml
+ includes = [cmd_args.lib2geom_dir,
+ os.path.join(cmd_args.lib2geom_dir, 'src'),
+ "/usr/include/boost"]
+
+ config = parser.config_t(compiler='gcc',
+ include_paths=includes,
+ cflags="")
+
+ file_to_parse = [os.path.join(cmd_args.lib2geom_dir, "src", "2geom", cmd_args.file_name)]
+
+ decls = parser.parse( file_to_parse, config )
+ global_namespace = declarations.get_global_namespace( decls )
+ wrapper = CythonWrapper(global_namespace)
+ wrapper.wrap_class(cmd_args.class_name, cmd_args.file_name)
+
+main()
+
+#run with
+#python2 wrapper.py --lib2geom_dir ../../.. --file_name int-point.h --class_name IntPoint
+#from cython-bindings directory
|