Sketcher: Reorder rounded rectangle code next to normal rectangle code
This commit is contained in:
@@ -615,6 +615,429 @@ bool CmdSketcherCreateRectangle::isActive(void)
|
||||
return isCreateGeoActive(getActiveGuiDocument());
|
||||
}
|
||||
|
||||
/* Create rounded oblong =======================================================*/
|
||||
|
||||
class DrawSketchHandlerOblong : public DrawSketchHandler
|
||||
{
|
||||
public:
|
||||
DrawSketchHandlerOblong()
|
||||
: Mode(STATUS_SEEK_First)
|
||||
, lengthX(0), lengthY(0), radius(0), signX(1), signY(1)
|
||||
, EditCurve(37)
|
||||
{
|
||||
}
|
||||
virtual ~DrawSketchHandlerOblong() {}
|
||||
/// mode table
|
||||
enum BoxMode {
|
||||
STATUS_SEEK_First, /**< enum value ----. */
|
||||
STATUS_SEEK_Second, /**< enum value ----. */
|
||||
STATUS_End
|
||||
};
|
||||
|
||||
virtual void activated(ViewProviderSketch*)
|
||||
{
|
||||
setCrosshairCursor("Sketcher_Pointer_Oblong");
|
||||
}
|
||||
|
||||
virtual void mouseMove(Base::Vector2d onSketchPos)
|
||||
{
|
||||
|
||||
if (Mode == STATUS_SEEK_First) {
|
||||
setPositionText(onSketchPos);
|
||||
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) {
|
||||
renderSuggestConstraintsCursor(sugConstr1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (Mode == STATUS_SEEK_Second) {
|
||||
float distanceX = onSketchPos.x - StartPos.x;
|
||||
float distanceY = onSketchPos.y - StartPos.y;
|
||||
|
||||
lengthX = distanceX; lengthY = distanceY;
|
||||
signX = Base::sgn(distanceX);
|
||||
signY = Base::sgn(distanceY);
|
||||
if (fabs(distanceX) > fabs(distanceY)) {
|
||||
radius = fabs(distanceY) / 4; // we use a fourth of the smaller distance as default radius
|
||||
}
|
||||
else {
|
||||
radius = fabs(distanceX) / 4;
|
||||
}
|
||||
|
||||
// we draw the lines with 36 segments, 8 for each arc and 4 lines
|
||||
// draw the arcs
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// calculate the x,y positions forming the the arc
|
||||
double angle = i * M_PI / 16.0;
|
||||
double x_i = -radius * sin(angle);
|
||||
double y_i = -radius * cos(angle);
|
||||
// we are drawing clockwise starting with the arc that is besides StartPos
|
||||
if (signX == signY) {
|
||||
EditCurve[i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + signY * (radius + y_i));
|
||||
EditCurve[9 + i] = Base::Vector2d(StartPos.x + signY * (radius + y_i), StartPos.y + lengthY - signX * (radius + x_i));
|
||||
EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i));
|
||||
EditCurve[27 + i] = Base::Vector2d(StartPos.x + lengthX - signY * (radius + y_i), StartPos.y + signX * (radius + x_i));
|
||||
}
|
||||
else {
|
||||
EditCurve[i] = Base::Vector2d(StartPos.x - signY * (radius + y_i), StartPos.y - signX * (radius + x_i));
|
||||
EditCurve[9 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + signY * (radius + y_i));
|
||||
EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX + signY * (radius + y_i), StartPos.y + lengthY + signX * (radius + x_i));
|
||||
EditCurve[27 + i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i));
|
||||
}
|
||||
}
|
||||
// draw the lines
|
||||
if (signX == signY) {
|
||||
EditCurve[8] = Base::Vector2d(StartPos.x, StartPos.y + (signY * radius));
|
||||
EditCurve[17] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y + lengthY);
|
||||
EditCurve[26] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + lengthY - (signY * radius));
|
||||
EditCurve[35] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y);
|
||||
}
|
||||
else {
|
||||
EditCurve[8] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y);
|
||||
EditCurve[17] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + (signY * radius));
|
||||
EditCurve[26] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY);
|
||||
EditCurve[35] = Base::Vector2d(StartPos.x, StartPos.y + lengthY - (signY * radius));
|
||||
}
|
||||
// close the curve
|
||||
EditCurve[36] = EditCurve[0];
|
||||
|
||||
SbString text;
|
||||
text.sprintf(" (%.1fR %.1fX %.1fY)", radius, lengthX, lengthY);
|
||||
setPositionText(onSketchPos, text);
|
||||
|
||||
sketchgui->drawEdit(EditCurve);
|
||||
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f, 0.f))) {
|
||||
renderSuggestConstraintsCursor(sugConstr2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
applyCursor();
|
||||
}
|
||||
|
||||
virtual bool pressButton(Base::Vector2d onSketchPos)
|
||||
{
|
||||
if (Mode == STATUS_SEEK_First) {
|
||||
StartPos = onSketchPos;
|
||||
Mode = STATUS_SEEK_Second;
|
||||
}
|
||||
else {
|
||||
EndPos = onSketchPos;
|
||||
Mode = STATUS_End;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool releaseButton(Base::Vector2d onSketchPos)
|
||||
{
|
||||
Q_UNUSED(onSketchPos);
|
||||
if (Mode == STATUS_End) {
|
||||
unsetCursor();
|
||||
resetPositionText();
|
||||
|
||||
int firstCurve = getHighestCurveIndex() + 1;
|
||||
// add the geometry to the sketch
|
||||
// first determine the angles for the first arc
|
||||
double start = 0;
|
||||
double end = M_PI / 2;
|
||||
if (signX > 0 && signY > 0) {
|
||||
start = -2 * end;
|
||||
end = -1 * end;
|
||||
}
|
||||
else if (signX > 0 && signY < 0) {
|
||||
start = end;
|
||||
end = 2 * end;
|
||||
}
|
||||
else if (signX < 0 && signY > 0) {
|
||||
start = -1 * end;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add rounded rectangle"));
|
||||
Gui::Command::doCommand(Gui::Command::Doc,
|
||||
// syntax for arcs: Part.ArcOfCircle(Part.Circle(center, axis, radius), startangle, endangle)
|
||||
"geoList = []\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"%s.addGeometry(geoList, %s)\n"
|
||||
"conList = []\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Horizontal', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Horizontal', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Vertical', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Vertical', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
|
||||
"%s.addConstraint(conList)\n",
|
||||
StartPos.x + (signX * radius), StartPos.y + (signY * radius), // center of the arc 1
|
||||
radius,
|
||||
start, end, // start and end angle of arc1
|
||||
EditCurve[8].x, EditCurve[8].y, EditCurve[9].x, EditCurve[9].y, // line 1
|
||||
signX == signY ? StartPos.x + (signX * radius) : StartPos.x + lengthX - (signX * radius), // center of the arc 2
|
||||
signX == signY ? StartPos.y + lengthY - (signY * radius) : StartPos.y + (signY * radius),
|
||||
radius,
|
||||
// start and end angle of arc 2
|
||||
// the logic is that end is start + M_PI / 2 and start is the previous end - M_PI
|
||||
end - M_PI,
|
||||
end - 0.5 * M_PI,
|
||||
EditCurve[17].x, EditCurve[17].y, EditCurve[18].x, EditCurve[18].y, // line 2
|
||||
StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY - (signY * radius), // center of the arc 3
|
||||
radius,
|
||||
end - 1.5 * M_PI,
|
||||
end - M_PI,
|
||||
EditCurve[26].x, EditCurve[26].y, EditCurve[27].x, EditCurve[27].y, // line 3
|
||||
signX == signY ? StartPos.x + lengthX - (signX * radius) : StartPos.x + (signX * radius), // center of the arc 4
|
||||
signX == signY ? StartPos.y + (signY * radius) : StartPos.y + lengthY - (signY * radius),
|
||||
radius,
|
||||
end - 2 * M_PI,
|
||||
end - 1.5 * M_PI,
|
||||
EditCurve[35].x, EditCurve[35].y, EditCurve[36].x, EditCurve[36].y, // line 4
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
|
||||
geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not
|
||||
firstCurve, firstCurve + 1, // tangent 1
|
||||
firstCurve + 1, firstCurve + 2, // tangent 2
|
||||
firstCurve + 2, firstCurve + 3, // tangent 3
|
||||
firstCurve + 3, firstCurve + 4, // tangent 4
|
||||
firstCurve + 4, firstCurve + 5, // tangent 5
|
||||
firstCurve + 5, firstCurve + 6, // tangent 6
|
||||
firstCurve + 6, firstCurve + 7, // tangent 7
|
||||
firstCurve + 7, firstCurve, // tangent 8
|
||||
signX == signY ? firstCurve + 3 : firstCurve + 1, // horizontal constraint
|
||||
signX == signY ? firstCurve + 7 : firstCurve + 5, // horizontal constraint
|
||||
signX == signY ? firstCurve + 1 : firstCurve + 3, // vertical constraint
|
||||
signX == signY ? firstCurve + 5 : firstCurve + 7, // vertical constraint
|
||||
firstCurve, firstCurve + 2, // equal 1
|
||||
firstCurve + 2, firstCurve + 4, // equal 2
|
||||
firstCurve + 4, firstCurve + 6, // equal 3
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
|
||||
|
||||
// now add contruction geometry - two points used to take suggested constraints
|
||||
Gui::Command::doCommand(Gui::Command::Doc,
|
||||
"geoList = []\n"
|
||||
"geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n"
|
||||
"%s.addGeometry(geoList, True)\n" // geometry as construction
|
||||
"conList = []\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"%s.addConstraint(conList)\n",
|
||||
StartPos.x, StartPos.y, // point at StartPos
|
||||
EndPos.x, EndPos.y, // point at EndPos
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
|
||||
firstCurve + 8, firstCurve + 1, // point on object constraint
|
||||
firstCurve + 8, firstCurve + 7, // point on object constraint
|
||||
firstCurve + 9, firstCurve + 3, // point on object constraint
|
||||
firstCurve + 9, firstCurve + 5, // point on object constraint
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
|
||||
|
||||
Gui::Command::commitCommand();
|
||||
|
||||
// add auto constraints at the StartPos auxiliar point
|
||||
if (sugConstr1.size() > 0) {
|
||||
createAutoConstraints(sugConstr1, getHighestCurveIndex() - 1, Sketcher::start);
|
||||
sugConstr1.clear();
|
||||
}
|
||||
|
||||
// add auto constraints at the EndPos auxiliar point
|
||||
if (sugConstr2.size() > 0) {
|
||||
createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::start);
|
||||
sugConstr2.clear();
|
||||
}
|
||||
|
||||
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
Base::Console().Error("Failed to add rounded rectangle: %s\n", e.what());
|
||||
Gui::Command::abortCommand();
|
||||
|
||||
tryAutoRecompute(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
|
||||
}
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
|
||||
bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true);
|
||||
|
||||
if (continuousMode) {
|
||||
// This code enables the continuous creation mode.
|
||||
Mode = STATUS_SEEK_First;
|
||||
EditCurve.clear();
|
||||
sketchgui->drawEdit(EditCurve);
|
||||
EditCurve.resize(37);
|
||||
applyCursor();
|
||||
/* this is ok not to call to purgeHandler
|
||||
* in continuous creation mode because the
|
||||
* handler is destroyed by the quit() method on pressing the
|
||||
* right button of the mouse */
|
||||
}
|
||||
else {
|
||||
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected:
|
||||
BoxMode Mode;
|
||||
Base::Vector2d StartPos, EndPos;
|
||||
double lengthX, lengthY, radius;
|
||||
float signX, signY;
|
||||
std::vector<Base::Vector2d> EditCurve;
|
||||
std::vector<AutoConstraint> sugConstr1, sugConstr2;
|
||||
};
|
||||
|
||||
DEF_STD_CMD_AU(CmdSketcherCreateOblong)
|
||||
|
||||
CmdSketcherCreateOblong::CmdSketcherCreateOblong()
|
||||
: Command("Sketcher_CreateOblong")
|
||||
{
|
||||
sAppModule = "Sketcher";
|
||||
sGroup = QT_TR_NOOP("Sketcher");
|
||||
sMenuText = QT_TR_NOOP("Create rounded rectangle");
|
||||
sToolTipText = QT_TR_NOOP("Create a rounded rectangle in the sketch");
|
||||
sWhatsThis = "Sketcher_CreateOblong";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Sketcher_CreateOblong";
|
||||
sAccel = "";
|
||||
eType = ForEdit;
|
||||
}
|
||||
|
||||
void CmdSketcherCreateOblong::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong());
|
||||
}
|
||||
|
||||
void CmdSketcherCreateOblong::updateAction(int mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case Normal:
|
||||
if (getAction())
|
||||
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
|
||||
break;
|
||||
case Construction:
|
||||
if (getAction())
|
||||
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CmdSketcherCreateOblong::isActive(void)
|
||||
{
|
||||
return isCreateGeoActive(getActiveGuiDocument());
|
||||
}
|
||||
|
||||
/* Rectangles Comp command =========================================*/
|
||||
|
||||
DEF_STD_CMD_ACLU(CmdSketcherCompCreateRectangles)
|
||||
|
||||
CmdSketcherCompCreateRectangles::CmdSketcherCompCreateRectangles()
|
||||
: Command("Sketcher_CompCreateRectangles")
|
||||
{
|
||||
sAppModule = "Sketcher";
|
||||
sGroup = QT_TR_NOOP("Sketcher");
|
||||
sMenuText = QT_TR_NOOP("Create rectangles");
|
||||
sToolTipText = QT_TR_NOOP("Creates a rectangle in the sketch");
|
||||
sWhatsThis = "Sketcher_CompCreateRectangles";
|
||||
sStatusTip = sToolTipText;
|
||||
eType = ForEdit;
|
||||
}
|
||||
|
||||
void CmdSketcherCompCreateRectangles::activated(int iMsg)
|
||||
{
|
||||
if (iMsg == 0)
|
||||
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBox());
|
||||
else if (iMsg == 1)
|
||||
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong());
|
||||
else
|
||||
return;
|
||||
|
||||
// Since the default icon is reset when enabling/disabling the command we have
|
||||
// to explicitly set the icon of the used command.
|
||||
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
||||
QList<QAction*> a = pcAction->actions();
|
||||
|
||||
assert(iMsg < a.size());
|
||||
pcAction->setIcon(a[iMsg]->icon());
|
||||
}
|
||||
|
||||
Gui::Action* CmdSketcherCompCreateRectangles::createAction(void)
|
||||
{
|
||||
Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
|
||||
pcAction->setDropDownMenu(true);
|
||||
applyCommandData(this->className(), pcAction);
|
||||
|
||||
QAction* arc1 = pcAction->addAction(QString());
|
||||
arc1->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle"));
|
||||
QAction* arc2 = pcAction->addAction(QString());
|
||||
arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
|
||||
|
||||
_pcAction = pcAction;
|
||||
languageChange();
|
||||
|
||||
pcAction->setIcon(arc1->icon());
|
||||
int defaultId = 0;
|
||||
pcAction->setProperty("defaultAction", QVariant(defaultId));
|
||||
|
||||
return pcAction;
|
||||
}
|
||||
|
||||
void CmdSketcherCompCreateRectangles::updateAction(int mode)
|
||||
{
|
||||
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(getAction());
|
||||
if (!pcAction)
|
||||
return;
|
||||
|
||||
QList<QAction*> a = pcAction->actions();
|
||||
int index = pcAction->property("defaultAction").toInt();
|
||||
switch (mode) {
|
||||
case Normal:
|
||||
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle"));
|
||||
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
|
||||
getAction()->setIcon(a[index]->icon());
|
||||
break;
|
||||
case Construction:
|
||||
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Constr"));
|
||||
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr"));
|
||||
getAction()->setIcon(a[index]->icon());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CmdSketcherCompCreateRectangles::languageChange()
|
||||
{
|
||||
Command::languageChange();
|
||||
|
||||
if (!_pcAction)
|
||||
return;
|
||||
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
||||
QList<QAction*> a = pcAction->actions();
|
||||
|
||||
QAction* rectangle1 = a[0];
|
||||
rectangle1->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rectangle"));
|
||||
rectangle1->setToolTip(QApplication::translate("Sketcher_CreateRectangle", "Create a rectangle"));
|
||||
rectangle1->setStatusTip(rectangle1->toolTip());
|
||||
QAction* rectangle2 = a[1];
|
||||
rectangle2->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rounded rectangle"));
|
||||
rectangle2->setToolTip(QApplication::translate("Sketcher_CreateOblong", "Create a rounded rectangle"));
|
||||
rectangle2->setStatusTip(rectangle2->toolTip());
|
||||
}
|
||||
|
||||
bool CmdSketcherCompCreateRectangles::isActive(void)
|
||||
{
|
||||
return isCreateGeoActive(getActiveGuiDocument());
|
||||
}
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
@@ -6637,432 +7060,6 @@ bool CmdSketcherCreateSlot::isActive(void)
|
||||
return isCreateGeoActive(getActiveGuiDocument());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create rounded oblong
|
||||
*/
|
||||
class DrawSketchHandlerOblong : public DrawSketchHandler
|
||||
{
|
||||
public:
|
||||
DrawSketchHandlerOblong()
|
||||
: Mode(STATUS_SEEK_First)
|
||||
, lengthX(0), lengthY(0), radius(0), signX(1), signY(1)
|
||||
, EditCurve(37)
|
||||
{
|
||||
}
|
||||
virtual ~DrawSketchHandlerOblong() {}
|
||||
/// mode table
|
||||
enum BoxMode {
|
||||
STATUS_SEEK_First, /**< enum value ----. */
|
||||
STATUS_SEEK_Second, /**< enum value ----. */
|
||||
STATUS_End
|
||||
};
|
||||
|
||||
virtual void activated(ViewProviderSketch*)
|
||||
{
|
||||
setCrosshairCursor("Sketcher_Pointer_Oblong");
|
||||
}
|
||||
|
||||
virtual void mouseMove(Base::Vector2d onSketchPos)
|
||||
{
|
||||
|
||||
if (Mode == STATUS_SEEK_First) {
|
||||
setPositionText(onSketchPos);
|
||||
if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) {
|
||||
renderSuggestConstraintsCursor(sugConstr1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (Mode == STATUS_SEEK_Second) {
|
||||
float distanceX = onSketchPos.x - StartPos.x;
|
||||
float distanceY = onSketchPos.y - StartPos.y;
|
||||
|
||||
lengthX = distanceX; lengthY = distanceY;
|
||||
signX = Base::sgn(distanceX);
|
||||
signY = Base::sgn(distanceY);
|
||||
if (fabs(distanceX) > fabs(distanceY)) {
|
||||
radius = fabs(distanceY) / 4; // we use a fourth of the smaller distance as default radius
|
||||
}
|
||||
else {
|
||||
radius = fabs(distanceX) / 4;
|
||||
}
|
||||
|
||||
// we draw the lines with 36 segments, 8 for each arc and 4 lines
|
||||
// draw the arcs
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// calculate the x,y positions forming the the arc
|
||||
double angle = i * M_PI / 16.0;
|
||||
double x_i = -radius * sin(angle);
|
||||
double y_i = -radius * cos(angle);
|
||||
// we are drawing clockwise starting with the arc that is besides StartPos
|
||||
if (signX == signY) {
|
||||
EditCurve[i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + signY * (radius + y_i));
|
||||
EditCurve[9 + i] = Base::Vector2d(StartPos.x + signY * (radius + y_i), StartPos.y + lengthY - signX * (radius + x_i));
|
||||
EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i));
|
||||
EditCurve[27 + i] = Base::Vector2d(StartPos.x + lengthX - signY * (radius + y_i), StartPos.y + signX * (radius + x_i));
|
||||
}
|
||||
else {
|
||||
EditCurve[i] = Base::Vector2d(StartPos.x - signY * (radius + y_i), StartPos.y - signX * (radius + x_i));
|
||||
EditCurve[9 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + signY * (radius + y_i));
|
||||
EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX + signY * (radius + y_i), StartPos.y + lengthY + signX * (radius + x_i));
|
||||
EditCurve[27 + i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i));
|
||||
}
|
||||
}
|
||||
// draw the lines
|
||||
if (signX == signY) {
|
||||
EditCurve[8] = Base::Vector2d(StartPos.x, StartPos.y + (signY * radius));
|
||||
EditCurve[17] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y + lengthY);
|
||||
EditCurve[26] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + lengthY - (signY * radius));
|
||||
EditCurve[35] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y);
|
||||
}
|
||||
else {
|
||||
EditCurve[8] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y);
|
||||
EditCurve[17] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + (signY * radius));
|
||||
EditCurve[26] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY);
|
||||
EditCurve[35] = Base::Vector2d(StartPos.x, StartPos.y + lengthY - (signY * radius));
|
||||
}
|
||||
// close the curve
|
||||
EditCurve[36] = EditCurve[0];
|
||||
|
||||
SbString text;
|
||||
text.sprintf(" (%.1fR %.1fX %.1fY)", radius, lengthX, lengthY);
|
||||
setPositionText(onSketchPos, text);
|
||||
|
||||
sketchgui->drawEdit(EditCurve);
|
||||
if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f, 0.f))) {
|
||||
renderSuggestConstraintsCursor(sugConstr2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
applyCursor();
|
||||
}
|
||||
|
||||
virtual bool pressButton(Base::Vector2d onSketchPos)
|
||||
{
|
||||
if (Mode == STATUS_SEEK_First) {
|
||||
StartPos = onSketchPos;
|
||||
Mode = STATUS_SEEK_Second;
|
||||
}
|
||||
else {
|
||||
EndPos = onSketchPos;
|
||||
Mode = STATUS_End;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool releaseButton(Base::Vector2d onSketchPos)
|
||||
{
|
||||
Q_UNUSED(onSketchPos);
|
||||
if (Mode == STATUS_End) {
|
||||
unsetCursor();
|
||||
resetPositionText();
|
||||
|
||||
int firstCurve = getHighestCurveIndex() + 1;
|
||||
// add the geometry to the sketch
|
||||
// first determine the angles for the first arc
|
||||
double start = 0;
|
||||
double end = M_PI / 2;
|
||||
if (signX > 0 && signY > 0) {
|
||||
start = -2 * end;
|
||||
end = -1 * end;
|
||||
}
|
||||
else if (signX > 0 && signY < 0) {
|
||||
start = end;
|
||||
end = 2 * end;
|
||||
}
|
||||
else if (signX < 0 && signY > 0) {
|
||||
start = -1 * end;
|
||||
end = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add rounded rectangle"));
|
||||
Gui::Command::doCommand(Gui::Command::Doc,
|
||||
// syntax for arcs: Part.ArcOfCircle(Part.Circle(center, axis, radius), startangle, endangle)
|
||||
"geoList = []\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n"
|
||||
"geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n"
|
||||
"%s.addGeometry(geoList, %s)\n"
|
||||
"conList = []\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n"
|
||||
"conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n"
|
||||
"conList.append(Sketcher.Constraint('Horizontal', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Horizontal', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Vertical', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Vertical', %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
|
||||
"conList.append(Sketcher.Constraint('Equal', %i, %i))\n"
|
||||
"%s.addConstraint(conList)\n",
|
||||
StartPos.x + (signX * radius), StartPos.y + (signY * radius), // center of the arc 1
|
||||
radius,
|
||||
start, end, // start and end angle of arc1
|
||||
EditCurve[8].x, EditCurve[8].y, EditCurve[9].x, EditCurve[9].y, // line 1
|
||||
signX == signY ? StartPos.x + (signX * radius) : StartPos.x + lengthX - (signX * radius), // center of the arc 2
|
||||
signX == signY ? StartPos.y + lengthY - (signY * radius) : StartPos.y + (signY * radius),
|
||||
radius,
|
||||
// start and end angle of arc 2
|
||||
// the logic is that end is start + M_PI / 2 and start is the previous end - M_PI
|
||||
end - M_PI,
|
||||
end - 0.5 * M_PI,
|
||||
EditCurve[17].x, EditCurve[17].y, EditCurve[18].x, EditCurve[18].y, // line 2
|
||||
StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY - (signY * radius), // center of the arc 3
|
||||
radius,
|
||||
end - 1.5 * M_PI,
|
||||
end - M_PI,
|
||||
EditCurve[26].x, EditCurve[26].y, EditCurve[27].x, EditCurve[27].y, // line 3
|
||||
signX == signY ? StartPos.x + lengthX - (signX * radius) : StartPos.x + (signX * radius), // center of the arc 4
|
||||
signX == signY ? StartPos.y + (signY * radius) : StartPos.y + lengthY - (signY * radius),
|
||||
radius,
|
||||
end - 2 * M_PI,
|
||||
end - 1.5 * M_PI,
|
||||
EditCurve[35].x, EditCurve[35].y, EditCurve[36].x, EditCurve[36].y, // line 4
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
|
||||
geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not
|
||||
firstCurve, firstCurve + 1, // tangent 1
|
||||
firstCurve + 1, firstCurve + 2, // tangent 2
|
||||
firstCurve + 2, firstCurve + 3, // tangent 3
|
||||
firstCurve + 3, firstCurve + 4, // tangent 4
|
||||
firstCurve + 4, firstCurve + 5, // tangent 5
|
||||
firstCurve + 5, firstCurve + 6, // tangent 6
|
||||
firstCurve + 6, firstCurve + 7, // tangent 7
|
||||
firstCurve + 7, firstCurve, // tangent 8
|
||||
signX == signY ? firstCurve + 3 : firstCurve + 1, // horizontal constraint
|
||||
signX == signY ? firstCurve + 7 : firstCurve + 5, // horizontal constraint
|
||||
signX == signY ? firstCurve + 1 : firstCurve + 3, // vertical constraint
|
||||
signX == signY ? firstCurve + 5 : firstCurve + 7, // vertical constraint
|
||||
firstCurve, firstCurve + 2, // equal 1
|
||||
firstCurve + 2, firstCurve + 4, // equal 2
|
||||
firstCurve + 4, firstCurve + 6, // equal 3
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
|
||||
|
||||
// now add contruction geometry - two points used to take suggested constraints
|
||||
Gui::Command::doCommand(Gui::Command::Doc,
|
||||
"geoList = []\n"
|
||||
"geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n"
|
||||
"geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n"
|
||||
"%s.addGeometry(geoList, True)\n" // geometry as construction
|
||||
"conList = []\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n"
|
||||
"%s.addConstraint(conList)\n",
|
||||
StartPos.x, StartPos.y, // point at StartPos
|
||||
EndPos.x, EndPos.y, // point at EndPos
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch
|
||||
firstCurve + 8, firstCurve + 1, // point on object constraint
|
||||
firstCurve + 8, firstCurve + 7, // point on object constraint
|
||||
firstCurve + 9, firstCurve + 3, // point on object constraint
|
||||
firstCurve + 9, firstCurve + 5, // point on object constraint
|
||||
Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch
|
||||
|
||||
Gui::Command::commitCommand();
|
||||
|
||||
// add auto constraints at the StartPos auxiliar point
|
||||
if (sugConstr1.size() > 0) {
|
||||
createAutoConstraints(sugConstr1, getHighestCurveIndex() - 1, Sketcher::start);
|
||||
sugConstr1.clear();
|
||||
}
|
||||
|
||||
// add auto constraints at the EndPos auxiliar point
|
||||
if (sugConstr2.size() > 0) {
|
||||
createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::start);
|
||||
sugConstr2.clear();
|
||||
}
|
||||
|
||||
tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
Base::Console().Error("Failed to add rounded rectangle: %s\n", e.what());
|
||||
Gui::Command::abortCommand();
|
||||
|
||||
tryAutoRecompute(static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
|
||||
}
|
||||
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
|
||||
bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true);
|
||||
|
||||
if (continuousMode) {
|
||||
// This code enables the continuous creation mode.
|
||||
Mode = STATUS_SEEK_First;
|
||||
EditCurve.clear();
|
||||
sketchgui->drawEdit(EditCurve);
|
||||
EditCurve.resize(37);
|
||||
applyCursor();
|
||||
/* this is ok not to call to purgeHandler
|
||||
* in continuous creation mode because the
|
||||
* handler is destroyed by the quit() method on pressing the
|
||||
* right button of the mouse */
|
||||
}
|
||||
else {
|
||||
sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected:
|
||||
BoxMode Mode;
|
||||
Base::Vector2d StartPos, EndPos;
|
||||
double lengthX, lengthY, radius;
|
||||
float signX, signY;
|
||||
std::vector<Base::Vector2d> EditCurve;
|
||||
std::vector<AutoConstraint> sugConstr1, sugConstr2;
|
||||
};
|
||||
|
||||
DEF_STD_CMD_AU(CmdSketcherCreateOblong)
|
||||
|
||||
CmdSketcherCreateOblong::CmdSketcherCreateOblong()
|
||||
: Command("Sketcher_CreateOblong")
|
||||
{
|
||||
sAppModule = "Sketcher";
|
||||
sGroup = QT_TR_NOOP("Sketcher");
|
||||
sMenuText = QT_TR_NOOP("Create rounded rectangle");
|
||||
sToolTipText = QT_TR_NOOP("Create a rounded rectangle in the sketch");
|
||||
sWhatsThis = "Sketcher_CreateOblong";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Sketcher_CreateOblong";
|
||||
sAccel = "";
|
||||
eType = ForEdit;
|
||||
}
|
||||
|
||||
void CmdSketcherCreateOblong::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong());
|
||||
}
|
||||
|
||||
void CmdSketcherCreateOblong::updateAction(int mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case Normal:
|
||||
if (getAction())
|
||||
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
|
||||
break;
|
||||
case Construction:
|
||||
if (getAction())
|
||||
getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CmdSketcherCreateOblong::isActive(void)
|
||||
{
|
||||
return isCreateGeoActive(getActiveGuiDocument());
|
||||
}
|
||||
|
||||
|
||||
DEF_STD_CMD_ACLU(CmdSketcherCompCreateRectangles)
|
||||
|
||||
CmdSketcherCompCreateRectangles::CmdSketcherCompCreateRectangles()
|
||||
: Command("Sketcher_CompCreateRectangles")
|
||||
{
|
||||
sAppModule = "Sketcher";
|
||||
sGroup = QT_TR_NOOP("Sketcher");
|
||||
sMenuText = QT_TR_NOOP("Create rectangles");
|
||||
sToolTipText = QT_TR_NOOP("Creates a rectangle in the sketch");
|
||||
sWhatsThis = "Sketcher_CompCreateRectangles";
|
||||
sStatusTip = sToolTipText;
|
||||
eType = ForEdit;
|
||||
}
|
||||
|
||||
void CmdSketcherCompCreateRectangles::activated(int iMsg)
|
||||
{
|
||||
if (iMsg == 0)
|
||||
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBox());
|
||||
else if (iMsg == 1)
|
||||
ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong());
|
||||
else
|
||||
return;
|
||||
|
||||
// Since the default icon is reset when enabling/disabling the command we have
|
||||
// to explicitly set the icon of the used command.
|
||||
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
||||
QList<QAction*> a = pcAction->actions();
|
||||
|
||||
assert(iMsg < a.size());
|
||||
pcAction->setIcon(a[iMsg]->icon());
|
||||
}
|
||||
|
||||
Gui::Action* CmdSketcherCompCreateRectangles::createAction(void)
|
||||
{
|
||||
Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
|
||||
pcAction->setDropDownMenu(true);
|
||||
applyCommandData(this->className(), pcAction);
|
||||
|
||||
QAction* arc1 = pcAction->addAction(QString());
|
||||
arc1->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle"));
|
||||
QAction* arc2 = pcAction->addAction(QString());
|
||||
arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
|
||||
|
||||
_pcAction = pcAction;
|
||||
languageChange();
|
||||
|
||||
pcAction->setIcon(arc1->icon());
|
||||
int defaultId = 0;
|
||||
pcAction->setProperty("defaultAction", QVariant(defaultId));
|
||||
|
||||
return pcAction;
|
||||
}
|
||||
|
||||
void CmdSketcherCompCreateRectangles::updateAction(int mode)
|
||||
{
|
||||
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(getAction());
|
||||
if (!pcAction)
|
||||
return;
|
||||
|
||||
QList<QAction*> a = pcAction->actions();
|
||||
int index = pcAction->property("defaultAction").toInt();
|
||||
switch (mode) {
|
||||
case Normal:
|
||||
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle"));
|
||||
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong"));
|
||||
getAction()->setIcon(a[index]->icon());
|
||||
break;
|
||||
case Construction:
|
||||
a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Constr"));
|
||||
a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr"));
|
||||
getAction()->setIcon(a[index]->icon());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CmdSketcherCompCreateRectangles::languageChange()
|
||||
{
|
||||
Command::languageChange();
|
||||
|
||||
if (!_pcAction)
|
||||
return;
|
||||
Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
|
||||
QList<QAction*> a = pcAction->actions();
|
||||
|
||||
QAction* rectangle1 = a[0];
|
||||
rectangle1->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rectangle"));
|
||||
rectangle1->setToolTip(QApplication::translate("Sketcher_CreateRectangle", "Create a rectangle"));
|
||||
rectangle1->setStatusTip(rectangle1->toolTip());
|
||||
QAction* rectangle2 = a[1];
|
||||
rectangle2->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rounded rectangle"));
|
||||
rectangle2->setToolTip(QApplication::translate("Sketcher_CreateOblong", "Create a rounded rectangle"));
|
||||
rectangle2->setStatusTip(rectangle2->toolTip());
|
||||
}
|
||||
|
||||
bool CmdSketcherCompCreateRectangles::isActive(void)
|
||||
{
|
||||
return isCreateGeoActive(getActiveGuiDocument());
|
||||
}
|
||||
|
||||
|
||||
/* Create Regular Polygon ==============================================*/
|
||||
|
||||
class DrawSketchHandlerRegularPolygon: public DrawSketchHandler
|
||||
|
||||
Reference in New Issue
Block a user