summaryrefslogtreecommitdiffstats
path: root/src/cython
diff options
context:
space:
mode:
Diffstat (limited to 'src/cython')
-rw-r--r--src/cython/CMakeLists.txt131
-rw-r--r--src/cython/README.md29
-rw-r--r--src/cython/_common_decl.pxd14
-rw-r--r--src/cython/_common_decl.pyx12
-rw-r--r--src/cython/_cy_affine.pxd247
-rw-r--r--src/cython/_cy_affine.pyx736
-rw-r--r--src/cython/_cy_conicsection.pxd50
-rw-r--r--src/cython/_cy_conicsection.pyx183
-rw-r--r--src/cython/_cy_curves.pxd421
-rw-r--r--src/cython/_cy_curves.pyx1945
-rw-r--r--src/cython/_cy_path.pxd124
-rw-r--r--src/cython/_cy_path.pyx457
-rw-r--r--src/cython/_cy_primitives.pxd237
-rw-r--r--src/cython/_cy_primitives.pyx846
-rw-r--r--src/cython/_cy_rectangle.pxd442
-rw-r--r--src/cython/_cy_rectangle.pyx2202
-rw-r--r--src/cython/cy2geom.pyx71
-rw-r--r--src/cython/report.md237
-rw-r--r--src/cython/test-affine.py249
-rw-r--r--src/cython/test-conicsection.py137
-rw-r--r--src/cython/test-curves.py458
-rw-r--r--src/cython/test-path.py218
-rw-r--r--src/cython/test-primitives.py288
-rw-r--r--src/cython/test-rectangle.py601
-rw-r--r--src/cython/utils.py52
-rw-r--r--src/cython/wrapped-pyobject.h237
-rw-r--r--src/cython/wrapper.py360
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