LibreCAD / plugins /gear /gear.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
a5ffdcd verified
/*****************************************************************************/
/* gear.cpp - plugin gear for LibreCAD */
/* */
/* Copyright (C) 2016 Cédric Bosdonnat cedric@bosdonnat.fr */
/* Edited 2017 Luis Colorado <luiscoloradourcola@gmail.com> */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 2 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
#include <QGridLayout>
#include <QPushButton>
#include <QSettings>
#include <QMessageBox>
#include <QTransform>
#include <QDoubleSpinBox>
#include <QSpinBox>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <vector>
#include <cmath>
#include <cfloat>
#include "document_interface.h"
#include "gear.h"
QString LC_Gear::name() const
{
return (tr("Gear creation plugin"));
}
PluginCapabilities LC_Gear::getCapabilities() const
{
PluginCapabilities pluginCapabilities;
pluginCapabilities.menuEntryPoints
<< PluginMenuLocation("plugins_menu", tr("Gear plugin"));
return pluginCapabilities;
}
void LC_Gear::execComm([[maybe_unused]] Document_Interface *doc,
QWidget *parent, [[maybe_unused]] QString cmd)
{
QPointF center;
if (!doc->getPoint(&center, QString("select center"))) {
return;
}
if (!parameters_dialog) {
parameters_dialog = new lc_Geardlg(parent);
if (!parameters_dialog) {
return;
}
}
int result = parameters_dialog->exec();
if (result == QDialog::Accepted)
parameters_dialog->processAction(doc, cmd, center);
}
/*****************************/
lc_Geardlg::lc_Geardlg(QWidget *parent) :
QDialog(parent),
settings(QSettings::IniFormat, QSettings::UserScope, "LibreCAD", "gear_plugin")
{
const char *windowTitle = "Draw a gear";
setWindowTitle(tr(windowTitle));
QLabel *label;
QGridLayout *mainLayout = new QGridLayout(this);
int i = 0, j = 0;
#define RST() do{ if(j) { ++i; j = 0; } } while(0)
#define Q(name, type, text, min, max, stp) do { \
label = new QLabel((text), this); \
name = new type(this); \
name->setMinimum(min); \
name->setMaximum(max); \
name->setSingleStep(stp); \
mainLayout->addWidget(label, i, 0); \
mainLayout->addWidget((name), i, 1); \
} while(0)
#define QDSB(name, text, min, max, stp, dec) do { \
RST(); \
Q(name, QDoubleSpinBox, (text),(min),(max),(stp)); \
name->setDecimals(dec); \
++i; j = 0; \
} while(0)
#define QSB(name, text, min, max, stp) do { \
RST(); \
Q(name, QSpinBox, (text),(min),(max), (stp)); \
++i; j = 0; \
} while(0)
#define QCB(name, text) do { \
name = new QCheckBox((text), this); \
mainLayout->addWidget(name, i, j); \
j++; if (j >= 2) { j = 0; i++; } \
} while(0)
QDSB(rotateBox, tr("Rotation angle"), -360.0, 360.0, 1.0, 6);
QSB (nteethBox, tr("Number of teeth"), 1, 2000, 1);
QDSB(modulusBox, tr("Modulus"), 1.0E-10, 1.0E+10, 0.1, 6);
QDSB(pressureBox, tr("Pressure angle (deg)"), 0.1, 89.9, 1.0, 5);
QDSB(addendumBox, tr("Addendum (rel. to modulus)"), 0.0, 5.0, 0.1, 5);
QDSB(dedendumBox, tr("Dedendum (rel. to modulus)"), 0.0, 5.0, 0.1, 5);
QSB (n1Box, tr("Number of segments to draw (dedendum)"), 1, 1024, 8);
QSB (n2Box, tr("Number of segments to draw (addendum)"), 1, 1024, 8);
QCB (drawAllTeethBox, tr("Draw all teeth?"));
QCB (drawBothSidesOfToothBox, tr("Draw symmetric face?"));
QCB (useLayersBox, tr("Use layers?")); RST();
QCB (drawAddendumCircleBox, tr("Draw addendum circle?"));
QCB (drawPitchCircleBox, tr("Draw pitch circle?"));
QCB (drawBaseCircleBox, tr("Draw base circle?"));
QCB (drawRootCircleBox, tr("Draw root circle?"));
QCB (drawPressureLineBox, tr("Draw pressure line?"));
QCB (drawPressureLimitBox, tr("Draw pressure limits?"));
QCB (calcInterferenceBox, tr("Calculate interference?"));
QSB (n3Box, tr("Number of segments to draw (interference)"), 1, 1024, 8);
QPushButton *acceptbut = new QPushButton(tr("Accept"), this);
QPushButton *cancelbut = new QPushButton(tr("Cancel"), this);
QHBoxLayout *acceptLayout = new QHBoxLayout();
acceptLayout->addStretch();
acceptLayout->addWidget(acceptbut);
acceptLayout->addStretch();
acceptLayout->addWidget(cancelbut);
acceptLayout->addStretch();
mainLayout->addLayout(acceptLayout, i, 0, 1, 2);
setLayout(mainLayout);
readSettings();
connect(cancelbut, SIGNAL(clicked()), this, SLOT(reject()));
connect(acceptbut, SIGNAL(clicked()), this, SLOT(checkAccept()));
}
/* calculate the radius of a point in canonical evoluta
* whose radius is given. */
static double radius2arg(const double radius, const double alpha = 0.0)
{
const double aux = 1.0 - alpha;
return sqrt(radius * radius - aux*aux);
}
/* canonical evolute is generated by a 1.0 radius circle.
* We consider it the next complex function:
* (1.0 - alpha - i*phi) * exp(i*phi)
*/
static double re_evolute(const double phi, const double alpha = 0.0)
{
return (1.0 - alpha) * cos(phi) + phi * sin(phi);
}
static double im_evolute(const double phi, const double alpha = 0.0)
{
return (1.0 - alpha) * sin(phi) - phi * cos(phi);
}
static double mod_evolute(const double phi, const double alpha = 0.0)
{
double aux = (1.0 - alpha);
return sqrt(aux*aux + phi*phi);
}
static double arg_evolute(const double phi, const double alpha = 0.0)
{
double aux = (1.0 - alpha);
return phi - atan2(phi, aux);
}
struct evolute {
static const double default_eps;
evolute(int n_t, double add, double ded, double p_ang);
QPointF evo0(const double phi); /* evolute for tooth face */
QPointF evo1(const double phi); /* evolute for tooth carving (interference) */
double aux(const double phi); /* auxiliary function */
double find_common_phi_evo1(const double eps = default_eps);
const int n_teeth;
const double
addendum, dedendum,
c_modulus,
p_angle, cos_p_angle, cos2_p_angle,
angle_0, cos_angle_0, sin_angle_0,
dedendum_radius, addendum_radius,
phi_at_dedendum, phi_at_addendum,
alpha, angle_1,
cos_angle_1, sin_angle_1;
};
const double evolute::default_eps = 8 * DBL_EPSILON;
evolute::evolute(int n_t, double add, double ded, double p_ang):
n_teeth(n_t),
addendum(add),
dedendum(ded),
c_modulus(2.0/n_teeth),
p_angle(p_ang),
cos_p_angle(std::cos(p_ang)),
cos2_p_angle(cos_p_angle * cos_p_angle),
angle_0(p_angle - tan(p_angle)),
cos_angle_0(std::cos(angle_0)),
sin_angle_0(sin(angle_0)),
dedendum_radius(1.0 - c_modulus * dedendum),
addendum_radius(1.0 + c_modulus * addendum),
phi_at_dedendum(dedendum_radius > cos_p_angle
? radius2arg(dedendum_radius / cos_p_angle)
: 0.0),
phi_at_addendum(radius2arg(addendum_radius / cos_p_angle)),
alpha(1.0 - dedendum_radius),
angle_1(-alpha * tan(p_angle)),
cos_angle_1(std::cos(angle_1)),
sin_angle_1(sin(angle_1))
{
}
/* this evolute calculates points for an argument phi for the
* curve that defines de active face of the tooth. */
QPointF evolute::evo0(const double phi)
{
double x = cos_p_angle * re_evolute(phi),
y = cos_p_angle * im_evolute(phi);
return QPointF(cos_angle_0 * x - sin_angle_0 * y,
sin_angle_0 * x + cos_angle_0 * y);
}
/* this evolute calculates points for an argument phi for the
* curve that defines the carved neck of the tooth in case of
* interference. */
QPointF evolute::evo1(const double phi)
{
double x = re_evolute(phi, alpha);
double y = im_evolute(phi, alpha);
return QPointF(cos_angle_1 * x - sin_angle_1 * y,
sin_angle_1 * x + cos_angle_1 * y);
}
/* Auxiliary function to determine if we are in one side of the
* primary evolute or in the other side. We look for a zero in this
* function to derive the phi angle of the secondary evolute at which
* it crosses the primary. This is the common point for both evolutes
*/
double evolute::aux(const double phi)
{
const double mod = mod_evolute(phi, alpha);
const double arg = arg_evolute(phi, alpha);
if (mod <= cos_p_angle) {
return arg + angle_1 - angle_0;
}
const double phi0 = radius2arg(mod / cos_p_angle);
return arg + angle_1 + atan(phi0) - phi0 - angle_0;
}
/* find the common point of both evolutes. this function uses two
* values a and b of phi in the evolute::evo1 curve to find a root of
* the function evolute::aux that gives the difference between the argument
* at which the evolute of the primary curve touches the base circle minus
* the argument at which an evolute that pases for the point calculated in
* the second curve hits the base circle. This being positive means the
* second evolute has already crossed the first. Being negative means it
* has not yet crossed the primary evolute. */
double evolute::find_common_phi_evo1(const double eps)
{
double a = -radius2arg(cos_p_angle, alpha);
double b = -radius2arg(1.0, alpha);
double f_a = aux(a), f_b = aux(b);
double x = a;
if (f_a > 0) do {
x = (a*f_b - b*f_a) / (f_b - f_a);
double f_x = aux(x);
if (fabs(x - a) < fabs(x - b)) {
b = x; f_b = f_x;
} else {
a = x; f_a = f_x;
}
} while (fabs(a-b) >= eps);
return x;
} /* find_common_phi_evo1 */
void lc_Geardlg::processAction([[maybe_unused]] Document_Interface *doc, [[maybe_unused]] const QString& cmd, [[maybe_unused]] QPointF& center)
{
std::vector<Plug_VertexData> polyline;
std::vector<QPointF> first_tooth;
QTransform rotate_and_disp;
/* we shall proceed by calculating the points for root radius,
* base of tooth, n1 line segments to the pitch circle (this makes
* possible to have a reference point on the pitch circle)
* n2 line segments to the addendum circle.
* The tooth face is aligned so the pitch point is aligned with the
* X axis, so we can align gears over this reference point.
*
* Once we get one face, we mirror it using as the axis one quarter
* of the pitch angular modulus (half of the tooth width) passing
* along the origin.
*
* Finally, we get the complete set of teeth by rotating them by the
* pitch angular modulus to get the whole gear */
evolute ev(nteethBox->value(), /* number of teeth */
addendumBox->value(), /* addendum */
dedendumBox->value(), /* dedendum */
M_PI / 180.0 * pressureBox->value()); /* pressure angle (converted to rad) */
const double modulus = modulusBox->value();
const double scale_factor = modulus / ev.c_modulus;
const int n1 = n1Box->value();
const int n2 = n2Box->value();
const double rotation = rotateBox->value() * M_PI / 180.0;
rotate_and_disp = rotate_and_disp
.translate(center.x(), center.y())
.rotateRadians(rotation);
double phi_0 = 0.0;
/* Build one tooth face */
if (calcInterferenceBox->isChecked()
&& ev.cos2_p_angle > ev.dedendum_radius)
{
const int n3 = n3Box->value();
double angle_2 = ev.find_common_phi_evo1();
phi_0 = radius2arg(mod_evolute(angle_2, ev.alpha) / ev.cos_p_angle);
double phi = 0.0,
delta = angle_2 / n3;
for(int i = 0; i < n3; i++) {
const QPointF point(scale_factor * ev.evo1(phi));
first_tooth.push_back(point);
polyline.push_back(Plug_VertexData(rotate_and_disp.map(point), 0.0));
phi += delta;
} /* for */
} else if (ev.cos_p_angle > ev.dedendum_radius) {
/* no interference calculation at all. just draw the point at the
* intersection of the root circle with the 0 press angle point. */
QPointF root(scale_factor * ev.dedendum_radius * ev.cos_angle_0,
scale_factor * ev.dedendum_radius * ev.sin_angle_0);
first_tooth.push_back(root);
polyline.push_back(Plug_VertexData(rotate_and_disp.map(root), 0.0));
}
if (phi_0 < ev.phi_at_dedendum) phi_0 = ev.phi_at_dedendum;
double phi = phi_0;
/* if the carving has eaten some active part of the tooth dedendum face */
if (phi < ev.p_angle - ev.angle_0) {
double delta = (ev.p_angle - ev.angle_0 - phi) / n1;
for (int i = 0; i < n1; ++i) {
const QPointF point(scale_factor * ev.evo0(phi));
first_tooth.push_back(point);
polyline.push_back(Plug_VertexData(rotate_and_disp.map(point), 0.0));
phi += delta;
} /* for */
}
double phi_1 = radius2arg(ev.addendum_radius / ev.cos_p_angle);
if (phi < phi_1) {
double delta = (phi_1 - phi) / n2;
for (int i = 0; i <= n2; ++i) {
const QPointF point(scale_factor * ev.evo0(phi));
first_tooth.push_back(point);
polyline.push_back(Plug_VertexData(rotate_and_disp.map(point), 0.0));
phi += delta;
}
}
/* calculate the symmetric face from the original points */
/* one half of pitch angular modulus = double of symmetry axis.
* symmetry on an axis that passes through origin is calculated as follows:
* x' = cos(2.0*axis_angle) * x + sin(2.0*axis_angle) * y
* y' = sin(2.0*axis_angle) * x - cos(2.0*axis_angle) * y
* (note: we don't use iterators as the array is growing as long as we
* navigate it)
*/
const double axis_angle_x_2 = M_PI / ev.n_teeth;
const double axis_angle = axis_angle_x_2 / 2.0;
const double cos_axis_angle_x_2 = std::cos(axis_angle_x_2);
const double sin_axis_angle_x_2 = std::sin(axis_angle_x_2);
/* remember size, as we don't want to duplicate next point */
const double n_to_mirror = first_tooth.size();
if (drawBothSidesOfToothBox->isChecked()) {
/* symmetry axis point (at top of tooth) */
QPointF mirror_point(scale_factor * ev.addendum_radius * std::cos(axis_angle),
scale_factor * ev.addendum_radius * std::sin(axis_angle));
first_tooth.push_back(mirror_point);
polyline.push_back(Plug_VertexData(rotate_and_disp.map(mirror_point), 0.0));
/* for all points we have to mirror (all but the last one) */
for (int i = n_to_mirror - 1; i >= 0; --i) {
const QPointF& orig(first_tooth[i]);
QPointF target(cos_axis_angle_x_2 * orig.x() + sin_axis_angle_x_2 * orig.y(),
sin_axis_angle_x_2 * orig.x() - cos_axis_angle_x_2 * orig.y());
first_tooth.push_back(target);
polyline.push_back(Plug_VertexData(rotate_and_disp.map(target), 0.0));
} /* for */
if (drawAllTeethBox->isChecked()) {
/* symmetry axis point (at interteeth) */
QPointF mirror_point2(scale_factor * ev.dedendum_radius * std::cos(axis_angle + axis_angle_x_2),
scale_factor * ev.dedendum_radius * std::sin(axis_angle + axis_angle_x_2));
first_tooth.push_back(mirror_point2);
polyline.push_back(Plug_VertexData(rotate_and_disp.map(mirror_point2), 0.0));
/* now, we have to rotate the tooth to get all the teeth missing. */
for (int i = 1; i < ev.n_teeth; i++) {
const double angle = M_PI * ev.c_modulus * i;
const double cos_angle = std::cos(angle);
const double sin_angle = std::sin(angle);
for (std::vector<QPointF>::iterator it = first_tooth.begin();
it != first_tooth.end(); ++it)
{
const QPointF& orig = *it;
polyline.push_back(Plug_VertexData(rotate_and_disp.map(QPointF(
cos_angle * orig.x() - sin_angle * orig.y(),
sin_angle * orig.x() + cos_angle * orig.y())),
0.0));
} /* for */
} /* for */
}
}
QString lastLayer = doc->getCurrentLayer();
#define LAYER(fmt) do { \
if (useLayersBox->isChecked()) { \
char buffer[128]; \
snprintf(buffer, sizeof buffer, \
"gear_M%6.4f_" fmt, modulus); \
doc->setLayer(buffer); \
} \
} while(0)
LAYER("shapes");
doc->addPolyline(polyline,
drawAllTeethBox->isChecked()
&& drawBothSidesOfToothBox->isChecked());
if (drawPitchCircleBox->isChecked()) {
LAYER("pitch_circles");
doc->addCircle(&center, scale_factor);
}
if (drawAddendumCircleBox->isChecked()) {
LAYER("addendums");
doc->addCircle(&center, scale_factor * ev.addendum_radius);
}
if (drawRootCircleBox->isChecked()) {
LAYER("dedendums");
doc->addCircle(&center, scale_factor * ev.dedendum_radius);
}
if (drawBaseCircleBox->isChecked()) {
LAYER("base_lines");
doc->addCircle(&center, scale_factor * ev.cos_p_angle);
}
if (drawPressureLineBox->isChecked() || drawPressureLimitBox->isChecked()) {
LAYER("action_lines");
QPointF p1(scale_factor * std::cos(ev.p_angle + rotation) * ev.cos_p_angle,
scale_factor * std::sin(ev.p_angle + rotation) * ev.cos_p_angle),
p2(scale_factor * cos(rotation),
scale_factor * sin(rotation));
p1 += center; p2 += center;
if (drawPressureLimitBox->isChecked())
doc->addLine(&center, &p1);
if (drawPressureLineBox->isChecked())
doc->addLine(&p1, &p2);
}
if (useLayersBox->isChecked())
doc->setLayer(lastLayer);
writeSettings();
}
void lc_Geardlg::checkAccept()
{
accept();
}
lc_Geardlg::~lc_Geardlg() = default;
void lc_Geardlg::closeEvent(QCloseEvent *event)
{
QWidget::closeEvent(event);
}
void lc_Geardlg::readSettings()
{
QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
QSize size = settings.value("size", QSize(430,140)).toSize();
#define R(var,toFunc, defval) do { \
var ## Box->setValue(settings.value(#var, defval).toFunc()); \
} while(0)
#define RB(var,defval) do { \
var ## Box->setChecked(settings.value(#var, defval).toBool()); \
} while (0)
R(rotate, toDouble, 0.0 );
R(nteeth, toInt, 20 );
R(modulus, toDouble, 1.0 );
R(pressure, toDouble, 20.0 );
R(addendum, toDouble, 1.0 );
R(dedendum, toDouble, 1.25);
R(n1, toInt, 16 );
R(n2, toInt, 16 );
RB(drawAllTeeth, true );
RB(drawBothSidesOfTooth, true );
RB(useLayers, true );
RB(drawAddendumCircle, false );
RB(drawPitchCircle, true );
RB(drawBaseCircle, true );
RB(drawRootCircle, false );
RB(drawPressureLine, true );
RB(drawPressureLimit, false );
RB(calcInterference, false );
R(n3, toInt, 16 );
resize(size);
move(pos);
}
void lc_Geardlg::writeSettings()
{
#define W(var, vfunc) do { \
settings.setValue(#var, var##Box->vfunc()); \
} while (0)
#define WN(var) W(var, value)
#define WB(var) W(var, isChecked)
settings.setValue("pos", pos());
settings.setValue("size", size());
WN(nteeth);
WN(modulus);
WN(pressure);
WN(addendum);
WN(dedendum);
WN(n1);
WN(n2);
WB(drawAllTeeth);
WB(drawBothSidesOfTooth);
WB(useLayers);
WB(drawAddendumCircle);
WB(drawPitchCircle);
WB(drawBaseCircle);
WB(drawRootCircle);
WB(drawPressureLine);
WB(drawPressureLimit);
WB(calcInterference);
WN(n3);
}