LibreCAD / plugins /divide /divide.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
a5ffdcd verified
/****************************************************************************
* divide.cpp - divide lines, circles and arcs *
* *
* Copyright (C) 2018 mad-hatter *
* some code borrowed from *
* list.cpp - Copyrighted by Rallaz, rallazz@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 <complex>
#include <QtGui>
#include <QApplication>
#include <QDialog>
#include <QGroupBox>
#include <QDialogButtonBox>
#include <QScrollArea>
#include <QGridLayout>
#include <QMessageBox>
#include <QLabel>
#include "divide.h"
#include "dividedlg.h"
QString divide::name() const
{
return ( tr( "Divide" ) );
}
PluginCapabilities divide::getCapabilities() const
{
PluginCapabilities pluginCapabilities;
pluginCapabilities.menuEntryPoints
<< PluginMenuLocation( "plugins_menu", tr( "Divide" ) );
return pluginCapabilities;
}
void divide::execComm( Document_Interface *doc,
[[maybe_unused]] QWidget *parent,
[[maybe_unused]] QString cmd )
{
d = doc;
QList<Plug_Entity *> obj;
bool yes = doc->getSelect( &obj, tr( "Select a line, circle or"
" arc and press return" ) );
if ( ! yes || obj.isEmpty() || ( obj.size() > 1 ) ) //none or multiple
{ //entity selection
QMessageBox msgBox;
msgBox.setStyleSheet( "QLabel, QPushButton { color: blue; }"
"QMessageBox { border: none;"
//"background: rgb( 255, 0, 0 );" //white is default
//"font-family: Arial;" //use default font
//text colour is blue from QLabel
"font-style: italic; font-size: 11pt; }" );
msgBox.setWindowTitle( tr( "Error" ) );
QString text = ( "Select a line, circle or arc.<br>" );
if ( obj.size() > 1 )
text.append( "One entity only!<br>" );
text.append( "Press \"<b>Enter/Return</b>\"." );
msgBox.setText( text );
msgBox.setIcon( QMessageBox::Warning );
/* Why are we messing with screen geometry just to show an error message?
* Assume Qt will just take care of it!
*
* msgBox.show(); //need show to get msgBox size
* QPoint centerXY = findWindowCentre();
* int x = centerXY.rx() - ( msgBox.width() / 2 );
* int y = centerXY.ry() - ( msgBox.height() / 2 );
*
* QRect screenGeometry = QApplication::desktop()->availableGeometry();
* //in case msgBox is wholly or partially offscreen
* if ( x >= ( screenGeometry.width() - msgBox.width() ) )
* x = QApplication::desktop()->width() - msgBox.width() - 10;
* if ( y >= ( screenGeometry.height() - msgBox.height() ) )
* y = QApplication::desktop()->height() - msgBox.height() - 60;
*
* msgBox.move( x, y );
*********************************/
msgBox.exec();
while ( ! obj.isEmpty() )
delete obj.takeFirst();
return;
}
QString passedData;
for ( int i = 0; i < obj.size(); ++i )
{
passedData.append( QString("%1 %2: ").arg( tr("n") ).arg( i + 1 ) );
passedData.append( getStrData( obj.at( i ) ) );
passedData.append( "\n" );
}
dividedlg dlg( d, passedData, parent );
QObject::connect( &dlg, SIGNAL( returnData( QString ) ), this,
SLOT( gotReturnedDataSlot( QString ) ) );
if ( dlg.exec() == QDialog::Accepted )
{
QList<QString> data = ( returnedData.split( ":" ) );
int qty = data.at( 1 ).toInt(); //defaults to zero if
if ( qty <= 0 ) return; //qty is empty
bool ticks = ( data.at( 3 ) == "t" ) ? true : false;
bool breaks = ( data.at( 4 ) == "t" ) ? true : false;
if ( ticks || breaks )
{
double radius { 0.0 };
double tickAngle { 0.0 };
double tickLength { 0.0 };
QPointF startX { 0.0, 0.0 };
QPointF startXY { 0.0, 0.0 };
QPointF centerCircle { 0.0, 0.0 };
QString test { "" };
QList<QString> xy { "" } ;
QString oldLayer = doc->getCurrentLayer();
int dataSize = data.size();
if ( data.at( dataSize - 1 ) == "lay" )
doc->setLayer( data.at( dataSize - 2 ) ); //layer for ticks
QList<QString> pData = ( passedData.split
( QRegularExpression ( "[\\n\\t\\r]" ) ) );
doc->updateView();
QString entType = data.at( 0 ).simplified().toLower();
//***********
if ( entType.startsWith( "ci" ) ) //circle
{
for ( int i = 0; i < pData.size(); i++ )
{
test = pData.at( i ).simplified().toLower();
if ( test.startsWith( "ra" ) ) //radius
radius = ( test.split( ":" ).at( 1 ) ).toDouble();
else if ( test.startsWith( "ce" ) ) //center point
{
xy = ( ( test.split( ":" ) ).at( 1 ).split( " " ) );
centerCircle = { ( ( ( xy.at( 1 )
.split( "=" ) ).at( 1 ) ).toDouble() ),
( ( ( xy.at( 2 )
.split( "=" ) ).at( 1 ) ).toDouble() ) };
}
}
//if data.at(5) is empty, startAngle defaults to zero
double startAngle = ( data.at( 5 ).toDouble() * M_PI ) / 180.0; //radians
tickLength = ( 2.0 * radius * ( data.at( 2 ).toDouble() / 100.0 ) ); //%
if ( data.at( dataSize - 3 ) == "i") //ticks inside/outside circle
tickLength *= -1;
for ( int i = 1; i <= qty; i++ ) {
tickAngle = ( ( ( 2 * M_PI ) / qty ) * i ) + startAngle;
if ( ticks )
drawTick( ( findStartX( radius, tickAngle, centerCircle ) ),
tickLength, tickAngle );
if ( breaks )
segment( & centerCircle, radius,
( startAngle * (180.0 / M_PI) )
+ ( ( 360.0 / qty ) * ( i - 1 ) ),
( 360.0 / qty ), entType );
}
} // end if CIRCLE
//***********
else if ( entType.startsWith( "li" ) ) //line
{
QPointF endXY ( 0.0, 0.0 );
QList<QString> part { "" };
QString test { "" };
int size = data.at( 2 ).toInt();
for ( int i = 0; i < pData.size(); i++ )
{
test = pData.at( i ).simplified().toLower();
part = test.split( ":");
if ( test.startsWith( "fr" ) ) //from point
{
part = part.at( 1 ).split( " " );
startXY += QPointF( ((part.at( 1 ).split("=")).at( 1 )).toDouble(),
((part.at( 2 ).split("=")).at( 1 )).toDouble() );
}
else if ( test.startsWith( "to" ) ) //to point
{
part = part.at( 1 ).split( " " );
endXY += QPointF( ((part.at( 1 ).split("=")).at( 1 )).toDouble(),
((part.at( 2 ).split("=")).at( 1 )).toDouble() );
}
else if ( test.startsWith( "an" ) ) //angle
tickAngle = ( (part.at( 1 ).simplified().toDouble() + 90.0 )
* M_PI ) / 180; //rads
else if ( test.startsWith( "le" ) ) //length
tickLength = ( test.split( ":" ).at( 1 )
.toDouble() ) * size / 100.0 ; //%
else if ( test.startsWith("in") ) //inc
{
//test if line goes left to right or right to left
part = part.at( 1 ).split( "=" );
if ( part.at( 1 ).startsWith( "-" ) ) //inc, minus 'x' pos
if ( tickAngle >= M_PI )
tickAngle -= M_PI; //180°
}
}
if ( data.at( dataSize - 3 ) == "i") //ticks above/below linw
tickLength *= -1;
qty += 1;
for ( int i = 1; i <= qty; i++ ) {
startX.setX( ( ((endXY.rx() - startXY.rx()) / qty) * i )
+ startXY.rx() );
startX.setY( ( ((endXY.ry() - startXY.ry()) / qty) * i )
+ startXY.ry() );
if ( ticks && ( i < qty ) )
drawTick( startX, tickLength, tickAngle );
if ( breaks )
segmentLine( startX, endXY, startXY, entType, qty, i );
}
} //end if LINE
//***********
else if ( entType.startsWith( "ar" ) ) //arc
{
double initial { 0.0 };
double final { 0.0 };
double arcLength { 0.0 };
double res { 0.0 };
QString copyTest { "" };
for ( int i = 0; i < pData.size(); i++ )
{
test = pData.at( i ).simplified().toLower();
if ( test.isEmpty() ) continue; //not valid data for 'res = (...'
copyTest = test;
res = ( copyTest.split( ":" ).at( 1 ) ).toDouble();
if ( test.startsWith( "ce" ) ) //center
{
xy = ( ( test.split( ":" ) ).at( 1 ).split( " " ) );
centerCircle = { ( ( ( xy.at( 1 )
.split( "=" ) ).at( 1 ) ).toDouble() ),
( ( ( xy.at( 2 )
.split( "=" ) ).at( 1 ) ).toDouble() ) };
}
else if ( test.startsWith( "ra" ) ) //radius
radius = res;
else if ( test.startsWith( "in" ) ) //initial angle
initial = res / 180.0 * M_PI; //degrees to rads
else if ( test.startsWith( "fi" ) ) //final angle
final = res / 180.0 * M_PI; //degrees to rads
else if ( test.startsWith( "le" ) ) //length
arcLength = res;
}
tickLength = ( 2 * radius * ( data.at( 2 ).toDouble() / 100.0 ) ); //%
if ( data.at( dataSize - 3 ) == "i" ) //ticks inside/outside arc
tickLength *= -1;
qty += 1;
double angle = ( final > initial ) ? final : initial;
double diff = ( final > initial ) ? ( initial - final ) / qty
: ( arcLength / radius ) / qty;
for ( int i = 1; i <= qty; i++ ) {
tickAngle = ( diff * i ) + angle;
if ( ticks && ( i < qty ) )
drawTick( ( findStartX( radius, tickAngle, centerCircle ) ),
tickLength, tickAngle );
if ( breaks )
segment( & centerCircle, radius,
( ( tickAngle * 180.0 ) / M_PI ),
( ( diff * 180.0 ) / M_PI ) * -1, entType );
}
} //end if ARC
//***********
/* else if ( entType.startsWith( "po" ) ) //polyline - to do
{
qDebug() << "*** pData " << pData;
qDebug() << "*** data " << data;
qDebug() << "*** ret " << returnedData;
QVector<double> XX;
QVector<double> YY;
//QVector<double> lengths;
int count = 0;
for ( int i = 0; i < pData.size(); i++ )
{
if (pData.at( i ).contains("point"))
{
count += 1;
XX.append( (pData.at( i ).split(":").at( 1 ).split("=")
.at( 1 ).split(" ").at( 0 )).toDouble() ) ;
YY.append( pData.at( i ).split("=").at( 2 ).toDouble() );
}
}
double totalLength = 0;
for ( int i = 0; i < count - 1; i++ )
{
totalLength += findHypLength( XX.at( i ), XX.at( i + 1 ),
YY.at( i ), YY.at( i + 1 ) );
}
x = ( dataSize.split(":") );
QString tickQty = x.at( 1 );
double tickQ = ( tickQty.remove("\n") ).toDouble();
double div = totalLength / tickQ;
qDebug() << "X" << XX << "Y" << YY << count
<< totalLength << tickQ << div;
} //POLYLINE */
doc->setLayer( oldLayer );
} //end if ( ticks || breaks )
doc->updateView(); //updates & removes highlights
while ( ! obj.isEmpty() )
delete obj.takeFirst();
} //end dlg.exec()
// else //rejected
// {
// qDebug() << "reject " << returnedData; //cancel button
// strData.prepend( strEntity.arg
// (tr("MUST be a line, circle or arc")) );
// while ( ! obj.isEmpty() )
// delete obj.takeFirst();
//}
} // end execComm
void divide::segmentLine( QPointF startPoint, QPointF lineEnd, QPointF lineStart,
QString type, int qty, int count )
{
QString layerName = ( "Break layer - " + type );
QString cl = d->getCurrentLayer();
d->setLayer( layerName );
static QPointF newStart;
if ( count == 1 ) { //1st line
d->addLine( &lineStart, &startPoint );
newStart = startPoint;
}
else if ( ( count > 1 ) && ( count < qty ) ) { //mid line
d->addLine( &newStart, &startPoint );
newStart = startPoint;
}
else if ( count == qty) //end line
d->addLine( &newStart, &lineEnd );
d->setLayer( cl ); //restore layer
}
void divide::segment( QPointF * centerCircle, double radius,
double angle, double arcAngle, QString type )
{
QString layerName = ( "Break layer - " + type );
QString cl = d->getCurrentLayer();
d->setLayer( layerName );
d->addArc( centerCircle, radius, angle, ( angle + arcAngle ) );
d->setLayer( cl );
}
double divide::findHypLength( double h1, double h2, double v1, double v2 )
{
return ( std::hypot( h1 - h2, v1 - v2 ) ); //needs (C++14 or later) - hypotenuse
//see - http://www.cplusplus.com/reference/cmath/hypot/ - C99
}
//line in any quadrant
//angle ± value in radians (range - double)
//startX, startY and length ± value (range - double)
//take care with values near double max and min limits ???
QPointF divide::findLineEndPoint( double startX, double startY,
double length, double angle ) //radians
{
auto endPoint = ( std::complex<double> ( startX, startY )
+ std::polar<double> ( length, angle ) );
return ( QPointF ( endPoint.real(), endPoint.imag() ) );
}
void divide::drawTick( QPointF startX, double tickLength, double tickAngle )
{
QPointF result = findLineEndPoint( startX.rx(), startX.ry(),
tickLength, tickAngle );
d->addLine( & startX, & result );
}
QPointF divide::findStartX( double radius, double angle, QPointF centerCircle )
{
QPointF startPoint;
startPoint.setX( ( radius * cos( angle ) ) + centerCircle.rx() );
startPoint.setY( ( radius * sin( angle ) ) + centerCircle.ry() );
return startPoint;
}
void divide::gotReturnedDataSlot( QString data )
{
returnedData = data;
}
QString divide::getStrData( Plug_Entity *ent )
{
if ( nullptr == ent )
return QString( "%1\n" ).arg( tr( "Empty Entity" ) );
QHash<int, QVariant> data;
QString strData(""),
strEntity( "%1\n" ),
strCommon( " %1: %2\n" ),
strSpecific( " %1: %2\n" ),
strSpecificXY( QString(" %1: %2=%3 %4=%5\n")
.arg("%1",tr("X"), "%2",tr("Y"),"%3") );
double numA {0.0};
double numB {0.0};
double numC {0.0};
QPointF ptA, ptB, ptC;
//common entity data
ent->getData(&data);
strData = strCommon.arg(tr("Layer")).arg(data.value(DPI::LAYER).toString());
int col = data.value(DPI::COLOR).toInt();
strData.append( strCommon.arg(tr("Color")).arg( ent->intColor2str(col)) );
strData.append( strCommon.arg(tr("Line type"))
.arg(data.value(DPI::LTYPE).toString()) );
strData.append( strCommon.arg(tr("Line thickness"))
.arg(data.value(DPI::LWIDTH).toString()) );
strData.append( strCommon.arg(tr("ID")).arg(data.value(DPI::EID).toLongLong()) );
//specific entity data
int et = data.value(DPI::ETYPE).toInt();
switch ( et )
{
case DPI::LINE:
strData.prepend( strEntity.arg(tr("LINE")));
ptA.setX( data.value(DPI::STARTX).toDouble());
ptA.setY( data.value(DPI::STARTY).toDouble());
ptB.setX( data.value(DPI::ENDX).toDouble());
ptB.setY( data.value(DPI::ENDY).toDouble());
strData.append( strSpecificXY.arg(tr("from point")).
arg(d->realToStr(ptA.x())).
arg(d->realToStr(ptA.y())));
strData.append( strSpecificXY.arg(tr("to point")).
arg(d->realToStr(ptB.x())).
arg(d->realToStr(ptB.y())));
ptC = ptB - ptA;
numA = sqrt( (ptC.x()*ptC.x()) + (ptC.y()*ptC.y()) );
strData.append( strSpecific.arg(tr("length")).arg( d->realToStr(numA)));
numB = asin(ptC.y() / numA);
numC = numB*180/M_PI;
if (ptC.x() < 0) numC = 180 - numC;
if (numC < 0) numC = 360 + numC;
strData.append( strSpecific.arg(tr("Angle in XY plane"))
.arg(d->realToStr(numC)));
strData.append( strSpecificXY.arg(tr("Inc.")).
arg(d->realToStr(ptC.x())).
arg(d->realToStr(ptC.y())));
break;
case DPI::ARC:
strData.prepend( strEntity.arg(tr("ARC")));
strData.append( strSpecificXY.arg(tr("center point")).
arg(d->realToStr(data.value(DPI::STARTX).toDouble())).
arg(d->realToStr(data.value(DPI::STARTY).toDouble())));
numA = data.value(DPI::RADIUS).toDouble();
numB = data.value(DPI::STARTANGLE).toDouble();
numC = data.value(DPI::ENDANGLE).toDouble();
strData.append( strSpecific.arg(tr("radius")).arg(d->realToStr(numA)));
strData.append( strSpecific.arg(tr("initial angle"))
.arg(d->realToStr(numB*180/M_PI)) );
strData.append( strSpecific.arg(tr("final angle"))
.arg(d->realToStr(numC*180/M_PI)) );
if( numB > numC) {
numB -= 2.0 * M_PI;
}
strData.append( strSpecific.arg(tr("length"))
.arg( d->realToStr((numC-numB)*numA)) );
break;
case DPI::CIRCLE:
strData.prepend( strEntity.arg(tr("CIRCLE")));
strData.append( strSpecificXY.arg(tr("center point"))
.arg(d->realToStr(data.value(DPI::STARTX).toDouble()))
.arg(d->realToStr(data.value(DPI::STARTY).toDouble())));
numA = data.value(DPI::RADIUS).toDouble();
strData.append( strSpecific.arg(tr("radius")).arg(d->realToStr(numA)));
strData.append( strSpecific.arg(tr("circumference"))
.arg(d->realToStr(numA*2*M_PI)));
strData.append( strSpecific.arg(tr("area"))
.arg(d->realToStr(numA*numA*M_PI)));
break;
case DPI::POLYLINE: {
strData.prepend( strEntity.arg(tr("POLYLINE")));
strData.append( strSpecific.arg(tr("Closed"))
.arg( (0 == data.value(DPI::CLOSEPOLY).toInt())
? tr("No") : tr("Yes")));
strData.append( strSpecific.arg(tr("Vertices")).arg(""));
QList<Plug_VertexData> vl;
ent->getPolylineData(&vl);
int iVertices = vl.size();
for (int i = 0; i < iVertices; ++i) {
strData.append( strSpecificXY.arg(tr("in point")).
arg(d->realToStr(vl.at( i ).point.x()) ).
arg(d->realToStr(vl.at( i ).point.y())) );
//***
if ( 0.0 != vl.at( i ).bulge) { //was 0
//***
strData.append( strSpecific.arg( tr("radius") )
.arg(d->realToStr
(polylineRadius
(vl.at( i ), vl.at(( i + 1 ) % iVertices)))) );
}
}
break;
}
default:
strData.prepend( strEntity.arg
( tr( "MUST be a line, circle or arc" ) ) );
break;
}
return strData;
}
//return center of active window
QPoint divide::findWindowCentre()
{
QPoint centXY;
centXY.setX( ( QApplication::activeWindow()->width() / 2 )
+ QApplication::activeWindow()->x() );
centXY.setY( ( QApplication::activeWindow()->height() / 2 )
+ QApplication::activeWindow()->y() );
return (centXY);
}
double divide::polylineRadius( const Plug_VertexData& ptA,
const Plug_VertexData& ptB )
{
double dChord = sqrt( pow(ptA.point.x() - ptB.point.x(), 2) +
pow(ptA.point.y() - ptB.point.y(), 2) );
return fabs( 0.5 * dChord / sin( 2.0 * atan( ptA.bulge ) ) );
}