1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Author(s):
4 * Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
5 *
6 * Copyright (C) 2014 Author(s)
7 *
8 *
9 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
10 */
11
12 #include <2geom/elliptical-arc.h>
13 #include <boost/optional.hpp>
14
15 #include "lpe-fillet-chamfer.h"
16
17 #include "display/curve.h"
18
19 #include "helper/geom-curves.h"
20 #include "helper/geom-satellite.h"
21 #include "helper/geom.h"
22
23 #include "object/sp-shape.h"
24
25 #include "ui/knot/knot-holder.h"
26 #include "ui/tools/tool-base.h"
27
28 // TODO due to internal breakage in glibmm headers, this must be last:
29 #include <glibmm/i18n.h>
30
31 namespace Inkscape {
32 namespace LivePathEffect {
33
34 static const Util::EnumData<Filletmethod> FilletmethodData[] = {
35 { FM_AUTO, N_("Auto"), "auto" },
36 { FM_ARC, N_("Force arc"), "arc" },
37 { FM_BEZIER, N_("Force bezier"), "bezier" }
38 };
39 static const Util::EnumDataConverter<Filletmethod> FMConverter(FilletmethodData, FM_END);
40
LPEFilletChamfer(LivePathEffectObject * lpeobject)41 LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject)
42 : Effect(lpeobject),
43 unit(_("Unit:"), _("Unit"), "unit", &wr, this, "px"),
44 satellites_param("Satellites_param", "Satellites_param",
45 "satellites_param", &wr, this),
46 method(_("Method:"), _("Method to calculate the fillet or chamfer"),
47 "method", FMConverter, &wr, this, FM_AUTO),
48 mode(_("Mode:"), _("Mode, e.g. fillet or chamfer"),
49 "mode", &wr, this, "F", true),
50 radius(_("Radius:"), _("Radius, in unit or %"), "radius", &wr,
51 this, 0.0),
52 chamfer_steps(_("Chamfer steps:"), _("Chamfer steps"), "chamfer_steps",
53 &wr, this, 1),
54 flexible(_("Radius in %"), _("Flexible radius size (%)"),
55 "flexible", &wr, this, false),
56 only_selected(_("Change only selected nodes"),
57 _("Change only selected nodes"), "only_selected", &wr, this,
58 false),
59 use_knot_distance(_("Use knots distance instead radius"),
60 _("Use knots distance instead radius"),
61 "use_knot_distance", &wr, this, true),
62 hide_knots(_("Hide knots"), _("Hide knots"), "hide_knots", &wr, this,
63 false),
64 apply_no_radius(_("Apply changes if radius = 0"), _("Apply changes if radius = 0"), "apply_no_radius", &wr, this, true),
65 apply_with_radius(_("Apply changes if radius > 0"), _("Apply changes if radius > 0"), "apply_with_radius", &wr, this, true),
66 _pathvector_satellites(nullptr),
67 _degenerate_hide(false)
68 {
69 registerParameter(&satellites_param);
70 registerParameter(&unit);
71 registerParameter(&method);
72 registerParameter(&mode);
73 registerParameter(&radius);
74 registerParameter(&chamfer_steps);
75 registerParameter(&flexible);
76 registerParameter(&use_knot_distance);
77 registerParameter(&apply_no_radius);
78 registerParameter(&apply_with_radius);
79 registerParameter(&only_selected);
80 registerParameter(&hide_knots);
81
82 radius.param_set_range(0.0, std::numeric_limits<double>::max());
83 radius.param_set_increments(1, 1);
84 radius.param_set_digits(4);
85 radius.param_set_undo(false);
86 chamfer_steps.param_set_range(1, std::numeric_limits<gint>::max());
87 chamfer_steps.param_set_increments(1, 1);
88 chamfer_steps.param_make_integer();
89 _provides_knotholder_entities = true;
90 helperpath = false;
91 previous_unit = Glib::ustring("");
92 }
93
doOnApply(SPLPEItem const * lpeItem)94 void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem)
95 {
96 SPLPEItem *splpeitem = const_cast<SPLPEItem *>(lpeItem);
97 SPShape *shape = dynamic_cast<SPShape *>(splpeitem);
98 if (shape) {
99 Geom::PathVector const pathv = pathv_to_linear_and_cubic_beziers(shape->curve()->get_pathvector());
100 Satellites satellites;
101 double power = radius;
102 if (!flexible) {
103 SPDocument *document = getSPDoc();
104 Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str();
105 power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str());
106 }
107 SatelliteType satellite_type = FILLET;
108 std::map<std::string, SatelliteType> gchar_map_to_satellite_type =
109 boost::assign::map_list_of("F", FILLET)("IF", INVERSE_FILLET)("C", CHAMFER)("IC", INVERSE_CHAMFER)("KO", INVALID_SATELLITE);
110 auto mode_str = mode.param_getSVGValue();
111 std::map<std::string, SatelliteType>::iterator it = gchar_map_to_satellite_type.find(mode_str.raw());
112 if (it != gchar_map_to_satellite_type.end()) {
113 satellite_type = it->second;
114 }
115 Geom::PathVector pathvres;
116 for (const auto & path_it : pathv) {
117 if (path_it.empty() || count_path_nodes(path_it) < 2) {
118 continue;
119 }
120 std::vector<Satellite> subpath_satellites;
121 Geom::Path::const_iterator curve_it = path_it.begin();
122 Geom::Path::const_iterator curve_endit = path_it.end_default();
123 if (path_it.closed()) {
124 const Geom::Curve &closingline = path_it.back_closed();
125 // the closing line segment is always of type
126 // Geom::LineSegment.
127 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
128 // closingline.isDegenerate() did not work, because it only checks for
129 // *exact* zero length, which goes wrong for relative coordinates and
130 // rounding errors...
131 // the closing line segment has zero-length. So stop before that one!
132 curve_endit = path_it.end_open();
133 }
134 }
135 Geom::Path pathresult(curve_it->initialPoint());
136 while (curve_it != curve_endit) {
137 if (pathresult.size()) {
138 pathresult.setFinal(curve_it->initialPoint());
139 }
140 pathresult.append(*curve_it);
141 ++curve_it;
142 Satellite satellite(satellite_type);
143 satellite.setSteps(chamfer_steps);
144 satellite.setAmount(power);
145 satellite.setIsTime(flexible);
146 satellite.setHasMirror(true);
147 satellite.setHidden(hide_knots);
148 subpath_satellites.push_back(satellite);
149 }
150
151 //we add the last satellite on open path because _pathvector_satellites is related to nodes, not curves
152 //so maybe in the future we can need this last satellite in other effects
153 //don't remove for this effect because _pathvector_satellites class has methods when the path is modified
154 //and we want one method for all uses
155 if (!path_it.closed()) {
156 Satellite satellite(satellite_type);
157 satellite.setSteps(chamfer_steps);
158 satellite.setAmount(power);
159 satellite.setIsTime(flexible);
160 satellite.setHasMirror(true);
161 satellite.setHidden(hide_knots);
162 subpath_satellites.push_back(satellite);
163 }
164 pathresult.close(path_it.closed());
165 pathvres.push_back(pathresult);
166 pathresult.clear();
167 satellites.push_back(subpath_satellites);
168 }
169 _pathvector_satellites = new PathVectorSatellites();
170 _pathvector_satellites->setPathVector(pathvres);
171 _pathvector_satellites->setSatellites(satellites);
172 satellites_param.setPathVectorSatellites(_pathvector_satellites);
173 } else {
174 g_warning("LPE Fillet/Chamfer can only be applied to shapes (not groups).");
175 SPLPEItem *item = const_cast<SPLPEItem *>(lpeItem);
176 item->removeCurrentPathEffect(false);
177 }
178 }
179
newWidget()180 Gtk::Widget *LPEFilletChamfer::newWidget()
181 {
182 // use manage here, because after deletion of Effect object, others might
183 // still be pointing to this widget.
184 Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
185
186 vbox->set_border_width(5);
187 vbox->set_homogeneous(false);
188 vbox->set_spacing(2);
189 std::vector<Parameter *>::iterator it = param_vector.begin();
190 while (it != param_vector.end()) {
191 if ((*it)->widget_is_visible) {
192 Parameter *param = *it;
193 Gtk::Widget *widg = param->param_newWidget();
194 if (param->param_key == "radius") {
195 Inkscape::UI::Widget::Scalar *widg_registered =
196 Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg));
197 widg_registered->signal_value_changed().connect(
198 sigc::mem_fun(*this, &LPEFilletChamfer::updateAmount));
199 widg = widg_registered;
200 if (widg) {
201 Gtk::Box *scalar_parameter = dynamic_cast<Gtk::Box *>(widg);
202 std::vector<Gtk::Widget *> childList = scalar_parameter->get_children();
203 Gtk::Entry *entry_widget = dynamic_cast<Gtk::Entry *>(childList[1]);
204 entry_widget->set_width_chars(6);
205 }
206 } else if (param->param_key == "chamfer_steps") {
207 Inkscape::UI::Widget::Scalar *widg_registered =
208 Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg));
209 widg_registered->signal_value_changed().connect(
210 sigc::mem_fun(*this, &LPEFilletChamfer::updateChamferSteps));
211 widg = widg_registered;
212 if (widg) {
213 Gtk::Box *scalar_parameter = dynamic_cast<Gtk::Box *>(widg);
214 std::vector<Gtk::Widget *> childList = scalar_parameter->get_children();
215 Gtk::Entry *entry_widget = dynamic_cast<Gtk::Entry *>(childList[1]);
216 entry_widget->set_width_chars(3);
217 }
218 } else if (param->param_key == "only_selected") {
219 Gtk::manage(widg);
220 }
221 Glib::ustring *tip = param->param_getTooltip();
222 if (widg) {
223 vbox->pack_start(*widg, true, true, 2);
224 if (tip) {
225 widg->set_tooltip_text(*tip);
226 } else {
227 widg->set_tooltip_text("");
228 widg->set_has_tooltip(false);
229 }
230 }
231 }
232 ++it;
233 }
234
235 Gtk::Box *fillet_container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
236 Gtk::Button *fillet = Gtk::manage(new Gtk::Button(Glib::ustring(_("Fillet"))));
237 fillet->signal_clicked()
238 .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),FILLET));
239
240 fillet_container->pack_start(*fillet, true, true, 2);
241 Gtk::Button *inverse_fillet = Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse fillet"))));
242 inverse_fillet->signal_clicked()
243 .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),INVERSE_FILLET));
244 fillet_container->pack_start(*inverse_fillet, true, true, 2);
245
246 Gtk::Box *chamfer_container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
247 Gtk::Button *chamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Chamfer"))));
248 chamfer->signal_clicked()
249 .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),CHAMFER));
250
251 chamfer_container->pack_start(*chamfer, true, true, 2);
252 Gtk::Button *inverse_chamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse chamfer"))));
253 inverse_chamfer->signal_clicked()
254 .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),INVERSE_CHAMFER));
255 chamfer_container->pack_start(*inverse_chamfer, true, true, 2);
256
257 vbox->pack_start(*fillet_container, true, true, 2);
258 vbox->pack_start(*chamfer_container, true, true, 2);
259 if(Gtk::Widget* widg = defaultParamSet()) {
260 vbox->pack_start(*widg, true, true, 2);
261 }
262 return vbox;
263 }
264
refreshKnots()265 void LPEFilletChamfer::refreshKnots()
266 {
267 if (satellites_param._knoth) {
268 satellites_param._knoth->update_knots();
269 }
270 }
271
updateAmount()272 void LPEFilletChamfer::updateAmount()
273 {
274 setSelected(_pathvector_satellites);
275 double power = radius;
276 if (!flexible) {
277 SPDocument *document = getSPDoc();
278 Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str();
279 power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str());
280 }
281 _pathvector_satellites->updateAmount(power, apply_no_radius, apply_with_radius, only_selected,
282 use_knot_distance, flexible);
283 satellites_param.setPathVectorSatellites(_pathvector_satellites);
284 }
285
updateChamferSteps()286 void LPEFilletChamfer::updateChamferSteps()
287 {
288 setSelected(_pathvector_satellites);
289 _pathvector_satellites->updateSteps(chamfer_steps, apply_no_radius, apply_with_radius, only_selected);
290 satellites_param.setPathVectorSatellites(_pathvector_satellites);
291 }
292
updateSatelliteType(SatelliteType satellitetype)293 void LPEFilletChamfer::updateSatelliteType(SatelliteType satellitetype)
294 {
295 std::map<SatelliteType, gchar const *> satellite_type_to_gchar_map =
296 boost::assign::map_list_of(FILLET, "F")(INVERSE_FILLET, "IF")(CHAMFER, "C")(INVERSE_CHAMFER, "IC")(INVALID_SATELLITE, "KO");
297 mode.param_setValue((Glib::ustring)satellite_type_to_gchar_map.at(satellitetype));
298 setSelected(_pathvector_satellites);
299 _pathvector_satellites->updateSatelliteType(satellitetype, apply_no_radius, apply_with_radius, only_selected);
300 satellites_param.setPathVectorSatellites(_pathvector_satellites);
301 }
302
setSelected(PathVectorSatellites * _pathvector_satellites)303 void LPEFilletChamfer::setSelected(PathVectorSatellites *_pathvector_satellites){
304 std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
305 if (lpeitems.size() == 1) {
306 sp_lpe_item = lpeitems[0];
307 if (!_pathvector_satellites) {
308 sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
309 } else {
310 Geom::PathVector const pathv = _pathvector_satellites->getPathVector();
311 Satellites satellites = _pathvector_satellites->getSatellites();
312 for (size_t i = 0; i < satellites.size(); ++i) {
313 for (size_t j = 0; j < satellites[i].size(); ++j) {
314 Geom::Curve const &curve_in = pathv[i][j];
315 if (only_selected && isNodePointSelected(curve_in.initialPoint()) ){
316 satellites[i][j].setSelected(true);
317 } else {
318 satellites[i][j].setSelected(false);
319 }
320 }
321 }
322 _pathvector_satellites->setSatellites(satellites);
323 }
324 }
325 }
326
doBeforeEffect(SPLPEItem const * lpeItem)327 void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem)
328 {
329 if (!pathvector_before_effect.empty()) {
330 //fillet chamfer specific calls
331 satellites_param.setUseDistance(use_knot_distance);
332 satellites_param.setCurrentZoom(current_zoom);
333 //mandatory call
334 satellites_param.setEffectType(effectType());
335 Geom::PathVector const pathv = pathv_to_linear_and_cubic_beziers(pathvector_before_effect);
336 Geom::PathVector pathvres;
337 for (const auto &path_it : pathv) {
338 if (path_it.empty() || count_path_nodes(path_it) < 2) {
339 continue;
340 }
341 Geom::Path::const_iterator curve_it = path_it.begin();
342 Geom::Path::const_iterator curve_endit = path_it.end_default();
343 if (path_it.closed()) {
344 const Geom::Curve &closingline = path_it.back_closed();
345 // the closing line segment is always of type
346 // Geom::LineSegment.
347 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
348 // closingline.isDegenerate() did not work, because it only checks for
349 // *exact* zero length, which goes wrong for relative coordinates and
350 // rounding errors...
351 // the closing line segment has zero-length. So stop before that one!
352 curve_endit = path_it.end_open();
353 }
354 }
355 Geom::Path pathresult(curve_it->initialPoint());
356 while (curve_it != curve_endit) {
357 if (pathresult.size()) {
358 pathresult.setFinal(curve_it->initialPoint());
359 }
360 if (Geom::are_near((*curve_it).initialPoint(), (*curve_it).finalPoint())) {
361 return;
362 }
363 pathresult.append(*curve_it);
364 ++curve_it;
365 }
366 pathresult.close(path_it.closed());
367 pathvres.push_back(pathresult);
368 pathresult.clear();
369 } // if are different sizes call to recalculate
370 Satellites satellites = satellites_param.data();
371 if (satellites.empty()) {
372 doOnApply(lpeItem); // dont want _impl to not update versioning
373 satellites = satellites_param.data();
374 }
375 bool write = false;
376 if (_pathvector_satellites) {
377 size_t number_nodes = count_pathvector_nodes(pathvres);
378 size_t previous_number_nodes = _pathvector_satellites->getTotalSatellites();
379 if (number_nodes != previous_number_nodes) {
380 double power = radius;
381 if (!flexible) {
382 SPDocument *document = getSPDoc();
383 Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str();
384 power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str());
385 }
386 SatelliteType satellite_type = FILLET;
387 std::map<std::string, SatelliteType> gchar_map_to_satellite_type =
388 boost::assign::map_list_of("F", FILLET)("IF", INVERSE_FILLET)("C", CHAMFER)("IC", INVERSE_CHAMFER)("KO", INVALID_SATELLITE);
389 auto mode_str = mode.param_getSVGValue();
390 std::map<std::string, SatelliteType>::iterator it = gchar_map_to_satellite_type.find(mode_str.raw());
391 if (it != gchar_map_to_satellite_type.end()) {
392 satellite_type = it->second;
393 }
394 Satellite satellite(satellite_type);
395 satellite.setSteps(chamfer_steps);
396 satellite.setAmount(power);
397 satellite.setIsTime(flexible);
398 satellite.setHasMirror(true);
399 satellite.setHidden(hide_knots);
400 _pathvector_satellites->recalculateForNewPathVector(pathvres, satellite);
401 satellites = _pathvector_satellites->getSatellites();
402 write = true;
403 }
404 }
405
406 if (_degenerate_hide) {
407 satellites_param.setGlobalKnotHide(true);
408 } else {
409 satellites_param.setGlobalKnotHide(false);
410 }
411 for (size_t i = 0; i < satellites.size(); ++i) {
412 for (size_t j = 0; j < satellites[i].size(); ++j) {
413 if (j >= count_path_nodes(pathvres[i])) {
414 // we are on the end of a open path
415 // for the moment we dont want to use
416 // this satellite so simplest do nothing with it
417 continue;
418 }
419 Geom::Curve const &curve_in = pathvres[i][j];
420 if (satellites[i][j].is_time != flexible) {
421 satellites[i][j].is_time = flexible;
422 double amount = satellites[i][j].amount;
423 if (satellites[i][j].is_time) {
424 double time = timeAtArcLength(amount, curve_in);
425 satellites[i][j].amount = time;
426 } else {
427 double size = arcLengthAt(amount, curve_in);
428 satellites[i][j].amount = size;
429 }
430 }
431 satellites[i][j].hidden = hide_knots;
432 if (only_selected && isNodePointSelected(curve_in.initialPoint()) ){
433 satellites[i][j].setSelected(true);
434 }
435 }
436 if (!pathvres[i].closed()) {
437 satellites[i][0].amount = 0;
438 satellites[i][count_path_nodes(pathvres[i]) - 1].amount = 0;
439 }
440 }
441 if (!_pathvector_satellites) {
442 _pathvector_satellites = new PathVectorSatellites();
443 }
444 _pathvector_satellites->setPathVector(pathvres);
445 _pathvector_satellites->setSatellites(satellites);
446 satellites_param.setPathVectorSatellites(_pathvector_satellites, write);
447 size_t number_nodes = count_pathvector_nodes(pathvres);
448 size_t previous_number_nodes = _pathvector_satellites->getTotalSatellites();
449 if (number_nodes != previous_number_nodes) {
450 doOnApply(lpeItem); // dont want _impl to not update versioning
451 satellites = satellites_param.data();
452 satellites_param.setPathVectorSatellites(_pathvector_satellites, write);
453 }
454 Glib::ustring current_unit = Glib::ustring(unit.get_abbreviation());
455 if (previous_unit != current_unit && previous_unit != "") {
456 updateAmount();
457 }
458 if (write) {
459 satellites_param.reloadKnots();
460 } else {
461 refreshKnots();
462 }
463 previous_unit = current_unit;
464 } else {
465 g_warning("LPE Fillet can only be applied to shapes (not groups).");
466 }
467 }
468
469 void
addCanvasIndicators(SPLPEItem const *,std::vector<Geom::PathVector> & hp_vec)470 LPEFilletChamfer::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
471 {
472 hp_vec.push_back(_hp);
473 }
474
475 void
addChamferSteps(Geom::Path & tmp_path,Geom::Path path_chamfer,Geom::Point end_arc_point,size_t steps)476 LPEFilletChamfer::addChamferSteps(Geom::Path &tmp_path, Geom::Path path_chamfer, Geom::Point end_arc_point, size_t steps)
477 {
478 setSelected(_pathvector_satellites);
479 double path_subdivision = 1.0 / steps;
480 for (size_t i = 1; i < steps; i++) {
481 Geom::Point chamfer_step = path_chamfer.pointAt(path_subdivision * i);
482 tmp_path.appendNew<Geom::LineSegment>(chamfer_step);
483 }
484 tmp_path.appendNew<Geom::LineSegment>(end_arc_point);
485 }
486
487 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)488 LPEFilletChamfer::doEffect_path(Geom::PathVector const &path_in)
489 {
490 const double GAP_HELPER = 0.00001;
491 Geom::PathVector path_out;
492 size_t path = 0;
493 const double K = (4.0 / 3.0) * (sqrt(2.0) - 1.0);
494 _degenerate_hide = false;
495 Geom::PathVector const pathv = _pathvector_satellites->getPathVector();
496 Satellites satellites = _pathvector_satellites->getSatellites();
497 for (const auto &path_it : pathv) {
498 Geom::Path tmp_path;
499
500 double time0 = 0;
501 size_t curve = 0;
502 Geom::Path::const_iterator curve_it1 = path_it.begin();
503 Geom::Path::const_iterator curve_endit = path_it.end_default();
504 if (path_it.closed()) {
505 const Geom::Curve &closingline = path_it.back_closed();
506 // the closing line segment is always of type
507 // Geom::LineSegment.
508 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
509 // closingline.isDegenerate() did not work, because it only checks for
510 // *exact* zero length, which goes wrong for relative coordinates and
511 // rounding errors...
512 // the closing line segment has zero-length. So stop before that one!
513 curve_endit = path_it.end_open();
514 }
515 }
516 while (curve_it1 != curve_endit) {
517 size_t next_index = curve + 1;
518 if (curve == count_path_nodes(pathv[path]) - 1 && pathv[path].closed()) {
519 next_index = 0;
520 }
521 //append last extreme of paths on open paths
522 if (curve == count_path_nodes(pathv[path]) - 1 && !pathv[path].closed()) { // the path is open and we are at
523 // end of path
524 if (time0 != 1) { //Previous satellite not at 100% amount
525 Geom::Curve *last_curve = curve_it1->portion(time0, 1);
526 last_curve->setInitial(tmp_path.finalPoint());
527 tmp_path.append(*last_curve);
528 }
529 ++curve_it1;
530 continue;
531 }
532 Geom::Curve const &curve_it2 = pathv[path][next_index];
533 Satellite satellite = satellites[path][next_index];
534 if (Geom::are_near((*curve_it1).initialPoint(), (*curve_it1).finalPoint())) {
535 _degenerate_hide = true;
536 g_warning("Knots hidden if consecutive nodes has the same position.");
537 return path_in;
538 }
539 if (!curve) { //curve == 0
540 if (!path_it.closed()) {
541 time0 = 0;
542 } else {
543 time0 = satellites[path][0].time(*curve_it1);
544 }
545 }
546 double s = satellite.arcDistance(curve_it2);
547 double time1 = satellite.time(s, true, (*curve_it1));
548 double time2 = satellite.time(curve_it2);
549 if (time1 <= time0) {
550 time1 = time0;
551 }
552 if (time2 > 1) {
553 time2 = 1;
554 }
555 Geom::Curve *knot_curve_1 = curve_it1->portion(time0, time1);
556 Geom::Curve *knot_curve_2 = curve_it2.portion(time2, 1);
557 if (curve > 0) {
558 knot_curve_1->setInitial(tmp_path.finalPoint());
559 } else {
560 tmp_path.start((*curve_it1).pointAt(time0));
561 }
562
563 Geom::Point start_arc_point = knot_curve_1->finalPoint();
564 Geom::Point end_arc_point = curve_it2.pointAt(time2);
565 //add a gap helper
566 if (time2 == 1) {
567 end_arc_point = curve_it2.pointAt(time2 - GAP_HELPER);
568 }
569 if (time1 == time0) {
570 start_arc_point = curve_it1->pointAt(time1 + GAP_HELPER);
571 }
572
573 double k1 = distance(start_arc_point, curve_it1->finalPoint()) * K;
574 double k2 = distance(curve_it2.initialPoint(), end_arc_point) * K;
575 Geom::CubicBezier const *cubic_1 = dynamic_cast<Geom::CubicBezier const *>(&*knot_curve_1);
576 Geom::CubicBezier const *cubic_2 = dynamic_cast<Geom::CubicBezier const *>(&*knot_curve_2);
577 Geom::Ray ray_1(start_arc_point, curve_it1->finalPoint());
578 Geom::Ray ray_2(curve_it2.initialPoint(), end_arc_point);
579 if (cubic_1) {
580 ray_1.setPoints((*cubic_1)[2], start_arc_point);
581 }
582 if (cubic_2) {
583 ray_2.setPoints(end_arc_point, (*cubic_2)[1]);
584 }
585 bool ccw_toggle = cross(curve_it1->finalPoint() - start_arc_point, end_arc_point - start_arc_point) < 0;
586 double angle = angle_between(ray_1, ray_2, ccw_toggle);
587 double handle_angle_1 = ray_1.angle() - angle;
588 double handle_angle_2 = ray_2.angle() + angle;
589 if (ccw_toggle) {
590 handle_angle_1 = ray_1.angle() + angle;
591 handle_angle_2 = ray_2.angle() - angle;
592 }
593 Geom::Point handle_1 = Geom::Point::polar(ray_1.angle(), k1) + start_arc_point;
594 Geom::Point handle_2 = end_arc_point - Geom::Point::polar(ray_2.angle(), k2);
595 Geom::Point inverse_handle_1 = Geom::Point::polar(handle_angle_1, k1) + start_arc_point;
596 Geom::Point inverse_handle_2 = end_arc_point - Geom::Point::polar(handle_angle_2, k2);
597 if (time0 == 1) {
598 handle_1 = start_arc_point;
599 inverse_handle_1 = start_arc_point;
600 }
601 //remove gap helper
602 if (time2 == 1) {
603 end_arc_point = curve_it2.pointAt(time2);
604 }
605 if (time1 == time0) {
606 start_arc_point = curve_it1->pointAt(time0);
607 }
608 if (time1 != 1 && !Geom::are_near(angle,Geom::rad_from_deg(360))) {
609 if (time1 != time0 || (time1 == 1 && time0 == 1)) {
610 if (!knot_curve_1->isDegenerate()) {
611 tmp_path.append(*knot_curve_1);
612 }
613 }
614 SatelliteType type = satellite.satellite_type;
615 size_t steps = satellite.steps;
616 if (!steps) steps = 1;
617 Geom::Line const x_line(Geom::Point(0, 0), Geom::Point(1, 0));
618 Geom::Line const angled_line(start_arc_point, end_arc_point);
619 double arc_angle = Geom::angle_between(x_line, angled_line);
620 double radius = Geom::distance(start_arc_point, middle_point(start_arc_point, end_arc_point)) /
621 sin(angle / 2.0);
622 Geom::Coord rx = radius;
623 Geom::Coord ry = rx;
624 bool eliptical = (is_straight_curve(*curve_it1) &&
625 is_straight_curve(curve_it2) && method != FM_BEZIER) ||
626 method == FM_ARC;
627 switch (type) {
628 case CHAMFER:
629 {
630 Geom::Path path_chamfer;
631 path_chamfer.start(tmp_path.finalPoint());
632 if (eliptical) {
633 ccw_toggle = ccw_toggle ? false : true;
634 path_chamfer.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, false, ccw_toggle, end_arc_point);
635 } else {
636 path_chamfer.appendNew<Geom::CubicBezier>(handle_1, handle_2, end_arc_point);
637 }
638 addChamferSteps(tmp_path, path_chamfer, end_arc_point, steps);
639 }
640 break;
641 case INVERSE_CHAMFER:
642 {
643 Geom::Path path_chamfer;
644 path_chamfer.start(tmp_path.finalPoint());
645 if (eliptical) {
646 path_chamfer.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, false, ccw_toggle, end_arc_point);
647 } else {
648 path_chamfer.appendNew<Geom::CubicBezier>(inverse_handle_1, inverse_handle_2, end_arc_point);
649 }
650 addChamferSteps(tmp_path, path_chamfer, end_arc_point, steps);
651 }
652 break;
653 case INVERSE_FILLET:
654 {
655 if (eliptical) {
656 bool side = false;
657 if (helperpath && !getSPDoc()->is_yaxisdown()) {
658 side = true;
659 ccw_toggle = ccw_toggle ? false : true;
660 }
661 tmp_path.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, side, ccw_toggle, end_arc_point);
662 } else {
663 tmp_path.appendNew<Geom::CubicBezier>(inverse_handle_1, inverse_handle_2, end_arc_point);
664 }
665 }
666 break;
667 default: //fillet
668 {
669 if (eliptical) {
670 bool side = false;
671 if (helperpath && !getSPDoc()->is_yaxisdown()) {
672 side = true;
673 } else {
674 ccw_toggle = ccw_toggle ? false : true;
675 }
676 tmp_path.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, side, ccw_toggle, end_arc_point);
677 } else {
678 tmp_path.appendNew<Geom::CubicBezier>(handle_1, handle_2, end_arc_point);
679 }
680 }
681 break;
682 }
683 } else {
684 if (!knot_curve_1->isDegenerate()) {
685 tmp_path.append(*knot_curve_1);
686 }
687 }
688 curve++;
689 ++curve_it1;
690 time0 = time2;
691 }
692 if (path_it.closed()) {
693 tmp_path.close();
694 }
695 path++;
696 path_out.push_back(tmp_path);
697 }
698 if (helperpath) {
699 _hp = path_out;
700 return pathvector_after_effect;
701 }
702 _hp.clear();
703 return path_out;
704 }
705
706 }; //namespace LivePathEffect
707 }; /* namespace Inkscape */
708
709 /*
710 Local Variables:
711 mode:c++
712 c-file-style:"stroustrup"
713 c-file-offset:((innamespace . 0)(inline-open . 0)(case-label . +))
714 indent-tabs-mode:nil
715 fill-column:99
716 End:
717 */
718 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
719