Merge lp:~inkscape+alexander/inkscape/clones into lp:~inkscape.dev/inkscape/trunk

Proposed by Alexander Brock
Status: Merged
Approved by: Mc
Approved revision: 15209
Merged at revision: 15220
Proposed branch: lp:~inkscape+alexander/inkscape/clones
Merge into: lp:~inkscape.dev/inkscape/trunk
Diff against target: 415 lines (+251/-40)
6 files modified
src/menus-skeleton.h (+1/-0)
src/object-set.h (+14/-2)
src/selection-chemistry.cpp (+92/-38)
src/verbs.cpp (+5/-0)
src/verbs.h (+1/-0)
testfiles/src/object-set-test.cpp (+138/-0)
To merge this branch: bzr merge lp:~inkscape+alexander/inkscape/clones
Reviewer Review Type Date Requested Status
Martin Owens Pending
Mc code, refactor Pending
Review via email: mp+310065@code.launchpad.net

This proposal supersedes a proposal from 2016-11-04.

Commit message

Add "Edit->Clone->Unlink Clones recursively" function (ObjectSet::unlinkRecursive)

Description of the change

I removed all the gratuitous spacing differences.

To post a comment you must log in.
Revision history for this message
Mc (mc...) wrote : Posted in a previous version of this proposal

Looks mostly fine, but I'm not a big fan of the _functions which seems to duplicate the code a bit and double number of functions. I'd prefer a ", bool flash = true" or something like that in the already present function and "if(doflash) desktop->messageStack()->flash(whatever)"

I'll re-read the main new function during the week-end and extensively test it.

Revision history for this message
Mc (mc...) wrote : Posted in a previous version of this proposal

Ok, so with the small change I did to the codebase, here's what I think should be done:

-> add a "bool skip_undo" flag to ::setMask(), ::unsetMask(), and ::unlink() similar to what's done in other methods in the file. (Also add it to your recursive method).

-> make a ObjectSet with no SPDesktop (just the SPDocument) containing the selection, so that it does not flash recursively.

-> make all the calls with the skip_undo flag on your ObjectSet.

review: Needs Resubmitting (code, refactor)
Revision history for this message
Alexander Brock (brock-alexander) wrote : Posted in a previous version of this proposal

> Ok, so with the small change I did to the codebase, here's what I think should
> be done:
>
> -> add a "bool skip_undo" flag to ::setMask(), ::unsetMask(), and ::unlink()
> similar to what's done in other methods in the file. (Also add it to your
> recursive method).
>
> -> make a ObjectSet with no SPDesktop (just the SPDocument) containing the
> selection, so that it does not flash recursively.
>
> -> make all the calls with the skip_undo flag on your ObjectSet.

I tried to merge your changes with my branch, failed and restarted from scratch.
I added an unlinkRecursive method to ObjectSet, added the bool skip_undo where I needed it and force-pushed the code to the old location (lp:~inkscape+alexander/inkscape/clones).

Revision history for this message
Mc (mc...) wrote : Posted in a previous version of this proposal

Good !
I did a merge proposal to your branch to propose a simpler version of the recursive function, though it can be improved and further simplified by putting the unclip/reclip business in the unlink() function where it probably belongs.
If you find the courage, you can also propose unit tests to test the function (see testfiles/src/object-set-test.cpp )

Revision history for this message
Tavmjong Bah (tavmjong-free) wrote : Posted in a previous version of this proposal

Please get rid of gratuitous spacing differences. It makes it much harder to see what is really being changed.

Revision history for this message
Alexander Brock (brock-alexander) wrote : Posted in a previous version of this proposal

@mc...: Thank you for rewriting the unlinkRecursive function. I checked out your version, tested it, found some problems and fixed them:

1.

unlinked = unlinked || tmp_set.unlinkRecursive(true);

From the C++ standard: "Unlike |, || guarantees left-to-right evaluation; moreover, the second operand is not evaluated if the first operand evaluates to true."

So if unlinked is already true then the unlinkRecursive function is not called, preventing groups of clones from beeing unlinked. I changed it:

unlinked = tmp_set.unlinkRecursive(true) || unlinked;

2.

if(SP_IS_GROUP(it)) {
......
} else if(dynamic_cast<SPUse*>(it) || dynamic_cast<SPText*>(it) || dynamic_cast<SPTRef*>(it)) {

This causes two problems: First, if an Object is a group masked (or clipped) by a clone that clone would not be unlinked.
Second, if the object is not SPUse, SPText or SPTRef it still is necessary to run the code, it might be clipped or masked by a clone of some object.
I removed the else if so the code runs unconditionally.

3.

unlinked = unlinked || tmp_set.unlink();

This causes calls to DocumentUndo::done (also see 1.), so I changed it:

unlinked = tmp_set.unlink(true) || unlinked;

15206. By Alexander Brock <email address hidden>

Preserve clips when unlinking clones

Revision history for this message
Alexander Brock (brock-alexander) wrote :

The current behaviour for "Edit->Clone->unlink" is to remove the clip of every selected clone. I changed to to first remove the clip, then unlink (possibly both, the object and the clip) and then reapply the clip (or mask).

15207. By Alexander Brock <email address hidden>

Make ObjectSet::unlink() work for clones which are both clipped and masked

Revision history for this message
Alexander Brock (brock-alexander) wrote :

I changed the ObjectSet::unlink function so it also works if a clone is both clipped and masked. Also I removed that part of the code from the ObjectSet::unlinkRecursive function to avoid code duplication.

15208. By Alexander Brock <email address hidden>

Remove duplicate code from unlinkRecursive() and fix issue with clones of groups

Revision history for this message
Alexander Brock (brock-alexander) wrote :

I removed the duplicated code for handling objects clipped by clones from ObjectSet::unlinkRecursive() and fixed a n issue with the function unlinking clones of groups but not recursing into the unlinked group afterwards. Both issues kindly reported by Mc..., thank you.

15209. By Alexander Brock <email address hidden>

Fix selection issue with ObjectSet::unlinkRecursive() and add tests for it.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/menus-skeleton.h'
2--- src/menus-skeleton.h 2016-04-15 14:39:49 +0000
3+++ src/menus-skeleton.h 2016-11-06 21:37:32 +0000
4@@ -66,6 +66,7 @@
5 " <verb verb-id=\"EditClone\" />\n"
6 " <verb verb-id=\"DialogClonetiler\" />\n"
7 " <verb verb-id=\"EditUnlinkClone\" />\n"
8+" <verb verb-id=\"EditUnlinkCloneRecursive\" />\n"
9 " <verb verb-id=\"EditRelinkClone\" />\n"
10 " <verb verb-id=\"EditCloneSelectOriginal\" />\n"
11 " <verb verb-id=\"EditCloneOriginalPathLPE\" />\n"
12
13=== modified file 'src/object-set.h'
14--- src/object-set.h 2016-11-06 15:33:01 +0000
15+++ src/object-set.h 2016-11-06 21:37:32 +0000
16@@ -330,7 +330,19 @@
17 void deleteItems();
18 void duplicate(bool suppressDone = false, bool duplicateLayer = false);
19 void clone();
20- void unlink();
21+
22+ /**
23+ * @brief Unlink all directly selected clones.
24+ * @param skip_undo If this is set to true the call to DocumentUndo::done is omitted.
25+ * @return True if anything was unlinked, otherwise false.
26+ */
27+ bool unlink(const bool skip_undo = false);
28+ /**
29+ * @brief Recursively unlink any clones present in the current selection,
30+ * including clones which are used to clip other objects, groups of clones etc.
31+ * @return true if anything was unlinked, otherwise false.
32+ */
33+ bool unlinkRecursive(const bool skip_undo = false);
34 void relink();
35 void cloneOriginal();
36 void cloneOriginalPathLPE();
37@@ -376,7 +388,7 @@
38 void createBitmapCopy();
39 void setMask(bool apply_clip_path, bool apply_to_layer = false, bool skip_undo = false);
40 void editMask(bool clip);
41- void unsetMask(bool apply_clip_path);
42+ void unsetMask(const bool apply_clip_path, const bool skip_undo = false);
43 void setClipGroup();
44
45 // moves
46
47=== modified file 'src/selection-chemistry.cpp'
48--- src/selection-chemistry.cpp 2016-11-06 15:33:01 +0000
49+++ src/selection-chemistry.cpp 2016-11-06 21:37:32 +0000
50@@ -2653,12 +2653,12 @@
51 }
52
53
54-void ObjectSet::unlink()
55+bool ObjectSet::unlink(const bool skip_undo)
56 {
57 if (isEmpty()) {
58 if(desktop())
59 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
60- return;
61+ return false;
62 }
63
64 // Get a copy of current selection.
65@@ -2669,41 +2669,59 @@
66 for (auto i=items_.rbegin();i!=items_.rend();++i){
67 SPItem *item = *i;
68
69- if (dynamic_cast<SPText *>(item)) {
70- SPObject *tspan = sp_tref_convert_to_tspan(item);
71-
72- if (tspan) {
73- item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
74+ ObjectSet tmp_set(document());
75+ tmp_set.set(item);
76+ Inkscape::URIReference *clip = item->clip_ref;
77+ Inkscape::URIReference *mask = item->mask_ref;
78+ if ((NULL != clip) && (NULL != clip->getObject())) {
79+ tmp_set.unsetMask(true,true);
80+ unlinked = tmp_set.unlink(true) || unlinked;
81+ tmp_set.setMask(true,false,true);
82+ new_select.push_back(tmp_set.singleItem());
83+ }
84+ else if ((NULL != mask) && (NULL != mask->getObject())) {
85+ tmp_set.unsetMask(false,true);
86+ unlinked = tmp_set.unlink(true) || unlinked;
87+ tmp_set.setMask(false,false,true);
88+ new_select.push_back(tmp_set.singleItem());
89+ }
90+ else {
91+ if (dynamic_cast<SPText *>(item)) {
92+ SPObject *tspan = sp_tref_convert_to_tspan(item);
93+
94+ if (tspan) {
95+ item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
96+ }
97+
98+ // Set unlink to true, and fall into the next if which
99+ // will include this text item in the new selection
100+ unlinked = true;
101 }
102
103- // Set unlink to true, and fall into the next if which
104- // will include this text item in the new selection
105- unlinked = true;
106- }
107-
108- if (!(dynamic_cast<SPUse *>(item) || dynamic_cast<SPTRef *>(item))) {
109- // keep the non-use item in the new selection
110- new_select.push_back(item);
111- continue;
112- }
113-
114- SPItem *unlink = NULL;
115- SPUse *use = dynamic_cast<SPUse *>(item);
116- if (use) {
117- unlink = use->unlink();
118- // Unable to unlink use (external or invalid href?)
119- if (!unlink) {
120+ if (!(dynamic_cast<SPUse *>(item) || dynamic_cast<SPTRef *>(item))) {
121+ // keep the non-use item in the new selection
122 new_select.push_back(item);
123 continue;
124 }
125- } else /*if (SP_IS_TREF(use))*/ {
126- unlink = dynamic_cast<SPItem *>(sp_tref_convert_to_tspan(item));
127- g_assert(unlink != NULL);
128+
129+ SPItem *unlink = NULL;
130+ SPUse *use = dynamic_cast<SPUse *>(item);
131+ if (use) {
132+ unlink = use->unlink();
133+ // Unable to unlink use (external or invalid href?)
134+ if (!unlink) {
135+ new_select.push_back(item);
136+ continue;
137+ }
138+ } else /*if (SP_IS_TREF(use))*/ {
139+ unlink = dynamic_cast<SPItem *>(sp_tref_convert_to_tspan(item));
140+ g_assert(unlink != NULL);
141+ }
142+
143+ unlinked = true;
144+ // Add ungrouped items to the new selection.
145+ new_select.push_back(unlink);
146 }
147-
148- unlinked = true;
149- // Add ungrouped items to the new selection.
150- new_select.push_back(unlink);
151 }
152
153 if (!new_select.empty()) { // set new selection
154@@ -2715,8 +2733,42 @@
155 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
156 }
157
158- DocumentUndo::done(document(), SP_VERB_EDIT_UNLINK_CLONE,
159- _("Unlink clone"));
160+ if (!skip_undo) {
161+ DocumentUndo::done(document(), SP_VERB_EDIT_UNLINK_CLONE,
162+ _("Unlink clone"));
163+ }
164+ return unlinked;
165+}
166+
167+bool ObjectSet::unlinkRecursive(const bool skip_undo) {
168+ if (isEmpty()){
169+ if (desktop())
170+ desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
171+ return false;
172+ }
173+ bool unlinked = false;
174+ ObjectSet tmp_set(document());
175+ std::vector<SPItem*> items_(items().begin(), items().end());
176+ for (auto& it:items_) {
177+ tmp_set.set(it);
178+ unlinked = tmp_set.unlink(true) || unlinked;
179+ it = tmp_set.singleItem();
180+ if (SP_IS_GROUP(it)) {
181+ std::vector<SPObject*> c = it->childList(false);
182+ tmp_set.setList(c);
183+ unlinked = tmp_set.unlinkRecursive(true) || unlinked;
184+ }
185+ }
186+ if (!unlinked) {
187+ if(desktop())
188+ desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
189+ }
190+ if (!skip_undo) {
191+ DocumentUndo::done(document(), SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE,
192+ _("Unlink clone recursively"));
193+ }
194+ setList(items_);
195+ return unlinked;
196 }
197
198 void ObjectSet::cloneOriginal()
199@@ -3960,7 +4012,7 @@
200 }
201 }
202
203-void ObjectSet::unsetMask(bool apply_clip_path) {
204+void ObjectSet::unsetMask(const bool apply_clip_path, const bool skip_undo) {
205 SPDocument *doc = document();
206 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
207
208@@ -4082,10 +4134,12 @@
209 // rebuild selection
210 addList(items_to_select);
211
212- if (apply_clip_path) {
213- DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_CLIPPATH, _("Release clipping path"));
214- } else {
215- DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_MASK, _("Release mask"));
216+ if (!skip_undo) {
217+ if (apply_clip_path) {
218+ DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_CLIPPATH, _("Release clipping path"));
219+ } else {
220+ DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_MASK, _("Release mask"));
221+ }
222 }
223 }
224
225
226=== modified file 'src/verbs.cpp'
227--- src/verbs.cpp 2016-11-06 15:33:01 +0000
228+++ src/verbs.cpp 2016-11-06 21:37:32 +0000
229@@ -1009,6 +1009,9 @@
230 case SP_VERB_EDIT_UNLINK_CLONE:
231 dt->selection->unlink();
232 break;
233+ case SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE:
234+ dt->selection->unlinkRecursive();
235+ break;
236 case SP_VERB_EDIT_RELINK_CLONE:
237 dt->selection->relink();
238 break;
239@@ -2530,6 +2533,8 @@
240 N_("Create a clone (a copy linked to the original) of selected object"), INKSCAPE_ICON("edit-clone")),
241 new EditVerb(SP_VERB_EDIT_UNLINK_CLONE, "EditUnlinkClone", N_("Unlin_k Clone"),
242 N_("Cut the selected clones' links to the originals, turning them into standalone objects"), INKSCAPE_ICON("edit-clone-unlink")),
243+ new EditVerb(SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE, "EditUnlinkCloneRecursive", N_("Unlink Clones _recursively"),
244+ N_("Unlink all clones in the selection, even if they are in groups."), INKSCAPE_ICON("edit-clone-unlink")),
245 new EditVerb(SP_VERB_EDIT_RELINK_CLONE, "EditRelinkClone", N_("Relink to Copied"),
246 N_("Relink the selected clones to the object currently on the clipboard"), NULL),
247 new EditVerb(SP_VERB_EDIT_CLONE_SELECT_ORIGINAL, "EditCloneSelectOriginal", N_("Select _Original"),
248
249=== modified file 'src/verbs.h'
250--- src/verbs.h 2016-10-08 06:16:53 +0000
251+++ src/verbs.h 2016-11-06 21:37:32 +0000
252@@ -83,6 +83,7 @@
253 SP_VERB_EDIT_DUPLICATE,
254 SP_VERB_EDIT_CLONE,
255 SP_VERB_EDIT_UNLINK_CLONE,
256+ SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE,
257 SP_VERB_EDIT_RELINK_CLONE,
258 SP_VERB_EDIT_CLONE_SELECT_ORIGINAL,
259 SP_VERB_EDIT_CLONE_ORIGINAL_PATH_LPE,
260
261=== modified file 'testfiles/src/object-set-test.cpp'
262--- testfiles/src/object-set-test.cpp 2016-11-02 23:08:41 +0000
263+++ testfiles/src/object-set-test.cpp 2016-11-06 21:37:32 +0000
264@@ -105,6 +105,25 @@
265 ObjectSet* set2;
266 };
267
268+#define SP_IS_CLONE(obj) (dynamic_cast<const SPUse*>(obj) != NULL)
269+
270+bool containsClone(ObjectSet* set) {
271+ for (auto it : set->items()) {
272+ if (SP_IS_CLONE(it)) {
273+ return true;
274+ }
275+ if (SP_IS_GROUP(it)) {
276+ ObjectSet tmp_set(set->document());
277+ std::vector<SPObject*> c = it->childList(false);
278+ tmp_set.setList(c);
279+ if (containsClone(&tmp_set)) {
280+ return true;
281+ }
282+ }
283+ }
284+ return false;
285+}
286+
287 TEST_F(ObjectSetTest, Basics) {
288 EXPECT_EQ(0, set->size());
289 set->add(A);
290@@ -429,6 +448,125 @@
291 SetUpTestCase();
292 }
293
294+TEST_F(ObjectSetTest, unlinkRecursiveBasic) {
295+ // This is the same as the test (ObjectSetTest, Ops), but with unlinkRecursive instead of unlink.
296+ set->set(r1.get());
297+ set->add(r2.get());
298+ set->add(r3.get());
299+ EXPECT_FALSE(containsClone(set));
300+ set->duplicate();
301+ EXPECT_FALSE(containsClone(set));
302+ EXPECT_EQ(9, _doc->getRoot()->children.size());//metadata, defs, namedview, and those 3x2 rects.
303+ EXPECT_EQ(3, set->size());
304+ EXPECT_FALSE(set->includes(r1.get()));
305+ set->deleteItems();
306+ EXPECT_FALSE(containsClone(set));
307+ EXPECT_TRUE(set->isEmpty());
308+ set->add(r1.get());
309+ set->add(r2.get());
310+ set->add(r3.get());
311+ EXPECT_FALSE(containsClone(set));
312+ set->group();//r1-3 are now invalid (grouping makes copies)
313+ r1.release();
314+ r2.release();
315+ r3.release();
316+ EXPECT_FALSE(containsClone(set));
317+ EXPECT_EQ(4, _doc->getRoot()->children.size());
318+ EXPECT_EQ(1, set->size());
319+ set->ungroup();
320+ EXPECT_FALSE(containsClone(set));
321+ EXPECT_EQ(6, _doc->getRoot()->children.size());
322+ EXPECT_EQ(3, set->size());
323+ /* Uncomment this when toNextLayer is made desktop-independent
324+ set->group();
325+ set2->add(set->singleItem()->childList(false)[0]);
326+ EXPECT_EQ(3, set->singleItem()->children.size());
327+ EXPECT_EQ(4, _doc->getRoot()->children.size());
328+ set2->popFromGroup();
329+ EXPECT_EQ(2, set->singleItem()->children.size());
330+ EXPECT_EQ(5, _doc->getRoot()->children.size());
331+ set->ungroup();
332+ set->add(set2->singleItem());
333+ */
334+ set->clone();
335+ EXPECT_TRUE(containsClone(set));
336+ EXPECT_EQ(9, _doc->getRoot()->children.size());
337+ EXPECT_EQ(3, set->size());
338+ EXPECT_NE(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
339+ EXPECT_EQ(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
340+ set->unlinkRecursive();
341+ EXPECT_FALSE(containsClone(set));
342+ EXPECT_EQ(9, _doc->getRoot()->children.size());
343+ EXPECT_EQ(3, set->size());
344+ EXPECT_EQ(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
345+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
346+ set->clone(); //creates 3 clones
347+ EXPECT_TRUE(containsClone(set));
348+ set->clone(); //creates 3 clones of clones
349+ EXPECT_TRUE(containsClone(set));
350+ EXPECT_EQ(15, _doc->getRoot()->children.size());
351+ EXPECT_EQ(3, set->size());
352+ EXPECT_NE(nullptr, dynamic_cast<SPUse*>( ((SPUse*)(*(set->items().begin())))->get_original()));//"original is a Use"
353+ set->unlinkRecursive(); //clone of clone of rect -> rect
354+ EXPECT_FALSE(containsClone(set));
355+ EXPECT_EQ(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
356+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
357+ set->clone();
358+ EXPECT_TRUE(containsClone(set));
359+ set->set(*(set->items().begin()));
360+ set->cloneOriginal();//get clone original
361+ EXPECT_EQ(18, _doc->getRoot()->children.size());
362+ EXPECT_EQ(1, set->size());
363+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
364+ TearDownTestCase();
365+ SetUpTestCase();
366+}
367+
368+TEST_F(ObjectSetTest, unlinkRecursiveAdvanced) {
369+ set->set(r1.get());
370+ set->add(r2.get());
371+ set->add(r3.get());
372+ set->group();//r1-3 are now invalid (grouping makes copies)
373+ r1.release();
374+ r2.release();
375+ r3.release();
376+ EXPECT_FALSE(containsClone(set));
377+ EXPECT_EQ(1, set->size());
378+ SPItem* original = set->singleItem();
379+ set->clone();
380+ EXPECT_TRUE(containsClone(set));
381+ EXPECT_EQ(1, set->size());
382+ set->add(original);
383+ EXPECT_TRUE(containsClone(set));
384+ EXPECT_EQ(2, set->size());
385+ set->group();
386+ EXPECT_TRUE(containsClone(set));
387+ EXPECT_EQ(1, set->size());
388+ original = set->singleItem();
389+ set->clone();
390+ EXPECT_TRUE(containsClone(set));
391+ EXPECT_EQ(1, set->size());
392+ set->add(original);
393+ EXPECT_TRUE(containsClone(set));
394+ EXPECT_EQ(2, set->size());
395+ set->group();
396+ EXPECT_TRUE(containsClone(set));
397+ EXPECT_EQ(1, set->size());
398+ original = set->singleItem();
399+ set->clone();
400+ EXPECT_TRUE(containsClone(set));
401+ EXPECT_EQ(1, set->size());
402+ set->add(original);
403+ EXPECT_TRUE(containsClone(set));
404+ EXPECT_EQ(2, set->size());
405+ set->unlinkRecursive();
406+ EXPECT_FALSE(containsClone(set));
407+ EXPECT_EQ(2, set->size());
408+
409+ TearDownTestCase();
410+ SetUpTestCase();
411+}
412+
413 TEST_F(ObjectSetTest, ZOrder) {
414 //sp_object_compare_position_bool == true iff "r1<r2" iff r1 is "before" r2 in the file, ie r1 is lower than r2
415 EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));