1 #define INKSCAPE_LPE_DOEFFECT_STACK_CPP
3 /*
4 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
9 #include "live_effects/lpe-gears.h"
11 #include <vector>
12 #include <2geom/d2.h>
13 #include <2geom/sbasis.h>
14 #include <2geom/bezier-to-sbasis.h>
15 #include <2geom/path.h>
17 #include "nodepath.h"
19 using std::vector;
20 using namespace Geom;
22 class Gear {
23 public:
24 // pitch circles touch on two properly meshed gears
25 // all measurements are taken from the pitch circle
26 double pitch_diameter() {return (_number_of_teeth * _module) / M_PI;}
27 double pitch_radius() {return pitch_diameter() / 2.0;}
28 void pitch_radius(double R) {_module = (2 * M_PI * R) / _number_of_teeth;}
30 // base circle serves as the basis for the involute toothe profile
31 double base_diameter() {return pitch_diameter() * cos(_pressure_angle);}
32 double base_radius() {return base_diameter() / 2.0;}
34 // diametrical pitch
35 double diametrical_pitch() {return _number_of_teeth / pitch_diameter();}
37 // height of the tooth above the pitch circle
38 double addendum() {return 1.0 / diametrical_pitch();}
39 // depth of the tooth below the pitch circle
40 double dedendum() {return addendum() + _clearance;}
42 // root circle specifies the bottom of the fillet between teeth
43 double root_radius() {return pitch_radius() - dedendum();}
44 double root_diameter() {return root_radius() * 2.0;}
46 // outer circle is the outside diameter of the gear
47 double outer_radius() {return pitch_radius() + addendum();}
48 double outer_diameter() {return outer_radius() * 2.0;}
50 // angle covered by the tooth on the pitch circle
51 double tooth_thickness_angle() {return M_PI / _number_of_teeth;}
53 Geom::Point centre() {return _centre;}
54 void centre(Geom::Point c) {_centre = c;}
56 double angle() {return _angle;}
57 void angle(double a) {_angle = a;}
59 int number_of_teeth() {return _number_of_teeth;}
61 Geom::Path path();
62 Gear spawn(Geom::Point p);
64 Gear(int n, double m, double phi) {
65 _number_of_teeth = n;
66 _module = m;
67 _pressure_angle = phi;
68 _clearance = 0.0;
69 _angle = 0.0;
70 _centre = Geom::Point(0.0,0.0);
71 }
72 private:
73 int _number_of_teeth;
74 double _pressure_angle;
75 double _module;
76 double _clearance;
77 double _angle;
78 Geom::Point _centre;
79 D2<SBasis> _involute(double start, double stop) {
80 D2<SBasis> B;
81 D2<SBasis> I;
82 Linear bo = Linear(start,stop);
84 B[0] = cos(bo,2);
85 B[1] = sin(bo,2);
87 I = B - Linear(0,1) * derivative(B);
88 I = I*base_radius() + _centre;
89 return I;
90 }
91 D2<SBasis> _arc(double start, double stop, double R) {
92 D2<SBasis> B;
93 Linear bo = Linear(start,stop);
95 B[0] = cos(bo,2);
96 B[1] = sin(bo,2);
98 B = B*R + _centre;
99 return B;
100 }
101 // angle of the base circle used to create the involute to a certain radius
102 double involute_swath_angle(double R) {
103 if (R <= base_radius()) return 0.0;
104 return sqrt(R*R - base_radius()*base_radius())/base_radius();
105 }
107 // angle of the base circle between the origin of the involute and the intersection on another radius
108 double involute_intersect_angle(double R) {
109 if (R <= base_radius()) return 0.0;
110 return (sqrt(R*R - base_radius()*base_radius())/base_radius()) - acos(base_radius()/R);
111 }
112 };
114 void makeContinuous(D2<SBasis> &a, Point const b) {
115 for(unsigned d=0;d<2;d++)
116 a[d][0][0] = b[d];
117 }
119 Geom::Path Gear::path() {
120 Geom::Path pb;
122 // angle covered by a full tooth and fillet
123 double tooth_rotation = 2.0 * tooth_thickness_angle();
124 // angle covered by an involute
125 double involute_advance = involute_intersect_angle(outer_radius()) - involute_intersect_angle(root_radius());
126 // angle covered by the tooth tip
127 double tip_advance = tooth_thickness_angle() - (2 * (involute_intersect_angle(outer_radius()) - involute_intersect_angle(pitch_radius())));
128 // angle covered by the toothe root
129 double root_advance = (tooth_rotation - tip_advance) - (2.0 * involute_advance);
130 // begin drawing the involute at t if the root circle is larger than the base circle
131 double involute_t = involute_swath_angle(root_radius())/involute_swath_angle(outer_radius());
133 //rewind angle to start drawing from the leading edge of the tooth
134 double first_tooth_angle = _angle - ((0.5 * tip_advance) + involute_advance);
136 Geom::Point prev;
137 for (int i=0; i < _number_of_teeth; i++)
138 {
139 double cursor = first_tooth_angle + (i * tooth_rotation);
141 D2<SBasis> leading_I = compose(_involute(cursor, cursor + involute_swath_angle(outer_radius())), Linear(involute_t,1));
142 if(i != 0) makeContinuous(leading_I, prev);
143 pb.append(SBasisCurve(leading_I));
144 cursor += involute_advance;
145 prev = leading_I.at1();
147 D2<SBasis> tip = _arc(cursor, cursor+tip_advance, outer_radius());
148 makeContinuous(tip, prev);
149 pb.append(SBasisCurve(tip));
150 cursor += tip_advance;
151 prev = tip.at1();
153 cursor += involute_advance;
154 D2<SBasis> trailing_I = compose(_involute(cursor, cursor - involute_swath_angle(outer_radius())), Linear(1,involute_t));
155 makeContinuous(trailing_I, prev);
156 pb.append(SBasisCurve(trailing_I));
157 prev = trailing_I.at1();
159 if (base_radius() > root_radius()) {
160 Geom::Point leading_start = trailing_I.at1();
161 Geom::Point leading_end = (root_radius() * unit_vector(leading_start - _centre)) + _centre;
162 prev = leading_end;
163 pb.appendNew<LineSegment>(leading_end);
164 }
166 D2<SBasis> root = _arc(cursor, cursor+root_advance, root_radius());
167 makeContinuous(root, prev);
168 pb.append(SBasisCurve(root));
169 cursor += root_advance;
170 prev = root.at1();
172 if (base_radius() > root_radius()) {
173 Geom::Point trailing_start = root.at1();
174 Geom::Point trailing_end = (base_radius() * unit_vector(trailing_start - _centre)) + _centre;
175 pb.appendNew<LineSegment>(trailing_end);
176 prev = trailing_end;
177 }
178 }
180 return pb;
181 }
183 Gear Gear::spawn(Geom::Point p) {
184 double radius = Geom::distance(this->centre(), p) - this->pitch_radius();
185 int N = (int) floor( (radius / this->pitch_radius()) * this->number_of_teeth() );
187 Gear gear(N, _module, _pressure_angle);
188 gear.centre(p);
190 double a = atan2(p - this->centre());
191 double new_angle = 0.0;
192 if (gear.number_of_teeth() % 2 == 0)
193 new_angle -= gear.tooth_thickness_angle();
194 new_angle -= (_angle) * (pitch_radius() / gear.pitch_radius());
195 new_angle += (a) * (pitch_radius() / gear.pitch_radius());
196 gear.angle(new_angle + a);
197 return gear;
198 }
202 // #################################################################
206 namespace Inkscape {
207 namespace LivePathEffect {
210 LPEGears::LPEGears(LivePathEffectObject *lpeobject) :
211 Effect(lpeobject),
212 teeth(_("Teeth"), _("The number of teeth"), "teeth", &wr, this, 10),
213 phi(_("Phi"), _("Tooth pressure angle (typically 20-25 deg). The ratio of teeth not in contact."), "phi", &wr, this, 5)
214 {
215 /* Tooth pressure angle: The angle between the tooth profile and a perpendicular to the pitch
216 * circle, usually at the point where the pitch circle meets the tooth profile. Standard angles
217 * are 20 and 25 degrees. The pressure angle affects the force that tends to separate mating
218 * gears. A high pressure angle means that higher ratio of teeth not in contact. However, this
219 * allows the teeth to have higher capacity and also allows fewer teeth without undercutting.
220 */
222 teeth.param_make_integer();
223 teeth.param_set_range(3, NR_HUGE);
224 registerParameter( dynamic_cast<Parameter *>(&teeth) );
225 registerParameter( dynamic_cast<Parameter *>(&phi) );
226 }
228 LPEGears::~LPEGears()
229 {
231 }
233 std::vector<Geom::Path>
234 LPEGears::doEffect_path (std::vector<Geom::Path> const & path_in)
235 {
236 std::vector<Geom::Path> path_out;
237 Geom::Path gearpath = path_in[0];
239 Geom::Path::iterator it(gearpath.begin());
240 if ( it == gearpath.end() ) return path_out;
242 Gear * gear = new Gear(teeth, 200.0, phi * M_PI / 180);
243 Geom::Point gear_centre = (*it).finalPoint();
244 gear->centre(gear_centre);
245 gear->angle(atan2((*it).initialPoint() - gear_centre));
247 it++; if ( it == gearpath.end() ) return path_out;
248 gear->pitch_radius(Geom::distance(gear_centre, (*it).finalPoint()));
250 path_out.push_back( gear->path());
252 for (it++ ; it != gearpath.end() ; it++) {
253 // iterate through Geom::Curve in path_in
254 Gear* gearnew = new Gear(gear->spawn( (*it).finalPoint() ));
255 path_out.push_back( gearnew->path() );
256 delete gear;
257 gear = gearnew;
258 }
259 delete gear;
261 return path_out;
262 }
264 void
265 LPEGears::setup_nodepath(Inkscape::NodePath::Path *np)
266 {
267 Effect::setup_nodepath(np);
268 sp_nodepath_make_straight_path(np);
269 }
271 } // namespace LivePathEffect
272 } /* namespace Inkscape */
274 /*
275 Local Variables:
276 mode:c++
277 c-file-style:"stroustrup"
278 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
279 indent-tabs-mode:nil
280 fill-column:99
281 End:
282 */
283 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :