diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/live_effects/lpe-fillet-chamfer.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.tar.xz inkscape-12fc8abae6d434cac7670a59ed3a67301cc2eb10.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/live_effects/lpe-fillet-chamfer.cpp')
-rw-r--r-- | src/live_effects/lpe-fillet-chamfer.cpp | 723 |
1 files changed, 723 insertions, 0 deletions
diff --git a/src/live_effects/lpe-fillet-chamfer.cpp b/src/live_effects/lpe-fillet-chamfer.cpp new file mode 100644 index 0000000..2aa63fd --- /dev/null +++ b/src/live_effects/lpe-fillet-chamfer.cpp @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author(s): + * Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es> + * + * Copyright (C) 2014 Author(s) + * + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "lpe-fillet-chamfer.h" + +#include <2geom/elliptical-arc.h> +#include <boost/optional.hpp> + +#include "display/curve.h" +#include "helper/geom-curves.h" +#include "helper/geom-nodesatellite.h" +#include "helper/geom.h" +#include "object/sp-shape.h" +#include "ui/knot/knot-holder.h" +#include "ui/tools/tool-base.h" + +// TODO due to internal breakage in glibmm headers, this must be last: +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace LivePathEffect { + +static const Util::EnumData<Filletmethod> FilletmethodData[] = { + { FM_AUTO, N_("Auto"), "auto" }, + { FM_ARC, N_("Force arc"), "arc" }, + { FM_BEZIER, N_("Force bezier"), "bezier" } +}; +static const Util::EnumDataConverter<Filletmethod> FMConverter(FilletmethodData, FM_END); + +LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject) + : Effect(lpeobject), + unit(_("Unit:"), _("Unit"), "unit", &wr, this, "px"), + nodesatellites_param("NodeSatellite_param", "NodeSatellite_param", + "nodesatellites_param", &wr, this), + method(_("Method:"), _("Method to calculate the fillet or chamfer"), + "method", FMConverter, &wr, this, FM_AUTO), + mode(_("Mode:"), _("Mode, e.g. fillet or chamfer"), + "mode", &wr, this, "F", true), + radius(_("Radius:"), _("Radius, in unit or %"), "radius", &wr, + this, 0.0), + chamfer_steps(_("Chamfer steps:"), _("Chamfer steps"), "chamfer_steps", + &wr, this, 1), + flexible(_("Radius in %"), _("Flexible radius size (%)"), + "flexible", &wr, this, false), + only_selected(_("Change only selected nodes"), + _("Change only selected nodes"), "only_selected", &wr, this, + false), + use_knot_distance(_("Use knots distance instead radius"), + _("Use knots distance instead radius"), + "use_knot_distance", &wr, this, true), + hide_knots(_("Hide knots"), _("Hide knots"), "hide_knots", &wr, this, + false), + apply_no_radius(_("Apply changes if radius = 0"), _("Apply changes if radius = 0"), "apply_no_radius", &wr, this, true), + apply_with_radius(_("Apply changes if radius > 0"), _("Apply changes if radius > 0"), "apply_with_radius", &wr, this, true), + _pathvector_nodesatellites(nullptr), + _degenerate_hide(false) +{ + // fix legacy < 1.2: + const gchar * satellites_param = getLPEObj()->getAttribute("satellites_param"); + if (satellites_param){ + getLPEObj()->setAttribute("nodesatellites_param", satellites_param); + }; + registerParameter(&nodesatellites_param); + registerParameter(&unit); + registerParameter(&method); + registerParameter(&mode); + registerParameter(&radius); + registerParameter(&chamfer_steps); + registerParameter(&flexible); + registerParameter(&use_knot_distance); + registerParameter(&apply_no_radius); + registerParameter(&apply_with_radius); + registerParameter(&only_selected); + registerParameter(&hide_knots); + + radius.param_set_range(0.0, std::numeric_limits<double>::max()); + radius.param_set_increments(1, 1); + radius.param_set_digits(4); + radius.param_set_undo(false); + chamfer_steps.param_set_range(1, std::numeric_limits<gint>::max()); + chamfer_steps.param_set_increments(1, 1); + chamfer_steps.param_make_integer(); + _provides_knotholder_entities = true; + helperpath = false; + previous_unit = Glib::ustring(""); +} + +void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem) +{ + SPLPEItem *splpeitem = const_cast<SPLPEItem *>(lpeItem); + SPShape *shape = dynamic_cast<SPShape *>(splpeitem); + if (shape) { + Geom::PathVector const pathv = pathv_to_linear_and_cubic_beziers(shape->curve()->get_pathvector()); + NodeSatellites nodesatellites; + double power = radius; + if (!flexible) { + SPDocument *document = getSPDoc(); + Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str(); + power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str()); + } + NodeSatelliteType nodesatellite_type = FILLET; + std::map<std::string, NodeSatelliteType> gchar_map_to_nodesatellite_type = boost::assign::map_list_of( + "F", FILLET)("IF", INVERSE_FILLET)("C", CHAMFER)("IC", INVERSE_CHAMFER)("KO", INVALID_SATELLITE); + auto mode_str = mode.param_getSVGValue(); + std::map<std::string, NodeSatelliteType>::iterator it = gchar_map_to_nodesatellite_type.find(mode_str.raw()); + if (it != gchar_map_to_nodesatellite_type.end()) { + nodesatellite_type = it->second; + } + Geom::PathVector pathvres; + for (const auto & path_it : pathv) { + if (path_it.empty() || count_path_nodes(path_it) < 2) { + continue; + } + std::vector<NodeSatellite> subpath_nodesatellites; + Geom::Path::const_iterator curve_it = 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(); + } + } + Geom::Path pathresult(curve_it->initialPoint()); + while (curve_it != curve_endit) { + if (pathresult.size()) { + pathresult.setFinal(curve_it->initialPoint()); + } + pathresult.append(*curve_it); + ++curve_it; + NodeSatellite nodesatellite(nodesatellite_type); + nodesatellite.setSteps(chamfer_steps); + nodesatellite.setAmount(power); + nodesatellite.setIsTime(flexible); + nodesatellite.setHasMirror(true); + nodesatellite.setHidden(hide_knots); + subpath_nodesatellites.push_back(nodesatellite); + } + + // we add the last nodesatellite on open path because _pathvector_nodesatellites is related to nodes, not + // curves so maybe in the future we can need this last nodesatellite in other effects don't remove for this + // effect because _pathvector_nodesatellites class has methods when the path is modified and we want one + // method for all uses + if (!path_it.closed()) { + NodeSatellite nodesatellite(nodesatellite_type); + nodesatellite.setSteps(chamfer_steps); + nodesatellite.setAmount(power); + nodesatellite.setIsTime(flexible); + nodesatellite.setHasMirror(true); + nodesatellite.setHidden(hide_knots); + subpath_nodesatellites.push_back(nodesatellite); + } + pathresult.close(path_it.closed()); + pathvres.push_back(pathresult); + pathresult.clear(); + nodesatellites.push_back(subpath_nodesatellites); + } + _pathvector_nodesatellites = new PathVectorNodeSatellites(); + _pathvector_nodesatellites->setPathVector(pathvres); + _pathvector_nodesatellites->setNodeSatellites(nodesatellites); + nodesatellites_param.setPathVectorNodeSatellites(_pathvector_nodesatellites); + } else { + g_warning("LPE Fillet/Chamfer can only be applied to shapes (not groups)."); + SPLPEItem *item = const_cast<SPLPEItem *>(lpeItem); + item->removeCurrentPathEffect(false); + } +} + +Gtk::Widget *LPEFilletChamfer::newWidget() +{ + // use manage here, because after deletion of Effect object, others might + // still be pointing to this widget. + Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + + 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 = param->param_newWidget(); + if (param->param_key == "radius") { + Inkscape::UI::Widget::Scalar *widg_registered = + Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg)); + widg_registered->signal_value_changed().connect( + sigc::mem_fun(*this, &LPEFilletChamfer::updateAmount)); + widg = widg_registered; + if (widg) { + Gtk::Box *scalar_parameter = dynamic_cast<Gtk::Box *>(widg); + std::vector<Gtk::Widget *> childList = scalar_parameter->get_children(); + Gtk::Entry *entry_widget = dynamic_cast<Gtk::Entry *>(childList[1]); + entry_widget->set_width_chars(6); + } + } else if (param->param_key == "chamfer_steps") { + Inkscape::UI::Widget::Scalar *widg_registered = + Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg)); + widg_registered->signal_value_changed().connect( + sigc::mem_fun(*this, &LPEFilletChamfer::updateChamferSteps)); + widg = widg_registered; + if (widg) { + Gtk::Box *scalar_parameter = dynamic_cast<Gtk::Box *>(widg); + std::vector<Gtk::Widget *> childList = scalar_parameter->get_children(); + Gtk::Entry *entry_widget = dynamic_cast<Gtk::Entry *>(childList[1]); + entry_widget->set_width_chars(3); + } + } else if (param->param_key == "only_selected") { + Gtk::manage(widg); + } + 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::Box *fillet_container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + Gtk::Button *fillet = Gtk::manage(new Gtk::Button(Glib::ustring(_("Fillet")))); + fillet->signal_clicked().connect( + sigc::bind<NodeSatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateNodeSatelliteType), FILLET)); + + fillet_container->pack_start(*fillet, true, true, 2); + Gtk::Button *inverse_fillet = Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse fillet")))); + inverse_fillet->signal_clicked().connect(sigc::bind<NodeSatelliteType>( + sigc::mem_fun(*this, &LPEFilletChamfer::updateNodeSatelliteType), INVERSE_FILLET)); + fillet_container->pack_start(*inverse_fillet, true, true, 2); + + Gtk::Box *chamfer_container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0)); + Gtk::Button *chamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Chamfer")))); + chamfer->signal_clicked().connect( + sigc::bind<NodeSatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateNodeSatelliteType), CHAMFER)); + + chamfer_container->pack_start(*chamfer, true, true, 2); + Gtk::Button *inverse_chamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse chamfer")))); + inverse_chamfer->signal_clicked().connect(sigc::bind<NodeSatelliteType>( + sigc::mem_fun(*this, &LPEFilletChamfer::updateNodeSatelliteType), INVERSE_CHAMFER)); + chamfer_container->pack_start(*inverse_chamfer, true, true, 2); + + vbox->pack_start(*fillet_container, true, true, 2); + vbox->pack_start(*chamfer_container, true, true, 2); + if(Gtk::Widget* widg = defaultParamSet()) { + vbox->pack_start(*widg, true, true, 2); + } + return vbox; +} + +void LPEFilletChamfer::refreshKnots() +{ + if (nodesatellites_param._knoth) { + nodesatellites_param._knoth->update_knots(); + } +} + +void LPEFilletChamfer::updateAmount() +{ + setSelected(_pathvector_nodesatellites); + double power = radius; + if (!flexible) { + SPDocument *document = getSPDoc(); + Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str(); + power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str()); + } + _pathvector_nodesatellites->updateAmount(power, apply_no_radius, apply_with_radius, only_selected, + use_knot_distance, flexible); + nodesatellites_param.setPathVectorNodeSatellites(_pathvector_nodesatellites); +} + +void LPEFilletChamfer::updateChamferSteps() +{ + setSelected(_pathvector_nodesatellites); + _pathvector_nodesatellites->updateSteps(chamfer_steps, apply_no_radius, apply_with_radius, only_selected); + nodesatellites_param.setPathVectorNodeSatellites(_pathvector_nodesatellites); +} + +void LPEFilletChamfer::updateNodeSatelliteType(NodeSatelliteType nodesatellitetype) +{ + std::map<NodeSatelliteType, gchar const *> nodesatellite_type_to_gchar_map = boost::assign::map_list_of( + FILLET, "F")(INVERSE_FILLET, "IF")(CHAMFER, "C")(INVERSE_CHAMFER, "IC")(INVALID_SATELLITE, "KO"); + mode.param_setValue((Glib::ustring)nodesatellite_type_to_gchar_map.at(nodesatellitetype)); + setSelected(_pathvector_nodesatellites); + _pathvector_nodesatellites->updateNodeSatelliteType(nodesatellitetype, apply_no_radius, apply_with_radius, + only_selected); + nodesatellites_param.setPathVectorNodeSatellites(_pathvector_nodesatellites); +} + +void LPEFilletChamfer::setSelected(PathVectorNodeSatellites *_pathvector_nodesatellites) +{ + std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems(); + if (lpeitems.size() == 1) { + sp_lpe_item = lpeitems[0]; + if (!_pathvector_nodesatellites) { + sp_lpe_item_update_patheffect(sp_lpe_item, false, false); + } else { + Geom::PathVector const pathv = _pathvector_nodesatellites->getPathVector(); + NodeSatellites nodesatellites = _pathvector_nodesatellites->getNodeSatellites(); + for (size_t i = 0; i < nodesatellites.size(); ++i) { + for (size_t j = 0; j < nodesatellites[i].size(); ++j) { + Geom::Curve const &curve_in = pathv[i][j]; + if (only_selected && isNodePointSelected(curve_in.initialPoint()) ){ + nodesatellites[i][j].setSelected(true); + } else { + nodesatellites[i][j].setSelected(false); + } + } + } + _pathvector_nodesatellites->setNodeSatellites(nodesatellites); + } + } +} + +void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem) +{ + if (!pathvector_before_effect.empty()) { + //fillet chamfer specific calls + nodesatellites_param.setUseDistance(use_knot_distance); + nodesatellites_param.setCurrentZoom(current_zoom); + //mandatory call + nodesatellites_param.setEffectType(effectType()); + Geom::PathVector const pathv = pathv_to_linear_and_cubic_beziers(pathvector_before_effect); + Geom::PathVector pathvres; + for (const auto &path_it : pathv) { + if (path_it.empty() || count_path_nodes(path_it) < 2) { + continue; + } + Geom::Path::const_iterator curve_it = 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(); + } + } + Geom::Path pathresult(curve_it->initialPoint()); + while (curve_it != curve_endit) { + if (pathresult.size()) { + pathresult.setFinal(curve_it->initialPoint()); + } + if (Geom::are_near((*curve_it).initialPoint(), (*curve_it).finalPoint())) { + return; + } + pathresult.append(*curve_it); + ++curve_it; + } + pathresult.close(path_it.closed()); + pathvres.push_back(pathresult); + pathresult.clear(); + } // if are different sizes call to recalculate + NodeSatellites nodesatellites = nodesatellites_param.data(); + if (nodesatellites.empty()) { + doOnApply(lpeItem); // dont want _impl to not update versioning + nodesatellites = nodesatellites_param.data(); + } + bool write = false; + if (_pathvector_nodesatellites) { + size_t number_nodes = count_pathvector_nodes(pathvres); + size_t previous_number_nodes = _pathvector_nodesatellites->getTotalNodeSatellites(); + if (number_nodes != previous_number_nodes) { + double power = radius; + if (!flexible) { + SPDocument *document = getSPDoc(); + Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str(); + power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str()); + } + NodeSatelliteType nodesatellite_type = FILLET; + std::map<std::string, NodeSatelliteType> gchar_map_to_nodesatellite_type = boost::assign::map_list_of( + "F", FILLET)("IF", INVERSE_FILLET)("C", CHAMFER)("IC", INVERSE_CHAMFER)("KO", INVALID_SATELLITE); + auto mode_str = mode.param_getSVGValue(); + std::map<std::string, NodeSatelliteType>::iterator it = + gchar_map_to_nodesatellite_type.find(mode_str.raw()); + if (it != gchar_map_to_nodesatellite_type.end()) { + nodesatellite_type = it->second; + } + NodeSatellite nodesatellite(nodesatellite_type); + nodesatellite.setSteps(chamfer_steps); + nodesatellite.setAmount(power); + nodesatellite.setIsTime(flexible); + nodesatellite.setHasMirror(true); + nodesatellite.setHidden(hide_knots); + _pathvector_nodesatellites->recalculateForNewPathVector(pathvres, nodesatellite); + nodesatellites = _pathvector_nodesatellites->getNodeSatellites(); + write = true; + } + } + + if (_degenerate_hide) { + nodesatellites_param.setGlobalKnotHide(true); + } else { + nodesatellites_param.setGlobalKnotHide(false); + } + for (size_t i = 0; i < nodesatellites.size(); ++i) { + for (size_t j = 0; j < nodesatellites[i].size(); ++j) { + if (pathvres.size() <= i || j >= count_path_nodes(pathvres[i])) { + // we are on the end of a open path + // for the moment we dont want to use + // this nodesatellite so simplest do nothing with it + continue; + } + Geom::Curve const &curve_in = pathvres[i][j]; + if (nodesatellites[i][j].is_time != flexible) { + nodesatellites[i][j].is_time = flexible; + double amount = nodesatellites[i][j].amount; + if (nodesatellites[i][j].is_time) { + double time = timeAtArcLength(amount, curve_in); + nodesatellites[i][j].amount = time; + } else { + double size = arcLengthAt(amount, curve_in); + nodesatellites[i][j].amount = size; + } + } + nodesatellites[i][j].hidden = hide_knots; + if (only_selected && isNodePointSelected(curve_in.initialPoint()) ){ + nodesatellites[i][j].setSelected(true); + } + } + if (pathvres.size() > i && !pathvres[i].closed()) { + nodesatellites[i][0].amount = 0; + nodesatellites[i][count_path_nodes(pathvres[i]) - 1].amount = 0; + } + } + if (!_pathvector_nodesatellites) { + _pathvector_nodesatellites = new PathVectorNodeSatellites(); + } + _pathvector_nodesatellites->setPathVector(pathvres); + _pathvector_nodesatellites->setNodeSatellites(nodesatellites); + nodesatellites_param.setPathVectorNodeSatellites(_pathvector_nodesatellites, write); + size_t number_nodes = count_pathvector_nodes(pathvres); + size_t previous_number_nodes = _pathvector_nodesatellites->getTotalNodeSatellites(); + if (number_nodes != previous_number_nodes) { + doOnApply(lpeItem); // dont want _impl to not update versioning + nodesatellites = nodesatellites_param.data(); + nodesatellites_param.setPathVectorNodeSatellites(_pathvector_nodesatellites, write); + } + Glib::ustring current_unit = Glib::ustring(unit.get_abbreviation()); + if (previous_unit != current_unit && previous_unit != "") { + updateAmount(); + } + if (write) { + nodesatellites_param.reloadKnots(); + } else { + refreshKnots(); + } + previous_unit = current_unit; + } else { + g_warning("LPE Fillet can only be applied to shapes (not groups)."); + } +} + +void +LPEFilletChamfer::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec) +{ + hp_vec.push_back(_hp); +} + +void +LPEFilletChamfer::addChamferSteps(Geom::Path &tmp_path, Geom::Path path_chamfer, Geom::Point end_arc_point, size_t steps) +{ + setSelected(_pathvector_nodesatellites); + double path_subdivision = 1.0 / steps; + for (size_t i = 1; i < steps; i++) { + Geom::Point chamfer_step = path_chamfer.pointAt(path_subdivision * i); + tmp_path.appendNew<Geom::LineSegment>(chamfer_step); + } + tmp_path.appendNew<Geom::LineSegment>(end_arc_point); +} + +Geom::PathVector +LPEFilletChamfer::doEffect_path(Geom::PathVector const &path_in) +{ + const double GAP_HELPER = 0.00001; + Geom::PathVector path_out; + size_t path = 0; + const double K = (4.0 / 3.0) * (sqrt(2.0) - 1.0); + _degenerate_hide = false; + Geom::PathVector const pathv = _pathvector_nodesatellites->getPathVector(); + NodeSatellites nodesatellites = _pathvector_nodesatellites->getNodeSatellites(); + for (const auto &path_it : pathv) { + Geom::Path tmp_path; + + double time0 = 0; + size_t curve = 0; + Geom::Path::const_iterator curve_it1 = 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(); + } + } + while (curve_it1 != curve_endit) { + size_t next_index = curve + 1; + if (curve == count_path_nodes(pathv[path]) - 1 && pathv[path].closed()) { + next_index = 0; + } + //append last extreme of paths on open paths + if (curve == count_path_nodes(pathv[path]) - 1 && !pathv[path].closed()) { // the path is open and we are at + // end of path + if (time0 != 1) { // Previous nodesatellite not at 100% amount + Geom::Curve *last_curve = curve_it1->portion(time0, 1); + last_curve->setInitial(tmp_path.finalPoint()); + tmp_path.append(*last_curve); + } + ++curve_it1; + continue; + } + Geom::Curve const &curve_it2 = pathv[path][next_index]; + NodeSatellite nodesatellite = nodesatellites[path][next_index]; + if (Geom::are_near((*curve_it1).initialPoint(), (*curve_it1).finalPoint())) { + _degenerate_hide = true; + g_warning("Knots hidden if consecutive nodes has the same position."); + return path_in; + } + if (!curve) { //curve == 0 + if (!path_it.closed()) { + time0 = 0; + } else { + time0 = nodesatellites[path][0].time(*curve_it1); + } + } + double s = nodesatellite.arcDistance(curve_it2); + double time1 = nodesatellite.time(s, true, (*curve_it1)); + double time2 = nodesatellite.time(curve_it2); + if (time1 <= time0) { + time1 = time0; + } + if (time2 > 1) { + time2 = 1; + } + Geom::Curve *knot_curve_1 = curve_it1->portion(time0, time1); + Geom::Curve *knot_curve_2 = curve_it2.portion(time2, 1); + if (curve > 0) { + knot_curve_1->setInitial(tmp_path.finalPoint()); + } else { + tmp_path.start((*curve_it1).pointAt(time0)); + } + + Geom::Point start_arc_point = knot_curve_1->finalPoint(); + Geom::Point end_arc_point = curve_it2.pointAt(time2); + //add a gap helper + if (time2 == 1) { + end_arc_point = curve_it2.pointAt(time2 - GAP_HELPER); + } + if (time1 == time0) { + start_arc_point = curve_it1->pointAt(time1 + GAP_HELPER); + } + + double k1 = distance(start_arc_point, curve_it1->finalPoint()) * K; + double k2 = distance(curve_it2.initialPoint(), end_arc_point) * K; + Geom::CubicBezier const *cubic_1 = dynamic_cast<Geom::CubicBezier const *>(&*knot_curve_1); + Geom::CubicBezier const *cubic_2 = dynamic_cast<Geom::CubicBezier const *>(&*knot_curve_2); + Geom::Ray ray_1(start_arc_point, curve_it1->finalPoint()); + Geom::Ray ray_2(curve_it2.initialPoint(), end_arc_point); + if (cubic_1) { + ray_1.setPoints((*cubic_1)[2], start_arc_point); + } + if (cubic_2) { + ray_2.setPoints(end_arc_point, (*cubic_2)[1]); + } + bool ccw_toggle = cross(curve_it1->finalPoint() - start_arc_point, end_arc_point - start_arc_point) < 0; + double angle = angle_between(ray_1, ray_2, ccw_toggle); + double handle_angle_1 = ray_1.angle() - angle; + double handle_angle_2 = ray_2.angle() + angle; + if (ccw_toggle) { + handle_angle_1 = ray_1.angle() + angle; + handle_angle_2 = ray_2.angle() - angle; + } + Geom::Point handle_1 = Geom::Point::polar(ray_1.angle(), k1) + start_arc_point; + Geom::Point handle_2 = end_arc_point - Geom::Point::polar(ray_2.angle(), k2); + Geom::Point inverse_handle_1 = Geom::Point::polar(handle_angle_1, k1) + start_arc_point; + Geom::Point inverse_handle_2 = end_arc_point - Geom::Point::polar(handle_angle_2, k2); + if (time0 == 1) { + handle_1 = start_arc_point; + inverse_handle_1 = start_arc_point; + } + //remove gap helper + if (time2 == 1) { + end_arc_point = curve_it2.pointAt(time2); + } + if (time1 == time0) { + start_arc_point = curve_it1->pointAt(time0); + } + if (time1 != 1 && !Geom::are_near(angle,Geom::rad_from_deg(360))) { + if (time1 != time0 || (time1 == 1 && time0 == 1)) { + if (!knot_curve_1->isDegenerate()) { + tmp_path.append(*knot_curve_1); + } + } + NodeSatelliteType type = nodesatellite.nodesatellite_type; + size_t steps = nodesatellite.steps; + if (!steps) steps = 1; + Geom::Line const x_line(Geom::Point(0, 0), Geom::Point(1, 0)); + Geom::Line const angled_line(start_arc_point, end_arc_point); + double arc_angle = Geom::angle_between(x_line, angled_line); + double radius = Geom::distance(start_arc_point, middle_point(start_arc_point, end_arc_point)) / + sin(angle / 2.0); + Geom::Coord rx = radius; + Geom::Coord ry = rx; + bool eliptical = (is_straight_curve(*curve_it1) && + is_straight_curve(curve_it2) && method != FM_BEZIER) || + method == FM_ARC; + switch (type) { + case CHAMFER: + { + Geom::Path path_chamfer; + path_chamfer.start(tmp_path.finalPoint()); + if (eliptical) { + ccw_toggle = ccw_toggle ? false : true; + path_chamfer.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, false, ccw_toggle, end_arc_point); + } else { + path_chamfer.appendNew<Geom::CubicBezier>(handle_1, handle_2, end_arc_point); + } + addChamferSteps(tmp_path, path_chamfer, end_arc_point, steps); + } + break; + case INVERSE_CHAMFER: + { + Geom::Path path_chamfer; + path_chamfer.start(tmp_path.finalPoint()); + if (eliptical) { + path_chamfer.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, false, ccw_toggle, end_arc_point); + } else { + path_chamfer.appendNew<Geom::CubicBezier>(inverse_handle_1, inverse_handle_2, end_arc_point); + } + addChamferSteps(tmp_path, path_chamfer, end_arc_point, steps); + } + break; + case INVERSE_FILLET: + { + if (eliptical) { + bool side = false; + if (helperpath && !getSPDoc()->is_yaxisdown()) { + side = true; + ccw_toggle = ccw_toggle ? false : true; + } + tmp_path.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, side, ccw_toggle, end_arc_point); + } else { + tmp_path.appendNew<Geom::CubicBezier>(inverse_handle_1, inverse_handle_2, end_arc_point); + } + } + break; + default: //fillet + { + if (eliptical) { + bool side = false; + if (helperpath && !getSPDoc()->is_yaxisdown()) { + side = true; + } else { + ccw_toggle = ccw_toggle ? false : true; + } + tmp_path.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, side, ccw_toggle, end_arc_point); + } else { + tmp_path.appendNew<Geom::CubicBezier>(handle_1, handle_2, end_arc_point); + } + } + break; + } + } else { + if (!knot_curve_1->isDegenerate()) { + tmp_path.append(*knot_curve_1); + } + } + curve++; + ++curve_it1; + time0 = time2; + } + if (path_it.closed()) { + tmp_path.close(); + } + path++; + path_out.push_back(tmp_path); + } + if (helperpath) { + _hp = path_out; + return pathvector_after_effect; + } + _hp.clear(); + return path_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 : |