Merge lp:~danieljabailey/inkscape/arc_node_editor into lp:~inkscape.dev/inkscape/trunk

Proposed by Daniel
Status: Needs review
Proposed branch: lp:~danieljabailey/inkscape/arc_node_editor
Merge into: lp:~inkscape.dev/inkscape/trunk
Diff against target: 1019 lines (+555/-41)
13 files modified
share/icons/icons.svg (+23/-0)
src/helper/geom.cpp (+42/-0)
src/helper/geom.h (+1/-0)
src/ui/tool/control-point.cpp (+0/-1)
src/ui/tool/multi-path-manipulator.cpp (+32/-5)
src/ui/tool/multi-path-manipulator.h (+3/-0)
src/ui/tool/node-types.h (+2/-1)
src/ui/tool/node.cpp (+162/-14)
src/ui/tool/node.h (+26/-2)
src/ui/tool/path-manipulator.cpp (+182/-18)
src/ui/tool/path-manipulator.h (+5/-0)
src/widgets/node-toolbar.cpp (+72/-0)
src/widgets/toolbox.cpp (+5/-0)
To merge this branch: bzr merge lp:~danieljabailey/inkscape/arc_node_editor
Reviewer Review Type Date Requested Status
Inkscape Developers Pending
Review via email: mp+294742@code.launchpad.net

Description of the change

Hi, This is my first time contributing to inkscape and also my first time using bazaar.

I noticed that inkscape converts arc segments in paths into bezier curves when you edit them using the node editor. I had an SVG file that contained some arcs that I wanted to manipulate and so decided to add this feature to avoid having the arcs changed to beziers.

Arc segments are manipulated with a pair of perpendicular handles that pivot around the mid point between two nodes. (This is not always the arc's mid-point) They control the rotation and the radii of the elipse upon which the arc sits. The two nodes define two arc end points and two flags control the direction and 'largeness'. The two flags can be controlled with buttons in the toolbar.

This feature required a change to the handle class so that the origin of a handle could be somewhere other than its parent node. This has been done by adding an 'offset' relative to the parent position. The length of the handle is measured from the offset point to the handle point, not from the parent. In most cases however, the offset is zero and the length, as before is from the parent node to the handle point.

This change adds four buttons to the node editor toolbar. I have created icons for these using the new arc editor tools. :-)
The four buttons are: "Make selected segments arcs", "Make selected arc segments shallow", "Make selected arc segments bulge" and "Flip selected arc segments"

I have done my best to stick to the style guides, however there was some code that I copied and pasted from something that did not match the style guide particularly well, I was unsure if I should clean up the old code or the new code and so left them both as they were (see geom.cpp function pathv_to_linear_and_cubic_beziers_and_arcs).

Please let me know if this is a valuable change that can be merged.
Also let me know if I need to fix anything.

I am aware of one slight bug, but wanted to check if there was interest in this change before I put effort into fixing it.
When an arc segment is dragged (as you would to manipulate a bezier segment) the bezier handles appear and so both the arc handles and bezier handles are visible.
Let me know if there is interest in this feature and I will start hunting this bug.

Thanks,
Dan.

To post a comment you must log in.
14891. By Daniel Bailey <email address hidden>

Handle conversion from arcs to beziers in node editor

14892. By Daniel Bailey <email address hidden>

fix bug where closing arc segment sometimes becomes as straight line

14893. By Daniel Bailey <email address hidden>

Merge upstream changes

Revision history for this message
Jabiertxof (jabiertxof) wrote :

Added some comments about coding style, also start compiling to see the UX and give a better review.
Great work!

Revision history for this message
Jabiertxof (jabiertxof) wrote :

When compiling wirh CMAKE I get this error:
[ 81%] Building CXX object src/CMakeFiles/inkscape_base.dir/version.cpp.o
make[2]: *** No rule to make target 'src/inkscape-version.cpp', needed by 'src/CMakeFiles/inkscape_base.dir/inkscape-version.cpp.o'. Stop.
CMakeFiles/Makefile2:597: recipe for target 'src/CMakeFiles/inkscape_base.dir/all' failed
make[1]: *** [src/CMakeFiles/inkscape_base.dir/all] Error 2
Makefile:127: recipe for target 'all' failed
make: *** [all] Error 2

Unmerged revisions

14893. By Daniel Bailey <email address hidden>

Merge upstream changes

14892. By Daniel Bailey <email address hidden>

fix bug where closing arc segment sometimes becomes as straight line

14891. By Daniel Bailey <email address hidden>

Handle conversion from arcs to beziers in node editor

14890. By Daniel Bailey <email address hidden>

fix TODO for arc segment handling

14889. By Daniel Bailey <email address hidden>

Added support for editting arc segments in the node editor

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'share/icons/icons.svg'
--- share/icons/icons.svg 2016-04-14 19:09:11 +0000
+++ share/icons/icons.svg 2016-07-09 15:52:46 +0000
@@ -1109,6 +1109,29 @@
1109<use xlink:href="#rect4374" height="1250" width="1250" id="use5756" y="0" x="0" transform="translate(-5.018707,-0.0241656)" />1109<use xlink:href="#rect4374" height="1250" width="1250" id="use5756" y="0" x="0" transform="translate(-5.018707,-0.0241656)" />
1110<use xlink:href="#rect4374" height="1250" width="1250" id="use5758" y="0" x="0" transform="translate(6.0238,-11.02417)" />1110<use xlink:href="#rect4374" height="1250" width="1250" id="use5758" y="0" x="0" transform="translate(6.0238,-11.02417)" />
1111</g>1111</g>
1112<g id="node-segment-elliptical-arc" transform="translate(673.00134,25.751197)" inkscape:label="#node_curve">
1113<path style="fill:none;stroke:#646464;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1" d="m -38.600913,188.52882 a 6.0591792,7.0458508 48.505394 1 1 7.123238,-5.61527" id="path10679-5" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" />
1114<rect y="175" x="-45" height="16" width="16" id="rect10681-3" style="color:#000000;display:inline;fill:none;stroke:none;stroke-width:1;marker:none" />
1115<use xlink:href="#rect4374" height="1250" width="1250" id="use5745-8" y="0" x="0" transform="translate(-1.0764281,-0.01573529)" />
1116<use xlink:href="#rect4374" height="1250" width="1250" id="use5747-9" y="0" x="0" transform="translate(5.9949619,-5.5106093)" />
1117</g>
1118<g id="node-segment-elliptical-arc-bulge" transform="translate(693.93498,25.748197)" inkscape:label="#node_curve">
1119<rect y="175" x="-45" height="16" width="16" id="rect10681-3-6" style="color:#000000;display:inline;fill:none;stroke:none;stroke-width:1;marker:none" />
1120<path style="fill:none;stroke:#646464;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1" d="m -37.870441,189.45456 a 6.0591792,7.0458508 48.505394 1 1 7.123238,-5.61527" id="path10679-5-2" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" />
1121<path style="fill:none;stroke:#ff8080;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:1, 2;stroke-dashoffset:0;stroke-opacity:1" d="m -37.870438,189.45456 a 6.0591792,7.0458508 48.505394 0 1 7.12324,-5.61527" id="path10679-5-2-8" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" />
1122</g>
1123<g id="node-segment-elliptical-arc-flip" transform="translate(736.50086,25.748197)" inkscape:label="#node_curve">
1124<rect y="175" x="-45" height="16" width="16" id="rect10681-3-67" style="color:#000000;display:inline;fill:none;stroke:none;stroke-width:1;marker:none" />
1125<path style="fill:none;stroke:#646464;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1" d="m -40.865556,188.5577 a 5.0424426,7.9529262 56.470438 1 1 7.281336,-12.07083" id="path10679-5-5" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" />
1126<path style="fill:none;stroke:#ff5555;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1" d="m -40.866246,188.55761 a 5.0424427,7.9529265 56.470438 1 0 7.28134,-12.07083" id="path10679-5-5-6" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" />
1127<use xlink:href="#path4376" height="1250" width="1250" id="use5754-7" y="0" x="0" transform="rotate(90,-38.492674,181.50732)" />
1128<use xlink:href="#path4376" height="1250" width="1250" id="use5754-7-9" y="0" x="0" transform="matrix(0,1,1,0,-217.00767,220)" />
1129</g>
1130<g id="node-segment-elliptical-arc-shallow" transform="translate(714.9873,25.748197)" inkscape:label="#node_curve">
1131<rect y="175" x="-45" height="16" width="16" id="rect10681-3-6-6" style="color:#000000;display:inline;fill:none;stroke:none;stroke-width:1;marker:none" />
1132<path style="fill:none;stroke:#646464;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1" d="m -37.870441,189.45456 a 6.0591792,7.0458508 48.505394 0 1 7.123238,-5.61527" id="path10679-5-2-3" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" />
1133<path style="fill:none;stroke:#ff8080;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-dasharray:1.00000024, 2.00000048;stroke-dashoffset:0;stroke-opacity:1" d="m -37.870438,189.45456 a 6.0591792,7.0458508 48.505394 1 1 7.12324,-5.61527" id="path10679-5-2-8-2" sodipodi:nodetypes="cc" inkscape:connector-curvature="0" />
1134</g>
1112<g id="object-to-path" transform="translate(280.041,-149.9465)" inkscape:label="#object_tocurve">1135<g id="object-to-path" transform="translate(280.041,-149.9465)" inkscape:label="#object_tocurve">
1113<path style="fill:none;stroke:#646464;stroke-width:1.0000002;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0" d="M -37.0131,188.9855 C -35.0131,192.9855 -27.81159,188.3079 -29.81159,185.3079 -31.10674,183.3652 -39.24159,185.6729 -42.56979,183.4541 -45.56979,181.4541 -45.12074,174.9746 -40.0534,175.5422 -35.41611,176.0661 -38.62555,185.7606 -37.0131,188.9855 Z" id="path4438" sodipodi:nodetypes="cssss" inkscape:connector-curvature="0" />1136<path style="fill:none;stroke:#646464;stroke-width:1.0000002;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0" d="M -37.0131,188.9855 C -35.0131,192.9855 -27.81159,188.3079 -29.81159,185.3079 -31.10674,183.3652 -39.24159,185.6729 -42.56979,183.4541 -45.56979,181.4541 -45.12074,174.9746 -40.0534,175.5422 -35.41611,176.0661 -38.62555,185.7606 -37.0131,188.9855 Z" id="path4438" sodipodi:nodetypes="cssss" inkscape:connector-curvature="0" />
1114<rect y="175" x="-45" height="16" width="16" id="rect4440" style="color:#000000;fill:none" />1137<rect y="175" x="-45" height="16" width="16" id="rect4440" style="color:#000000;fill:none" />
11151138
=== modified file 'src/helper/geom.cpp'
--- src/helper/geom.cpp 2015-05-08 17:26:29 +0000
+++ src/helper/geom.cpp 2016-07-09 15:52:46 +0000
@@ -456,6 +456,48 @@
456456
457//#################################################################################457//#################################################################################
458458
459/**
460 * Converts all segments in all paths to Geom::LineSegment or Geom::HLineSegment or
461 * Geom::VLineSegment or Geom::CubicBezier or Geom::EllipticalArc.
462 */
463Geom::PathVector
464pathv_to_linear_and_cubic_beziers_and_arcs( Geom::PathVector const &pathv )
465{
466 Geom::PathVector output;
467
468 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
469 output.push_back( Geom::Path() );
470 output.back().setStitching(true);
471 output.back().start( pit->initialPoint() );
472
473 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) {
474 if (is_straight_curve(*cit)) {
475 Geom::LineSegment l(cit->initialPoint(), cit->finalPoint());
476 output.back().append(l);
477 } else {
478 Geom::EllipticalArc const *arc = dynamic_cast<Geom::EllipticalArc const *>(&*cit);
479 if (arc){
480 output.back().append(arc->duplicate());
481 }
482 else{
483 Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&*cit);
484 if (curve && curve->order() == 3) {
485 Geom::CubicBezier b((*curve)[0], (*curve)[1], (*curve)[2], (*curve)[3]);
486 output.back().append(b);
487 } else {
488 // convert all other curve types to cubicbeziers
489 Geom::Path cubicbezier_path = Geom::cubicbezierpath_from_sbasis(cit->toSBasis(), 0.1);
490 cubicbezier_path.close(false);
491 output.back().append(cubicbezier_path);
492 }
493 }
494 }
495 }
496 output.back().close( pit->closed() );
497 }
498 return output;
499}
500
459/*501/*
460 * Converts all segments in all paths to Geom::LineSegment or Geom::HLineSegment or502 * Converts all segments in all paths to Geom::LineSegment or Geom::HLineSegment or
461 * Geom::VLineSegment or Geom::CubicBezier.503 * Geom::VLineSegment or Geom::CubicBezier.
462504
=== modified file 'src/helper/geom.h'
--- src/helper/geom.h 2015-05-08 15:40:38 +0000
+++ src/helper/geom.h 2016-07-09 15:52:46 +0000
@@ -25,6 +25,7 @@
25 Geom::Rect *bbox, int *wind, Geom::Coord *dist,25 Geom::Rect *bbox, int *wind, Geom::Coord *dist,
26 Geom::Coord tolerance, Geom::Rect const *viewbox);26 Geom::Coord tolerance, Geom::Rect const *viewbox);
2727
28Geom::PathVector pathv_to_linear_and_cubic_beziers_and_arcs( Geom::PathVector const &pathv );
28Geom::PathVector pathv_to_linear_and_cubic_beziers( Geom::PathVector const &pathv );29Geom::PathVector pathv_to_linear_and_cubic_beziers( Geom::PathVector const &pathv );
29Geom::PathVector pathv_to_linear( Geom::PathVector const &pathv, double maxdisp );30Geom::PathVector pathv_to_linear( Geom::PathVector const &pathv, double maxdisp );
30Geom::PathVector pathv_to_cubicbezier( Geom::PathVector const &pathv);31Geom::PathVector pathv_to_cubicbezier( Geom::PathVector const &pathv);
3132
=== modified file 'src/ui/tool/control-point.cpp'
--- src/ui/tool/control-point.cpp 2015-05-30 18:27:42 +0000
+++ src/ui/tool/control-point.cpp 2016-07-09 15:52:46 +0000
@@ -225,7 +225,6 @@
225{225{
226 // NOTE the static variables below are shared for all points!226 // NOTE the static variables below are shared for all points!
227 // TODO handle clicks and drags from other buttons too227 // TODO handle clicks and drags from other buttons too
228
229 if (event == NULL)228 if (event == NULL)
230 {229 {
231 return false;230 return false;
232231
=== modified file 'src/ui/tool/multi-path-manipulator.cpp'
--- src/ui/tool/multi-path-manipulator.cpp 2015-08-08 20:19:02 +0000
+++ src/ui/tool/multi-path-manipulator.cpp 2016-07-09 15:52:46 +0000
@@ -319,11 +319,38 @@
319{319{
320 if (_selection.empty()) return;320 if (_selection.empty()) return;
321 invokeForAll(&PathManipulator::setSegmentType, type);321 invokeForAll(&PathManipulator::setSegmentType, type);
322 if (type == SEGMENT_STRAIGHT) {322 switch (type){
323 _done(_("Straighten segments"));323 case SEGMENT_STRAIGHT:
324 } else {324 _done(_("Straighten segments"));
325 _done(_("Make segments curves"));325 break;
326 }326 case SEGMENT_CUBIC_BEZIER:
327 _done(_("Make segments curves"));
328 break;
329 case SEGMENT_ELIPTICAL_ARC:
330 _done(_("Make segments arcs"));
331 break;
332 default:
333 g_error("Unknown segment type");
334 }
335}
336
337void MultiPathManipulator::setArcSegmentLarge(bool large)
338{
339 if (_selection.empty()) return;
340 invokeForAll(&PathManipulator::setArcSegmentLarge, large);
341 if (large){
342 _done(_("Make arc segments bulge"));
343 }
344 else{
345 _done(_("Make arc segments shallow"));
346 }
347}
348
349void MultiPathManipulator::toggleArcSegmentSweep()
350{
351 if (_selection.empty()) return;
352 invokeForAll(&PathManipulator::toggleArcSegmentSweep);
353 _done(_("Flip arc segments"));
327}354}
328355
329void MultiPathManipulator::insertNodes()356void MultiPathManipulator::insertNodes()
330357
=== modified file 'src/ui/tool/multi-path-manipulator.h'
--- src/ui/tool/multi-path-manipulator.h 2015-05-30 18:27:42 +0000
+++ src/ui/tool/multi-path-manipulator.h 2016-07-09 15:52:46 +0000
@@ -51,6 +51,9 @@
51 void setNodeType(NodeType t);51 void setNodeType(NodeType t);
52 void setSegmentType(SegmentType t);52 void setSegmentType(SegmentType t);
5353
54 void setArcSegmentLarge(bool large);
55 void toggleArcSegmentSweep();
56
54 void insertNodesAtExtrema(ExtremumType extremum);57 void insertNodesAtExtrema(ExtremumType extremum);
55 void insertNodes();58 void insertNodes();
56 void insertNode(Geom::Point pt);59 void insertNode(Geom::Point pt);
5760
=== modified file 'src/ui/tool/node-types.h'
--- src/ui/tool/node-types.h 2010-11-17 02:12:56 +0000
+++ src/ui/tool/node-types.h 2016-07-09 15:52:46 +0000
@@ -28,7 +28,8 @@
28/** Types of segments supported in the node tool. */28/** Types of segments supported in the node tool. */
29enum SegmentType {29enum SegmentType {
30 SEGMENT_STRAIGHT, ///< Straight linear segment30 SEGMENT_STRAIGHT, ///< Straight linear segment
31 SEGMENT_CUBIC_BEZIER ///< Bezier curve with two control points31 SEGMENT_CUBIC_BEZIER, ///< Bezier curve with two control points
32 SEGMENT_ELIPTICAL_ARC ///< Eliptical arc (two radii, rotation, size flag, direction flag)
32};33};
3334
34} // namespace UI35} // namespace UI
3536
=== modified file 'src/ui/tool/node.cpp'
--- src/ui/tool/node.cpp 2016-05-29 10:13:55 +0000
+++ src/ui/tool/node.cpp 2016-07-09 15:52:46 +0000
@@ -112,7 +112,8 @@
112 _handle_colors, data.handle_group),112 _handle_colors, data.handle_group),
113 _parent(parent),113 _parent(parent),
114 _handle_line(ControlManager::getManager().createControlLine(data.handle_line_group)),114 _handle_line(ControlManager::getManager().createControlLine(data.handle_line_group)),
115 _degenerate(true)115 _degenerate(true),
116 _offset(Geom::Point())// Start with 0 offset
116{117{
117 setVisible(false);118 setVisible(false);
118}119}
@@ -184,8 +185,8 @@
184185
185 if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {186 if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
186 // restrict movement to the line joining the nodes187 // restrict movement to the line joining the nodes
187 Geom::Point direction = _parent->position() - node_away->position();188 Geom::Point direction = _parent->position() + _offset - node_away->position();
188 Geom::Point delta = new_pos - _parent->position();189 Geom::Point delta = new_pos - _parent->position() + _offset;
189 // project the relative position on the direction line190 // project the relative position on the direction line
190 Geom::Point new_delta = (Geom::dot(delta, direction)191 Geom::Point new_delta = (Geom::dot(delta, direction)
191 / Geom::L2sq(direction)) * direction;192 / Geom::L2sq(direction)) * direction;
@@ -212,7 +213,7 @@
212 } break;213 } break;
213 case NODE_SYMMETRIC:214 case NODE_SYMMETRIC:
214 // for symmetric nodes, place the other handle on the opposite side215 // for symmetric nodes, place the other handle on the opposite side
215 other->setRelativePos(-(new_pos - _parent->position()));216 other->setRelativePos(-(new_pos - _parent->position() + _offset));
216 break;217 break;
217 default: break;218 default: break;
218 }219 }
@@ -229,10 +230,10 @@
229void Handle::setPosition(Geom::Point const &p)230void Handle::setPosition(Geom::Point const &p)
230{231{
231 ControlPoint::setPosition(p);232 ControlPoint::setPosition(p);
232 _handle_line->setCoords(_parent->position(), position());233 _handle_line->setCoords(_parent->position() + _offset, position());
233234
234 // update degeneration info and visibility235 // update degeneration info and visibility
235 if (Geom::are_near(position(), _parent->position()))236 if (Geom::are_near(position(), _parent->position() + _offset))
236 _degenerate = true;237 _degenerate = true;
237 else _degenerate = false;238 else _degenerate = false;
238239
@@ -243,6 +244,15 @@
243 }244 }
244}245}
245246
247/** Set the offset between the parent node and the origin */
248void Handle::setOffset(Geom::Point const &offset){
249 _offset = offset;
250 _handle_line->setCoords(_parent->position() + _offset, position());
251}
252
253/** Set the length of the handle, if the handle is degenerate then
254 the function returns without changing the handle position as
255 it cannot determine the desired direction of the handle */
246void Handle::setLength(double len)256void Handle::setLength(double len)
247{257{
248 if (isDegenerate()) return;258 if (isDegenerate()) return;
@@ -250,9 +260,30 @@
250 setRelativePos(dir * len);260 setRelativePos(dir * len);
251}261}
252262
263/** Set the angle of the handle, if the handle is degenerate then
264 the function returns without changing the handle position as
265 it is not possible to set the orientation of a zero length line */
266void Handle::setAngle(Geom::Angle a){
267 if (isDegenerate()) return;
268 setRelativePos(Geom::Point::polar(a, length()));
269}
270
271/** Set the angle and the length of the handle by moving the handle position
272 unlike Handle::setLength and Handle::setAngle, this function works for
273 degenerate handles as both the desired length and angle are known*/
274void Handle::setAngleAndLength(Geom::Angle a, double len){
275 // Degenerate case can be handled by setting the handle relative position
276 // to be any vector of non-zero length. A unit vector is used here.
277 if (isDegenerate()) {
278 setRelativePos(Geom::Point(0,1));
279 }
280 setAngle(a);
281 setLength(len);
282}
283
253void Handle::retract()284void Handle::retract()
254{285{
255 move(_parent->position());286 move(_parent->position() + _offset);
256}287}
257288
258void Handle::setDirection(Geom::Point const &from, Geom::Point const &to)289void Handle::setDirection(Geom::Point const &from, Geom::Point const &to)
@@ -428,6 +459,7 @@
428 new_pos=_last_drag_origin();459 new_pos=_last_drag_origin();
429 }460 }
430 move(new_pos); // needed for correct update, even though it's redundant461 move(new_pos); // needed for correct update, even though it's redundant
462 _parent->updateArcHandleConstriants(this);
431 _pm().update();463 _pm().update();
432}464}
433465
@@ -585,6 +617,10 @@
585 node_colors, data.node_group),617 node_colors, data.node_group),
586 _front(data, initial_pos, this),618 _front(data, initial_pos, this),
587 _back(data, initial_pos, this),619 _back(data, initial_pos, this),
620 _arc_rx(data, initial_pos, this),
621 _arc_ry(data, initial_pos, this),
622 _arc_large(true),
623 _arc_sweep(false),
588 _type(NODE_CUSP),624 _type(NODE_CUSP),
589 _handles_shown(false)625 _handles_shown(false)
590{626{
@@ -622,6 +658,52 @@
622 }658 }
623}659}
624660
661/** Update constrained arc handles given a new constraint.
662 This is used to keep the arc radius handles at right angles
663 @param constraint Pointer to the handle that was forced to a new position.
664 If the constraint is anything other than _arc_rx or _arc_ry then this function does nothing */
665void Node::updateArcHandleConstriants(Handle *constraint)
666{
667 Handle *updateTarget = NULL;
668 Geom::Angle rotation = 0.0;
669 // Check which handle moved and set the update target and rotation
670 if (constraint == &_arc_rx){ updateTarget = &_arc_ry; rotation = Geom::rad_from_deg( 90.0); }
671 else if (constraint == &_arc_ry){ updateTarget = &_arc_rx; rotation = Geom::rad_from_deg(-90.0); }
672 else return;
673 // Calculate the new position and move the constrained handle (updateTarget)
674 Geom::Angle newAngle = constraint->angle() + rotation;
675 Geom::Coord newLength = updateTarget->length();
676 Geom::Point newPos = Geom::Point::polar(newAngle, newLength);
677 updateTarget->setRelativePos(newPos);
678}
679
680/** Move the arc handles so that their origin is half way along the line between the nodes */
681void Node::moveArcHandles(Geom::Point lineBetweenNodes, double lenX, double lenY, Geom::Angle rotX, Geom::Angle rotY)
682{
683 // Calculate the mid point between the two nodes, put the origin of the handles there.
684 // This usually changes the length and angle of the handle lines
685 Geom::Point midPointOffset = lineBetweenNodes / 2.0;
686 _arc_rx.setOffset(midPointOffset);
687 _arc_ry.setOffset(midPointOffset);
688
689 _arc_rx.setPosition(position());
690 _arc_ry.setPosition(position());
691
692 // Apply the stored angle and length
693 _arc_rx.setAngleAndLength(rotX, lenX);
694 _arc_ry.setAngleAndLength(rotY, lenY);
695
696}
697
698/** Retract this node's handles for controling arcs */
699void Node::retractArcHandles()
700{
701 _arc_rx.setOffset(Geom::Point());
702 _arc_ry.setOffset(Geom::Point());
703 _arc_rx.retract();
704 _arc_ry.retract();
705}
706
625void Node::move(Geom::Point const &new_pos)707void Node::move(Geom::Point const &new_pos)
626{708{
627 // move handles when the node moves.709 // move handles when the node moves.
@@ -643,11 +725,47 @@
643 nextNodeWeight = _pm()._bsplineHandlePosition(nextNode->back());725 nextNodeWeight = _pm()._bsplineHandlePosition(nextNode->back());
644 }726 }
645727
728 // Save the lengths and angles of the two arc handles to apply again after the node is moved.
729 double lenX, lenY;
730 Geom::Angle rotX, rotY;
731 lenX = _arc_rx.length(); lenY = _arc_ry.length();
732 rotX = _arc_rx.angle(); rotY = _arc_ry.angle();
733
646 setPosition(new_pos);734 setPosition(new_pos);
647735
736 // _front and _back are translated with the node to which they belong.
737 // This means that if they were degenrate then they are still degenerate.
648 _front.setPosition(_front.position() + delta);738 _front.setPosition(_front.position() + delta);
649 _back.setPosition(_back.position() + delta);739 _back.setPosition(_back.position() + delta);
650740
741 // For arc manipulation handles, the position relative to their parent nodes
742 // may change as the node moves. For this reason, the degenerate case must be
743 // handled as a special case.
744 if (nextNode) { // Must have a next node for this to be an arc segment
745 if (_arc_rx.isDegenerate() || _arc_ry.isDegenerate()){
746 retractArcHandles();
747 }
748 else{
749 moveArcHandles(nextNode->position() - position(), lenX, lenY, rotX, rotY);
750 }
751 }
752 // If this is the end of an arc segment then the arc data for prevNode needs updating
753 if (prevNode){
754 if (prevNode->arc_rx()->isDegenerate() || prevNode->arc_ry()->isDegenerate()){
755 prevNode->retractArcHandles();
756 }
757 else{
758 // In this case, the parent node for the arc handles hasn't moved,
759 // for this reason, we didn't need to save the length and rotation earlier,
760 // we can get the length and rotation now.
761 lenX = prevNode->arc_rx()->length();
762 lenY = prevNode->arc_ry()->length();
763 rotX = prevNode->arc_rx()->angle();
764 rotY = prevNode->arc_ry()->angle();
765 prevNode->moveArcHandles(n->position() - prevNode->position(), lenX, lenY, rotX, rotY);
766 }
767 }
768
651 // if the node has a smooth handle after a line segment, it should be kept colinear769 // if the node has a smooth handle after a line segment, it should be kept colinear
652 // with the segment770 // with the segment
653 _fixNeighbors(old_pos, new_pos);771 _fixNeighbors(old_pos, new_pos);
@@ -667,7 +785,6 @@
667785
668void Node::transform(Geom::Affine const &m)786void Node::transform(Geom::Affine const &m)
669{787{
670
671 Geom::Point old_pos = position();788 Geom::Point old_pos = position();
672789
673 // save the previous nodes strength to apply it again once the node is moved 790 // save the previous nodes strength to apply it again once the node is moved
@@ -685,10 +802,26 @@
685 nextNodeWeight = _pm()._bsplineHandlePosition(nextNode->back());802 nextNodeWeight = _pm()._bsplineHandlePosition(nextNode->back());
686 }803 }
687804
805 // Save the lengths and angles of the two arc handles to apply again after the node is moved.
806 double lenX, lenY;
807 Geom::Angle rotX, rotY;
808 lenX = _arc_rx.length(); lenY = _arc_ry.length();
809 rotX = _arc_rx.angle(); rotY = _arc_ry.angle();
810
688 setPosition(position() * m);811 setPosition(position() * m);
689 _front.setPosition(_front.position() * m);812 _front.setPosition(_front.position() * m);
690 _back.setPosition(_back.position() * m);813 _back.setPosition(_back.position() * m);
691814
815
816 if (nextNode){
817 if (_arc_rx.isDegenerate() || _arc_ry.isDegenerate()){
818 retractArcHandles();
819 }
820 else{
821 moveArcHandles(nextNode->position() - position(), lenX, lenY, rotX, rotY);
822 }
823 }
824
692 /* Affine transforms keep handle invariants for smooth and symmetric nodes,825 /* Affine transforms keep handle invariants for smooth and symmetric nodes,
693 * but smooth nodes at ends of linear segments and auto nodes need special treatment */826 * but smooth nodes at ends of linear segments and auto nodes need special treatment */
694 _fixNeighbors(old_pos, position());827 _fixNeighbors(old_pos, position());
@@ -789,7 +922,12 @@
789 if (!_back.isDegenerate()) {922 if (!_back.isDegenerate()) {
790 _back.setVisible(v);923 _back.setVisible(v);
791 }924 }
792925 if (!_arc_rx.isDegenerate()) {
926 _arc_rx.setVisible(v);
927 }
928 if (!_arc_ry.isDegenerate()) {
929 _arc_ry.setVisible(v);
930 }
793}931}
794932
795void Node::updateHandles()933void Node::updateHandles()
@@ -798,6 +936,8 @@
798936
799 _front._handleControlStyling();937 _front._handleControlStyling();
800 _back._handleControlStyling();938 _back._handleControlStyling();
939 _arc_rx._handleControlStyling();
940 _arc_ry._handleControlStyling();
801}941}
802942
803943
@@ -1332,7 +1472,6 @@
1332 }1472 }
13331473
1334 sm.unSetup();1474 sm.unSetup();
1335
1336 SelectableControlPoint::dragged(new_pos, event);1475 SelectableControlPoint::dragged(new_pos, event);
1337}1476}
13381477
@@ -1361,6 +1500,15 @@
1361 return SnapCandidatePoint(position(), _snapSourceType(), _snapTargetType());1500 return SnapCandidatePoint(position(), _snapSourceType(), _snapTargetType());
1362}1501}
13631502
1503Geom::EllipticalArc Node::getEllipticalArc(){
1504 // Must have a next node for this to be an arc
1505 if (not _next()) return Geom::EllipticalArc();
1506 Geom::Point r = Geom::Point(_arc_rx.length(), _arc_ry.length());
1507 Geom::Coord rot = _arc_rx.angle();
1508 // Create an arc and return it
1509 return Geom::EllipticalArc(position(), r, rot, _arc_large, _arc_sweep, _next()->position());
1510}
1511
1364Handle *Node::handleToward(Node *to)1512Handle *Node::handleToward(Node *to)
1365{1513{
1366 if (_next() == to) {1514 if (_next() == to) {
@@ -1378,7 +1526,7 @@
1378 if (front() == dir) {1526 if (front() == dir) {
1379 return _next();1527 return _next();
1380 }1528 }
1381 if (back() == dir) {1529 if ( (back() == dir) || (arc_rx() == dir) || (arc_ry() == dir) ) {
1382 return _prev();1530 return _prev();
1383 }1531 }
1384 g_error("Node::nodeToward(): handle is not a child of this node!");1532 g_error("Node::nodeToward(): handle is not a child of this node!");
@@ -1402,7 +1550,7 @@
1402 if (front() == h) {1550 if (front() == h) {
1403 return _prev();1551 return _prev();
1404 }1552 }
1405 if (back() == h) {1553 if ( (back() == h) || (arc_rx() == h) || (arc_ry() == h) ) {
1406 return _next();1554 return _next();
1407 }1555 }
1408 g_error("Node::nodeAwayFrom(): handle is not a child of this node!");1556 g_error("Node::nodeAwayFrom(): handle is not a child of this node!");
@@ -1492,9 +1640,9 @@
1492{1640{
1493 if (!first || !second) return false;1641 if (!first || !second) return false;
1494 if (first->_next() == second)1642 if (first->_next() == second)
1495 return first->_front.isDegenerate() && second->_back.isDegenerate();1643 return first->_front.isDegenerate() && second->_back.isDegenerate() && first->_arc_rx.isDegenerate() && first->_arc_ry.isDegenerate();
1496 if (second->_next() == first)1644 if (second->_next() == first)
1497 return second->_front.isDegenerate() && first->_back.isDegenerate();1645 return second->_front.isDegenerate() && first->_back.isDegenerate() && second->_arc_rx.isDegenerate() && second->_arc_ry.isDegenerate();
1498 return false;1646 return false;
1499}1647}
15001648
15011649
=== modified file 'src/ui/tool/node.h'
--- src/ui/tool/node.h 2015-04-30 09:17:07 +0000
+++ src/ui/tool/node.h 2016-07-09 15:52:46 +0000
@@ -29,6 +29,7 @@
2929
30#include <boost/enable_shared_from_this.hpp>30#include <boost/enable_shared_from_this.hpp>
31#include <boost/shared_ptr.hpp>31#include <boost/shared_ptr.hpp>
32#include <2geom/elliptical-arc.h>
32#include "ui/tool/selectable-control-point.h"33#include "ui/tool/selectable-control-point.h"
33#include "snapped-point.h"34#include "snapped-point.h"
34#include "ui/tool/node-types.h"35#include "ui/tool/node-types.h"
@@ -100,6 +101,7 @@
100 virtual ~Handle();101 virtual ~Handle();
101 inline Geom::Point relativePos() const;102 inline Geom::Point relativePos() const;
102 inline double length() const;103 inline double length() const;
104 inline Geom::Angle angle() const;
103 bool isDegenerate() const { return _degenerate; } // True if the handle is retracted, i.e. has zero length.105 bool isDegenerate() const { return _degenerate; } // True if the handle is retracted, i.e. has zero length.
104106
105 virtual void setVisible(bool);107 virtual void setVisible(bool);
@@ -107,7 +109,10 @@
107109
108 virtual void setPosition(Geom::Point const &p);110 virtual void setPosition(Geom::Point const &p);
109 inline void setRelativePos(Geom::Point const &p);111 inline void setRelativePos(Geom::Point const &p);
112 virtual void setOffset(Geom::Point const &offset);
110 void setLength(double len);113 void setLength(double len);
114 void setAngle(Geom::Angle a);
115 void setAngleAndLength(Geom::Angle a, double len);
111 void retract();116 void retract();
112 void setDirection(Geom::Point const &from, Geom::Point const &to);117 void setDirection(Geom::Point const &from, Geom::Point const &to);
113 void setDirection(Geom::Point const &dir);118 void setDirection(Geom::Point const &dir);
@@ -140,6 +145,8 @@
140 SPCtrlLine *_handle_line;145 SPCtrlLine *_handle_line;
141 bool _degenerate; // True if the handle is retracted, i.e. has zero length. This is used often internally so it makes sense to cache this146 bool _degenerate; // True if the handle is retracted, i.e. has zero length. This is used often internally so it makes sense to cache this
142147
148 Geom::Point _offset;
149
143 /**150 /**
144 * Control point of a cubic Bezier curve in a path.151 * Control point of a cubic Bezier curve in a path.
145 *152 *
@@ -183,6 +190,10 @@
183 void showHandles(bool v);190 void showHandles(bool v);
184191
185 void updateHandles();192 void updateHandles();
193 void updateArcHandleConstriants(Handle *constraint);
194 void moveArcHandles(Geom::Point lineBetweenNodes, double lenX, double lenY, Geom::Angle rotX, Geom::Angle rotY);
195
196 void retractArcHandles();
186197
187198
188 /**199 /**
@@ -195,6 +206,11 @@
195 bool isEndNode() const;206 bool isEndNode() const;
196 Handle *front() { return &_front; }207 Handle *front() { return &_front; }
197 Handle *back() { return &_back; }208 Handle *back() { return &_back; }
209 Handle *arc_rx() { return &_arc_rx; }
210 Handle *arc_ry() { return &_arc_ry; }
211 bool *arc_large() { return &_arc_large; }
212 bool *arc_sweep() { return &_arc_sweep; }
213 Geom::EllipticalArc getEllipticalArc();
198214
199 /**215 /**
200 * Gets the handle that faces the given adjacent node.216 * Gets the handle that faces the given adjacent node.
@@ -278,6 +294,11 @@
278 // as a line segment294 // as a line segment
279 Handle _front; ///< Node handle in the backward direction of the path295 Handle _front; ///< Node handle in the backward direction of the path
280 Handle _back; ///< Node handle in the forward direction of the path296 Handle _back; ///< Node handle in the forward direction of the path
297 // The arc control points and flags relate to an arc starting at this node and ending at the next node
298 Handle _arc_rx; ///< Handle for controling the x radius and rotation of the elipse (in eliptical arc mode)
299 Handle _arc_ry; ///< Handle for controling the y radius and rotation of the elipse (in eliptical arc mode)
300 bool _arc_large; ///< Flag that determines if arc mode is shallow or bulge
301 bool _arc_sweep; ///< Flag that determines the direction of an arc's sweep
281 NodeType _type; ///< Type of node - cusp, smooth...302 NodeType _type; ///< Type of node - cusp, smooth...
282 bool _handles_shown;303 bool _handles_shown;
283 static ColorSet node_colors;304 static ColorSet node_colors;
@@ -489,14 +510,17 @@
489510
490// define inline Handle funcs after definition of Node511// define inline Handle funcs after definition of Node
491inline Geom::Point Handle::relativePos() const {512inline Geom::Point Handle::relativePos() const {
492 return position() - _parent->position();513 return position() - (_parent->position() + _offset);
493}514}
494inline void Handle::setRelativePos(Geom::Point const &p) {515inline void Handle::setRelativePos(Geom::Point const &p) {
495 setPosition(_parent->position() + p);516 setPosition(_parent->position() + _offset + p);
496}517}
497inline double Handle::length() const {518inline double Handle::length() const {
498 return relativePos().length();519 return relativePos().length();
499}520}
521inline Geom::Angle Handle::angle() const {
522 return Geom::Angle(position() - _parent->position() - _offset);
523}
500inline PathManipulator &Handle::_pm() {524inline PathManipulator &Handle::_pm() {
501 return _parent->_pm();525 return _parent->_pm();
502}526}
503527
=== modified file 'src/ui/tool/path-manipulator.cpp'
--- src/ui/tool/path-manipulator.cpp 2016-05-19 22:11:42 +0000
+++ src/ui/tool/path-manipulator.cpp 2016-07-09 15:52:46 +0000
@@ -21,6 +21,7 @@
21#include <2geom/bezier-curve.h>21#include <2geom/bezier-curve.h>
22#include <2geom/bezier-utils.h>22#include <2geom/bezier-utils.h>
23#include <2geom/path-sink.h>23#include <2geom/path-sink.h>
24#include <2geom/pathvector.h>
24#include <glibmm/i18n.h>25#include <glibmm/i18n.h>
25#include "ui/tool/path-manipulator.h"26#include "ui/tool/path-manipulator.h"
26#include "desktop.h"27#include "desktop.h"
@@ -801,7 +802,10 @@
801 }802 }
802}803}
803804
804/** Make selected segments curves / lines. */805// \todo The three functions setSegmentType, setArcSegmentLarge, setArcSegmentSweep could be
806// refactored to share the code that iterates over all selected segments.
807
808/** Make selected segments curves / lines / arcs. */
805void PathManipulator::setSegmentType(SegmentType type)809void PathManipulator::setSegmentType(SegmentType type)
806{810{
807 if (_num_selected == 0) return;811 if (_num_selected == 0) return;
@@ -811,18 +815,81 @@
811 if (!(k && j->selected() && k->selected())) continue;815 if (!(k && j->selected() && k->selected())) continue;
812 switch (type) {816 switch (type) {
813 case SEGMENT_STRAIGHT:817 case SEGMENT_STRAIGHT:
814 if (j->front()->isDegenerate() && k->back()->isDegenerate())818 if ((j->front()->isDegenerate() && k->back()->isDegenerate())
819 && (j->arc_rx()->isDegenerate() && j->arc_ry()->isDegenerate()))
815 break;820 break;
816 j->front()->move(*j);821 j->front()->move(*j);
817 k->back()->move(*k);822 k->back()->move(*k);
823 j->retractArcHandles();
818 break;824 break;
819 case SEGMENT_CUBIC_BEZIER:825 case SEGMENT_CUBIC_BEZIER:
820 if (!j->front()->isDegenerate() || !k->back()->isDegenerate())826 if (!j->front()->isDegenerate() || !k->back()->isDegenerate()){
821 break;827 // Already a cubic bezier
828 break;
829 }
830 if (!j->arc_rx()->isDegenerate() || !j->arc_ry()->isDegenerate()){
831 // This is an elliptical arc that is being converted to a cubic bezier
832 // Generate the bezier path and use it to replace the current segment
833 Geom::Path cubicbezier_path = Geom::cubicbezierpath_from_sbasis(j->getEllipticalArc().toSBasis(), 0.1);
834 replaceSegmentWithPath(j, cubicbezier_path);
835 break;
836 }
837
822 // move both handles to 1/3 of the line838 // move both handles to 1/3 of the line
823 j->front()->move(j->position() + (k->position() - j->position()) / 3);839 j->front()->move(j->position() + (k->position() - j->position()) / 3);
824 k->back()->move(k->position() + (j->position() - k->position()) / 3);840 k->back()->move(k->position() + (j->position() - k->position()) / 3);
825 break;841 j->arc_rx()->move(*j);
842 j->arc_ry()->move(*j);
843 j->retractArcHandles();
844 break;
845 case SEGMENT_ELIPTICAL_ARC:
846 if (!(j->front()->isDegenerate() && k->back()->isDegenerate())){
847 j->front()->move(*j);
848 k->back()->move(*k);
849 }
850
851 if (j->arc_rx()->isDegenerate() && j->arc_ry()->isDegenerate()){
852 Geom::Point midPointOffset = ((k->position() - j->position()) / 2);
853 Geom::Point handleOrigin = j->position()+midPointOffset;
854
855 j->arc_rx()->setOffset(midPointOffset);
856 j->arc_ry()->setOffset(midPointOffset);
857 j->arc_rx()->move(j->position() + ((k->position() - handleOrigin) / 2));
858 j->updateArcHandleConstriants(j->arc_rx());
859 }
860 break;
861 }
862 }
863 }
864}
865
866/** Set the large flag on selected arcs */
867void PathManipulator::setArcSegmentLarge(bool large){
868 if (_num_selected == 0) return;
869 for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
870 for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
871 NodeList::iterator k = j.next();
872 if (!(k && j->selected() && k->selected())) continue;
873 // This code executes for every selected segment
874 if (!j->arc_rx()->isDegenerate() || !j->arc_ry()->isDegenerate()){
875 // This code executes for every selected ARC segment
876 *(j->arc_large()) = large;
877 }
878 }
879 }
880}
881
882/** Set the sweep flag on selected arcs */
883void PathManipulator::toggleArcSegmentSweep(){
884 if (_num_selected == 0) return;
885 for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
886 for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
887 NodeList::iterator k = j.next();
888 if (!(k && j->selected() && k->selected())) continue;
889 // This code executes for every selected segment
890 if (!j->arc_rx()->isDegenerate() || !j->arc_ry()->isDegenerate()){
891 // This code executes for every selected ARC segment
892 *(j->arc_sweep()) = !*j->arc_sweep();
826 }893 }
827 }894 }
828 }895 }
@@ -1094,6 +1161,75 @@
1094 return match;1161 return match;
1095}1162}
10961163
1164/** Replace a segment with a path.
1165 * @param segment The segment to replace
1166 * @param newPath The path to insert in place of 'segment'
1167 *
1168 * Currently, newPath must be composed only of cubic beziers, the caller must
1169 * ensure that the path only contains cubic beziers (until other segments are
1170 * implemented in this function) */
1171void PathManipulator::replaceSegmentWithPath(NodeList::iterator segment, Geom::Path newPath)
1172{
1173 if (!segment) throw std::invalid_argument("Invalid iterator for replacement");
1174 NodeList &list = NodeList::get(segment);
1175 NodeList::iterator second = segment.next();
1176 if (!second) throw std::invalid_argument("Replace after last node in open path");
1177
1178 // Retract all handles relating to this segment
1179 segment->retractArcHandles();
1180 segment->front()->retract();
1181
1182 // get the insertion point
1183 NodeList::iterator insert_at = segment;
1184 ++insert_at;
1185
1186 // Keep the previous node handy to update its handles when needed
1187 Node *prevNode = &(*insert_at);
1188
1189 // Path is to be inserted in reverse order
1190 Geom::Path reversedPath = newPath.reversed();
1191
1192 // Iterate over the path
1193 for (Geom::Path::iterator i = reversedPath.begin(); i != reversedPath.end(); ++i){
1194 const Geom::Curve & thisCurve = *i;
1195
1196 // Try converting to a bezier
1197 const Geom::BezierCurve * bezier = dynamic_cast<const Geom::BezierCurve*>(&thisCurve);
1198 if (bezier) {
1199 // Check order of bezier (currently only cubic beziers are supported)
1200 if (bezier->order() == 3)
1201 {
1202 // Create one new node
1203 Node *newNode = new Node(_multi_path_manipulator._path_data.node_data, bezier->finalPoint());
1204 // Set the control points for this node and the previous node
1205 newNode->front() ->setPosition((*bezier)[2]);
1206 prevNode->back()->setPosition((*bezier)[1]);
1207 // All new nodes are smooth
1208 newNode->setType(NODE_SMOOTH, false);
1209
1210 // Insert new node
1211 list.insert(insert_at, newNode);
1212 // Move along to next node
1213 prevNode = newNode;
1214 insert_at--;
1215 }
1216 else{
1217 // TODO, Is there a better exception to raise here?
1218 // TODO, implement this if needed in future
1219 throw std::invalid_argument("Only cubic bezier curves are implemented in PathManipulator::replaceSegment."
1220 " newPath contains beziers with order!=3.");
1221 }
1222 }
1223 else{
1224 // Not a bezier
1225 // TODO, Is there a better exception to raise here?
1226 // TODO, implement this if needed in future
1227 throw std::invalid_argument("Only cubic bezier curves are implemented in PathManipulator::replaceSegment."
1228 " newPath contains non-bezier segments.");
1229 }
1230 }
1231}
1232
1097/** Called by the XML observer when something else than us modifies the path. */1233/** Called by the XML observer when something else than us modifies the path. */
1098void PathManipulator::_externalChange(unsigned type)1234void PathManipulator::_externalChange(unsigned type)
1099{1235{
@@ -1149,7 +1285,8 @@
11491285
1150 // sanitize pathvector and store it in SPCurve,1286 // sanitize pathvector and store it in SPCurve,
1151 // so that _updateDragPoint doesn't crash on paths with naked movetos1287 // so that _updateDragPoint doesn't crash on paths with naked movetos
1152 Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers(_spcurve->get_pathvector());1288 Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers_and_arcs(_spcurve->get_pathvector());
1289
1153 for (Geom::PathVector::iterator i = pathv.begin(); i != pathv.end(); ) {1290 for (Geom::PathVector::iterator i = pathv.begin(); i != pathv.end(); ) {
1154 // NOTE: this utilizes the fact that Geom::PathVector is an std::vector.1291 // NOTE: this utilizes the fact that Geom::PathVector is an std::vector.
1155 // When we erase an element, the next one slides into position,1292 // When we erase an element, the next one slides into position,
@@ -1199,6 +1336,15 @@
1199 previous_node->front()->setPosition((*bezier)[1]);1336 previous_node->front()->setPosition((*bezier)[1]);
1200 current_node ->back() ->setPosition((*bezier)[2]);1337 current_node ->back() ->setPosition((*bezier)[2]);
1201 }1338 }
1339 Geom::EllipticalArc const *arc = dynamic_cast<Geom::EllipticalArc const*>(&*cit);
1340 if (arc)
1341 {
1342 Geom::Coord angleX = arc->rotationAngle();
1343 Geom::Coord angleY = angleX + Geom::rad_from_deg(90);
1344 previous_node->moveArcHandles(current_node->position() - previous_node->position(), arc->ray(Geom::X), arc->ray(Geom::Y), angleX, angleY);
1345 *(previous_node->arc_large()) = arc->largeArc();
1346 *(previous_node->arc_sweep()) = arc->sweep();
1347 }
1202 previous_node = current_node;1348 previous_node = current_node;
1203 }1349 }
1204 // If the path is closed, make the list cyclic1350 // If the path is closed, make the list cyclic
@@ -1355,8 +1501,9 @@
1355 }1501 }
1356 if (subpath->closed()) {1502 if (subpath->closed()) {
1357 // Here we link the last and first node if the path is closed.1503 // Here we link the last and first node if the path is closed.
1358 // If the last segment is Bezier, we add it.1504 // If the last segment is Bezier or arc, we add it.
1359 if (!prev->front()->isDegenerate() || !subpath->begin()->back()->isDegenerate()) {1505 if (!prev->front()->isDegenerate() || !subpath->begin()->back()->isDegenerate()
1506 || !prev->arc_rx()->isDegenerate() || !prev->arc_ry()->isDegenerate()) {
1360 build_segment(builder, prev.ptr(), subpath->begin().ptr());1507 build_segment(builder, prev.ptr(), subpath->begin().ptr());
1361 }1508 }
1362 // if that segment is linear, we just call closePath().1509 // if that segment is linear, we just call closePath().
@@ -1397,16 +1544,33 @@
1397 * @relates PathManipulator */1544 * @relates PathManipulator */
1398void build_segment(Geom::PathBuilder &builder, Node *prev_node, Node *cur_node)1545void build_segment(Geom::PathBuilder &builder, Node *prev_node, Node *cur_node)
1399{1546{
1400 if (cur_node->back()->isDegenerate() && prev_node->front()->isDegenerate())1547 if (prev_node->arc_rx()->isDegenerate() || prev_node->arc_ry()->isDegenerate() ){
1401 {1548 // This is not an eliptical arc, check if it is a straight line or bezier
1402 // NOTE: It seems like the renderer cannot correctly handle vline / hline segments,1549 if (cur_node->back()->isDegenerate() && prev_node->front()->isDegenerate())
1403 // and trying to display a path using them results in funny artifacts.1550 {
1404 builder.lineTo(cur_node->position());1551 // NOTE: It seems like the renderer cannot correctly handle vline / hline segments,
1405 } else {1552 // and trying to display a path using them results in funny artifacts.
1406 // this is a bezier segment1553 builder.lineTo(cur_node->position());
1407 builder.curveTo(1554 } else {
1408 prev_node->front()->position(),1555 // this is a bezier segment
1409 cur_node->back()->position(),1556 builder.curveTo(
1557 prev_node->front()->position(),
1558 cur_node->back()->position(),
1559 cur_node->position());
1560 }
1561 }
1562 else{
1563 // This is an eliptical arc, get the x and y set by the xy handle
1564 Geom::Coord rx = prev_node->arc_rx()->length();
1565 Geom::Coord ry = prev_node->arc_ry()->length();
1566 Geom::Angle rot = prev_node->arc_rx()->angle();
1567
1568 builder.arcTo(
1569 rx,
1570 ry,
1571 rot,
1572 *prev_node->arc_large(),
1573 *prev_node->arc_sweep(),
1410 cur_node->position());1574 cur_node->position());
1411 }1575 }
1412}1576}
14131577
=== modified file 'src/ui/tool/path-manipulator.h'
--- src/ui/tool/path-manipulator.h 2015-08-13 23:23:05 +0000
+++ src/ui/tool/path-manipulator.h 2016-07-09 15:52:46 +0000
@@ -15,6 +15,7 @@
15#include <memory>15#include <memory>
16#include <2geom/pathvector.h>16#include <2geom/pathvector.h>
17#include <2geom/affine.h>17#include <2geom/affine.h>
18#include <2geom/sbasis-to-bezier.h>
18#include <boost/shared_ptr.hpp>19#include <boost/shared_ptr.hpp>
19#include <boost/weak_ptr.hpp>20#include <boost/weak_ptr.hpp>
20#include "ui/tool/node.h"21#include "ui/tool/node.h"
@@ -81,6 +82,9 @@
81 void reverseSubpaths(bool selected_only);82 void reverseSubpaths(bool selected_only);
82 void setSegmentType(SegmentType);83 void setSegmentType(SegmentType);
8384
85 void setArcSegmentLarge(bool large);
86 void toggleArcSegmentSweep();
87
84 void scaleHandle(Node *n, int which, int dir, bool pixel);88 void scaleHandle(Node *n, int which, int dir, bool pixel);
85 void rotateHandle(Node *n, int which, int dir, bool pixel);89 void rotateHandle(Node *n, int which, int dir, bool pixel);
8690
@@ -97,6 +101,7 @@
97 NodeList::iterator subdivideSegment(NodeList::iterator after, double t);101 NodeList::iterator subdivideSegment(NodeList::iterator after, double t);
98 NodeList::iterator extremeNode(NodeList::iterator origin, bool search_selected,102 NodeList::iterator extremeNode(NodeList::iterator origin, bool search_selected,
99 bool search_unselected, bool closest);103 bool search_unselected, bool closest);
104 void replaceSegmentWithPath(NodeList::iterator segment, Geom::Path newPath);
100105
101 int _bsplineGetSteps() const;106 int _bsplineGetSteps() const;
102 // this is necessary for Tab-selection in MultiPathManipulator107 // this is necessary for Tab-selection in MultiPathManipulator
103108
=== modified file 'src/widgets/node-toolbar.cpp'
--- src/widgets/node-toolbar.cpp 2014-12-21 21:58:32 +0000
+++ src/widgets/node-toolbar.cpp 2016-07-09 15:52:46 +0000
@@ -171,6 +171,38 @@
171 }171 }
172}172}
173173
174static void sp_node_path_edit_toarc(void)
175{
176 NodeTool *nt = get_node_tool();
177 if (nt) {
178 nt->_multipath->setSegmentType(Inkscape::UI::SEGMENT_ELIPTICAL_ARC);
179 }
180}
181
182static void sp_node_path_edit_arc_shallow(void)
183{
184 NodeTool *nt = get_node_tool();
185 if (nt) {
186 nt->_multipath->setArcSegmentLarge(false);
187 }
188}
189
190static void sp_node_path_edit_arc_bulge(void)
191{
192 NodeTool *nt = get_node_tool();
193 if (nt) {
194 nt->_multipath->setArcSegmentLarge(true);
195 }
196}
197
198static void sp_node_path_edit_arc_flip(void)
199{
200 NodeTool *nt = get_node_tool();
201 if (nt) {
202 nt->_multipath->toggleArcSegmentSweep();
203 }
204}
205
174static void sp_node_path_edit_cusp(void)206static void sp_node_path_edit_cusp(void)
175{207{
176 NodeTool *nt = get_node_tool();208 NodeTool *nt = get_node_tool();
@@ -486,6 +518,36 @@
486 }518 }
487519
488 {520 {
521 InkAction* inky = ink_action_new( "NodeArcShallowAction",
522 _("Node Arc Shallow"),
523 _("Make selected arc segments shallow"),
524 INKSCAPE_ICON("node-segment-elliptical-arc-shallow"),
525 secondarySize );
526 g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_arc_shallow), 0 );
527 gtk_action_group_add_action( mainActions, GTK_ACTION(inky) );
528 }
529
530 {
531 InkAction* inky = ink_action_new( "NodeArcBulgeAction",
532 _("Node Arc Bulge"),
533 _("Make selected arc segments bulge"),
534 INKSCAPE_ICON("node-segment-elliptical-arc-bulge"),
535 secondarySize );
536 g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_arc_bulge), 0 );
537 gtk_action_group_add_action( mainActions, GTK_ACTION(inky) );
538 }
539
540 {
541 InkAction* inky = ink_action_new( "NodeArcFlipAction",
542 _("Node Arc Flip"),
543 _("Flip selected arc segments"),
544 INKSCAPE_ICON("node-segment-elliptical-arc-flip"),
545 secondarySize );
546 g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_arc_flip), 0 );
547 gtk_action_group_add_action( mainActions, GTK_ACTION(inky) );
548 }
549
550 {
489 InkAction* inky = ink_action_new( "NodeLineAction",551 InkAction* inky = ink_action_new( "NodeLineAction",
490 _("Node Line"),552 _("Node Line"),
491 _("Make selected segments lines"),553 _("Make selected segments lines"),
@@ -506,6 +568,16 @@
506 }568 }
507569
508 {570 {
571 InkAction* inky = ink_action_new( "NodeArcAction",
572 _("Node Arc"),
573 _("Make selected segments arcs"),
574 INKSCAPE_ICON("node-segment-elliptical-arc"),
575 secondarySize );
576 g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_toarc), 0 );
577 gtk_action_group_add_action( mainActions, GTK_ACTION(inky) );
578 }
579
580 {
509 InkToggleAction* act = ink_toggle_action_new( "NodesShowTransformHandlesAction",581 InkToggleAction* act = ink_toggle_action_new( "NodesShowTransformHandlesAction",
510 _("Show Transform Handles"),582 _("Show Transform Handles"),
511 _("Show transformation handles for selected nodes"),583 _("Show transformation handles for selected nodes"),
512584
=== modified file 'src/widgets/toolbox.cpp'
--- src/widgets/toolbox.cpp 2016-05-22 00:49:33 +0000
+++ src/widgets/toolbox.cpp 2016-07-09 15:52:46 +0000
@@ -282,6 +282,11 @@
282 " <separator />"282 " <separator />"
283 " <toolitem action='NodeLineAction' />"283 " <toolitem action='NodeLineAction' />"
284 " <toolitem action='NodeCurveAction' />"284 " <toolitem action='NodeCurveAction' />"
285 " <toolitem action='NodeArcAction' />"
286 " <separator />"
287 " <toolitem action='NodeArcShallowAction' />"
288 " <toolitem action='NodeArcBulgeAction' />"
289 " <toolitem action='NodeArcFlipAction' />"
285 " <separator />"290 " <separator />"
286 " <toolitem action='ObjectToPath' />"291 " <toolitem action='ObjectToPath' />"
287 " <toolitem action='StrokeToPath' />"292 " <toolitem action='StrokeToPath' />"