Fixed #40 (Crash in Python binding)

Plus the same effect was observed for Ruby and fixed there as well.
This commit is contained in:
Matthias Koefferlein 2017-12-21 00:55:40 +01:00
parent 6f66e04c8e
commit 4855231342
18 changed files with 830 additions and 64 deletions

View File

@ -514,7 +514,7 @@ struct simple_polygon_defs
) +
method_ext ("touches?", &touches_edge, gsi::arg ("edge"),
"@brief Returns true, if the polygon touches the given edge.\n"
"The box and the polygon touch if they overlap or the edge shares at least one point with the polygon's contour.\n"
"The edge and the polygon touch if they overlap or the edge shares at least one point with the polygon's contour.\n"
"\n"
"This method was introduced in version 0.25.1.\n"
) +
@ -1509,7 +1509,7 @@ struct polygon_defs
) +
method_ext ("touches?", &touches_edge, gsi::arg ("edge"),
"@brief Returns true, if the polygon touches the given edge.\n"
"The box and the polygon touch if they overlap or the edge shares at least one point with the polygon's contour.\n"
"The edge and the polygon touch if they overlap or the edge shares at least one point with the polygon's contour.\n"
"\n"
"This method was introduced in version 0.25.1.\n"
) +

View File

@ -1021,7 +1021,7 @@ method_adaptor (int mid, PyObject *self, PyObject *args)
// In case of an error upon write, pop the arguments to clean them up.
// Without this, there is a risk to keep dead objects on the stack.
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && arglist; ++a) {
pop_arg (*a, arglist, NULL, heap);
pop_arg (*a, arglist, 0, heap);
}
throw;
@ -1572,7 +1572,7 @@ method_init_adaptor (int mid, PyObject *self, PyObject *args)
// In case of an error upon write, pop the arguments to clean them up.
// Without this, there is a risk to keep dead objects on the stack.
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && arglist; ++a) {
pop_arg (*a, arglist, NULL, heap);
pop_arg (*a, arglist, 0, heap);
}
throw;

View File

@ -359,7 +359,7 @@ tl::Variant python2c<tl::Variant> (PyObject *rval, tl::Heap *heap)
}
PyObject *
object_to_python (void *obj, const gsi::ArgType &atype)
object_to_python (void *obj, PYAObjectBase *self, const gsi::ArgType &atype)
{
const gsi::ClassBase *cls = atype.cls()->subclass_decl (obj);
@ -369,7 +369,7 @@ object_to_python (void *obj, const gsi::ArgType &atype)
bool prefer_copy = atype.is_cref ();
bool can_destroy = prefer_copy || atype.is_ptr ();
return object_to_python (obj, cls, pass_obj, is_const, prefer_copy, can_destroy);
return object_to_python (obj, self, cls, pass_obj, is_const, prefer_copy, can_destroy);
}
/**
@ -390,7 +390,7 @@ void correct_constness (PyObject *obj, bool const_required)
}
PyObject *
object_to_python (void *obj, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy)
object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy)
{
if (! obj || ! cls) {
Py_RETURN_NONE;
@ -402,7 +402,13 @@ object_to_python (void *obj, const gsi::ClassBase *cls, bool pass_obj, bool is_c
}
PYAObjectBase *pya_object = 0;
if (! clsact->adapted_type_info () && clsact->is_managed ()) {
if (self && self->obj () == obj) {
// reuse self if the object to be converted is self
pya_object = self;
} else if (! clsact->adapted_type_info () && clsact->is_managed ()) {
StatusChangedListener *client = clsact->gsi_object (obj)->find_client<StatusChangedListener> ();
if (client) {
@ -504,7 +510,7 @@ PyObject *c2python<tl::Variant> (const tl::Variant &c)
const gsi::ClassBase *cls = c.gsi_cls ();
if (cls) {
void *obj = const_cast<void *> (c.to_user ());
return object_to_python (obj, c.user_cls ()->gsi_cls (), false, false, true, false);
return object_to_python (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false);
} else {
// not a known type -> return nil
Py_RETURN_NONE;

View File

@ -43,12 +43,15 @@ namespace gsi
namespace pya
{
class PYAObjectBase;
// -------------------------------------------------------------------
// Conversion of a generic object to a Python object
/**
* @brief Translates an object to a Python object (PyObject)
* @param obj The generic object pointer
* @param self If there is an object where the returned object may be a member of or 0 if there isn't
* @param cls The class of the object
* @param pass_obj If true, the Python object will own the original object which gets destroyed when the Ruby object is finalized
* @param is_const If true, the Python object will be a const one unless the original object is already bound in a non-const way
@ -57,14 +60,14 @@ namespace pya
* @return The Python object
*/
PyObject *
object_to_python (void *obj, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy);
object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy);
/**
* @brief Translates an object to a Python object (PyObject)
* This version takes it's flags from the atype given.
*/
PyObject *
object_to_python (void *obj, const gsi::ArgType &atype);
object_to_python (void *obj, PYAObjectBase *self, const gsi::ArgType &atype);
// -------------------------------------------------------------------
// Type checks

View File

@ -366,7 +366,7 @@ pya_plain_iterator_next (PyObject *self)
gsi::SerialArgs args (iter->iter->serial_size ());
iter->iter->get (args);
PythonRef obj = pop_arg (*iter->value_type, args, NULL, heap);
PythonRef obj = pop_arg (*iter->value_type, args, 0, heap);
iter->iter->inc ();

View File

@ -522,7 +522,7 @@ struct reader<void *>
template <>
struct reader<gsi::StringType>
{
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PyObject * /*self*/, const gsi::ArgType &, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase * /*self*/, const gsi::ArgType &, tl::Heap *heap)
{
std::auto_ptr<gsi::StringAdaptor> a ((gsi::StringAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -534,7 +534,7 @@ struct reader<gsi::StringType>
};
static
PyObject *object_from_variant (const tl::Variant &var, const gsi::ArgType &atype)
PyObject *object_from_variant (const tl::Variant &var, PYAObjectBase *self, const gsi::ArgType &atype)
{
if (var.is_user()) {
@ -579,7 +579,7 @@ PyObject *object_from_variant (const tl::Variant &var, const gsi::ArgType &atype
}
return object_to_python ((void *) var.to_user (), var.user_cls ()->gsi_cls (), pass_obj, is_const, prefer_copy, can_destroy);
return object_to_python ((void *) var.to_user (), self, var.user_cls ()->gsi_cls (), pass_obj, is_const, prefer_copy, can_destroy);
} else {
return c2python<tl::Variant> (var);
@ -592,7 +592,7 @@ PyObject *object_from_variant (const tl::Variant &var, const gsi::ArgType &atype
template <>
struct reader<gsi::VariantType>
{
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PyObject * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase *self, const gsi::ArgType &atype, tl::Heap *heap)
{
std::auto_ptr<gsi::VariantAdaptor> a ((gsi::VariantAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -601,9 +601,9 @@ struct reader<gsi::VariantType>
gsi::VariantAdaptorImpl<tl::Variant> *aa = dynamic_cast<gsi::VariantAdaptorImpl<tl::Variant> *> (a.get ());
if (aa) {
// A small optimization that saves one variant copy
*ret = object_from_variant (aa->var_ref (), atype);
*ret = object_from_variant (aa->var_ref (), self, atype);
} else {
*ret = object_from_variant (a->var (), atype);
*ret = object_from_variant (a->var (), self, atype);
}
}
}
@ -615,7 +615,7 @@ struct reader<gsi::VariantType>
template <>
struct reader<gsi::VectorType>
{
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PyObject * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
{
std::auto_ptr<gsi::VectorAdaptor> a ((gsi::VectorAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -635,7 +635,7 @@ struct reader<gsi::VectorType>
template <>
struct reader<gsi::MapType>
{
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PyObject * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
{
std::auto_ptr<gsi::MapAdaptor> a ((gsi::MapAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -656,13 +656,13 @@ struct reader<gsi::MapType>
template <>
struct reader<gsi::ObjectType>
{
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PyObject * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, PythonRef *ret, PYAObjectBase *self, const gsi::ArgType &atype, tl::Heap *heap)
{
void *obj = rr->read<void *> (*heap);
if (! obj) {
*ret = PythonRef (Py_None, false /*borrowed*/);
} else {
*ret = object_to_python (obj, atype);
*ret = object_to_python (obj, self, atype);
}
}
};
@ -673,14 +673,14 @@ struct reader<gsi::ObjectType>
template <>
struct reader<gsi::VoidType>
{
void operator() (gsi::SerialArgs *, PythonRef *, PyObject *, const gsi::ArgType &, tl::Heap *)
void operator() (gsi::SerialArgs *, PythonRef *, PYAObjectBase *, const gsi::ArgType &, tl::Heap *)
{
// .. nothing: void is not serialized
}
};
PythonRef
pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PyObject *self, tl::Heap &heap)
pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PYAObjectBase *self, tl::Heap &heap)
{
PythonRef ret;
gsi::do_on_type<reader> () (atype.type (), &aserial, &ret, self, atype, &heap);
@ -754,7 +754,7 @@ void PythonBasedVectorAdaptor::push (gsi::SerialArgs &r, tl::Heap &heap)
{
if (PyList_Check (m_array.get ())) {
PythonRef member;
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &member, (PyObject *) NULL, *mp_ainner, &heap);
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &member, (PYAObjectBase *) 0, *mp_ainner, &heap);
PyList_Append (m_array.get (), member.get ());
} else if (PyTuple_Check (m_array.get ())) {
throw tl::Exception (tl::to_string (QObject::tr ("Tuples cannot be modified and cannot be used as out parameters")));
@ -825,8 +825,8 @@ gsi::MapAdaptorIterator *PythonBasedMapAdaptor::create_iterator () const
void PythonBasedMapAdaptor::insert (gsi::SerialArgs &r, tl::Heap &heap)
{
PythonRef k, v;
gsi::do_on_type<reader> () (mp_ainner_k->type (), &r, &k, (PyObject *) NULL, *mp_ainner_k, &heap);
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &v, (PyObject *) NULL, *mp_ainner, &heap);
gsi::do_on_type<reader> () (mp_ainner_k->type (), &r, &k, (PYAObjectBase *) 0, *mp_ainner_k, &heap);
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &v, (PYAObjectBase *) 0, *mp_ainner, &heap);
PyDict_SetItem (m_hash.get (), k.get (), v.get ());
}

View File

@ -33,6 +33,8 @@
namespace pya
{
class PYAObjectBase;
/**
* @brief Serializes the given argument using the given type.
*
@ -56,7 +58,7 @@ push_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PyObject *arg, tl
* @param self The self object of the method call (for shortcut evaluation to return self if possible)
* @return The deserialized object (a new reference)
*/
PythonRef pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PyObject *self, tl::Heap &heap);
PythonRef pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, PYAObjectBase *self, tl::Heap &heap);
/**
* @brief Tests whether the given object is compatible with the given type

View File

@ -164,7 +164,7 @@ Callee::call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const
// TODO: callbacks with default arguments?
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
PyTuple_SetItem (argv.get (), arg4self + (a - meth->begin_arguments ()), pop_arg (*a, args, NULL, heap).release ());
PyTuple_SetItem (argv.get (), arg4self + (a - meth->begin_arguments ()), pop_arg (*a, args, 0, heap).release ());
}
PythonRef result (PyObject_CallObject (callable.get (), argv.get ()));
@ -218,7 +218,7 @@ void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gs
int args_avail = int (std::distance (meth->begin_arguments (), meth->end_arguments ()));
PythonRef argv (PyTuple_New (args_avail));
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
PyTuple_SetItem (argv.get (), int (a - meth->begin_arguments ()), pop_arg (*a, args, NULL, heap).release ());
PyTuple_SetItem (argv.get (), int (a - meth->begin_arguments ()), pop_arg (*a, args, 0, heap).release ());
}
// NOTE: in case one event handler deletes the object, it's safer to first collect the handlers and

View File

@ -99,6 +99,7 @@ void run_pythontest (tl::TestBase *_this, const std::string &fn)
PYTHONTEST (dbLayoutTest, "dbLayoutTest.py")
PYTHONTEST (dbRegionTest, "dbRegionTest.py")
PYTHONTEST (dbPCellsTest, "dbPCells.py")
PYTHONTEST (dbPolygonTest, "dbPolygonTest.py")
PYTHONTEST (tlTest, "tlTest.py")
#if defined(HAVE_QTBINDINGS)
PYTHONTEST (qtbinding, "qtbinding.py")

View File

@ -977,7 +977,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor)
// In case of an error upon write, pop the arguments to clean them up.
// Without this, there is a risk to keep dead objects on the stack.
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && arglist; ++a) {
pop_arg (*a, arglist, heap);
pop_arg (*a, 0, arglist, heap);
}
throw;
@ -1020,7 +1020,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor)
// In case of an error upon write, pop the arguments to clean them up.
// Without this, there is a risk to keep dead objects on the stack.
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); a != meth->end_arguments () && arglist; ++a) {
pop_arg (*a, arglist, heap);
pop_arg (*a, 0, arglist, heap);
}
throw;
@ -1046,7 +1046,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor)
rr.reset ();
iter->get (rr);
VALUE value = pop_arg (meth->ret_type (), rr, heap);
VALUE value = pop_arg (meth->ret_type (), p, rr, heap);
rba_yield_checked (value);
iter->inc ();
@ -1060,7 +1060,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor)
}
} else {
ret = pop_arg (meth->ret_type (), retlist, heap);
ret = pop_arg (meth->ret_type (), p, retlist, heap);
}
}
@ -1679,7 +1679,7 @@ rba_init (RubyInterpreterPrivateData *d)
gsi::SerialArgs arglist (c->meth->argsize ());
c->meth->call (0, arglist, retlist);
tl::Heap heap;
VALUE ret = pop_arg (c->meth->ret_type (), retlist, heap);
VALUE ret = pop_arg (c->meth->ret_type (), 0, retlist, heap);
rb_define_const (c->klass, c->name.c_str (), ret);
} catch (tl::Exception &ex) {

View File

@ -126,7 +126,7 @@ tl::Variant ruby2c<tl::Variant> (VALUE rval)
}
}
VALUE object_to_ruby (void *obj, const gsi::ArgType &atype)
VALUE object_to_ruby (void *obj, Proxy *self, const gsi::ArgType &atype)
{
const gsi::ClassBase *cls = atype.cls()->subclass_decl (obj);
@ -136,7 +136,7 @@ VALUE object_to_ruby (void *obj, const gsi::ArgType &atype)
bool prefer_copy = atype.is_cref ();
bool can_destroy = prefer_copy || atype.is_ptr ();
return object_to_ruby (obj, cls, pass_obj, is_const, prefer_copy, can_destroy);
return object_to_ruby (obj, self, cls, pass_obj, is_const, prefer_copy, can_destroy);
}
/**
@ -156,7 +156,7 @@ static void correct_constness (Proxy *p, bool const_required)
}
VALUE
object_to_ruby (void *obj, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy)
object_to_ruby (void *obj, Proxy *self, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy)
{
VALUE ret = Qnil;
if (! obj || ! cls) {
@ -170,7 +170,12 @@ object_to_ruby (void *obj, const gsi::ClassBase *cls, bool pass_obj, bool is_con
// Derive a Proxy reference if the object is already bound
Proxy *rba_data = 0;
if (! clsact->adapted_type_info () && clsact->is_managed ()) {
if (self && self->obj () == obj) {
// reuse "self" if the object to convert is self.
rba_data = self;
} else if (! clsact->adapted_type_info () && clsact->is_managed ()) {
rba_data = clsact->gsi_object (obj)->find_client<Proxy> ();
if (rba_data) {
@ -281,7 +286,7 @@ VALUE c2ruby<tl::Variant> (const tl::Variant &c)
const gsi::ClassBase *cls = c.gsi_cls ();
if (cls) {
void *obj = const_cast<void *> (c.to_user ());
return object_to_ruby (obj, c.user_cls ()->gsi_cls (), false, false, true, false);
return object_to_ruby (obj, 0, c.user_cls ()->gsi_cls (), false, false, true, false);
} else {
// not a known type -> return nil
return Qnil;

View File

@ -34,11 +34,14 @@
namespace rba
{
class Proxy;
// -------------------------------------------------------------------
/**
* @brief Translates an object to a Ruby object (VALUE)
* @param obj The generic object pointer
* @param self The object which the object is derived from (self in a method call) or 0 if there is no such object
* @param cls The class of the object
* @param pass_obj If true, the Ruby object will own the original object which gets destroyed when the Ruby object is finalized
* @param is_const If true, the Ruby object will be a const one unless the original object is already bound in a non-const way
@ -46,13 +49,13 @@ namespace rba
* @param can_destroy If true, the Ruby object can be destroyed explicitly
* @return The Ruby object
*/
VALUE object_to_ruby (void *obj, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy);
VALUE object_to_ruby (void *obj, Proxy *self, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy);
/**
* @brief Translates an object to a Ruby object (VALUE)
* This version takes it's flags from the atype given.
*/
VALUE object_to_ruby (void *obj, const gsi::ArgType &atype);
VALUE object_to_ruby (void *obj, Proxy *self, const gsi::ArgType &atype);
// -------------------------------------------------------------------
// Type checks

View File

@ -416,7 +416,7 @@ public:
meth->call (obj, arglist, retlist);
tl::Heap heap;
return pop_arg (meth->ret_type (), retlist, heap);
return pop_arg (meth->ret_type (), p, retlist, heap);
}

View File

@ -320,7 +320,7 @@ Proxy::call (int id, gsi::SerialArgs &args, gsi::SerialArgs &ret) const
// TODO: callbacks with default arguments?
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
rb_ary_push (argv, pop_arg (*a, args, heap));
rb_ary_push (argv, pop_arg (*a, 0, args, heap));
}
VALUE rb_ret = rba_funcall2_checked (m_self, mid, RARRAY_LEN (argv), RARRAY_PTR (argv));
@ -891,7 +891,7 @@ void SignalHandler::call (const gsi::MethodBase *meth, gsi::SerialArgs &args, gs
// TODO: signals with default arguments?
for (gsi::MethodBase::argument_iterator a = meth->begin_arguments (); args && a != meth->end_arguments (); ++a) {
rb_ary_push (argv, pop_arg (*a, args, heap));
rb_ary_push (argv, pop_arg (*a, 0, args, heap));
}
// call the signal handlers ... the last one will deliver the return value

View File

@ -481,7 +481,7 @@ push_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, VALUE arg, tl::He
template <class R>
struct reader
{
void operator() (gsi::SerialArgs *rr, VALUE *ret, const gsi::ArgType &arg, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, VALUE *ret, Proxy * /*self*/, const gsi::ArgType &arg, tl::Heap *heap)
{
if (arg.is_ref ()) {
*ret = c2ruby<R> (rr->template read<R &> (*heap));
@ -516,7 +516,7 @@ struct reader
template <>
struct reader<void *>
{
void operator() (gsi::SerialArgs *rr, VALUE *ret, const gsi::ArgType &arg, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, VALUE *ret, Proxy * /*self*/, const gsi::ArgType &arg, tl::Heap *heap)
{
tl_assert (! arg.is_cref ());
tl_assert (! arg.is_ref ());
@ -532,7 +532,7 @@ struct reader<void *>
template <>
struct reader<gsi::StringType>
{
void operator() (gsi::SerialArgs *rr, VALUE *ret, const gsi::ArgType &, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, VALUE *ret, Proxy * /*self*/, const gsi::ArgType &, tl::Heap *heap)
{
std::auto_ptr<gsi::StringAdaptor> a ((gsi::StringAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -543,7 +543,7 @@ struct reader<gsi::StringType>
}
};
static VALUE object_from_variant (const tl::Variant &var, const gsi::ArgType &atype)
static VALUE object_from_variant (const tl::Variant &var, Proxy *self, const gsi::ArgType &atype)
{
if (var.is_user()) {
@ -588,7 +588,7 @@ static VALUE object_from_variant (const tl::Variant &var, const gsi::ArgType &at
}
return object_to_ruby ((void *) var.to_user (), var.user_cls ()->gsi_cls (), pass_obj, is_const, prefer_copy, can_destroy);
return object_to_ruby ((void *) var.to_user (), self, var.user_cls ()->gsi_cls (), pass_obj, is_const, prefer_copy, can_destroy);
} else {
return c2ruby<tl::Variant> (var);
@ -601,7 +601,7 @@ static VALUE object_from_variant (const tl::Variant &var, const gsi::ArgType &at
template <>
struct reader<gsi::VariantType>
{
void operator() (gsi::SerialArgs *rr, VALUE *ret, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, VALUE *ret, Proxy *self, const gsi::ArgType &atype, tl::Heap *heap)
{
std::auto_ptr<gsi::VariantAdaptor> a ((gsi::VariantAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -610,9 +610,9 @@ struct reader<gsi::VariantType>
gsi::VariantAdaptorImpl<tl::Variant> *aa = dynamic_cast<gsi::VariantAdaptorImpl<tl::Variant> *> (a.get ());
if (aa) {
// A small optimization that saves one variant copy
*ret = object_from_variant (aa->var_ref (), atype);
*ret = object_from_variant (aa->var_ref (), self, atype);
} else {
*ret = object_from_variant (a->var (), atype);
*ret = object_from_variant (a->var (), self, atype);
}
}
}
@ -624,7 +624,7 @@ struct reader<gsi::VariantType>
template <>
struct reader<gsi::VectorType>
{
void operator() (gsi::SerialArgs *rr, VALUE *ret, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, VALUE *ret, Proxy * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
{
std::auto_ptr<gsi::VectorAdaptor> a ((gsi::VectorAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -644,7 +644,7 @@ struct reader<gsi::VectorType>
template <>
struct reader<gsi::MapType>
{
void operator() (gsi::SerialArgs *rr, VALUE *ret, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, VALUE *ret, Proxy * /*self*/, const gsi::ArgType &atype, tl::Heap *heap)
{
std::auto_ptr<gsi::MapAdaptor> a ((gsi::MapAdaptor *) rr->read<void *>(*heap));
if (!a.get ()) {
@ -665,13 +665,13 @@ struct reader<gsi::MapType>
template <>
struct reader<gsi::ObjectType>
{
void operator() (gsi::SerialArgs *rr, VALUE *ret, const gsi::ArgType &atype, tl::Heap *heap)
void operator() (gsi::SerialArgs *rr, VALUE *ret, Proxy *self, const gsi::ArgType &atype, tl::Heap *heap)
{
void *obj = rr->read<void *> (*heap);
if (! obj) {
*ret = Qnil;
} else {
*ret = object_to_ruby (obj, atype);
*ret = object_to_ruby (obj, self, atype);
}
}
};
@ -679,7 +679,7 @@ struct reader<gsi::ObjectType>
template <>
struct reader<gsi::VoidType>
{
void operator() (gsi::SerialArgs * /*rr*/, VALUE * /*ret*/, const gsi::ArgType & /*atype*/, tl::Heap * /*heap*/)
void operator() (gsi::SerialArgs * /*rr*/, VALUE * /*ret*/, Proxy * /*self*/, const gsi::ArgType & /*atype*/, tl::Heap * /*heap*/)
{
// .. nothing: void is not serialized
}
@ -755,7 +755,7 @@ gsi::VectorAdaptorIterator *RubyBasedVectorAdaptor::create_iterator () const
void RubyBasedVectorAdaptor::push (gsi::SerialArgs &r, tl::Heap &heap)
{
VALUE member;
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &member, *mp_ainner, &heap);
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &member, (Proxy *) 0, *mp_ainner, &heap);
rb_ary_push (m_array, member);
}
@ -831,8 +831,8 @@ gsi::MapAdaptorIterator *RubyBasedMapAdaptor::create_iterator () const
void RubyBasedMapAdaptor::insert (gsi::SerialArgs &r, tl::Heap &heap)
{
VALUE k, v;
gsi::do_on_type<reader> () (mp_ainner_k->type (), &r, &k, *mp_ainner_k, &heap);
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &v, *mp_ainner, &heap);
gsi::do_on_type<reader> () (mp_ainner_k->type (), &r, &k, (Proxy *) 0, *mp_ainner_k, &heap);
gsi::do_on_type<reader> () (mp_ainner->type (), &r, &v, (Proxy *) 0, *mp_ainner, &heap);
rb_hash_aset (m_hash, k, v);
}
@ -855,10 +855,10 @@ size_t RubyBasedMapAdaptor::serial_size () const
// Pops an argument from the call or return stack
VALUE
pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, tl::Heap &heap)
pop_arg (const gsi::ArgType &atype, Proxy *self, gsi::SerialArgs &aserial, tl::Heap &heap)
{
VALUE ret = Qnil;
gsi::do_on_type<reader> () (atype.type (), &aserial, &ret, atype, &heap);
gsi::do_on_type<reader> () (atype.type (), &aserial, &ret, self, atype, &heap);
return ret;
}

View File

@ -33,10 +33,14 @@
namespace rba
{
class Proxy;
/**
* @brief Pops an argument from the call or return stack
*
* "self" is a reference to the object that the method is called on or 0 if there is no such object.
*/
VALUE pop_arg (const gsi::ArgType &atype, gsi::SerialArgs &aserial, tl::Heap &heap);
VALUE pop_arg (const gsi::ArgType &atype, Proxy *self, gsi::SerialArgs &aserial, tl::Heap &heap);
/**
* @brief Pushes an argument on the call or return stack

731
testdata/python/dbPolygonTest.py vendored Normal file
View File

@ -0,0 +1,731 @@
# encoding: UTF-8
# KLayout Layout Viewer
# Copyright (C) 2006-2017 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import pya
import unittest
import sys
class DBPolygonTests(unittest.TestCase):
# DPolygon basics
def test_1_DPolygon(self):
a = pya.DPolygon()
self.assertEqual( str(a), "()" )
self.assertEqual( str(pya.DPolygon.from_s(str(a))), str(a) )
self.assertEqual( a.is_box(), False )
b = a.dup()
a = pya.DPolygon( [ pya.DPoint( 0, 1 ), pya.DPoint( 1, 5 ), pya.DPoint( 5, 5 ) ] )
self.assertEqual( str(a), "(0,1;1,5;5,5)" )
self.assertEqual( str(a * 2), "(0,2;2,10;10,10)" )
self.assertEqual( str(pya.DPolygon.from_s(str(a))), str(a) )
self.assertEqual( a.is_box(), False )
self.assertEqual( a.num_points_hull(), 3 )
c = a.dup()
self.assertEqual( a == b, False )
self.assertEqual( a == c, True )
self.assertEqual( a != b, True )
self.assertEqual( a != c, False )
a = pya.DPolygon( pya.DBox( 5, -10, 20, 15 ) )
self.assertEqual( a.is_box(), True )
self.assertEqual( str(a), "(5,-10;5,15;20,15;20,-10)" )
self.assertEqual( str(pya.Polygon(a)), "(5,-10;5,15;20,15;20,-10)" )
self.assertEqual( a.num_points_hull(), 4 )
self.assertEqual( a.area(), 15*25 )
self.assertEqual( a.perimeter(), 80 )
self.assertEqual( a.inside( pya.DPoint( 10, 0 ) ), True )
self.assertEqual( a.inside( pya.DPoint( 5, 0 ) ), True )
self.assertEqual( a.inside( pya.DPoint( 30, 0 ) ), False )
arr = []
for p in a.each_point_hull():
arr.append( str(p) )
self.assertEqual( arr, ["5,-10", "5,15", "20,15", "20,-10"] )
b = a.dup()
self.assertEqual( str(a.moved( pya.DPoint( 0, 1 ) )), "(5,-9;5,16;20,16;20,-9)" )
self.assertEqual( str(a.moved( 0, 1 )), "(5,-9;5,16;20,16;20,-9)" )
aa = a.dup()
aa.move( 1, 0 )
self.assertEqual( str(aa), "(6,-10;6,15;21,15;21,-10)" )
a.move( pya.DPoint( 1, 0 ) )
self.assertEqual( str(a), "(6,-10;6,15;21,15;21,-10)" )
b = b.transformed( pya.DTrans( pya.DTrans.R0, pya.DPoint( 1, 0 )) )
self.assertEqual( str(b), "(6,-10;6,15;21,15;21,-10)" )
m = pya.DCplxTrans( pya.DTrans(), 1.5 )
self.assertEqual( type(a.transformed(m)).__name__, "DPolygon" )
self.assertEqual( str(a.transformed(m)), "(9,-15;9,22.5;31.5,22.5;31.5,-15)" )
m = pya.VCplxTrans( 1000.0 )
self.assertEqual( type(a.transformed(m)).__name__, "Polygon" )
self.assertEqual( str(a.transformed(m)), "(6000,-10000;6000,15000;21000,15000;21000,-10000)" )
a.hull = [ pya.DPoint( 0, 1 ), pya.DPoint( 1, 1 ), pya.DPoint( 1, 5 ) ]
self.assertEqual( str(a.bbox()), "(0,1;1,5)" )
self.assertEqual( a.holes(), 0 )
a.insert_hole( [ pya.DPoint( 1, 2 ), pya.DPoint( 2, 2 ), pya.DPoint( 2, 6 ) ] )
self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" )
self.assertEqual( str(pya.DPolygon.from_s(str(a))), str(a) )
self.assertEqual( a.area(), 0 )
self.assertEqual( a.num_points_hole(0), 3 )
self.assertEqual( a.holes(), 1 )
self.assertEqual( str(a.point_hull(1)), "1,5" )
self.assertEqual( str(a.point_hull(0)), "0,1" )
self.assertEqual( str(a.point_hull(100)), "0,0" )
self.assertEqual( str(a.point_hole(0, 100)), "0,0" )
self.assertEqual( str(a.point_hole(0, 1)), "2,2" )
self.assertEqual( str(a.point_hole(1, 1)), "0,0" )
a.compress(False);
self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" )
a.compress(True);
self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" )
b = a.dup()
b.assign_hole(0, pya.DBox( 10, 20, 20, 60 ))
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60;10,60)" )
b.insert_hole(pya.DBox( 10, 20, 20, 60 ))
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60;10,60/10,20;20,20;20,60;10,60)" )
self.assertEqual( b.is_box(), False )
b = a.dup()
b.assign_hole(0, [ pya.DPoint( 10, 20 ), pya.DPoint( 20, 20 ), pya.DPoint( 20, 60 ) ])
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60)" )
b.assign_hole(1, [ pya.DPoint( 15, 25 ), pya.DPoint( 25, 25 ), pya.DPoint( 25, 65 ) ])
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60)" )
b.insert_hole( [ pya.DPoint( 1, 2 ), pya.DPoint( 2, 2 ), pya.DPoint( 2, 6 ) ] )
self.assertEqual( str(b), "(0,1;1,5;1,1/1,2;2,2;2,6/10,20;20,20;20,60)" )
b.assign_hole(0, [ pya.DPoint( 15, 25 ), pya.DPoint( 25, 25 ), pya.DPoint( 25, 65 ) ])
self.assertEqual( str(b), "(0,1;1,5;1,1/15,25;25,25;25,65/10,20;20,20;20,60)" )
arr = []
for p in a.each_point_hole(0):
arr.append( str(p) )
self.assertEqual( arr, ["1,2", "2,2", "2,6"] )
arr = []
for p in a.each_edge():
arr.append( str(p) )
self.assertEqual( arr, ["(0,1;1,5)", "(1,5;1,1)", "(1,1;0,1)", "(1,2;2,2)", "(2,2;2,6)", "(2,6;1,2)"] )
# Ellipse constructor
p = pya.DPolygon.ellipse( pya.DBox(-10000, -20000, 30000, 40000), 200 )
self.assertEqual(p.num_points(), 200)
self.assertEqual(str(p.bbox()), "(-10000,-20000;30000,40000)")
self.assertEqual(int(p.area()), 1884645544) # roughly box.area*PI/4
p = pya.DPolygon.ellipse( pya.DBox(-10000, -20000, 30000, 40000), 4 )
self.assertEqual(str(p), "(10000,-20000;-10000,10000;10000,40000;30000,10000)")
# Polygon basics
def test_1_Polygon(self):
a = pya.Polygon()
self.assertEqual( str(a), "()" )
self.assertEqual( str(pya.Polygon.from_s(str(a))), str(a) )
self.assertEqual( a.is_box(), False )
b = a.dup()
a = pya.Polygon( [ pya.Point( 0, 1 ), pya.Point( 1, 5 ), pya.Point( 5, 5 ) ] )
self.assertEqual( str(a), "(0,1;1,5;5,5)" )
self.assertEqual( str(a * 2), "(0,2;2,10;10,10)" )
self.assertEqual( str(pya.Polygon.from_s(str(a))), str(a) )
self.assertEqual( a.num_points_hull(), 3 )
c = a.dup()
self.assertEqual( a == b, False )
self.assertEqual( a == c, True )
self.assertEqual( a != b, True )
self.assertEqual( a != c, False )
a = pya.Polygon( pya.Box( 5, -10, 20, 15 ) )
self.assertEqual( a.is_box(), True )
self.assertEqual( str(a), "(5,-10;5,15;20,15;20,-10)" )
self.assertEqual( str(pya.DPolygon(a)), "(5,-10;5,15;20,15;20,-10)" )
self.assertEqual( a.num_points_hull(), 4 )
self.assertEqual( a.area(), 15*25 )
self.assertEqual( a.perimeter(), 80 )
self.assertEqual( a.inside( pya.Point( 10, 0 ) ), True )
self.assertEqual( a.inside( pya.Point( 5, 0 ) ), True )
self.assertEqual( a.inside( pya.Point( 30, 0 ) ), False )
arr = []
for p in a.each_point_hull():
arr.append(str(p))
self.assertEqual( arr, ["5,-10", "5,15", "20,15", "20,-10"] )
b = a.dup()
self.assertEqual( str(a.moved( pya.Point( 0, 1 ) )), "(5,-9;5,16;20,16;20,-9)" )
self.assertEqual( str(a.moved( 0, 1 )), "(5,-9;5,16;20,16;20,-9)" )
aa = a.dup()
aa.move( 1, 0 )
self.assertEqual( str(aa), "(6,-10;6,15;21,15;21,-10)" )
a.move( pya.Point( 1, 0 ) )
self.assertEqual( str(a), "(6,-10;6,15;21,15;21,-10)" )
b = b.transformed( pya.Trans( pya.Trans.R0, pya.Point( 1, 0 )) )
self.assertEqual( str(b), "(6,-10;6,15;21,15;21,-10)" )
m = pya.CplxTrans( pya.Trans(), 1.5 )
self.assertEqual( str(a.transformed(m)), "(9,-15;9,22.5;31.5,22.5;31.5,-15)" )
self.assertEqual( str(a.transformed(pya.ICplxTrans(m))), "(9,-15;9,23;32,23;32,-15)" )
a.hull = [ pya.Point( 0, 1 ), pya.Point( 1, 1 ), pya.Point( 1, 5 ) ]
self.assertEqual( str(a.bbox()), "(0,1;1,5)" )
self.assertEqual( a.holes(), 0 )
a.insert_hole( [ pya.Point( 1, 2 ), pya.Point( 2, 2 ), pya.Point( 2, 6 ) ] )
self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" )
self.assertEqual( str(pya.Polygon.from_s(str(a))), str(a) )
self.assertEqual( a.area(), 0 )
self.assertEqual( a.num_points_hole(0), 3 )
self.assertEqual( a.holes(), 1 )
self.assertEqual( str(a.point_hull(1)), "1,5" )
self.assertEqual( str(a.point_hull(0)), "0,1" )
self.assertEqual( str(a.point_hull(100)), "0,0" )
self.assertEqual( str(a.point_hole(0, 100)), "0,0" )
self.assertEqual( str(a.point_hole(0, 1)), "2,2" )
self.assertEqual( str(a.point_hole(1, 1)), "0,0" )
a.compress(False);
self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" )
a.compress(True);
self.assertEqual( str(a), "(0,1;1,5;1,1/1,2;2,2;2,6)" )
b = a.dup()
b.assign_hole(0, pya.Box( 10, 20, 20, 60 ))
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60;10,60)" )
self.assertEqual( b.is_box(), False )
b.insert_hole(pya.Box( 10, 20, 20, 60 ))
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60;10,60/10,20;20,20;20,60;10,60)" )
b = a.dup()
b.assign_hole(0, [ pya.Point( 10, 20 ), pya.Point( 20, 20 ), pya.Point( 20, 60 ) ])
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60)" )
b.assign_hole(1, [ pya.Point( 15, 25 ), pya.Point( 25, 25 ), pya.Point( 25, 65 ) ])
self.assertEqual( str(b), "(0,1;1,5;1,1/10,20;20,20;20,60)" )
b.insert_hole( [ pya.Point( 1, 2 ), pya.Point( 2, 2 ), pya.Point( 2, 6 ) ] )
self.assertEqual( str(b), "(0,1;1,5;1,1/1,2;2,2;2,6/10,20;20,20;20,60)" )
b.assign_hole(0, [ pya.Point( 15, 25 ), pya.Point( 25, 25 ), pya.Point( 25, 65 ) ])
self.assertEqual( str(b), "(0,1;1,5;1,1/15,25;25,25;25,65/10,20;20,20;20,60)" )
arr = []
for p in a.each_point_hole(0):
arr.append(str(p))
self.assertEqual( arr, ["1,2", "2,2", "2,6"] )
arr = []
for p in a.each_edge():
arr.append(str(p))
self.assertEqual( arr, ["(0,1;1,5)", "(1,5;1,1)", "(1,1;0,1)", "(1,2;2,2)", "(2,2;2,6)", "(2,6;1,2)"] )
a = pya.Polygon( [ pya.Point( 0, 1 ), pya.Point( 1, 5 ), pya.Point( 5, 5 ) ] )
self.assertEqual( str(a), "(0,1;1,5;5,5)" )
self.assertEqual( str(a.sized(2)), "(0,-2;-2,0;-1,7;7,7;8,5)" )
self.assertEqual( str(a.sized(2, 2)), "(0,-2;-2,0;-1,7;7,7;8,5)" )
aa = a.dup()
a.size(2, 2)
self.assertEqual( str(a), "(0,-2;-2,0;-1,7;7,7;8,5)" )
a = aa.dup()
a.size(2)
self.assertEqual( str(a), "(0,-2;-2,0;-1,7;7,7;8,5)" )
a = pya.Polygon( [ pya.Point( 0, 1 ), pya.Point( 1, 5 ), pya.Point( 5, 5 ) ] )
self.assertEqual( str(a), "(0,1;1,5;5,5)" )
self.assertEqual( str(a.sized(2, 0, 2)), "(-2,1;-1,5;7,5;2,1)" )
a.size(2, 0, 2);
self.assertEqual( str(a), "(-2,1;-1,5;7,5;2,1)" )
a = pya.Polygon()
self.assertEqual( str(a), "()" )
# corner rounding
a = pya.Polygon( [ pya.Point(0, 0), pya.Point(0, 2000), pya.Point(4000, 2000),
pya.Point(4000, 1000), pya.Point(2000, 1000), pya.Point(2000, 0) ] )
ar = a.round_corners(100, 200, 8)
self.assertEqual( str(ar), "(117,0;0,117;0,1883;117,2000;3883,2000;4000,1883;4000,1117;3883,1000;2059,1000;2000,941;2000,117;1883,0)" )
ar = a.round_corners(200, 100, 32)
self.assertEqual( str(ar), "(90,0;71,4;53,11;36,22;22,36;11,53;4,71;0,90;0,1910;4,1929;11,1947;22,1964;36,1978;53,1989;71,1996;90,2000;3910,2000;3929,1996;3947,1989;3964,1978;3978,1964;3989,1947;3996,1929;4000,1910;4000,1090;3996,1071;3989,1053;3978,1036;3964,1022;3947,1011;3929,1004;3910,1000;2180,1000;2142,992;2105,977;2073,955;2045,927;2023,895;2008,858;2000,820;2000,90;1996,71;1989,53;1978,36;1964,22;1947,11;1929,4;1910,0)" )
# Minkowsky sums
p = pya.Polygon( [ pya.Point.new(0, -100), pya.Point.new(0, -50), pya.Point.new(-100, -75), pya.Point.new(0, 100), pya.Point.new(50, 50), pya.Point.new(100, 75), pya.Point.new(100, 0), pya.Point.new(100, -50) ] )
self.assertEqual(str(p.minkowsky_sum(pya.Edge.new(pya.Point.new(10, 10), pya.Point.new(210, 110)), True)), "(10,-90;10,-40;-90,-65;10,110;210,210;260,160;310,185;310,60)")
self.assertEqual(str(p.minkowsky_sum([pya.Point.new(10, 10), pya.Point.new(10, 310), pya.Point.new(510, 310), pya.Point.new(510, 10), pya.Point.new(10, 10) ], False)), "(10,-90;10,-65;-90,-65;-90,235;10,410;510,410;535,385;610,385;610,-40;510,-90/110,110;410,110;410,210;110,210)")
self.assertEqual(str(p.minkowsky_sum([pya.Point.new(10, 10), pya.Point.new(10, 310), pya.Point.new(510, 310), pya.Point.new(510, 10), pya.Point.new(10, 10) ], True)), "(10,-90;10,-65;-90,-65;-90,210;110,210;110,110;410,110;410,210;-90,210;-90,235;10,410;510,410;535,385;610,385;610,-40;510,-90)")
self.assertEqual(str(p.minkowsky_sum(pya.Box.new(pya.Point.new(10, 10), pya.Point.new(210, 110)), True)), "(10,-90;10,-65;-90,-65;-90,35;10,210;210,210;235,185;310,185;310,-40;210,-90)")
self.assertEqual(str(p.minkowsky_sum(pya.Box.new(pya.Point.new(10, 10), pya.Point.new(210, 10)), True)), "(10,-90;10,-65;-90,-65;10,110;210,110;235,85;310,85;310,-40;210,-90)")
self.assertEqual(str(p.minkowsky_sum(pya.Polygon.new(pya.Box.new(pya.Point.new(10, 10), pya.Point.new(210, 110))), True)), "(10,-90;10,-65;-90,-65;-90,35;10,210;210,210;235,185;310,185;310,-40;210,-90)")
# Smoothing
p = pya.Polygon( [ pya.Point.new(0, 0), pya.Point.new(10, 50), pya.Point.new(0, 100), pya.Point.new(200, 100), pya.Point.new(200, 0) ])
self.assertEqual(str(p.smooth(5)), "(0,0;10,50;0,100;200,100;200,0)")
self.assertEqual(str(p.smooth(15)), "(0,0;0,100;200,100;200,0)")
# Ellipse constructor
p = pya.Polygon.ellipse( pya.Box(-10000, -20000, 30000, 40000), 200 )
self.assertEqual(p.num_points(), 200)
self.assertEqual(str(p.bbox()), "(-10000,-20000;30000,40000)")
self.assertEqual(p.area(), 1884651158) # roughly box.area*PI/4
p = pya.Polygon.ellipse( pya.Box(-10000, -20000, 30000, 40000), 4 )
self.assertEqual(str(p), "(10000,-20000;-10000,10000;10000,40000;30000,10000)")
# Polygon parametrized edge iterator
def test_2(self):
hull = [ pya.Point(0, 0), pya.Point(6000, 0),
pya.Point(6000, 3000), pya.Point(0, 3000) ]
hole1 = [ pya.Point(1000, 1000), pya.Point(2000, 1000),
pya.Point(2000, 2000), pya.Point(1000, 2000) ]
hole2 = [ pya.Point(3000, 1000), pya.Point(4000, 1000),
pya.Point(4000, 2000), pya.Point(3000, 2000) ]
poly = pya.Polygon(hull)
poly.insert_hole(hole1)
poly.insert_hole(hole2)
es = []
for e in poly.each_edge():
es.append(str(e))
self.assertEqual( "/".join(es), "(0,0;0,3000)/(0,3000;6000,3000)/(6000,3000;6000,0)/(6000,0;0,0)/(1000,1000;2000,1000)/(2000,1000;2000,2000)/(2000,2000;1000,2000)/(1000,2000;1000,1000)/(3000,1000;4000,1000)/(4000,1000;4000,2000)/(4000,2000;3000,2000)/(3000,2000;3000,1000)" )
es = []
for e in poly.each_edge(0):
es.append(str(e))
self.assertEqual( "/".join(es), "(0,0;0,3000)/(0,3000;6000,3000)/(6000,3000;6000,0)/(6000,0;0,0)" )
es = []
for e in poly.each_edge(1):
es.append(str(e))
self.assertEqual( "/".join(es), "(1000,1000;2000,1000)/(2000,1000;2000,2000)/(2000,2000;1000,2000)/(1000,2000;1000,1000)" )
es = []
for e in poly.each_edge(2):
es.append(str(e))
self.assertEqual( "/".join(es), "(3000,1000;4000,1000)/(4000,1000;4000,2000)/(4000,2000;3000,2000)/(3000,2000;3000,1000)" )
es = []
for e in poly.each_edge(3):
es.append(str(e))
self.assertEqual( "/".join(es), "" )
hull = [ pya.DPoint(0, 0), pya.DPoint(6000, 0),
pya.DPoint(6000, 3000), pya.DPoint(0, 3000) ]
hole1 = [ pya.DPoint(1000, 1000), pya.DPoint(2000, 1000),
pya.DPoint(2000, 2000), pya.DPoint(1000, 2000) ]
hole2 = [ pya.DPoint(3000, 1000), pya.DPoint(4000, 1000),
pya.DPoint(4000, 2000), pya.DPoint(3000, 2000) ]
poly = pya.DPolygon(hull)
poly.insert_hole(hole1)
poly.insert_hole(hole2)
es = []
for e in poly.each_edge():
es.append(str(e))
self.assertEqual( "/".join(es), "(0,0;0,3000)/(0,3000;6000,3000)/(6000,3000;6000,0)/(6000,0;0,0)/(1000,1000;2000,1000)/(2000,1000;2000,2000)/(2000,2000;1000,2000)/(1000,2000;1000,1000)/(3000,1000;4000,1000)/(4000,1000;4000,2000)/(4000,2000;3000,2000)/(3000,2000;3000,1000)" )
es = []
for e in poly.each_edge(0):
es.append(str(e))
self.assertEqual( "/".join(es), "(0,0;0,3000)/(0,3000;6000,3000)/(6000,3000;6000,0)/(6000,0;0,0)" )
es = []
for e in poly.each_edge(1):
es.append(str(e))
self.assertEqual( "/".join(es), "(1000,1000;2000,1000)/(2000,1000;2000,2000)/(2000,2000;1000,2000)/(1000,2000;1000,1000)" )
es = []
for e in poly.each_edge(2):
es.append(str(e))
self.assertEqual( "/".join(es), "(3000,1000;4000,1000)/(4000,1000;4000,2000)/(4000,2000;3000,2000)/(3000,2000;3000,1000)" )
es = []
for e in poly.each_edge(3):
es.append(str(e))
self.assertEqual( "/".join(es), "" )
# raw mode polygons
def test_2_Polygon(self):
pts = [ pya.Point(0, 0) ]
p = pya.Polygon(pts, False)
self.assertEqual(str(p), "()")
pts = [ pya.Point(0, 0) ]
p = pya.Polygon(pts)
self.assertEqual(str(p), "()")
pts = [ pya.Point(0, 0) ]
p = pya.Polygon(pts, True)
self.assertEqual(str(p), "(0,0)")
arr = []
for e in p.each_edge():
arr.append(str(e))
self.assertEqual( arr, ["(0,0;0,0)"] )
p = pya.Polygon(pya.Box(0, 0, 100, 100))
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0)")
p.insert_hole( [ pya.Point(0, 0), pya.Point(10, 0) ] )
# TODO: this isn't nice (empty hole):
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0/)")
p = pya.Polygon(pya.Box(0, 0, 100, 100))
p.insert_hole( [ pya.Point(0, 0), pya.Point(10, 0) ], True )
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0/0,0;10,0)")
p.assign_hole(0, [ pya.Point(0, 0), pya.Point(10, 0) ] )
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0/)")
p.assign_hole(0, [ pya.Point(0, 0), pya.Point(10, 0) ], True )
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0/0,0;10,0)")
pts = [ pya.Point(0, 0), pya.Point(10, 0) ]
p = pya.Polygon(pts, True)
self.assertEqual(str(p), "(0,0;10,0)")
# conversion of degenerated polygon to simple polygon is not supported currently:
self.assertEqual(str(p.to_simple_polygon()), "()")
self.assertEqual(str(pya.DPolygon(p)), "(0,0;10,0)")
p.hull = []
self.assertEqual(str(p), "()")
p.hull = [ pya.Point(0, 0), pya.Point(10, 0) ]
self.assertEqual(str(p), "(0,0;10,0)")
p.assign_hull([ pya.Point(0, 0), pya.Point(10, 0) ], False)
self.assertEqual(str(p), "()")
p.assign_hull([ pya.Point(0, 0), pya.Point(10, 0) ], True)
self.assertEqual(str(p), "(0,0;10,0)")
arr = []
for e in p.each_edge():
arr.append(str(e))
self.assertEqual( arr, ["(0,0;10,0)", "(10,0;0,0)"] )
self.assertEqual(str(p.moved(1, 2)), "(1,2;11,2)")
self.assertEqual(str(p.sized(2)), "(0,-2;0,2;10,2;10,-2)")
self.assertEqual(str(p * 2), "(0,0;20,0)")
self.assertEqual(str(p.transformed(pya.Trans(pya.Trans.R90))), "(0,0;0,10)")
pp = p.dup()
pp.transform(pya.Trans(pya.Trans.R90))
self.assertEqual(str(pp), "(0,0;0,10)")
p = pya.Polygon([ pya.Point(0, 0), pya.Point(0, 10) ], True)
q = pya.Polygon([ pya.Point(1, 1), pya.Point(-9, 1) ], True)
self.assertEqual(str(p.minkowsky_sum(q, False)), "(-9,1;-9,11;1,11;1,1)")
# raw mode polygons
def test_2_DPolygon(self):
pts = [ pya.DPoint(0, 0) ]
p = pya.DPolygon(pts, True)
self.assertEqual(str(p), "(0,0)")
arr = []
for e in p.each_edge():
arr.append(str(e))
self.assertEqual( arr, ["(0,0;0,0)"] )
p = pya.DPolygon(pya.DBox(0, 0, 100, 100))
p.insert_hole( [ pya.DPoint(0, 0), pya.DPoint(10, 0) ], True )
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0/0,0;10,0)")
p.assign_hole(0, [ pya.DPoint(0, 0), pya.DPoint(10, 0) ] )
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0/0,0;10,0)")
p.assign_hole(0, [ pya.DPoint(0, 0), pya.DPoint(10, 0) ], True )
self.assertEqual(str(p), "(0,0;0,100;100,100;100,0/0,0;10,0)")
pts = [ pya.DPoint(0, 0), pya.DPoint(10, 0) ]
p = pya.DPolygon(pts, True)
self.assertEqual(str(p), "(0,0;10,0)")
self.assertEqual(str(pya.Polygon(p)), "(0,0;10,0)")
p.hull = []
self.assertEqual(str(p), "()")
p.hull = [ pya.DPoint(0, 0), pya.DPoint(10, 0) ]
self.assertEqual(str(p), "(0,0;10,0)")
p.assign_hull([ pya.DPoint(0, 0), pya.DPoint(10, 0) ], True)
self.assertEqual(str(p), "(0,0;10,0)")
arr = []
for e in p.each_edge():
arr.append(str(e))
self.assertEqual( arr, ["(0,0;10,0)", "(10,0;0,0)"] )
self.assertEqual(str(p.moved(1, 2)), "(1,2;11,2)")
self.assertEqual(str(p.sized(2)), "(0,-2;0,2;10,2;10,-2)")
self.assertEqual(str(p * 2), "(0,0;20,0)")
self.assertEqual(str(p.transformed(pya.DTrans(pya.DTrans.R90))), "(0,0;0,10)")
pp = p.dup()
pp.transform(pya.DTrans(pya.DTrans.R90))
self.assertEqual(str(pp), "(0,0;0,10)")
# is_convex
def test_IsConvex(self):
self.assertEqual(pya.Polygon(pya.Box(0, 0, 10, 10)).is_convex(), True)
p = pya.Polygon.from_s("(0,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
self.assertEqual(p.is_convex(), False)
# polygon decomposition
def test_PolygonDecompose(self):
p = pya.Polygon.from_s("(0,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
self.assertEqual(str(p.decompose_convex()), "[(0,10;0,30;10,30;10,10), (0,30;0,40;30,40;30,30), (30,10;30,40;40,40;40,10), (0,0;0,10;40,10;40,0)]")
self.assertEqual(str(p.decompose_convex(pya.Polygon.PO_any)), "[(0,10;0,30;10,30;10,10), (0,30;0,40;30,40;30,30), (30,10;30,40;40,40;40,10), (0,0;0,10;40,10;40,0)]")
self.assertEqual(str(p.decompose_convex(pya.Polygon.PO_horizontal)), "[(0,10;0,30;10,30;10,10), (0,30;0,40;40,40;40,30), (30,10;30,30;40,30;40,10), (0,0;0,10;40,10;40,0)]")
self.assertEqual(str(p.decompose_convex(pya.Polygon.PO_vertical)), "[(10,0;10,10;30,10;30,0), (0,0;0,40;10,40;10,0), (10,30;10,40;30,40;30,30), (30,0;30,40;40,40;40,0)]")
self.assertEqual(str(p.decompose_convex(pya.Polygon.PO_htrapezoids)), "[(0,10;0,30;10,30;10,10), (0,30;0,40;30,40;30,30), (30,10;30,40;40,40;40,10), (0,0;0,10;40,10;40,0)]")
self.assertEqual(str(p.decompose_convex(pya.Polygon.PO_vtrapezoids)), "[(10,0;10,10;30,10;30,0), (0,0;0,30;10,30;10,0), (0,30;0,40;30,40;30,30), (30,0;30,40;40,40;40,0)]")
self.assertEqual(str(p.decompose_trapezoids()), "[(0,0;0,10;40,10;40,0), (0,10;0,30;10,30;10,10), (30,10;30,30;40,30;40,10), (0,30;0,40;40,40;40,30)]")
self.assertEqual(str(p.decompose_trapezoids(pya.Polygon.TD_simple)), "[(0,0;0,10;40,10;40,0), (0,10;0,30;10,30;10,10), (30,10;30,30;40,30;40,10), (0,30;0,40;40,40;40,30)]")
self.assertEqual(str(p.decompose_trapezoids(pya.Polygon.TD_htrapezoids)), "[(0,10;0,30;10,30;10,10), (0,30;0,40;30,40;30,30), (30,10;30,40;40,40;40,10), (0,0;0,10;40,10;40,0)]")
self.assertEqual(str(p.decompose_trapezoids(pya.Polygon.TD_vtrapezoids)), "[(10,0;10,10;30,10;30,0), (0,0;0,30;10,30;10,0), (0,30;0,40;30,40;30,30), (30,0;30,40;40,40;40,0)]")
# polygon decomposition
def test_extractRad(self):
ex = pya.SimplePolygon().extract_rad()
self.assertEqual(repr(ex), "[]")
sp = pya.SimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)")
sp = sp.round_corners(10000, 5000, 200)
ex = sp.extract_rad()
self.assertEqual(ex, [pya.SimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)"), 10000.0, 5000.0, 200])
ex = pya.Polygon().extract_rad()
self.assertEqual(ex, [])
sp = pya.Polygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)")
sp = sp.round_corners(10000, 5000, 200)
ex = sp.extract_rad()
self.assertEqual(ex, [pya.Polygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)"), 10000.0, 5000.0, 200])
# double coords too ...
ex = pya.DSimplePolygon().extract_rad()
self.assertEqual(ex, [])
sp = pya.DSimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)")
sp = sp.round_corners(10000, 5000, 200)
ex = sp.extract_rad()
# round to integers for better comparison
ex[0] = pya.SimplePolygon(ex[0])
self.assertEqual(ex, [pya.SimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)"), 10000.0, 5000.0, 200])
ex = pya.DPolygon().extract_rad()
self.assertEqual(ex, [])
sp = pya.DPolygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)")
sp = sp.round_corners(10000, 5000, 200)
ex = sp.extract_rad()
# round to integers for better comparison
ex[0] = pya.Polygon(ex[0])
self.assertEqual(ex, [pya.Polygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)"), 10000.0, 5000.0, 200])
# fuzzy compare
def test_FuzzyCompare(self):
p1 = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p2a = pya.DPolygon.from_s("(0.0000001,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p2b = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0/10.0000001,10;30,10;30,30;10,30)")
p3a = pya.DPolygon.from_s("(0.0001,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p3b = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0/10.0001,10;30,10;30,30;10,30)")
p4a = pya.DPolygon.from_s("(0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p4b = pya.DPolygon.from_s("(0,0;1,1;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p4c = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0)")
p4d = pya.DPolygon.from_s("(0,0;1,1;0,40;40,40;40,0/10,10;30,10;30,30;10,30/15,15;16,15;16,16;15,16)")
self.assertEqual(p1 == p2a, True)
self.assertEqual(p1 == p2b, True)
self.assertEqual(p1 == p3a, False)
self.assertEqual(p1 == p3b, False)
self.assertEqual(p1 == p4a, False)
self.assertEqual(p1 == p4b, False)
self.assertEqual(p1 == p4c, False)
self.assertEqual(p1 == p4d, False)
self.assertEqual(p1 < p2a, False)
self.assertEqual(p1 < p2b, False)
self.assertEqual(p1 < p3a, True)
self.assertEqual(p1 < p3b, True)
self.assertEqual(p1 < p4a, False)
self.assertEqual(p1 < p4b, True)
self.assertEqual(p1 < p4c, False)
self.assertEqual(p1 < p4d, True)
self.assertEqual(p4a < p4b, True)
self.assertEqual(p4a < p4c, False)
self.assertEqual(p4a < p4d, True)
self.assertEqual(p4b < p4c, False)
self.assertEqual(p4b < p4d, True)
self.assertEqual(p4c < p4d, True)
self.assertEqual(p2a < p1, False)
self.assertEqual(p2b < p1, False)
self.assertEqual(p3a < p1, False)
self.assertEqual(p3b < p1, False)
self.assertEqual(p4a < p1, True)
self.assertEqual(p4b < p1, False)
self.assertEqual(p4c < p1, True)
self.assertEqual(p4d < p1, False)
self.assertEqual(p4b < p4a, False)
self.assertEqual(p4c < p4a, True)
self.assertEqual(p4d < p4a, False)
self.assertEqual(p4c < p4b, True)
self.assertEqual(p4d < p4b, False)
self.assertEqual(p4d < p4c, False)
# hash values
def test_HashValues(self):
p1 = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p2a = pya.DPolygon.from_s("(0.0000001,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p2b = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0/10.0000001,10;30,10;30,30;10,30)")
p3a = pya.DPolygon.from_s("(0.0001,0;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p3b = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0/10.0001,10;30,10;30,30;10,30)")
p4a = pya.DPolygon.from_s("(0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p4b = pya.DPolygon.from_s("(0,0;1,1;0,40;40,40;40,0/10,10;30,10;30,30;10,30)")
p4c = pya.DPolygon.from_s("(0,0;0,40;40,40;40,0)")
p4d = pya.DPolygon.from_s("(0,0;1,1;0,40;40,40;40,0/10,10;30,10;30,30;10,30/15,15;16,15;16,16;15,16)")
self.assertEqual(p1.hash() == p2a.hash(), True)
self.assertEqual(p1.hash() == p2b.hash(), True)
self.assertEqual(p1.hash() == p3a.hash(), False)
self.assertEqual(p1.hash() == p3b.hash(), False)
self.assertEqual(p1.hash() == p4a.hash(), False)
self.assertEqual(p1.hash() == p4b.hash(), False)
self.assertEqual(p1.hash() == p4c.hash(), False)
self.assertEqual(p1.hash() == p4d.hash(), False)
self.assertEqual(p4a.hash() == p4b.hash(), False)
self.assertEqual(p4a.hash() == p4c.hash(), False)
self.assertEqual(p4a.hash() == p4d.hash(), False)
self.assertEqual(p4b.hash() == p4c.hash(), False)
self.assertEqual(p4b.hash() == p4d.hash(), False)
self.assertEqual(p4c.hash() == p4d.hash(), False)
if False:
# TODO: Currently, complex types are not allowed as hash keys:
h = { p1: "p1", p3a: "p3a", p3b: "p3b", p4a: "p4a", p4b: "p4b", p4c: "p4c", p4d: "p4d" }
self.assertEqual(h[p1], "p1")
self.assertEqual(h[p2a], "p1")
self.assertEqual(h[p2b], "p1")
self.assertEqual(h[p3a], "p3a")
self.assertEqual(h[p3b], "p3b")
self.assertEqual(h[p4a], "p4a")
self.assertEqual(h[p4b], "p4b")
self.assertEqual(h[p4c], "p4c")
self.assertEqual(h[p4d], "p4d")
# touches predicate
def test_touches(self):
p1 = pya.Polygon(pya.Box(10, 20, 30, 40))
self.assertEqual(p1.touches(pya.Polygon(pya.Box(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.Polygon(pya.Box(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.Polygon(pya.Box(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.Box(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.Box(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.Box(29, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.Edge(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.Edge(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.Edge(29, 20, 40, 50)), True)
p1 = pya.SimplePolygon(pya.Box(10, 20, 30, 40))
self.assertEqual(p1.touches(pya.Polygon(pya.Box(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.Polygon(pya.Box(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.Polygon(pya.Box(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.Box(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.Box(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.Box(29, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.Edge(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.Edge(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.Edge(29, 20, 40, 50)), True)
p1 = pya.DPolygon(pya.DBox(10, 20, 30, 40))
self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DBox(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.DBox(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.DBox(29, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.DEdge(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.DEdge(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.DEdge(29, 20, 40, 50)), True)
p1 = pya.DSimplePolygon(pya.DBox(10, 20, 30, 40))
self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(30, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(31, 20, 40, 50))), False)
self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(29, 20, 40, 50))), True)
self.assertEqual(p1.touches(pya.DBox(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.DBox(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.DBox(29, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.DEdge(30, 20, 40, 50)), True)
self.assertEqual(p1.touches(pya.DEdge(31, 20, 40, 50)), False)
self.assertEqual(p1.touches(pya.DEdge(29, 20, 40, 50)), True)
def test_selfRef(self):
# p1 is a reference to the new'd object:
p1 = pya.Polygon(pya.Box(10, 20, 30, 40)).move(10, 20)
self.assertEqual(str(p1), "(20,40;20,60;40,60;40,40)")
pp = pya.Polygon(pya.Box(10, 20, 30, 40))
p1 = pp.move(10, 20)
self.assertEqual(str(p1), "(20,40;20,60;40,60;40,40)")
self.assertEqual(str(pp), "(20,40;20,60;40,60;40,40)")
pp.move(1, 2)
# p1 and pp are the same object
self.assertEqual(str(p1), "(21,42;21,62;41,62;41,42)")
self.assertEqual(str(pp), "(21,42;21,62;41,62;41,42)")
# run unit tests
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(DBPolygonTests)
if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful():
sys.exit(1)

View File

@ -720,6 +720,17 @@ class DBPolygon_TestClass < TestBase
end
def test_selfRef
# p1 is a reference to the new'd object:
p1 = RBA::Polygon::new(RBA::Box::new(10, 20, 30, 40)).move(10, 20)
assert_equal(p1.to_s, "(20,40;20,60;40,60;40,40)")
GC.start
# after the GC cleaned up the new'd object, the reference still needs to be valid
assert_equal(p1.to_s, "(20,40;20,60;40,60;40,40)")
end
end
load("test_epilogue.rb")