summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJabier Arraiza Cenoz <jabier.arraiza@marker.es>2014-07-02 11:14:35 +0000
committerJabiertxof <jtx@jtx.marker.es>2014-07-02 11:14:35 +0000
commit371f45365a6b6ea42d17c8ea33cc0072f318967e (patch)
treeaf2bdac0d1597e5ab4c6cd9ebb0da007954b474c /src
parentpen tool: add back removal of curvature handle when backspace-deleting a node... (diff)
downloadinkscape-371f45365a6b6ea42d17c8ea33cc0072f318967e.tar.gz
inkscape-371f45365a6b6ea42d17c8ea33cc0072f318967e.zip
Add LPE fillet-chamfer
(bzr r13341.1.74)
Diffstat (limited to 'src')
-rw-r--r--src/knotholder.h2
-rw-r--r--src/live_effects/Makefile_insert2
-rw-r--r--src/live_effects/effect.cpp3
-rw-r--r--src/live_effects/lpe-fillet-chamfer.cpp687
-rw-r--r--src/live_effects/lpe-fillet-chamfer.h68
-rw-r--r--src/live_effects/parameter/Makefile_insert2
-rw-r--r--src/live_effects/parameter/filletchamferpointarray.cpp653
-rw-r--r--src/live_effects/parameter/filletchamferpointarray.h117
-rw-r--r--src/ui/dialog/Makefile_insert2
-rw-r--r--src/ui/dialog/lpe-fillet-chamfer-properties.cpp257
-rw-r--r--src/ui/dialog/lpe-fillet-chamfer-properties.h112
-rw-r--r--src/ui/tool/multi-path-manipulator.h3
-rw-r--r--src/ui/tool/path-manipulator.cpp20
13 files changed, 1922 insertions, 6 deletions
diff --git a/src/knotholder.h b/src/knotholder.h
index 3632635f5..dc2300105 100644
--- a/src/knotholder.h
+++ b/src/knotholder.h
@@ -28,6 +28,7 @@ class Node;
}
namespace LivePathEffect {
class PowerStrokePointArrayParamKnotHolderEntity;
+class FilletPointArrayParamKnotHolderEntity;
}
}
@@ -60,6 +61,7 @@ public:
friend class ShapeEditor;
friend class Inkscape::LivePathEffect::PowerStrokePointArrayParamKnotHolderEntity;
+ friend class Inkscape::LivePathEffect::FilletPointArrayParamKnotHolderEntity;
protected:
diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert
index c47003747..263b33b3c 100644
--- a/src/live_effects/Makefile_insert
+++ b/src/live_effects/Makefile_insert
@@ -32,6 +32,8 @@ ink_common_sources += \
live_effects/lpe-curvestitch.h \
live_effects/lpe-constructgrid.cpp \
live_effects/lpe-constructgrid.h \
+ live_effects/lpe-fillet-chamfer.cpp \
+ live_effects/lpe-fillet-chamfer.h \
live_effects/lpe-gears.cpp \
live_effects/lpe-gears.h \
live_effects/lpe-interpolate.cpp \
diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp
index d0a168a14..f70a540e8 100644
--- a/src/live_effects/effect.cpp
+++ b/src/live_effects/effect.cpp
@@ -263,6 +263,9 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
case ENVELOPE_PERSPECTIVE:
neweffect = static_cast<Effect*> ( new LPEEnvelopePerspective(lpeobj) );
break;
+ case FILLET_CHAMFER:
+ neweffect = static_cast<Effect*> ( new LPEFilletChamfer(lpeobj) );
+ break;
default:
g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
neweffect = NULL;
diff --git a/src/live_effects/lpe-fillet-chamfer.cpp b/src/live_effects/lpe-fillet-chamfer.cpp
new file mode 100644
index 000000000..c25e3bffd
--- /dev/null
+++ b/src/live_effects/lpe-fillet-chamfer.cpp
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
+ * Special thanks to Johan Engelen for the base of the effect -powerstroke-
+ * Also to ScislaC for point me to the idea
+ * Also su_v for his construvtive feedback and time
+ * Also to Mc- (IRC nick) for his important contribution to find real time
+ * values based on
+ * and finaly to Liam P. White for his big help on coding, that save me a lot of
+ * hours
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/lpe-fillet-chamfer.h"
+#include <glibmm/i18n.h>
+#include "style.h"
+#include "live_effects/parameter/filletchamferpointarray.h"
+//for update knot
+#include <2geom/svg-elliptical-arc.h>
+#include <2geom/line.h>
+#include "selection.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/selectable-control-point.h"
+#include <2geom/sbasis-to-bezier.h>
+#include "ui/tool/node.h"
+// FIXME: The following are only needed to convert the path's SPCurve* to pwd2.
+// There must be a more convenient way to achieve this.
+#include "display/curve.h"
+#include "helper/geom-nodetype.h"
+#include "desktop.h"
+#include "ui/tools/node-tool.h"
+#include <iostream>
+#include <tools-switch.h>
+#include <cmath>
+
+using namespace Geom;
+namespace Inkscape {
+namespace LivePathEffect {
+
+const double tolerance = 0.001;
+const double gapHelper = 0.00001;
+
+LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject)
+ : Effect(lpeobject),
+ fillet_chamfer_values(_("Fillet point"), _("Fillet point"),
+ "fillet_chamfer_values", &wr, this),
+ hide_knots(_("Hide knots"), _("Hide knots"), "hide_knots", &wr, this,
+ false),
+ ignore_radius_0(_("Ignore 0 radius knots"), _("Ignore 0 radius knots"),
+ "ignore_radius_0", &wr, this, false),
+ only_selected(_("Change only selected nodes"),
+ _("Change only selected nodes"), "only_selected", &wr, this,
+ false),
+ flexible(_("Flexible radius size (%)"), _("Flexible radius size (%)"),
+ "flexible", &wr, this, false),
+ unit(_("Unit"), _("Unit"), "unit", &wr, this),
+ radius(_("Radius (unit or %)"), _("Radius, in unit or %"), "radius", &wr,
+ this, 0.),
+ helper_size(_("Helper size with direction"),
+ _("Helper size with direction"), "helper_size", &wr, this,
+ 0)
+{
+ registerParameter(dynamic_cast<Parameter *>(&fillet_chamfer_values));
+ registerParameter(dynamic_cast<Parameter *>(&unit));
+ registerParameter(dynamic_cast<Parameter *>(&radius));
+ registerParameter(dynamic_cast<Parameter *>(&helper_size));
+ registerParameter(dynamic_cast<Parameter *>(&flexible));
+ registerParameter(dynamic_cast<Parameter *>(&ignore_radius_0));
+ registerParameter(dynamic_cast<Parameter *>(&only_selected));
+ registerParameter(dynamic_cast<Parameter *>(&hide_knots));
+ radius.param_set_range(0., infinity());
+ radius.param_set_increments(1, 1);
+ radius.param_set_digits(4);
+ helper_size.param_set_range(0, infinity());
+ helper_size.param_set_increments(5, 5);
+ helper_size.param_set_digits(0);
+}
+
+LPEFilletChamfer::~LPEFilletChamfer() {}
+
+Gtk::Widget *LPEFilletChamfer::newWidget()
+{
+ // use manage here, because after deletion of Effect object, others might
+ // still be pointing to this widget.
+ Gtk::VBox *vbox = Gtk::manage(new Gtk::VBox(Effect::newWidget()));
+
+ vbox->set_border_width(5);
+ vbox->set_homogeneous(false);
+ vbox->set_spacing(2);
+ std::vector<Parameter *>::iterator it = param_vector.begin();
+ while (it != param_vector.end()) {
+ if ((*it)->widget_is_visible) {
+ Parameter *param = *it;
+ Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget());
+ if (param->param_key == "radius") {
+ Inkscape::UI::Widget::Scalar *widgRegistered =
+ Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg));
+ widgRegistered->signal_value_changed()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::updateFillet));
+ widg = dynamic_cast<Gtk::Widget *>(widgRegistered);
+ if (widg) {
+ Gtk::HBox *scalarParameter = dynamic_cast<Gtk::HBox *>(widg);
+ std::vector<Gtk::Widget *> childList =
+ scalarParameter->get_children();
+ Gtk::Entry *entryWidg = dynamic_cast<Gtk::Entry *>(childList[1]);
+ entryWidg->set_width_chars(6);
+ }
+ }
+ if (param->param_key == "flexible") {
+ Gtk::CheckButton *widgRegistered =
+ Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg));
+ widgRegistered->signal_clicked()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::toggleFlexFixed));
+ widg = dynamic_cast<Gtk::Widget *>(widgRegistered);
+ }
+ if (param->param_key == "helper_size") {
+ Inkscape::UI::Widget::Scalar *widgRegistered =
+ Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg));
+ widgRegistered->signal_value_changed()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::refreshKnots));
+ widg = dynamic_cast<Gtk::Widget *>(widgRegistered);
+
+ }
+ if (param->param_key == "hide_knots") {
+ Gtk::CheckButton *widgRegistered =
+ Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg));
+ widgRegistered->signal_clicked()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::toggleHide));
+ widg = dynamic_cast<Gtk::Widget *>(widgRegistered);
+ }
+ if (param->param_key == "only_selected") {
+ Gtk::CheckButton *widgRegistered =
+ Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg));
+ widg = dynamic_cast<Gtk::Widget *>(widgRegistered);
+ }
+ if (param->param_key == "ignore_radius_0") {
+ Gtk::CheckButton *widgRegistered =
+ Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg));
+ widg = dynamic_cast<Gtk::Widget *>(widgRegistered);
+ }
+ Glib::ustring *tip = param->param_getTooltip();
+ if (widg) {
+ vbox->pack_start(*widg, true, true, 2);
+ if (tip) {
+ widg->set_tooltip_text(*tip);
+ } else {
+ widg->set_tooltip_text("");
+ widg->set_has_tooltip(false);
+ }
+ }
+ }
+
+ ++it;
+ }
+
+ Gtk::HBox *filletButtonsContainer = Gtk::manage(new Gtk::HBox(true, 0));
+ Gtk::Button *fillet =
+ Gtk::manage(new Gtk::Button(Glib::ustring(_("Fillet"))));
+ fillet->signal_clicked()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::fillet));
+ filletButtonsContainer->pack_start(*fillet, true, true, 2);
+ Gtk::Button *inverse =
+ Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse fillet"))));
+ inverse->signal_clicked()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::inverse));
+ filletButtonsContainer->pack_start(*inverse, true, true, 2);
+
+ Gtk::HBox *chamferButtonsContainer = Gtk::manage(new Gtk::HBox(true, 0));
+ Gtk::Button *chamfer =
+ Gtk::manage(new Gtk::Button(Glib::ustring(_("Chamfer"))));
+ chamfer->signal_clicked()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::chamfer));
+ chamferButtonsContainer->pack_start(*chamfer, true, true, 2);
+ Gtk::Button *doubleChamfer =
+ Gtk::manage(new Gtk::Button(Glib::ustring(_("Double chamfer"))));
+ doubleChamfer->signal_clicked()
+ .connect(sigc::mem_fun(*this, &LPEFilletChamfer::doubleChamfer));
+ chamferButtonsContainer->pack_start(*doubleChamfer, true, true, 2);
+ vbox->pack_start(*filletButtonsContainer, true, true, 2);
+ vbox->pack_start(*chamferButtonsContainer, true, true, 2);
+
+ return dynamic_cast<Gtk::Widget *>(vbox);
+}
+
+void LPEFilletChamfer::toggleHide()
+{
+ std::vector<Point> filletChamferData = fillet_chamfer_values.data();
+ std::vector<Geom::Point> result;
+ for (std::vector<Point>::const_iterator point_it = filletChamferData.begin();
+ point_it != filletChamferData.end(); ++point_it) {
+ if (hide_knots) {
+ result.push_back(Point((*point_it)[X], abs((*point_it)[Y]) * -1));
+ } else {
+ result.push_back(Point((*point_it)[X], abs((*point_it)[Y])));
+ }
+ }
+ fillet_chamfer_values.param_set_and_write_new_value(result);
+ refreshKnots();
+}
+
+void LPEFilletChamfer::toggleFlexFixed()
+{
+ std::vector<Point> filletChamferData = fillet_chamfer_values.data();
+ std::vector<Geom::Point> result;
+ unsigned int i = 0;
+ for (std::vector<Point>::const_iterator point_it = filletChamferData.begin();
+ point_it != filletChamferData.end(); ++point_it) {
+ if (flexible) {
+ result.push_back(Point(fillet_chamfer_values.to_time(i, (*point_it)[X]),
+ (*point_it)[Y]));
+ } else {
+ result.push_back(Point(fillet_chamfer_values.to_len(i, (*point_it)[X]),
+ (*point_it)[Y]));
+ }
+ i++;
+ }
+ if (flexible) {
+ radius.param_set_range(0., 100);
+ radius.param_set_value(0);
+ } else {
+ radius.param_set_range(0., infinity());
+ radius.param_set_value(0);
+ }
+ fillet_chamfer_values.param_set_and_write_new_value(result);
+}
+
+void LPEFilletChamfer::updateFillet()
+{
+ double power = 0;
+ if (!flexible) {
+ power = Inkscape::Util::Quantity::convert(radius, unit.get_abbreviation(),
+ "px") * -1;
+ } else {
+ power = radius;
+ }
+ Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2();
+ doUpdateFillet(path_from_piecewise(pwd2, tolerance), power);
+}
+
+void LPEFilletChamfer::fillet()
+{
+ Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2();
+ doChangeType(path_from_piecewise(pwd2, tolerance), 1);
+}
+
+void LPEFilletChamfer::inverse()
+{
+ Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2();
+ doChangeType(path_from_piecewise(pwd2, tolerance), 2);
+}
+
+void LPEFilletChamfer::chamfer()
+{
+ Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2();
+ doChangeType(path_from_piecewise(pwd2, tolerance), 3);
+}
+
+void LPEFilletChamfer::doubleChamfer()
+{
+ Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2();
+ doChangeType(path_from_piecewise(pwd2, tolerance), 4);
+}
+
+void LPEFilletChamfer::refreshKnots()
+{
+ Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2();
+ fillet_chamfer_values.recalculate_knots(pwd2);
+ SPDesktop *desktop = inkscape_active_desktop();
+ if (tools_isactive(desktop, TOOLS_NODES)) {
+ tools_switch(desktop, TOOLS_SELECT);
+ tools_switch(desktop, TOOLS_NODES);
+ }
+}
+
+bool LPEFilletChamfer::nodeIsSelected(Geom::Point nodePoint,
+ std::vector<Geom::Point> point)
+{
+ if (point.size() > 0) {
+ for (std::vector<Geom::Point>::iterator i = point.begin(); i != point.end();
+ ++i) {
+ Geom::Point p = *i;
+ if (Geom::are_near(p, nodePoint, 2)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+void LPEFilletChamfer::doUpdateFillet(std::vector<Geom::Path> original_pathv,
+ double power)
+{
+ std::vector<Geom::Point> point;
+ SPDesktop *desktop = inkscape_active_desktop();
+ if (INK_IS_NODE_TOOL(desktop->event_context)) {
+ Inkscape::UI::Tools::NodeTool *nodeTool =
+ INK_NODE_TOOL(desktop->event_context);
+ Inkscape::UI::ControlPointSelection::Set &selection =
+ nodeTool->_selected_nodes->allPoints();
+ std::vector<Geom::Point>::iterator pBegin;
+ for (Inkscape::UI::ControlPointSelection::Set::iterator i =
+ selection.begin();
+ i != selection.end(); ++i) {
+ if ((*i)->selected()) {
+ Inkscape::UI::Node *n = dynamic_cast<Inkscape::UI::Node *>(*i);
+ pBegin = point.begin();
+ point.insert(pBegin, desktop->doc2dt(n->position()));
+ }
+ }
+ }
+ std::vector<Point> filletChamferData = fillet_chamfer_values.data();
+ std::vector<Geom::Point> result;
+ int counter = 0;
+ for (PathVector::const_iterator path_it = original_pathv.begin();
+ path_it != original_pathv.end(); ++path_it) {
+ int pathCounter = 0;
+ if (path_it->empty())
+ continue;
+
+ Geom::Path::const_iterator curve_it1 = path_it->begin();
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin());
+ Geom::Path::const_iterator curve_endit = path_it->end_default();
+ if (path_it->closed() && path_it->back_closed().isDegenerate()) {
+ const Curve &closingline = path_it->back_closed();
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ curve_endit = path_it->end_open();
+ }
+ }
+ double powerend = 0;
+ while (curve_it1 != curve_endit) {
+ powerend = power;
+ if (power > 0) {
+ powerend = counter + (power / 100);
+ }
+ if (ignore_radius_0 && (filletChamferData[counter][X] == 0 ||
+ filletChamferData[counter][X] == counter)) {
+ powerend = filletChamferData[counter][X];
+ }
+ if (filletChamferData[counter][Y] == 0) {
+ powerend = filletChamferData[counter][X];
+ }
+ if (only_selected && !nodeIsSelected(curve_it1->initialPoint(), point)) {
+ powerend = filletChamferData[counter][X];
+ }
+ result.push_back(Point(powerend, filletChamferData[counter][Y]));
+ ++curve_it1;
+ ++curve_it2;
+ counter++;
+ pathCounter++;
+ }
+ }
+ fillet_chamfer_values.param_set_and_write_new_value(result);
+}
+
+void LPEFilletChamfer::doChangeType(std::vector<Geom::Path> original_pathv,
+ int type)
+{
+ std::vector<Geom::Point> point;
+ SPDesktop *desktop = inkscape_active_desktop();
+ if (INK_IS_NODE_TOOL(desktop->event_context)) {
+ Inkscape::UI::Tools::NodeTool *nodeTool =
+ INK_NODE_TOOL(desktop->event_context);
+ Inkscape::UI::ControlPointSelection::Set &selection =
+ nodeTool->_selected_nodes->allPoints();
+ std::vector<Geom::Point>::iterator pBegin;
+ for (Inkscape::UI::ControlPointSelection::Set::iterator i =
+ selection.begin();
+ i != selection.end(); ++i) {
+ if ((*i)->selected()) {
+ Inkscape::UI::Node *n = dynamic_cast<Inkscape::UI::Node *>(*i);
+ pBegin = point.begin();
+ point.insert(pBegin, desktop->doc2dt(n->position()));
+ }
+ }
+ }
+ std::vector<Point> filletChamferData = fillet_chamfer_values.data();
+ std::vector<Geom::Point> result;
+ int counter = 0;
+ for (PathVector::const_iterator path_it = original_pathv.begin();
+ path_it != original_pathv.end(); ++path_it) {
+ int pathCounter = 0;
+ if (path_it->empty())
+ continue;
+
+ Geom::Path::const_iterator curve_it1 = path_it->begin();
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin());
+ Geom::Path::const_iterator curve_endit = path_it->end_default();
+ if (path_it->closed() && path_it->back_closed().isDegenerate()) {
+ const Curve &closingline = path_it->back_closed();
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ curve_endit = path_it->end_open();
+ }
+ }
+ while (curve_it1 != curve_endit) {
+ bool toggle = true;
+ if (filletChamferData[counter][Y] == 0 ||
+ (ignore_radius_0 && (filletChamferData[counter][X] == 0 ||
+ filletChamferData[counter][X] == counter)) ||
+ (only_selected &&
+ !nodeIsSelected(curve_it1->initialPoint(), point))) {
+ toggle = false;
+ }
+ if (toggle) {
+ result.push_back(Point(filletChamferData[counter][X], type));
+ } else {
+ result.push_back(filletChamferData[counter]);
+ }
+ ++curve_it1;
+ if (curve_it2 != curve_endit) {
+ ++curve_it2;
+ }
+ counter++;
+ pathCounter++;
+ }
+ }
+ fillet_chamfer_values.param_set_and_write_new_value(result);
+}
+
+void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem)
+{
+ if (SP_IS_SHAPE(lpeItem)) {
+ std::vector<Point> point;
+ PathVector const &original_pathv =
+ SP_SHAPE(lpeItem)->_curve->get_pathvector();
+ Piecewise<D2<SBasis> > pwd2_in = paths_to_pw(original_pathv);
+ for (PathVector::const_iterator path_it = original_pathv.begin();
+ path_it != original_pathv.end(); ++path_it) {
+ if (path_it->empty())
+ continue;
+
+ Geom::Path::const_iterator curve_it1 = path_it->begin();
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin());
+ Geom::Path::const_iterator curve_endit = path_it->end_default();
+ if (path_it->closed()) {
+ const Geom::Curve &closingline = path_it->back_closed();
+ // the closing line segment is always of type
+ // Geom::LineSegment.
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ // closingline.isDegenerate() did not work, because it only checks for
+ // *exact* zero length, which goes wrong for relative coordinates and
+ // rounding errors...
+ // the closing line segment has zero-length. So stop before that one!
+ curve_endit = path_it->end_open();
+ }
+ }
+ int counter = 0;
+ while (curve_it1 != curve_endit) {
+ Geom::NodeType nodetype;
+ if (counter == 0) {
+ if (path_it->closed()) {
+ nodetype = get_nodetype(path_it->back_default(), *curve_it1);
+ } else {
+ nodetype = NODE_NONE;
+ }
+ } else {
+ nodetype = get_nodetype((*path_it)[counter - 1], *curve_it1);
+ }
+ if (nodetype == NODE_CUSP) {
+ point.push_back(Point(0, 1));
+ } else {
+ point.push_back(Point(0, 0));
+ }
+ ++curve_it1;
+ if (curve_it2 != curve_endit) {
+ ++curve_it2;
+ }
+ counter++;
+ }
+ }
+ fillet_chamfer_values.param_set_and_write_new_value(point);
+ } else {
+ g_warning("LPE Fillet can only be applied to shapes (not groups).");
+ }
+}
+
+void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem)
+{
+ if (SP_IS_SHAPE(lpeItem)) {
+ fillet_chamfer_values.set_helper_size(helper_size);
+ fillet_chamfer_values.set_unit(unit.get_abbreviation());
+ SPCurve *c = SP_IS_PATH(lpeItem) ? static_cast<SPPath const *>(lpeItem)
+ ->get_original_curve()
+ : SP_SHAPE(lpeItem)->getCurve();
+ std::vector<Point> filletChamferData = fillet_chamfer_values.data();
+ if (!filletChamferData.empty() && getKnotsNumber(c) != (int)
+ filletChamferData.size()) {
+ PathVector const original_pathv = c->get_pathvector();
+ Piecewise<D2<SBasis> > pwd2_in = paths_to_pw(original_pathv);
+ fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(pwd2_in);
+ }
+ } else {
+ g_warning("LPE Fillet can only be applied to shapes (not groups).");
+ }
+}
+
+int LPEFilletChamfer::getKnotsNumber(SPCurve const *c)
+{
+ int nKnots = c->nodes_in_path();
+ PathVector const pv = c->get_pathvector();
+ for (std::vector<Geom::Path>::const_iterator path_it = pv.begin();
+ path_it != pv.end(); ++path_it) {
+ if (!(*path_it).closed()) {
+ nKnots--;
+ }
+ }
+ return nKnots;
+}
+
+void
+LPEFilletChamfer::adjustForNewPath(std::vector<Geom::Path> const &path_in)
+{
+ if (!path_in.empty()) {
+ fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(path_in[0]
+ .toPwSb());
+ }
+}
+
+std::vector<Geom::Path>
+LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in)
+{
+ std::vector<Geom::Path> pathvector_out;
+ Piecewise<D2<SBasis> > pwd2_in = paths_to_pw(path_in);
+ pwd2_in = remove_short_cuts(pwd2_in, .01);
+ Piecewise<D2<SBasis> > der = derivative(pwd2_in);
+ Piecewise<D2<SBasis> > n = rot90(unitVector(der));
+ fillet_chamfer_values.set_pwd2(pwd2_in, n);
+ std::vector<Point> filletChamferData = fillet_chamfer_values.data();
+ int counter = 0;
+ //from http://launchpadlibrarian.net/12692602/rcp.svg
+ const double K = (4. / 3) * (sqrt(2) - 1);
+ for (PathVector::const_iterator path_it = path_in.begin();
+ path_it != path_in.end(); ++path_it) {
+ if (path_it->empty())
+ continue;
+ Geom::Path path_out;
+ Geom::Path::const_iterator curve_it1 = path_it->begin();
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin());
+ Geom::Path::const_iterator curve_endit = path_it->end_default();
+ if (path_it->closed()) {
+ const Geom::Curve &closingline = path_it->back_closed();
+ // the closing line segment is always of type
+ // Geom::LineSegment.
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ // closingline.isDegenerate() did not work, because it only checks for
+ // *exact* zero length, which goes wrong for relative coordinates and
+ // rounding errors...
+ // the closing line segment has zero-length. So stop before that one!
+ curve_endit = path_it->end_open();
+ }
+ }
+ int counterCurves = 0;
+ while (curve_it1 != curve_endit) {
+ Coord it1_length = (*curve_it1).length(tolerance);
+ double time_it1, time_it2, time_it1_B, intpart;
+ time_it1 = modf(
+ fillet_chamfer_values.to_time(counter, filletChamferData[counter][X]),
+ &intpart);
+ if (filletChamferData[counter][Y] == 0) {
+ time_it1 = 0;
+ }
+ time_it2 = modf(fillet_chamfer_values.to_time(
+ counter + 1, filletChamferData[counter + 1][X]),
+ &intpart);
+ if (curve_it2 == curve_endit) {
+ time_it2 = modf(fillet_chamfer_values.to_time(
+ counter - counterCurves,
+ filletChamferData[counter - counterCurves][X]),
+ &intpart);
+ }
+ double resultLenght =
+ it1_length + fillet_chamfer_values.to_len(
+ counter + 1, filletChamferData[counter + 1][X]);
+ time_it1_B = 1;
+ if (path_it->closed() && curve_it2 == curve_endit) {
+ resultLenght =
+ it1_length + fillet_chamfer_values.to_len(
+ counter - counterCurves,
+ filletChamferData[counter - counterCurves][X]);
+ }
+ if (resultLenght > 0 && time_it2 != 0) {
+ time_it1_B = modf(fillet_chamfer_values.to_time(counter, -resultLenght),
+ &intpart);
+ } else {
+ if (time_it2 == 0) {
+ time_it1_B = 1;
+ } else {
+ time_it1_B = gapHelper;
+ }
+ }
+ if (filletChamferData[counter + 1][Y] == 0) {
+ time_it1_B = 1;
+ time_it2 = 0;
+ }
+ if (time_it1_B < time_it1) {
+ time_it1_B = time_it1 + gapHelper;
+ }
+ Curve *knotCurve1 = curve_it1->portion(time_it1, time_it1_B);
+ if (counterCurves > 0) {
+ knotCurve1->setInitial(path_out.finalPoint());
+ } else {
+ path_out.start((*curve_it1).pointAt(time_it1));
+ }
+ Curve *knotCurve2 = (*path_it).front().portion(time_it2, 1);
+ if (curve_it2 != curve_endit) {
+ knotCurve2 = (*curve_it2).portion(time_it2, 1);
+ }
+ Point startArcPoint = knotCurve1->finalPoint();
+ Point endArcPoint = (*path_it).front().pointAt(time_it2);
+ if (curve_it2 != curve_endit) {
+ endArcPoint = (*curve_it2).pointAt(time_it2);
+ }
+ double k1 = distance(startArcPoint, curve_it1->finalPoint()) * K;
+ double k2 = distance(endArcPoint, curve_it1->finalPoint()) * K;
+ Geom::CubicBezier const *cubic1 =
+ dynamic_cast<Geom::CubicBezier const *>(&*knotCurve1);
+ Ray ray1(startArcPoint, curve_it1->finalPoint());
+ if (cubic1) {
+ ray1.setPoints((*cubic1)[2], startArcPoint);
+ }
+ Point handle1 = Point::polar(ray1.angle(),k1) + startArcPoint;
+ Geom::CubicBezier const *cubic2 =
+ dynamic_cast<Geom::CubicBezier const *>(&*knotCurve2);
+ Ray ray2(curve_it1->finalPoint(), endArcPoint);
+ if (cubic2) {
+ ray2.setPoints(endArcPoint, (*cubic2)[1]);
+ }
+ Point handle2 = endArcPoint - Point::polar(ray2.angle(),k2);
+ bool ccwToggle = cross(curve_it1->finalPoint() - startArcPoint,
+ endArcPoint - startArcPoint) < 0;
+ double angle = angle_between(ray1, ray2, ccwToggle);
+ double handleAngle = ray1.angle() - angle;
+ if (ccwToggle) {
+ handleAngle = ray1.angle() + angle;
+ }
+ Point inverseHandle1 = Point::polar(handleAngle,k1) + startArcPoint;
+ handleAngle = ray2.angle() + angle;
+ if (ccwToggle) {
+ handleAngle = ray2.angle() - angle;
+ }
+ Point inverseHandle2 = endArcPoint - Point::polar(handleAngle,k2);
+ if (time_it1_B != 1) {
+ if (time_it1_B != gapHelper && time_it1_B != time_it1 + gapHelper) {
+ path_out.append(*knotCurve1);
+ }
+ int type = abs(filletChamferData[counter + 1][Y]);
+ if (type == 3 || type == 4) {
+ if (type == 4) {
+ Geom::Point central = middle_point(startArcPoint, endArcPoint);
+ LineSegment chamferCenter(central, curve_it1->finalPoint());
+ path_out.appendNew<Geom::LineSegment>(chamferCenter.pointAt(0.5));
+ }
+ path_out.appendNew<Geom::LineSegment>(endArcPoint);
+ } else if (type == 2) {
+ path_out.appendNew<Geom::CubicBezier>(inverseHandle1, inverseHandle2,
+ endArcPoint);
+ } else {
+ path_out.appendNew<Geom::CubicBezier>(handle1, handle2, endArcPoint);
+ }
+ } else {
+ path_out.append(*knotCurve1);
+ }
+ if (path_it->closed() && curve_it2 == curve_endit) {
+ path_out.close();
+ }
+ ++curve_it1;
+ if (curve_it2 != curve_endit) {
+ ++curve_it2;
+ }
+ counter++;
+ counterCurves++;
+ }
+ pathvector_out.push_back(path_out);
+ }
+ return pathvector_out;
+}
+
+}; //namespace LivePathEffect
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offset:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpe-fillet-chamfer.h b/src/live_effects/lpe-fillet-chamfer.h
new file mode 100644
index 000000000..f5eee0205
--- /dev/null
+++ b/src/live_effects/lpe-fillet-chamfer.h
@@ -0,0 +1,68 @@
+#ifndef INKSCAPE_LPE_FILLET_CHAMFER_H
+#define INKSCAPE_LPE_FILLET_CHAMFER_H
+
+/*
+ * Inkscape::LPEFilletChamfer
+ * Copyright (C) Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
+ * Special thanks to Johan Engelen for the base of the effect -powerstroke-
+ * Also to ScislaC for point me to the idea
+ * Also su_v for his construvtive feedback and time
+ * and finaly to Liam P. White for his big help on coding, that save me a lot of
+ * hours
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "live_effects/parameter/enum.h"
+#include "live_effects/parameter/unit.h"
+#include "live_effects/effect.h"
+#include "live_effects/parameter/bool.h"
+#include "live_effects/parameter/filletchamferpointarray.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+class LPEFilletChamfer : public Effect {
+
+public:
+ LPEFilletChamfer(LivePathEffectObject *lpeobject);
+ virtual ~LPEFilletChamfer();
+
+ std::vector<Geom::Path> doEffect_path(std::vector<Geom::Path> const &path_in);
+
+ virtual void doOnApply(SPLPEItem const *lpeItem);
+ virtual void doBeforeEffect(SPLPEItem const *lpeItem);
+ virtual int getKnotsNumber(SPCurve const *c);
+ virtual void adjustForNewPath(std::vector<Geom::Path> const &path_in);
+ virtual void toggleHide();
+ virtual void toggleFlexFixed();
+ virtual void chamfer();
+ virtual void fillet();
+ virtual void doubleChamfer();
+ virtual void inverse();
+ virtual void updateFillet();
+ virtual void doUpdateFillet(std::vector<Geom::Path> original_pathv,
+ double power);
+ virtual void doChangeType(std::vector<Geom::Path> original_pathv, int type);
+ virtual bool nodeIsSelected(Geom::Point nodePoint,
+ std::vector<Geom::Point> points);
+ virtual void refreshKnots();
+ virtual Gtk::Widget *newWidget();
+ FilletChamferPointArrayParam fillet_chamfer_values;
+
+private:
+
+ BoolParam hide_knots;
+ BoolParam ignore_radius_0;
+ BoolParam only_selected;
+ BoolParam flexible;
+ UnitParam unit;
+ ScalarParam radius;
+ ScalarParam helper_size;
+
+ LPEFilletChamfer(const LPEFilletChamfer &);
+ LPEFilletChamfer &operator=(const LPEFilletChamfer &);
+
+};
+
+}; //namespace LivePathEffect
+}; //namespace Inkscape
+#endif
diff --git a/src/live_effects/parameter/Makefile_insert b/src/live_effects/parameter/Makefile_insert
index 1026cf11c..1137ef34b 100644
--- a/src/live_effects/parameter/Makefile_insert
+++ b/src/live_effects/parameter/Makefile_insert
@@ -22,6 +22,8 @@ ink_common_sources += \
live_effects/parameter/originalpath.h \
live_effects/parameter/powerstrokepointarray.cpp \
live_effects/parameter/powerstrokepointarray.h \
+ live_effects/parameter/filletchamferpointarray.cpp \
+ live_effects/parameter/filletchamferpointarray.h \
live_effects/parameter/text.cpp \
live_effects/parameter/text.h \
live_effects/parameter/togglebutton.cpp \
diff --git a/src/live_effects/parameter/filletchamferpointarray.cpp b/src/live_effects/parameter/filletchamferpointarray.cpp
new file mode 100644
index 000000000..94a26dd8d
--- /dev/null
+++ b/src/live_effects/parameter/filletchamferpointarray.cpp
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
+ * Special thanks to Johan Engelen for the base of the effect -powerstroke-
+ * Also to ScislaC for point me to the idea
+ * Also su_v for his construvtive feedback and time
+ * and finaly to Liam P. White for his big help on coding, that save me a lot of
+ * hours
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/dialog/lpe-fillet-chamfer-properties.h"
+#include "live_effects/parameter/filletchamferpointarray.h"
+#include "live_effects/effect.h"
+#include "svg/svg.h"
+#include "svg/stringstream.h"
+#include "knotholder.h"
+#include "sp-lpe-item.h"
+#include <2geom/piecewise.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/sbasis-geometric.h>
+#include "selection.h"
+
+// needed for on-canvas editting:
+#include "desktop.h"
+#include "live_effects/lpeobject.h"
+#include "helper/geom-nodetype.h"
+#include "helper/geom-curves.h"
+#include <cmath>
+#include "ui/tools/node-tool.h"
+#include <glibmm/i18n.h>
+
+using namespace Geom;
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+FilletChamferPointArrayParam::FilletChamferPointArrayParam(
+ const Glib::ustring &label, const Glib::ustring &tip,
+ const Glib::ustring &key, Inkscape::UI::Widget::Registry *wr,
+ Effect *effect)
+ : ArrayParam<Point>(label, tip, key, wr, effect, 0)
+{
+ knot_shape = SP_KNOT_SHAPE_DIAMOND;
+ knot_mode = SP_KNOT_MODE_XOR;
+ knot_color = 0x00ff0000;
+}
+
+FilletChamferPointArrayParam::~FilletChamferPointArrayParam() {}
+
+Gtk::Widget *FilletChamferPointArrayParam::param_newWidget()
+{
+ return NULL;
+ /*
+ Inkscape::UI::Widget::RegisteredTransformedPoint * pointwdg =
+ Gtk::manage(
+ new Inkscape::UI::Widget::RegisteredTransformedPoint(
+ param_label,
+ param_tooltip,
+ param_key,
+ *param_wr,
+ param_effect->getRepr(),
+ param_effect->getSPDoc()
+ ) );
+ // TODO: fix to get correct desktop (don't use SP_ACTIVE_DESKTOP)
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ Affine transf = desktop->doc2dt();
+ pointwdg->setTransform(transf);
+ pointwdg->setValue( *this );
+ pointwdg->clearProgrammatically();
+ pointwdg->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT,
+ _("Change point parameter"));
+
+ Gtk::HBox * hbox = Gtk::manage( new Gtk::HBox() );
+ static_cast<Gtk::HBox*>(hbox)->pack_start(*pointwdg, true, true);
+ static_cast<Gtk::HBox*>(hbox)->show_all_children();
+
+ return dynamic_cast<Gtk::Widget *> (hbox);
+ */
+}
+
+void
+FilletChamferPointArrayParam::param_transform_multiply(Affine const &postmul,
+ bool /*set*/)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (prefs->getBool("/options/transform/rectcorners", true) &&
+ _vector[1][X] <= 0) {
+ std::vector<Geom::Point> result;
+ for (std::vector<Point>::const_iterator point_it = _vector.begin();
+ point_it != _vector.end(); ++point_it) {
+ Coord A =
+ (*point_it)[X] * ((postmul.expansionX() + postmul.expansionY()) / 2);
+ result.push_back(Point(A, (*point_it)[Y]));
+ }
+ param_set_and_write_new_value(result);
+ }
+
+ // param_set_and_write_new_value( (*this) * postmul );
+}
+
+/** call this method to recalculate the controlpoints such that they stay at the
+ * same location relative to the new path. Useful after adding/deleting nodes to
+ * the path.*/
+void FilletChamferPointArrayParam::recalculate_controlpoints_for_new_pwd2(
+ Piecewise<D2<SBasis> > const &pwd2_in)
+{
+ if (!last_pwd2.empty()) {
+ PathVector const pathv =
+ path_from_piecewise(remove_short_cuts(pwd2_in, 0.1), 0.001);
+ PathVector last_pathv =
+ path_from_piecewise(remove_short_cuts(last_pwd2, 0.1), 0.001);
+ std::vector<Point> result;
+ unsigned long counter = 0;
+ unsigned long counterPaths = 0;
+ unsigned long counterCurves = 0;
+ long offset = 0;
+ long offsetPaths = 0;
+ Geom::NodeType nodetype;
+ for (PathVector::const_iterator path_it = pathv.begin();
+ path_it != pathv.end(); ++path_it) {
+ if (path_it->empty()) {
+ counterPaths++;
+ counter++;
+ continue;
+ }
+ Geom::Path::const_iterator curve_it1 = path_it->begin();
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin());
+ Geom::Path::const_iterator curve_endit = path_it->end_default();
+ if (path_it->closed() && path_it->back_closed().isDegenerate()) {
+ const Curve &closingline = path_it->back_closed();
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ curve_endit = path_it->end_open();
+ }
+ }
+ counterCurves = 0;
+ while (curve_it1 != curve_endit) {
+ //if start a path get node type
+ if (counterCurves == 0) {
+ if (path_it->closed()) {
+ if (path_it->back_closed().isDegenerate()) {
+ nodetype = get_nodetype(path_it->back_open(), *curve_it1);
+ } else {
+ nodetype = get_nodetype(path_it->back_closed(), *curve_it1);
+ }
+ } else {
+ nodetype = NODE_NONE;
+ }
+ } else {
+ //check node type also whith straight lines because get_nodetype
+ //return non cusp node in a node inserted inside a straight line
+ //todo: if the path remove some nodes whith the result of a straight
+ //line but with handles, the node inserted into dont fire the knot
+ // because is not handle as cusp node by get_nodetype function
+ bool this_is_line = true;
+ bool next_is_line = is_straight_curve(*curve_it1);
+ this_is_line = is_straight_curve((*path_it)[counterCurves - 1]);
+ nodetype = get_nodetype((*path_it)[counterCurves - 1], *curve_it1);
+ if (this_is_line || next_is_line) {
+ nodetype = NODE_CUSP;
+ }
+ }
+ if (last_pathv.size() > pathv.size() ||
+ (last_pathv[counterPaths].size() > counter - offset &&
+ !are_near(curve_it1->initialPoint(),
+ last_pathv[counterPaths][counter - offset].initialPoint(),
+ 0.1))) {
+ if (last_pathv.size() > counterPaths && curve_it2 == curve_endit) {
+ if (last_pathv[counterPaths].size() < pathv[counterPaths].size()) {
+ offset = abs(last_pathv[counterPaths].size() -
+ pathv[counterPaths].size());
+ } else if (last_pathv[counterPaths].size() >
+ pathv[counterPaths].size()) {
+ offset = (abs(last_pathv[counterPaths].size() -
+ pathv[counterPaths].size())) * -1;
+ } else {
+ offset = 0;
+ }
+ offsetPaths += offset;
+ offset = offsetPaths;
+ } else if (counterCurves == 0 && last_pathv.size() <= pathv.size() &&
+ counter - offset <= last_pathv[counterPaths].size() &&
+ are_near(curve_it1->initialPoint(),
+ last_pathv[counterPaths].finalPoint(), 0.1) &&
+ !last_pathv[counterPaths].closed()) {
+ long e = counter - offset + 1;
+ std::vector<Point> tmp = _vector;
+ for (unsigned long i =
+ last_pathv[counterPaths].size() + counter - offset;
+ i > counterCurves - offset + 1; i--) {
+
+ if (tmp[i - 1][X] > 0) {
+ double fractpart, intpart;
+ fractpart = modf(tmp[i - 1][X], &intpart);
+ _vector[e] = Point(e + fractpart, tmp[i - 1][Y]);
+ } else {
+ _vector[e] = Point(tmp[i - 1][X], tmp[i - 1][Y]);
+ }
+ e++;
+ }
+ //delete temp vector
+ std::vector<Point>().swap(tmp);
+ if (last_pathv.size() > counterPaths) {
+ last_pathv[counterPaths] = last_pathv[counterPaths].reverse();
+ }
+ } else {
+ if (last_pathv.size() > counterPaths) {
+ if (last_pathv[counterPaths].size() <
+ pathv[counterPaths].size()) {
+ offset++;
+ } else if (last_pathv[counterPaths].size() >
+ pathv[counterPaths].size()) {
+ offset--;
+ continue;
+ }
+ } else {
+ offset++;
+ }
+ }
+ double xPos = 0;
+ if (_vector[1][X] > 0) {
+ xPos = nearest_point(curve_it1->initialPoint(), pwd2_in);
+ }
+ if (nodetype == NODE_CUSP) {
+ result.push_back(Point(xPos, 1));
+ } else {
+ result.push_back(Point(xPos, 0));
+ }
+ } else {
+ double xPos = _vector[counter - offset][X];
+ if (_vector.size() <= (unsigned)(counter - offset)) {
+ if (_vector[1][X] > 0) {
+ xPos = nearest_point(curve_it1->initialPoint(), pwd2_in);
+ } else {
+ xPos = 0;
+ }
+ }
+ if (nodetype == NODE_CUSP) {
+ double vectorY = _vector[counter - offset][Y];
+ if (_vector.size() <= (unsigned)(counter - offset) || vectorY == 0) {
+ vectorY = 1;
+ }
+ result.push_back(Point(xPos, vectorY));
+ } else {
+ if (_vector[1][X] < 0) {
+ xPos = 0;
+ }
+ result.push_back(Point(floor(xPos), 0));
+ }
+ }
+ ++curve_it1;
+ if (curve_it2 != curve_endit) {
+ ++curve_it2;
+ }
+ counter++;
+ counterCurves++;
+ }
+ counterPaths++;
+ }
+ _vector = result;
+ write_to_SVG();
+ }
+}
+
+void FilletChamferPointArrayParam::recalculate_knots(
+ Piecewise<D2<SBasis> > const &pwd2_in)
+{
+ bool change = false;
+ PathVector pathv = path_from_piecewise(pwd2_in, 0.001);
+ if (!pathv.empty()) {
+ std::vector<Point> result;
+ int counter = 0;
+ int counterCurves = 0;
+ Geom::NodeType nodetype;
+ for (PathVector::const_iterator path_it = pathv.begin();
+ path_it != pathv.end(); ++path_it) {
+ if (path_it->empty()) {
+ counter++;
+ continue;
+ }
+ Geom::Path::const_iterator curve_it1 = path_it->begin();
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin());
+ Geom::Path::const_iterator curve_endit = path_it->end_default();
+ if (path_it->closed() && path_it->back_closed().isDegenerate()) {
+ const Curve &closingline = path_it->back_closed();
+ if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
+ curve_endit = path_it->end_open();
+ }
+ }
+ counterCurves = 0;
+ while (curve_it1 != curve_endit) {
+ //if start a path get node type
+ if (counterCurves == 0) {
+ if (path_it->closed()) {
+ if (path_it->back_closed().isDegenerate()) {
+ nodetype = get_nodetype(path_it->back_open(), *curve_it1);
+ } else {
+ nodetype = get_nodetype(path_it->back_closed(), *curve_it1);
+ }
+ } else {
+ nodetype = NODE_NONE;
+ }
+ } else {
+ bool this_is_line = true;
+ bool next_is_line = is_straight_curve(*curve_it1);
+ this_is_line = is_straight_curve((*path_it)[counterCurves - 1]);
+ nodetype = get_nodetype((*path_it)[counterCurves - 1], *curve_it1);
+ if (this_is_line || next_is_line) {
+ nodetype = NODE_CUSP;
+ }
+ }
+ if (nodetype == NODE_CUSP) {
+ double vectorY = _vector[counter][Y];
+ if (vectorY == 0) {
+ vectorY = 1;
+ change = true;
+ }
+ result.push_back(Point(_vector[counter][X], vectorY));
+ } else {
+ double xPos = floor(_vector[counter][X]);
+ if (_vector[1][X] < 0) {
+ xPos = 0;
+ }
+ double vectorY = _vector[counter][Y];
+ if (vectorY != 0) {
+ change = true;
+ }
+ result.push_back(Point(xPos, 0));
+ }
+ ++curve_it1;
+ ++curve_it2;
+ if (curve_it2 != curve_endit) {
+ counter++;
+ }
+ counterCurves++;
+ }
+ }
+ if (change) {
+ _vector = result;
+ write_to_SVG();
+ }
+ }
+}
+
+void FilletChamferPointArrayParam::set_pwd2(
+ Piecewise<D2<SBasis> > const &pwd2_in,
+ Piecewise<D2<SBasis> > const &pwd2_normal_in)
+{
+ last_pwd2 = pwd2_in;
+ last_pwd2_normal = pwd2_normal_in;
+}
+
+void FilletChamferPointArrayParam::set_helper_size(int hs)
+{
+ helper_size = hs;
+}
+
+void FilletChamferPointArrayParam::set_unit(const gchar *abbr)
+{
+ unit = abbr;
+}
+
+void FilletChamferPointArrayParam::updateCanvasIndicators()
+{
+ std::vector<Point> ts = data();
+ hp.clear();
+ unsigned int i = 0;
+ Piecewise<D2<SBasis> > const &n = get_pwd2_normal();
+ for (std::vector<Point>::const_iterator point_it = ts.begin();
+ point_it != ts.end(); ++point_it) {
+ double Xvalue = to_time(i, (*point_it)[X]);
+ double XPlusValue = to_time(i, -helper_size) - i;
+ if (Xvalue == i) {
+ i++;
+ continue;
+ }
+ Point canvas_point = last_pwd2.valueAt(Xvalue) + 0 * n.valueAt(Xvalue);
+ Point start_point = last_pwd2.valueAt(Xvalue + XPlusValue) +
+ helper_size * n.valueAt(Xvalue + XPlusValue);
+ Point end_point = last_pwd2.valueAt(Xvalue + XPlusValue) -
+ helper_size * n.valueAt(Xvalue + XPlusValue);
+ Geom::Path arrow;
+ arrow.start(start_point);
+ arrow.appendNew<Geom::LineSegment>(canvas_point);
+ arrow.appendNew<Geom::LineSegment>(end_point);
+ hp.push_back(arrow);
+ i++;
+ }
+}
+
+void FilletChamferPointArrayParam::addCanvasIndicators(
+ SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
+{
+ hp_vec.push_back(hp);
+}
+
+double FilletChamferPointArrayParam::len_to_time(int index, double len)
+{
+ double t = 0;
+ if (last_pwd2.size() > (unsigned) index) {
+ if (len != 0) {
+ if (last_pwd2[index][0].degreesOfFreedom() != 2) {
+ Piecewise<D2<SBasis> > u;
+ u.push_cut(0);
+ u.push(last_pwd2[index], 1);
+ std::vector<double> t_roots = roots(arcLengthSb(u) - std::abs(len));
+ if (t_roots.size() > 0) {
+ t = t_roots[0];
+ }
+ } else {
+ double lenghtPart = 0;
+ if (last_pwd2.size() > (unsigned) index) {
+ lenghtPart = length(last_pwd2[index], EPSILON);
+ }
+ if (std::abs(len) < lenghtPart && lenghtPart != 0) {
+ t = std::abs(len) / lenghtPart;
+ }
+ }
+ }
+ t = double(index) + t;
+ } else {
+ t = double(last_pwd2.size() - 1);
+ }
+
+ return t;
+}
+
+double FilletChamferPointArrayParam::time_to_len(int index, double time)
+{
+ double intpart;
+ double len = 0;
+ time = modf(time, &intpart);
+ double lenghtPart = 0;
+ if (last_pwd2.size() <= (unsigned) index || time == 0) {
+ return len;
+ }
+ if (last_pwd2[index][0].degreesOfFreedom() != 2) {
+ Piecewise<D2<SBasis> > u;
+ u.push_cut(0);
+ u.push(last_pwd2[index], 1);
+ u = portion(u, 0, time);
+ return length(u, 0.001) * -1;
+ }
+ lenghtPart = length(last_pwd2[index], EPSILON);
+ return (time * lenghtPart) * -1;
+}
+
+double FilletChamferPointArrayParam::to_time(int index, double A)
+{
+ if (A > 0) {
+ return A;
+ } else {
+ return len_to_time(index, A);
+ }
+}
+
+double FilletChamferPointArrayParam::to_len(int index, double A)
+{
+ if (A > 0) {
+ return time_to_len(index, A);
+ } else {
+ return A;
+ }
+}
+
+void FilletChamferPointArrayParam::set_oncanvas_looks(SPKnotShapeType shape,
+ SPKnotModeType mode,
+ guint32 color)
+{
+ knot_shape = shape;
+ knot_mode = mode;
+ knot_color = color;
+}
+/*
+class FilletChamferPointArrayParamKnotHolderEntity : public KnotHolderEntity {
+public:
+ FilletChamferPointArrayParamKnotHolderEntity(FilletChamferPointArrayParam
+*p, unsigned int index);
+ virtual ~FilletChamferPointArrayParamKnotHolderEntity() {}
+
+ virtual void knot_set(Point const &p, Point const &origin, guint state);
+ virtual Point knot_get() const;
+ virtual void knot_click(guint state);
+ virtual void knot_doubleclicked(guint state);
+
+ /Checks whether the index falls within the size of the parameter's vector/
+ bool valid_index(unsigned int index) const {
+ return (_pparam->_vector.size() > index);
+ };
+
+private:
+ FilletChamferPointArrayParam *_pparam;
+ unsigned int _index;
+};
+/*/
+
+FilletChamferPointArrayParamKnotHolderEntity::
+FilletChamferPointArrayParamKnotHolderEntity(
+ FilletChamferPointArrayParam *p, unsigned int index)
+ : _pparam(p), _index(index) {}
+
+void FilletChamferPointArrayParamKnotHolderEntity::knot_set(Point const &p,
+ Point const &origin,
+ guint state)
+{
+ using namespace Geom;
+
+ if (!valid_index(_index)) {
+ return;
+ }
+ /// @todo how about item transforms???
+ Piecewise<D2<SBasis> > const &pwd2 = _pparam->get_pwd2();
+ //todo: add snapping
+ //Geom::Point const s = snap_knot_position(p, state);
+ double t = nearest_point(p, pwd2[_index]);
+ if (t == 1) {
+ t = 0.9999;
+ }
+ t += _index;
+
+ if (_pparam->_vector.at(_index)[X] <= 0) {
+ _pparam->_vector.at(_index) =
+ Point(_pparam->time_to_len(_index, t), _pparam->_vector.at(_index)[Y]);
+ } else {
+ _pparam->_vector.at(_index) = Point(t, _pparam->_vector.at(_index)[Y]);
+ }
+ sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false);
+}
+
+Point FilletChamferPointArrayParamKnotHolderEntity::knot_get() const
+{
+ using namespace Geom;
+
+ if (!valid_index(_index)) {
+ return Point(infinity(), infinity());
+ }
+
+ Piecewise<D2<SBasis> > const &pwd2 = _pparam->get_pwd2();
+
+ double time_it = _pparam->to_time(_index, _pparam->_vector.at(_index)[X]);
+ Point canvas_point = pwd2.valueAt(time_it);
+
+ _pparam->updateCanvasIndicators();
+ return canvas_point;
+
+}
+
+void FilletChamferPointArrayParamKnotHolderEntity::knot_click(guint state)
+{
+ if (state & GDK_CONTROL_MASK) {
+ using namespace Geom;
+ double type = _pparam->_vector.at(_index)[Y] + 1;
+ if (type > 4) {
+ type = 1;
+ }
+ _pparam->_vector.at(_index) = Point(_pparam->_vector.at(_index)[X], type);
+ _pparam->param_set_and_write_new_value(_pparam->_vector);
+ sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false);
+ const gchar *tip;
+ if (type == 3) {
+ tip = _("<b>Chamfer</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ } else if (type == 2) {
+ tip = _("<b>Inverse Fillet</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ } else if (type == 1) {
+ tip = _("<b>Fillet</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ } else {
+ tip = _("<b>Double Chamfer</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ }
+ this->knot->tip = g_strdup(tip);
+ this->knot->show();
+ //}
+ } else if ((state & GDK_MOD1_MASK) || (state & GDK_SHIFT_MASK)) {
+ Geom::Point offset = Geom::Point(_pparam->_vector.at(_index).x(),
+ _pparam->_vector.at(_index).y());
+ Inkscape::UI::Dialogs::FilletChamferPropertiesDialog::showDialog(
+ this->desktop, offset, this, _pparam->unit);
+ }
+
+}
+
+void
+FilletChamferPointArrayParamKnotHolderEntity::knot_doubleclicked(guint state)
+{
+ //todo: fill the double click dialog whith this parameters in the added file
+ //src/ui/dialog/lpe-fillet-chamfer-properties.cpp(.h)
+ //My idea for when have enought time is:
+ //label whith radius in percent
+ //label whith radius in size -maybe handle units-
+ //entry whith actual radius -in flexible % mode or fixed -?px-
+ //2 radio options to switch entry from fixed or flexible, also update the
+ //entry
+ //Checkbox or two radios to swith fillet or chamfer
+
+}
+
+void FilletChamferPointArrayParamKnotHolderEntity::knot_set_offset(
+ Geom::Point offset)
+{
+ _pparam->_vector.at(_index) = Geom::Point(offset.x(), offset.y());
+ this->parent_holder->knot_ungrabbed_handler(this->knot, 0);
+}
+
+void FilletChamferPointArrayParam::addKnotHolderEntities(KnotHolder *knotholder,
+ SPDesktop *desktop,
+ SPItem *item)
+{
+ recalculate_knots(get_pwd2());
+ for (unsigned int i = 0; i < _vector.size(); ++i) {
+ if (_vector[i][Y] <= 0) {
+ continue;
+ }
+ const gchar *tip;
+ if (_vector[i][Y] == 3) {
+ tip = _("<b>Chamfer</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ } else if (_vector[i][Y] == 2) {
+ tip = _("<b>Inverse Fillet</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ } else if (_vector[i][Y] == 1) {
+ tip = _("<b>Fillet</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ } else {
+ tip = _("<b>Double Chamfer</b>: <b>Ctrl+click</b> toogle type, "
+ "<b>Shift+click</b> open dialog");
+ }
+ FilletChamferPointArrayParamKnotHolderEntity *e =
+ new FilletChamferPointArrayParamKnotHolderEntity(this, i);
+ e->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _(tip),
+ knot_shape, knot_mode, knot_color);
+ knotholder->add(e);
+ }
+ updateCanvasIndicators();
+}
+
+} /* namespace LivePathEffect */
+
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/parameter/filletchamferpointarray.h b/src/live_effects/parameter/filletchamferpointarray.h
new file mode 100644
index 000000000..93d6e9f4c
--- /dev/null
+++ b/src/live_effects/parameter/filletchamferpointarray.h
@@ -0,0 +1,117 @@
+#ifndef INKSCAPE_LIVEPATHEFFECT_FILLET_CHAMFER_POINT_ARRAY_H
+#define INKSCAPE_LIVEPATHEFFECT_FILLET_CHAMFER_POINT_ARRAY_H
+
+/*
+ * Inkscape::LivePathEffectParameters
+ * Copyright (C) Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
+ * Special thanks to Johan Engelen for the base of the effect -powerstroke-
+ * Also to ScislaC for point me to the idea
+ * Also su_v for his construvtive feedback and time
+ * and finaly to Liam P. White for his big help on coding, that save me a lot of
+ * hours
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib.h>
+#include <2geom/point.h>
+
+#include "live_effects/parameter/array.h"
+
+#include "knot-holder-entity.h"
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+class FilletChamferPointArrayParamKnotHolderEntity;
+
+class FilletChamferPointArrayParam : public ArrayParam<Geom::Point> {
+public:
+ FilletChamferPointArrayParam(const Glib::ustring &label,
+ const Glib::ustring &tip,
+ const Glib::ustring &key,
+ Inkscape::UI::Widget::Registry *wr,
+ Effect *effect);
+ virtual ~FilletChamferPointArrayParam();
+
+ virtual Gtk::Widget *param_newWidget();
+
+ virtual void param_transform_multiply(Geom::Affine const &postmul,
+ bool /*set*/);
+
+ void set_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode,
+ guint32 color);
+ virtual double to_time(int index, double A);
+ virtual double to_len(int index, double A);
+ virtual double len_to_time(int index, double len);
+ virtual double time_to_len(int index, double time);
+ virtual void set_helper_size(int hs);
+ virtual void set_unit(const gchar *abbr);
+ virtual void addCanvasIndicators(SPLPEItem const *lpeitem,
+ std::vector<Geom::PathVector> &hp_vec);
+ virtual bool providesKnotHolderEntities() const {
+ return true;
+ }
+ virtual void updateCanvasIndicators();
+ virtual void addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop,
+ SPItem *item);
+
+ void set_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in,
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_normal_in);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > const &get_pwd2() const {
+ return last_pwd2;
+ }
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > const &get_pwd2_normal() const {
+ return last_pwd2_normal;
+ }
+
+ void recalculate_controlpoints_for_new_pwd2(
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in);
+ void recalculate_knots(
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in);
+ friend class FilletChamferPointArrayParamKnotHolderEntity;
+
+private:
+ FilletChamferPointArrayParam(const FilletChamferPointArrayParam &);
+ FilletChamferPointArrayParam &operator=(const FilletChamferPointArrayParam &);
+
+ SPKnotShapeType knot_shape;
+ SPKnotModeType knot_mode;
+ guint32 knot_color;
+ int helper_size;
+ const gchar *unit;
+ Geom::PathVector hp;
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > last_pwd2;
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > last_pwd2_normal;
+};
+
+class FilletChamferPointArrayParamKnotHolderEntity : public KnotHolderEntity {
+public:
+ FilletChamferPointArrayParamKnotHolderEntity(FilletChamferPointArrayParam *p,
+ unsigned int index);
+ virtual ~FilletChamferPointArrayParamKnotHolderEntity() {}
+
+ virtual void knot_set(Geom::Point const &p, Geom::Point const &origin,
+ guint state);
+ virtual Geom::Point knot_get() const;
+ virtual void knot_click(guint state);
+ virtual void knot_doubleclicked(guint state);
+ virtual void knot_set_offset(Geom::Point offset);
+
+ /*Checks whether the index falls within the size of the parameter's vector*/
+ bool valid_index(unsigned int index) const {
+ return (_pparam->_vector.size() > index);
+ }
+ ;
+
+private:
+ FilletChamferPointArrayParam *_pparam;
+ unsigned int _index;
+};
+
+} //namespace LivePathEffect
+
+} //namespace Inkscape
+
+#endif
diff --git a/src/ui/dialog/Makefile_insert b/src/ui/dialog/Makefile_insert
index daa8aea22..1bd939707 100644
--- a/src/ui/dialog/Makefile_insert
+++ b/src/ui/dialog/Makefile_insert
@@ -114,4 +114,6 @@ ink_common_sources += \
ui/dialog/undo-history.h \
ui/dialog/xml-tree.cpp \
ui/dialog/xml-tree.h \
+ ui/dialog/lpe-fillet-chamfer-properties.cpp \
+ ui/dialog/lpe-fillet-chamfer-properties.h \
$(inkboard_dialogs)
diff --git a/src/ui/dialog/lpe-fillet-chamfer-properties.cpp b/src/ui/dialog/lpe-fillet-chamfer-properties.cpp
new file mode 100644
index 000000000..a68c7ee08
--- /dev/null
+++ b/src/ui/dialog/lpe-fillet-chamfer-properties.cpp
@@ -0,0 +1,257 @@
+/**
+ * From the code of Liam P.White from his Power Stroke Knot dialog
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if GLIBMM_DISABLE_DEPRECATED &&HAVE_GLIBMM_THREADS_H
+#include <glibmm/threads.h>
+#endif
+
+#include "lpe-fillet-chamfer-properties.h"
+#include <boost/lexical_cast.hpp>
+#include <gtkmm/stock.h>
+#include <glibmm/main.h>
+#include <glibmm/i18n.h>
+#include "inkscape.h"
+#include "desktop.h"
+#include "document.h"
+#include "document-undo.h"
+#include "layer-manager.h"
+#include "message-stack.h"
+#include "desktop-handles.h"
+#include "sp-object.h"
+#include "sp-item.h"
+#include "verbs.h"
+#include "selection.h"
+#include "selection-chemistry.h"
+#include "ui/icon-names.h"
+#include "ui/widget/imagetoggler.h"
+#include <cmath>
+#include <gtkmm/radiobutton.h>
+#include "util/units.h"
+
+//#include "event-context.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialogs {
+
+FilletChamferPropertiesDialog::FilletChamferPropertiesDialog()
+ : _desktop(NULL), _knotpoint(NULL), _position_visible(false)
+{
+ Gtk::Box *mainVBox = get_vbox();
+ mainVBox->set_homogeneous(false);
+ _layout_table.set_spacings(4);
+ _layout_table.resize(2, 2);
+
+ // Layer name widgets
+ _fillet_chamfer_position_entry.set_activates_default(true);
+ _fillet_chamfer_position_label.set_label(_("Radius (pixels):"));
+ _fillet_chamfer_position_label.set_alignment(1.0, 0.5);
+
+ _layout_table.attach(_fillet_chamfer_position_label, 0, 1, 0, 1, Gtk::FILL,
+ Gtk::FILL);
+ _layout_table.attach(_fillet_chamfer_position_entry, 1, 2, 0, 1,
+ Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
+ _fillet_chamfer_type_fillet.set_label(_("Fillet"));
+ _fillet_chamfer_type_fillet.set_group(_fillet_chamfer_type_group);
+ _fillet_chamfer_type_inverse_fillet.set_label(_("Inverse fillet"));
+ _fillet_chamfer_type_inverse_fillet.set_group(_fillet_chamfer_type_group);
+ _fillet_chamfer_type_chamfer.set_label(_("Chamfer"));
+ _fillet_chamfer_type_chamfer.set_group(_fillet_chamfer_type_group);
+ _fillet_chamfer_type_double_chamfer.set_label(_("Double chamfer"));
+ _fillet_chamfer_type_double_chamfer.set_group(_fillet_chamfer_type_group);
+
+ mainVBox->pack_start(_layout_table, true, true, 4);
+ mainVBox->pack_start(_fillet_chamfer_type_fillet, true, true, 4);
+ mainVBox->pack_start(_fillet_chamfer_type_inverse_fillet, true, true, 4);
+ mainVBox->pack_start(_fillet_chamfer_type_chamfer, true, true, 4);
+ mainVBox->pack_start(_fillet_chamfer_type_double_chamfer, true, true, 4);
+
+ // Buttons
+ _close_button.set_use_stock(true);
+ _close_button.set_label(Gtk::Stock::CANCEL.id);
+ _close_button.set_can_default();
+
+ _apply_button.set_use_underline(true);
+ _apply_button.set_can_default();
+
+ _close_button.signal_clicked()
+ .connect(sigc::mem_fun(*this, &FilletChamferPropertiesDialog::_close));
+ _apply_button.signal_clicked()
+ .connect(sigc::mem_fun(*this, &FilletChamferPropertiesDialog::_apply));
+
+ signal_delete_event().connect(sigc::bind_return(
+ sigc::hide(sigc::mem_fun(*this, &FilletChamferPropertiesDialog::_close)),
+ true));
+
+ add_action_widget(_close_button, Gtk::RESPONSE_CLOSE);
+ add_action_widget(_apply_button, Gtk::RESPONSE_APPLY);
+
+ _apply_button.grab_default();
+
+ show_all_children();
+
+ set_focus(_fillet_chamfer_position_entry);
+}
+
+FilletChamferPropertiesDialog::~FilletChamferPropertiesDialog()
+{
+
+ _setDesktop(NULL);
+}
+
+void FilletChamferPropertiesDialog::showDialog(
+ SPDesktop *desktop, Geom::Point knotpoint,
+ const Inkscape::LivePathEffect::
+ FilletChamferPointArrayParamKnotHolderEntity *pt,
+ const gchar *unit)
+{
+ FilletChamferPropertiesDialog *dialog = new FilletChamferPropertiesDialog();
+
+ dialog->_setDesktop(desktop);
+ dialog->_setUnit(unit);
+ dialog->_setKnotPoint(knotpoint);
+ dialog->_setPt(pt);
+
+ dialog->set_title(_("Modify Fillet-Chamfer"));
+ dialog->_apply_button.set_label(_("_Modify"));
+
+ dialog->set_modal(true);
+ desktop->setWindowTransient(dialog->gobj());
+ dialog->property_destroy_with_parent() = true;
+
+ dialog->show();
+ dialog->present();
+}
+
+void FilletChamferPropertiesDialog::_apply()
+{
+ std::istringstream i_pos(_fillet_chamfer_position_entry.get_text());
+ double d_pos, d_width;
+ if (i_pos >> d_pos) {
+ if (_fillet_chamfer_type_fillet.get_active() == true) {
+ d_width = 1;
+ } else if (_fillet_chamfer_type_inverse_fillet.get_active() == true) {
+ d_width = 2;
+ } else if (_fillet_chamfer_type_chamfer.get_active() == true) {
+ d_width = 3;
+ } else {
+ d_width = 4;
+ }
+ if (_flexible) {
+ if (d_pos > 99.99999 || d_pos < 0) {
+ d_pos = 0;
+ }
+ d_pos = _index + (d_pos / 100);
+ } else {
+ d_pos = Inkscape::Util::Quantity::convert(d_pos, unit, "px");
+ d_pos = d_pos * -1;
+ }
+ _knotpoint->knot_set_offset(Geom::Point(d_pos, d_width));
+ }
+ _close();
+}
+
+void FilletChamferPropertiesDialog::_close()
+{
+ _setDesktop(NULL);
+ destroy_();
+ Glib::signal_idle().connect(
+ sigc::bind_return(
+ sigc::bind(sigc::ptr_fun(&::operator delete), this),
+ false
+ )
+ );
+}
+
+bool FilletChamferPropertiesDialog::_handleKeyEvent(GdkEventKey *event)
+{
+ return false;
+}
+
+void FilletChamferPropertiesDialog::_handleButtonEvent(GdkEventButton *event)
+{
+ if ((event->type == GDK_2BUTTON_PRESS) && (event->button == 1)) {
+ _apply();
+ }
+}
+
+void FilletChamferPropertiesDialog::_setKnotPoint(Geom::Point knotpoint)
+{
+ double position;
+ if (knotpoint.x() > 0) {
+ double intpart;
+ position = modf(knotpoint[Geom::X], &intpart) * 100;
+ _flexible = true;
+ _index = intpart;
+ _fillet_chamfer_position_label.set_label(_("Position (%):"));
+ } else {
+ _flexible = false;
+ std::string posConcat =
+ std::string(_("Position (")) + std::string(unit) + std::string(")");
+ _fillet_chamfer_position_label.set_label(_(posConcat.c_str()));
+ position = knotpoint[Geom::X] * -1;
+ position = Inkscape::Util::Quantity::convert(position, "px", unit);
+ }
+ std::ostringstream s;
+ s.imbue(std::locale::classic());
+ s << position;
+ _fillet_chamfer_position_entry.set_text(s.str());
+ if (knotpoint.y() == 1) {
+ _fillet_chamfer_type_fillet.set_active(true);
+ } else if (knotpoint.y() == 2) {
+ _fillet_chamfer_type_inverse_fillet.set_active(true);
+ } else if (knotpoint.y() == 3) {
+ _fillet_chamfer_type_chamfer.set_active(true);
+ } else if (knotpoint.y() == 1) {
+ _fillet_chamfer_type_double_chamfer.set_active(true);
+ }
+}
+
+void FilletChamferPropertiesDialog::_setPt(
+ const Inkscape::LivePathEffect::
+ FilletChamferPointArrayParamKnotHolderEntity *pt)
+{
+ _knotpoint = const_cast<
+ Inkscape::LivePathEffect::FilletChamferPointArrayParamKnotHolderEntity *>(
+ pt);
+}
+
+void FilletChamferPropertiesDialog::_setUnit(const gchar *abbr)
+{
+ unit = abbr;
+}
+
+void FilletChamferPropertiesDialog::_setDesktop(SPDesktop *desktop)
+{
+ if (desktop) {
+ Inkscape::GC::anchor(desktop);
+ }
+ if (_desktop) {
+ Inkscape::GC::release(_desktop);
+ }
+ _desktop = desktop;
+}
+
+} // namespace
+} // namespace
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim:
+// filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99
+// :
diff --git a/src/ui/dialog/lpe-fillet-chamfer-properties.h b/src/ui/dialog/lpe-fillet-chamfer-properties.h
new file mode 100644
index 000000000..a07b5ab66
--- /dev/null
+++ b/src/ui/dialog/lpe-fillet-chamfer-properties.h
@@ -0,0 +1,112 @@
+/**
+ *
+ * From the code of Liam P.White from his Power Stroke Knot dialog
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information
+ */
+
+#ifndef INKSCAPE_DIALOG_FILLET_CHAMFER_PROPERTIES_H
+#define INKSCAPE_DIALOG_FILLET_CHAMFER_PROPERTIES_H
+
+#include <2geom/point.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/label.h>
+#include <gtkmm/table.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/treeview.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/scrolledwindow.h>
+#include "live_effects/parameter/filletchamferpointarray.h"
+
+class SPDesktop;
+
+namespace Inkscape {
+namespace UI {
+namespace Dialogs {
+
+class FilletChamferPropertiesDialog : public Gtk::Dialog {
+public:
+ FilletChamferPropertiesDialog();
+ virtual ~FilletChamferPropertiesDialog();
+
+ Glib::ustring getName() const {
+ return "LayerPropertiesDialog";
+ }
+
+ static void showDialog(SPDesktop *desktop, Geom::Point knotpoint,
+ const Inkscape::LivePathEffect::
+ FilletChamferPointArrayParamKnotHolderEntity *pt,
+ const gchar *unit);
+
+protected:
+
+ SPDesktop *_desktop;
+ Inkscape::LivePathEffect::FilletChamferPointArrayParamKnotHolderEntity *
+ _knotpoint;
+
+ Gtk::Label _fillet_chamfer_position_label;
+ Gtk::Entry _fillet_chamfer_position_entry;
+ Gtk::RadioButton::Group _fillet_chamfer_type_group;
+ Gtk::RadioButton _fillet_chamfer_type_fillet;
+ Gtk::RadioButton _fillet_chamfer_type_inverse_fillet;
+ Gtk::RadioButton _fillet_chamfer_type_chamfer;
+ Gtk::RadioButton _fillet_chamfer_type_double_chamfer;
+
+ Gtk::Table _layout_table;
+ bool _position_visible;
+ double _index;
+
+ Gtk::Button _close_button;
+ Gtk::Button _apply_button;
+
+ sigc::connection _destroy_connection;
+
+ static FilletChamferPropertiesDialog &_instance() {
+ static FilletChamferPropertiesDialog instance;
+ return instance;
+ }
+
+ void _setDesktop(SPDesktop *desktop);
+ void _setPt(const Inkscape::LivePathEffect::
+ FilletChamferPointArrayParamKnotHolderEntity *pt);
+ void _setUnit(const gchar *abbr);
+ void _apply();
+ void _close();
+ bool _flexible;
+ const gchar *unit;
+ void _setKnotPoint(Geom::Point knotpoint);
+ void _prepareLabelRenderer(Gtk::TreeModel::const_iterator const &row);
+
+ bool _handleKeyEvent(GdkEventKey *event);
+ void _handleButtonEvent(GdkEventButton *event);
+
+ friend class Inkscape::LivePathEffect::
+ FilletChamferPointArrayParamKnotHolderEntity;
+
+private:
+ FilletChamferPropertiesDialog(
+ FilletChamferPropertiesDialog const &); // no copy
+ FilletChamferPropertiesDialog &operator=(
+ FilletChamferPropertiesDialog const &); // no assign
+};
+
+} // namespace
+} // namespace
+} // namespace
+
+#endif //INKSCAPE_DIALOG_LAYER_PROPERTIES_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim:
+// filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99
+// :
diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h
index 1328372c6..569a8e154 100644
--- a/src/ui/tool/multi-path-manipulator.h
+++ b/src/ui/tool/multi-path-manipulator.h
@@ -53,6 +53,7 @@ public:
void insertNodesAtExtrema(ExtremumType extremum);
void insertNodes();
+ void alertLPE();
void duplicateNodes();
void joinNodes();
void breakNodes();
@@ -104,7 +105,7 @@ private:
}
void _commit(CommitEvent cps);
- void _done(gchar const *reason, bool alert_LPE = false);
+ void _done(gchar const *reason, bool alert_LPE = true);
void _doneWithCleanup(gchar const *reason, bool alert_LPE = false);
guint32 _getOutlineColor(ShapeRole role);
diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp
index 3beeed049..01cff5ad7 100644
--- a/src/ui/tool/path-manipulator.cpp
+++ b/src/ui/tool/path-manipulator.cpp
@@ -11,6 +11,7 @@
*/
#include "live_effects/lpe-powerstroke.h"
+#include "live_effects/lpe-fillet-chamfer.h"
#include <string>
#include <sstream>
#include <deque>
@@ -1300,11 +1301,20 @@ void PathManipulator::_createGeometryFromControlPoints(bool alert_LPE)
_spcurve->set_pathvector(pathv);
if (alert_LPE) {
/// \todo note that _path can be an Inkscape::LivePathEffect::Effect* too, kind of confusing, rework member naming?
- if (SP_IS_LPE_ITEM(_path) && _path->hasPathEffect()) {
- PathEffectList effect_list = _path->getEffectList();
- LivePathEffect::LPEPowerStroke *lpe_pwr = dynamic_cast<LivePathEffect::LPEPowerStroke*>( effect_list.front()->lpeobject->get_lpe() );
- if (lpe_pwr) {
- lpe_pwr->adjustForNewPath(pathv);
+ if (SP_IS_LPE_ITEM(_path) && _path->hasPathEffect()){
+ Inkscape::LivePathEffect::Effect* thisEffect = SP_LPE_ITEM(_path)->getPathEffectOfType(Inkscape::LivePathEffect::POWERSTROKE);
+ if(thisEffect){
+ LivePathEffect::LPEPowerStroke *lpe_pwr = dynamic_cast<LivePathEffect::LPEPowerStroke*>(thisEffect->getLPEObj()->get_lpe());
+ if (lpe_pwr) {
+ lpe_pwr->adjustForNewPath(pathv);
+ }
+ }
+ thisEffect = SP_LPE_ITEM(_path)->getPathEffectOfType(Inkscape::LivePathEffect::FILLET_CHAMFER);
+ if(thisEffect){
+ LivePathEffect::LPEFilletChamfer *lpe_fll = dynamic_cast<LivePathEffect::LPEFilletChamfer*>(thisEffect->getLPEObj()->get_lpe());
+ if (lpe_fll) {
+ lpe_fll->adjustForNewPath(pathv);
+ }
}
}
}