======================================== Fonts: - SoDatumLabel internally uses QFont and thus points. It was being fed pixels, for which the configured size in preferences did not match the actual on screen size. This is corrected. - Font sizes fed directly from parameters to the coin nodes skeleton were not being updated, as the parameter observer is initialised after the nodes are created, and the parameter observer was not configured to update this nodes. This is corrected. Colors: - Some color parameters were not being being updated by the parameter observer. This is corrected.
2399 lines
124 KiB
C++
2399 lines
124 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2021 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
#ifndef _PreComp_
|
|
# include <memory>
|
|
# include <QPainter>
|
|
# include <QRegularExpression>
|
|
|
|
# include <Inventor/SbImage.h>
|
|
# include <Inventor/SbVec3f.h>
|
|
# include <Inventor/SoPickedPoint.h>
|
|
# include <Inventor/nodes/SoAnnotation.h>
|
|
# include <Inventor/nodes/SoDrawStyle.h>
|
|
# include <Inventor/nodes/SoGroup.h>
|
|
# include <Inventor/nodes/SoImage.h>
|
|
# include <Inventor/nodes/SoInfo.h>
|
|
# include <Inventor/nodes/SoMaterial.h>
|
|
# include <Inventor/nodes/SoMaterialBinding.h>
|
|
# include <Inventor/nodes/SoPickStyle.h>
|
|
# include <Inventor/nodes/SoSeparator.h>
|
|
# include <Inventor/nodes/SoTranslation.h>
|
|
#endif // #ifndef _PreComp_
|
|
|
|
#include <Base/Converter.h>
|
|
#include <Base/Exception.h>
|
|
#include <Base/Tools.h>
|
|
#include <Base/UnitsApi.h>
|
|
#include <Base/Vector3D.h>
|
|
#include <Gui/BitmapFactory.h>
|
|
#include <Gui/Tools.h>
|
|
#include <Gui/Utilities.h>
|
|
#include <Gui/Inventor/SmSwitchboard.h>
|
|
#include <Mod/Part/App/Geometry.h>
|
|
#include <Mod/Sketcher/App/GeometryFacade.h>
|
|
#include <Mod/Sketcher/App/SolverGeometryExtension.h>
|
|
#include <Mod/Sketcher/App/GeoEnum.h>
|
|
#include <Mod/Sketcher/App/Constraint.h>
|
|
#include <Mod/Sketcher/App/GeoList.h>
|
|
|
|
#include "EditModeConstraintCoinManager.h"
|
|
#include "SoDatumLabel.h"
|
|
#include "SoZoomTranslation.h"
|
|
#include "ViewProviderSketch.h"
|
|
#include "ViewProviderSketchCoinAttorney.h"
|
|
|
|
|
|
using namespace SketcherGui;
|
|
using namespace Sketcher;
|
|
|
|
//**************************** EditModeConstraintCoinManager class ******************************
|
|
|
|
EditModeConstraintCoinManager::EditModeConstraintCoinManager( ViewProviderSketch &vp,
|
|
DrawingParameters & drawingParams,
|
|
GeometryLayerParameters & geometryLayerParams,
|
|
ConstraintParameters & constraintParams,
|
|
EditModeScenegraphNodes & editModeScenegraph,
|
|
CoinMapping & coinMap):
|
|
viewProvider(vp),
|
|
drawingParameters(drawingParams),
|
|
geometryLayerParameters(geometryLayerParams),
|
|
constraintParameters(constraintParams),
|
|
editModeScenegraphNodes(editModeScenegraph),
|
|
coinMapping(coinMap)
|
|
{}
|
|
|
|
EditModeConstraintCoinManager::~EditModeConstraintCoinManager()
|
|
{}
|
|
|
|
void EditModeConstraintCoinManager::updateVirtualSpace()
|
|
{
|
|
const std::vector<Sketcher::Constraint *> &constrlist = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
|
|
|
|
bool isshownvirtualspace = ViewProviderSketchCoinAttorney::isShownVirtualSpace(viewProvider);
|
|
|
|
if (constrlist.size() == vConstrType.size()) {
|
|
|
|
editModeScenegraphNodes.constrGroup->enable.setNum(constrlist.size());
|
|
|
|
SbBool *sws = editModeScenegraphNodes.constrGroup->enable.startEditing();
|
|
|
|
for (size_t i = 0; i < constrlist.size(); i++)
|
|
sws[i] = !(constrlist[i]->isInVirtualSpace != isshownvirtualspace); // XOR of constraint mode and VP mode
|
|
|
|
|
|
editModeScenegraphNodes.constrGroup->enable.finishEditing();
|
|
}
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::processConstraints(const GeoListFacade & geolistfacade)
|
|
{
|
|
const auto &constrlist = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
|
|
|
|
auto zConstrH = ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider) * drawingParameters.zConstr;
|
|
|
|
// After an undo/redo it can happen that we have an empty geometry list but a non-empty constraint list
|
|
// In this case just ignore the constraints. (See bug #0000421)
|
|
if (geolistfacade.geomlist.size() <= 2 && !constrlist.empty()) {
|
|
rebuildConstraintNodes(geolistfacade);
|
|
return;
|
|
}
|
|
|
|
int extGeoCount = geolistfacade.getExternalCount();
|
|
int intGeoCount = geolistfacade.getInternalCount();
|
|
|
|
// reset point if the constraint type has changed
|
|
Restart:
|
|
// check if a new constraint arrived
|
|
if (constrlist.size() != vConstrType.size())
|
|
rebuildConstraintNodes(geolistfacade);
|
|
|
|
assert(int(constrlist.size()) == editModeScenegraphNodes.constrGroup->getNumChildren());
|
|
assert(int(vConstrType.size()) == editModeScenegraphNodes.constrGroup->getNumChildren());
|
|
|
|
// update the virtual space
|
|
updateVirtualSpace();
|
|
|
|
auto getNormal = [] (const GeoListFacade & geolistfacade, const int geoid, const Base::Vector3d & pointoncurve) {
|
|
auto geom = geolistfacade.getGeometryFromGeoId(geoid);
|
|
auto curve = dynamic_cast<const Part::GeomCurve *>(geom);
|
|
|
|
Base::Vector3d normal;
|
|
try {
|
|
if (!(curve && curve->normalAt(pointoncurve, normal))) {
|
|
normal = Base::Vector3d(1,0,0);
|
|
}
|
|
}
|
|
catch (const Base::CADKernelError&) {
|
|
normal = Base::Vector3d(1,0,0);
|
|
}
|
|
|
|
return normal;
|
|
};
|
|
|
|
// go through the constraints and update the position
|
|
int i = 0;
|
|
for (std::vector<Sketcher::Constraint *>::const_iterator it=constrlist.begin();
|
|
it != constrlist.end(); ++it, i++) {
|
|
// check if the type has changed
|
|
if ((*it)->Type != vConstrType[i]) {
|
|
// clearing the type vector will force a rebuild of the visual nodes
|
|
vConstrType.clear();
|
|
//TODO: The 'goto' here is unsafe as it can happen that we cause an endless loop (see bug #0001956).
|
|
goto Restart;
|
|
}
|
|
try{//because calculateNormalAtPoint, used in there, can throw
|
|
// root separator for this constraint
|
|
SoSeparator *sep = static_cast<SoSeparator *>(editModeScenegraphNodes.constrGroup->getChild(i));
|
|
const Constraint *Constr = *it;
|
|
|
|
if (Constr->First < -extGeoCount || Constr->First >= intGeoCount
|
|
|| (Constr->Second!=GeoEnum::GeoUndef
|
|
&& (Constr->Second < -extGeoCount || Constr->Second >= intGeoCount))
|
|
|| (Constr->Third!=GeoEnum::GeoUndef
|
|
&& (Constr->Third < -extGeoCount || Constr->Third >= intGeoCount)))
|
|
{
|
|
// Constraint can refer to non-existent geometry during undo/redo
|
|
continue;
|
|
}
|
|
|
|
// distinguish different constraint types to build up
|
|
switch (Constr->Type) {
|
|
case Block:
|
|
case Horizontal: // write the new position of the Horizontal constraint Same as vertical position.
|
|
case Vertical: // write the new position of the Vertical constraint
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
bool alignment = Constr->Type!=Block && Constr->Second != GeoEnum::GeoUndef;
|
|
|
|
// get the geometry
|
|
const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
|
|
if (!alignment) {
|
|
// Vertical & Horiz can only be a GeomLineSegment, but Blocked can be anything.
|
|
Base::Vector3d midpos;
|
|
Base::Vector3d dir;
|
|
Base::Vector3d norm;
|
|
|
|
if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg = static_cast<const Part::GeomLineSegment *>(geo);
|
|
|
|
// calculate the half distance between the start and endpoint
|
|
midpos = ((lineSeg->getEndPoint()+lineSeg->getStartPoint())/2);
|
|
|
|
//Get a set of vectors perpendicular and tangential to these
|
|
dir = (lineSeg->getEndPoint()-lineSeg->getStartPoint()).Normalize();
|
|
|
|
norm = Base::Vector3d(-dir.y,dir.x,0);
|
|
}
|
|
else if (geo->getTypeId() == Part::GeomBSplineCurve::getClassTypeId()) {
|
|
const Part::GeomBSplineCurve *bsp = static_cast<const Part::GeomBSplineCurve *>(geo);
|
|
midpos = Base::Vector3d(0,0,0);
|
|
|
|
std::vector<Base::Vector3d> poles = bsp->getPoles();
|
|
|
|
// Move center of gravity towards start not to collide with bspline degree information.
|
|
double ws = 1.0 / poles.size();
|
|
double w = 1.0;
|
|
|
|
for (std::vector<Base::Vector3d>::iterator it = poles.begin(); it != poles.end(); ++it) {
|
|
midpos += w*(*it);
|
|
w -= ws;
|
|
}
|
|
|
|
midpos /= poles.size();
|
|
|
|
dir = (bsp->getEndPoint() - bsp->getStartPoint()).Normalize();
|
|
norm = Base::Vector3d(-dir.y,dir.x,0);
|
|
}
|
|
else {
|
|
double ra=0,rb=0;
|
|
double angle,angleplus=0.;//angle = rotation of object as a whole; angleplus = arc angle (t parameter for ellipses).
|
|
if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo);
|
|
ra = circle->getRadius();
|
|
angle = M_PI/4;
|
|
midpos = circle->getCenter();
|
|
} else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo);
|
|
ra = arc->getRadius();
|
|
double startangle, endangle;
|
|
arc->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
angle = (startangle + endangle)/2;
|
|
midpos = arc->getCenter();
|
|
} else if (geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) {
|
|
const Part::GeomEllipse *ellipse = static_cast<const Part::GeomEllipse *>(geo);
|
|
ra = ellipse->getMajorRadius();
|
|
rb = ellipse->getMinorRadius();
|
|
Base::Vector3d majdir = ellipse->getMajorAxisDir();
|
|
angle = atan2(majdir.y, majdir.x);
|
|
angleplus = M_PI/4;
|
|
midpos = ellipse->getCenter();
|
|
} else if (geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) {
|
|
const Part::GeomArcOfEllipse *aoe = static_cast<const Part::GeomArcOfEllipse *>(geo);
|
|
ra = aoe->getMajorRadius();
|
|
rb = aoe->getMinorRadius();
|
|
double startangle, endangle;
|
|
aoe->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = aoe->getMajorAxisDir();
|
|
angle = atan2(majdir.y, majdir.x);
|
|
angleplus = (startangle + endangle)/2;
|
|
midpos = aoe->getCenter();
|
|
} else if (geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) {
|
|
const Part::GeomArcOfHyperbola *aoh = static_cast<const Part::GeomArcOfHyperbola *>(geo);
|
|
ra = aoh->getMajorRadius();
|
|
rb = aoh->getMinorRadius();
|
|
double startangle, endangle;
|
|
aoh->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = aoh->getMajorAxisDir();
|
|
angle = atan2(majdir.y, majdir.x);
|
|
angleplus = (startangle + endangle)/2;
|
|
midpos = aoh->getCenter();
|
|
} else if (geo->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) {
|
|
const Part::GeomArcOfParabola *aop = static_cast<const Part::GeomArcOfParabola *>(geo);
|
|
ra = aop->getFocal();
|
|
double startangle, endangle;
|
|
aop->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = - aop->getXAxisDir();
|
|
angle = atan2(majdir.y, majdir.x);
|
|
angleplus = (startangle + endangle)/2;
|
|
midpos = aop->getFocus();
|
|
} else
|
|
break;
|
|
|
|
if (geo->getTypeId() == Part::GeomEllipse::getClassTypeId() ||
|
|
geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ||
|
|
geo->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()){
|
|
|
|
Base::Vector3d majDir, minDir, rvec;
|
|
majDir = Base::Vector3d(cos(angle),sin(angle),0);//direction of major axis of ellipse
|
|
minDir = Base::Vector3d(-majDir.y,majDir.x,0);//direction of minor axis of ellipse
|
|
rvec = (ra*cos(angleplus)) * majDir + (rb*sin(angleplus)) * minDir;
|
|
midpos += rvec;
|
|
rvec.Normalize();
|
|
norm = rvec;
|
|
dir = Base::Vector3d(-rvec.y,rvec.x,0);//DeepSOIC: I'm not sure what dir is supposed to mean.
|
|
}
|
|
else {
|
|
norm = Base::Vector3d(cos(angle),sin(angle),0);
|
|
dir = Base::Vector3d(-norm.y,norm.x,0);
|
|
midpos += ra*norm;
|
|
}
|
|
}
|
|
|
|
Base::Vector3d relpos = seekConstraintPosition(midpos, norm, dir, 2.5, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(midpos.x, midpos.y, zConstrH); //Absolute Reference
|
|
|
|
//Reference Position that is scaled according to zoom
|
|
translation->translation = SbVec3f(relpos.x, relpos.y, 0);
|
|
}
|
|
else {
|
|
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
|
|
assert(Constr->FirstPos != Sketcher::PointPos::none && Constr->SecondPos != Sketcher::PointPos::none);
|
|
|
|
Base::Vector3d midpos1, dir1, norm1;
|
|
Base::Vector3d midpos2, dir2, norm2;
|
|
|
|
midpos1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
|
|
midpos2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
|
|
|
|
dir1 = (midpos2-midpos1).Normalize();
|
|
dir2 = -dir1;
|
|
norm1 = Base::Vector3d(-dir1.y,dir1.x,0.);
|
|
norm2 = norm1;
|
|
|
|
Base::Vector3d relpos1 = seekConstraintPosition(midpos1, norm1, dir1, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH);
|
|
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
|
|
|
|
Base::Vector3d relpos2 = seekConstraintPosition(midpos2, norm2, dir2, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
Base::Vector3d secondPos = midpos2 - midpos1;
|
|
|
|
translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(secondPos.x, secondPos.y, zConstrH);
|
|
translation->translation = SbVec3f(relpos2.x -relpos1.x, relpos2.y -relpos1.y, 0);
|
|
}
|
|
}
|
|
break;
|
|
case Perpendicular:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
|
|
// get the geometry
|
|
const Part::Geometry *geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
const Part::Geometry *geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
|
|
|
|
Base::Vector3d midpos1, dir1, norm1;
|
|
Base::Vector3d midpos2, dir2, norm2;
|
|
bool twoIcons = false;//a very local flag. It's set to true to indicate that the second dir+norm are valid and should be used
|
|
|
|
|
|
if (Constr->Third != GeoEnum::GeoUndef || //perpty via point
|
|
Constr->FirstPos != Sketcher::PointPos::none) { //endpoint-to-curve or endpoint-to-endpoint perpty
|
|
|
|
int ptGeoId;
|
|
Sketcher::PointPos ptPosId;
|
|
do {//dummy loop to use break =) Maybe goto?
|
|
ptGeoId = Constr->First;
|
|
ptPosId = Constr->FirstPos;
|
|
if (ptPosId != Sketcher::PointPos::none) break;
|
|
ptGeoId = Constr->Second;
|
|
ptPosId = Constr->SecondPos;
|
|
if (ptPosId != Sketcher::PointPos::none) break;
|
|
ptGeoId = Constr->Third;
|
|
ptPosId = Constr->ThirdPos;
|
|
if (ptPosId != Sketcher::PointPos::none) break;
|
|
assert(0);//no point found!
|
|
} while (false);
|
|
|
|
midpos1 = geolistfacade.getPoint(ptGeoId, ptPosId);
|
|
|
|
norm1 = getNormal(geolistfacade, Constr->Second, midpos1);
|
|
|
|
// TODO: Check the method above. This was the old one making use of the solver.
|
|
//norm1 = getSolvedSketch().calculateNormalAtPoint(Constr->Second, midpos1.x, midpos1.y);
|
|
|
|
norm1.Normalize();
|
|
dir1 = norm1; dir1.RotateZ(-M_PI/2.0);
|
|
|
|
} else if (Constr->FirstPos == Sketcher::PointPos::none) {
|
|
|
|
if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg1 = static_cast<const Part::GeomLineSegment *>(geo1);
|
|
midpos1 = ((lineSeg1->getEndPoint()+lineSeg1->getStartPoint())/2);
|
|
dir1 = (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()).Normalize();
|
|
norm1 = Base::Vector3d(-dir1.y,dir1.x,0.);
|
|
} else if (geo1->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo1);
|
|
double startangle, endangle, midangle;
|
|
arc->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
midangle = (startangle + endangle)/2;
|
|
norm1 = Base::Vector3d(cos(midangle),sin(midangle),0);
|
|
dir1 = Base::Vector3d(-norm1.y,norm1.x,0);
|
|
midpos1 = arc->getCenter() + arc->getRadius() * norm1;
|
|
} else if (geo1->getTypeId() == Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo1);
|
|
norm1 = Base::Vector3d(cos(M_PI/4),sin(M_PI/4),0);
|
|
dir1 = Base::Vector3d(-norm1.y,norm1.x,0);
|
|
midpos1 = circle->getCenter() + circle->getRadius() * norm1;
|
|
} else
|
|
break;
|
|
|
|
if (geo2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg2 = static_cast<const Part::GeomLineSegment *>(geo2);
|
|
midpos2 = ((lineSeg2->getEndPoint()+lineSeg2->getStartPoint())/2);
|
|
dir2 = (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()).Normalize();
|
|
norm2 = Base::Vector3d(-dir2.y,dir2.x,0.);
|
|
} else if (geo2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo2);
|
|
double startangle, endangle, midangle;
|
|
arc->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
midangle = (startangle + endangle)/2;
|
|
norm2 = Base::Vector3d(cos(midangle),sin(midangle),0);
|
|
dir2 = Base::Vector3d(-norm2.y,norm2.x,0);
|
|
midpos2 = arc->getCenter() + arc->getRadius() * norm2;
|
|
} else if (geo2->getTypeId() == Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo2);
|
|
norm2 = Base::Vector3d(cos(M_PI/4),sin(M_PI/4),0);
|
|
dir2 = Base::Vector3d(-norm2.y,norm2.x,0);
|
|
midpos2 = circle->getCenter() + circle->getRadius() * norm2;
|
|
} else
|
|
break;
|
|
twoIcons = true;
|
|
}
|
|
|
|
Base::Vector3d relpos1 = seekConstraintPosition(midpos1, norm1, dir1, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH);
|
|
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
|
|
|
|
if (twoIcons) {
|
|
Base::Vector3d relpos2 = seekConstraintPosition(midpos2, norm2, dir2, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
Base::Vector3d secondPos = midpos2 - midpos1;
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
|
|
translation->abPos = SbVec3f(secondPos.x, secondPos.y, zConstrH);
|
|
translation->translation = SbVec3f(relpos2.x -relpos1.x, relpos2.y -relpos1.y, 0);
|
|
}
|
|
|
|
}
|
|
break;
|
|
case Parallel:
|
|
case Equal:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
|
|
// get the geometry
|
|
const Part::Geometry *geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
const Part::Geometry *geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
|
|
|
|
Base::Vector3d midpos1, dir1, norm1;
|
|
Base::Vector3d midpos2, dir2, norm2;
|
|
if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() ||
|
|
geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) {
|
|
if (Constr->Type == Equal) {
|
|
double r1a=0,r1b=0,r2a=0,r2b=0;
|
|
double angle1,angle1plus=0., angle2, angle2plus=0.;//angle1 = rotation of object as a whole; angle1plus = arc angle (t parameter for ellipses).
|
|
if (geo1->getTypeId() == Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo1);
|
|
r1a = circle->getRadius();
|
|
angle1 = M_PI/4;
|
|
midpos1 = circle->getCenter();
|
|
} else if (geo1->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo1);
|
|
r1a = arc->getRadius();
|
|
double startangle, endangle;
|
|
arc->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
angle1 = (startangle + endangle)/2;
|
|
midpos1 = arc->getCenter();
|
|
} else if (geo1->getTypeId() == Part::GeomEllipse::getClassTypeId()) {
|
|
const Part::GeomEllipse *ellipse = static_cast<const Part::GeomEllipse *>(geo1);
|
|
r1a = ellipse->getMajorRadius();
|
|
r1b = ellipse->getMinorRadius();
|
|
Base::Vector3d majdir = ellipse->getMajorAxisDir();
|
|
angle1 = atan2(majdir.y, majdir.x);
|
|
angle1plus = M_PI/4;
|
|
midpos1 = ellipse->getCenter();
|
|
} else if (geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) {
|
|
const Part::GeomArcOfEllipse *aoe = static_cast<const Part::GeomArcOfEllipse *>(geo1);
|
|
r1a = aoe->getMajorRadius();
|
|
r1b = aoe->getMinorRadius();
|
|
double startangle, endangle;
|
|
aoe->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = aoe->getMajorAxisDir();
|
|
angle1 = atan2(majdir.y, majdir.x);
|
|
angle1plus = (startangle + endangle)/2;
|
|
midpos1 = aoe->getCenter();
|
|
} else if (geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) {
|
|
const Part::GeomArcOfHyperbola *aoh = static_cast<const Part::GeomArcOfHyperbola *>(geo1);
|
|
r1a = aoh->getMajorRadius();
|
|
r1b = aoh->getMinorRadius();
|
|
double startangle, endangle;
|
|
aoh->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = aoh->getMajorAxisDir();
|
|
angle1 = atan2(majdir.y, majdir.x);
|
|
angle1plus = (startangle + endangle)/2;
|
|
midpos1 = aoh->getCenter();
|
|
} else if (geo1->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) {
|
|
const Part::GeomArcOfParabola *aop = static_cast<const Part::GeomArcOfParabola *>(geo1);
|
|
r1a = aop->getFocal();
|
|
double startangle, endangle;
|
|
aop->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = - aop->getXAxisDir();
|
|
angle1 = atan2(majdir.y, majdir.x);
|
|
angle1plus = (startangle + endangle)/2;
|
|
midpos1 = aop->getFocus();
|
|
} else
|
|
break;
|
|
|
|
if (geo2->getTypeId() == Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo2);
|
|
r2a = circle->getRadius();
|
|
angle2 = M_PI/4;
|
|
midpos2 = circle->getCenter();
|
|
} else if (geo2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo2);
|
|
r2a = arc->getRadius();
|
|
double startangle, endangle;
|
|
arc->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
angle2 = (startangle + endangle)/2;
|
|
midpos2 = arc->getCenter();
|
|
} else if (geo2->getTypeId() == Part::GeomEllipse::getClassTypeId()) {
|
|
const Part::GeomEllipse *ellipse = static_cast<const Part::GeomEllipse *>(geo2);
|
|
r2a = ellipse->getMajorRadius();
|
|
r2b = ellipse->getMinorRadius();
|
|
Base::Vector3d majdir = ellipse->getMajorAxisDir();
|
|
angle2 = atan2(majdir.y, majdir.x);
|
|
angle2plus = M_PI/4;
|
|
midpos2 = ellipse->getCenter();
|
|
} else if (geo2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) {
|
|
const Part::GeomArcOfEllipse *aoe = static_cast<const Part::GeomArcOfEllipse *>(geo2);
|
|
r2a = aoe->getMajorRadius();
|
|
r2b = aoe->getMinorRadius();
|
|
double startangle, endangle;
|
|
aoe->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = aoe->getMajorAxisDir();
|
|
angle2 = atan2(majdir.y, majdir.x);
|
|
angle2plus = (startangle + endangle)/2;
|
|
midpos2 = aoe->getCenter();
|
|
} else if (geo2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) {
|
|
const Part::GeomArcOfHyperbola *aoh = static_cast<const Part::GeomArcOfHyperbola *>(geo2);
|
|
r2a = aoh->getMajorRadius();
|
|
r2b = aoh->getMinorRadius();
|
|
double startangle, endangle;
|
|
aoh->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = aoh->getMajorAxisDir();
|
|
angle2 = atan2(majdir.y, majdir.x);
|
|
angle2plus = (startangle + endangle)/2;
|
|
midpos2 = aoh->getCenter();
|
|
} else if (geo2->getTypeId() == Part::GeomArcOfParabola::getClassTypeId()) {
|
|
const Part::GeomArcOfParabola *aop = static_cast<const Part::GeomArcOfParabola *>(geo2);
|
|
r2a = aop->getFocal();
|
|
double startangle, endangle;
|
|
aop->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
Base::Vector3d majdir = -aop->getXAxisDir();
|
|
angle2 = atan2(majdir.y, majdir.x);
|
|
angle2plus = (startangle + endangle)/2;
|
|
midpos2 = aop->getFocus();
|
|
} else
|
|
break;
|
|
|
|
if (geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() ||
|
|
geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ||
|
|
geo1->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId() ){
|
|
|
|
Base::Vector3d majDir, minDir, rvec;
|
|
majDir = Base::Vector3d(cos(angle1),sin(angle1),0);//direction of major axis of ellipse
|
|
minDir = Base::Vector3d(-majDir.y,majDir.x,0);//direction of minor axis of ellipse
|
|
rvec = (r1a*cos(angle1plus)) * majDir + (r1b*sin(angle1plus)) * minDir;
|
|
midpos1 += rvec;
|
|
rvec.Normalize();
|
|
norm1 = rvec;
|
|
dir1 = Base::Vector3d(-rvec.y,rvec.x,0);//DeepSOIC: I'm not sure what dir is supposed to mean.
|
|
}
|
|
else {
|
|
norm1 = Base::Vector3d(cos(angle1),sin(angle1),0);
|
|
dir1 = Base::Vector3d(-norm1.y,norm1.x,0);
|
|
midpos1 += r1a*norm1;
|
|
}
|
|
|
|
|
|
if (geo2->getTypeId() == Part::GeomEllipse::getClassTypeId() ||
|
|
geo2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ||
|
|
geo2->getTypeId() == Part::GeomArcOfHyperbola::getClassTypeId()) {
|
|
|
|
Base::Vector3d majDir, minDir, rvec;
|
|
majDir = Base::Vector3d(cos(angle2),sin(angle2),0);//direction of major axis of ellipse
|
|
minDir = Base::Vector3d(-majDir.y,majDir.x,0);//direction of minor axis of ellipse
|
|
rvec = (r2a*cos(angle2plus)) * majDir + (r2b*sin(angle2plus)) * minDir;
|
|
midpos2 += rvec;
|
|
rvec.Normalize();
|
|
norm2 = rvec;
|
|
dir2 = Base::Vector3d(-rvec.y,rvec.x,0);
|
|
}
|
|
else {
|
|
norm2 = Base::Vector3d(cos(angle2),sin(angle2),0);
|
|
dir2 = Base::Vector3d(-norm2.y,norm2.x,0);
|
|
midpos2 += r2a*norm2;
|
|
}
|
|
|
|
} else // Parallel can only apply to a GeomLineSegment
|
|
break;
|
|
} else {
|
|
const Part::GeomLineSegment *lineSeg1 = static_cast<const Part::GeomLineSegment *>(geo1);
|
|
const Part::GeomLineSegment *lineSeg2 = static_cast<const Part::GeomLineSegment *>(geo2);
|
|
|
|
// calculate the half distance between the start and endpoint
|
|
midpos1 = ((lineSeg1->getEndPoint()+lineSeg1->getStartPoint())/2);
|
|
midpos2 = ((lineSeg2->getEndPoint()+lineSeg2->getStartPoint())/2);
|
|
//Get a set of vectors perpendicular and tangential to these
|
|
dir1 = (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()).Normalize();
|
|
dir2 = (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()).Normalize();
|
|
norm1 = Base::Vector3d(-dir1.y,dir1.x,0.);
|
|
norm2 = Base::Vector3d(-dir2.y,dir2.x,0.);
|
|
}
|
|
|
|
Base::Vector3d relpos1 = seekConstraintPosition(midpos1, norm1, dir1, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
Base::Vector3d relpos2 = seekConstraintPosition(midpos2, norm2, dir2, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH); //Absolute Reference
|
|
|
|
//Reference Position that is scaled according to zoom
|
|
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
|
|
|
|
Base::Vector3d secondPos = midpos2 - midpos1;
|
|
|
|
translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(secondPos.x, secondPos.y, zConstrH); //Absolute Reference
|
|
|
|
//Reference Position that is scaled according to zoom
|
|
translation->translation = SbVec3f(relpos2.x - relpos1.x, relpos2.y -relpos1.y, 0);
|
|
|
|
}
|
|
break;
|
|
case Distance:
|
|
case DistanceX:
|
|
case DistanceY:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
|
|
Base::Vector3d pnt1(0.,0.,0.), pnt2(0.,0.,0.);
|
|
if (Constr->SecondPos != Sketcher::PointPos::none) { // point to point distance
|
|
pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
|
|
pnt2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
|
|
} else if (Constr->Second != GeoEnum::GeoUndef) { // point to line distance
|
|
pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
|
|
|
|
const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->Second);
|
|
if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg = static_cast<const Part::GeomLineSegment *>(geo);
|
|
Base::Vector3d l2p1 = lineSeg->getStartPoint();
|
|
Base::Vector3d l2p2 = lineSeg->getEndPoint();
|
|
// calculate the projection of p1 onto line2
|
|
pnt2.ProjectToLine(pnt1-l2p1, l2p2-l2p1);
|
|
pnt2 += pnt1;
|
|
} else
|
|
break;
|
|
} else if (Constr->FirstPos != Sketcher::PointPos::none) {
|
|
pnt2 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
|
|
} else if (Constr->First != GeoEnum::GeoUndef) {
|
|
const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg = static_cast<const Part::GeomLineSegment *>(geo);
|
|
pnt1 = lineSeg->getStartPoint();
|
|
pnt2 = lineSeg->getEndPoint();
|
|
} else
|
|
break;
|
|
} else
|
|
break;
|
|
|
|
SoDatumLabel *asciiText = static_cast<SoDatumLabel *>(sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
|
|
// Get presentation string (w/o units if option is set)
|
|
asciiText->string = SbString( getPresentationString(Constr).toUtf8().constData() );
|
|
|
|
if (Constr->Type == Distance)
|
|
asciiText->datumtype = SoDatumLabel::DISTANCE;
|
|
else if (Constr->Type == DistanceX)
|
|
asciiText->datumtype = SoDatumLabel::DISTANCEX;
|
|
else if (Constr->Type == DistanceY)
|
|
asciiText->datumtype = SoDatumLabel::DISTANCEY;
|
|
|
|
// Assign the Datum Points
|
|
asciiText->pnts.setNum(2);
|
|
SbVec3f *verts = asciiText->pnts.startEditing();
|
|
|
|
verts[0] = SbVec3f (pnt1.x, pnt1.y, zConstrH);
|
|
verts[1] = SbVec3f (pnt2.x, pnt2.y, zConstrH);
|
|
|
|
asciiText->pnts.finishEditing();
|
|
|
|
//Assign the Label Distance
|
|
asciiText->param1 = Constr->LabelDistance;
|
|
asciiText->param2 = Constr->LabelPosition;
|
|
}
|
|
break;
|
|
case PointOnObject:
|
|
case Tangent:
|
|
case SnellsLaw:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
|
|
|
|
Base::Vector3d pos, relPos;
|
|
if (Constr->Type == PointOnObject ||
|
|
Constr->Type == SnellsLaw ||
|
|
(Constr->Type == Tangent && Constr->Third != GeoEnum::GeoUndef) || //Tangency via point
|
|
(Constr->Type == Tangent && Constr->FirstPos != Sketcher::PointPos::none) //endpoint-to-curve or endpoint-to-endpoint tangency
|
|
) {
|
|
|
|
//find the point of tangency/point that is on object
|
|
//just any point among first/second/third should be OK
|
|
int ptGeoId;
|
|
Sketcher::PointPos ptPosId;
|
|
do {//dummy loop to use break =) Maybe goto?
|
|
ptGeoId = Constr->First;
|
|
ptPosId = Constr->FirstPos;
|
|
if (ptPosId != Sketcher::PointPos::none) break;
|
|
ptGeoId = Constr->Second;
|
|
ptPosId = Constr->SecondPos;
|
|
if (ptPosId != Sketcher::PointPos::none) break;
|
|
ptGeoId = Constr->Third;
|
|
ptPosId = Constr->ThirdPos;
|
|
if (ptPosId != Sketcher::PointPos::none) break;
|
|
assert(0);//no point found!
|
|
} while (false);
|
|
|
|
pos = geolistfacade.getPoint(ptGeoId, ptPosId);
|
|
auto norm = getNormal(geolistfacade, Constr->Second, pos);
|
|
|
|
// TODO: Check substitution
|
|
// Base::Vector3d norm = getSolvedSketch().calculateNormalAtPoint(Constr->Second, pos.x, pos.y);
|
|
norm.Normalize();
|
|
Base::Vector3d dir = norm; dir.RotateZ(-M_PI/2.0);
|
|
|
|
relPos = seekConstraintPosition(pos, norm, dir, 2.5, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(pos.x, pos.y, zConstrH); //Absolute Reference
|
|
translation->translation = SbVec3f(relPos.x, relPos.y, 0);
|
|
}
|
|
else if (Constr->Type == Tangent) {
|
|
// get the geometry
|
|
const Part::Geometry *geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
const Part::Geometry *geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
|
|
|
|
if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId() &&
|
|
geo2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg1 = static_cast<const Part::GeomLineSegment *>(geo1);
|
|
const Part::GeomLineSegment *lineSeg2 = static_cast<const Part::GeomLineSegment *>(geo2);
|
|
// tangency between two lines
|
|
Base::Vector3d midpos1 = ((lineSeg1->getEndPoint()+lineSeg1->getStartPoint())/2);
|
|
Base::Vector3d midpos2 = ((lineSeg2->getEndPoint()+lineSeg2->getStartPoint())/2);
|
|
Base::Vector3d dir1 = (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()).Normalize();
|
|
Base::Vector3d dir2 = (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()).Normalize();
|
|
Base::Vector3d norm1 = Base::Vector3d(-dir1.y,dir1.x,0.f);
|
|
Base::Vector3d norm2 = Base::Vector3d(-dir2.y,dir2.x,0.f);
|
|
|
|
Base::Vector3d relpos1 = seekConstraintPosition(midpos1, norm1, dir1, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
Base::Vector3d relpos2 = seekConstraintPosition(midpos2, norm2, dir2, 4.0, editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH); //Absolute Reference
|
|
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
|
|
|
|
Base::Vector3d secondPos = midpos2 - midpos1;
|
|
|
|
translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(secondPos.x, secondPos.y, zConstrH); //Absolute Reference
|
|
translation->translation = SbVec3f(relpos2.x -relpos1.x, relpos2.y -relpos1.y, 0);
|
|
|
|
break;
|
|
}
|
|
else if (geo2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
std::swap(geo1,geo2);
|
|
}
|
|
|
|
if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg = static_cast<const Part::GeomLineSegment *>(geo1);
|
|
Base::Vector3d dir = (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Normalize();
|
|
Base::Vector3d norm(-dir.y, dir.x, 0);
|
|
if (geo2->getTypeId()== Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo2);
|
|
// tangency between a line and a circle
|
|
float length = (circle->getCenter() - lineSeg->getStartPoint())*dir;
|
|
|
|
pos = lineSeg->getStartPoint() + dir * length;
|
|
relPos = norm * 1; //TODO Huh?
|
|
}
|
|
else if (geo2->getTypeId()== Part::GeomEllipse::getClassTypeId() ||
|
|
geo2->getTypeId()== Part::GeomArcOfEllipse::getClassTypeId()) {
|
|
|
|
Base::Vector3d center;
|
|
if (geo2->getTypeId()== Part::GeomEllipse::getClassTypeId()){
|
|
const Part::GeomEllipse *ellipse = static_cast<const Part::GeomEllipse *>(geo2);
|
|
center=ellipse->getCenter();
|
|
} else {
|
|
const Part::GeomArcOfEllipse *aoc = static_cast<const Part::GeomArcOfEllipse *>(geo2);
|
|
center=aoc->getCenter();
|
|
}
|
|
|
|
// tangency between a line and an ellipse
|
|
float length = (center - lineSeg->getStartPoint())*dir;
|
|
|
|
pos = lineSeg->getStartPoint() + dir * length;
|
|
relPos = norm * 1;
|
|
}
|
|
else if (geo2->getTypeId()== Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo2);
|
|
// tangency between a line and an arc
|
|
float length = (arc->getCenter() - lineSeg->getStartPoint())*dir;
|
|
|
|
pos = lineSeg->getStartPoint() + dir * length;
|
|
relPos = norm * 1; //TODO Huh?
|
|
}
|
|
}
|
|
|
|
if (geo1->getTypeId()== Part::GeomCircle::getClassTypeId() &&
|
|
geo2->getTypeId()== Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle1 = static_cast<const Part::GeomCircle *>(geo1);
|
|
const Part::GeomCircle *circle2 = static_cast<const Part::GeomCircle *>(geo2);
|
|
// tangency between two cicles
|
|
Base::Vector3d dir = (circle2->getCenter() - circle1->getCenter()).Normalize();
|
|
pos = circle1->getCenter() + dir * circle1->getRadius();
|
|
relPos = dir * 1;
|
|
}
|
|
else if (geo2->getTypeId()== Part::GeomCircle::getClassTypeId()) {
|
|
std::swap(geo1,geo2);
|
|
}
|
|
|
|
if (geo1->getTypeId()== Part::GeomCircle::getClassTypeId() &&
|
|
geo2->getTypeId()== Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo1);
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo2);
|
|
// tangency between a circle and an arc
|
|
Base::Vector3d dir = (arc->getCenter() - circle->getCenter()).Normalize();
|
|
pos = circle->getCenter() + dir * circle->getRadius();
|
|
relPos = dir * 1;
|
|
}
|
|
else if (geo1->getTypeId()== Part::GeomArcOfCircle::getClassTypeId() &&
|
|
geo2->getTypeId()== Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc1 = static_cast<const Part::GeomArcOfCircle *>(geo1);
|
|
const Part::GeomArcOfCircle *arc2 = static_cast<const Part::GeomArcOfCircle *>(geo2);
|
|
// tangency between two arcs
|
|
Base::Vector3d dir = (arc2->getCenter() - arc1->getCenter()).Normalize();
|
|
pos = arc1->getCenter() + dir * arc1->getRadius();
|
|
relPos = dir * 1;
|
|
}
|
|
auto translation = static_cast<SoZoomTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->abPos = SbVec3f(pos.x, pos.y, zConstrH); //Absolute Reference
|
|
translation->translation = SbVec3f(relPos.x, relPos.y, 0);
|
|
}
|
|
}
|
|
break;
|
|
case Symmetric:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
|
|
|
|
Base::Vector3d pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
|
|
Base::Vector3d pnt2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
|
|
|
|
SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
|
|
SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
|
|
SbVec3f dir = (p2-p1);
|
|
dir.normalize();
|
|
SbVec3f norm (-dir[1],dir[0],0);
|
|
|
|
SoDatumLabel *asciiText = static_cast<SoDatumLabel *>(sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
asciiText->datumtype = SoDatumLabel::SYMMETRIC;
|
|
|
|
asciiText->pnts.setNum(2);
|
|
SbVec3f *verts = asciiText->pnts.startEditing();
|
|
|
|
verts[0] = p1;
|
|
verts[1] = p2;
|
|
|
|
asciiText->pnts.finishEditing();
|
|
|
|
auto translation = static_cast<SoTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
translation->translation = (p1 + p2)/2;
|
|
}
|
|
break;
|
|
case Angle:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
assert((Constr->Second >= -extGeoCount && Constr->Second < intGeoCount) ||
|
|
Constr->Second == GeoEnum::GeoUndef);
|
|
|
|
SbVec3f p0;
|
|
double startangle,range,endangle;
|
|
if (Constr->Second != GeoEnum::GeoUndef) {
|
|
Base::Vector3d dir1, dir2;
|
|
if (Constr->Third == GeoEnum::GeoUndef) { //angle between two lines
|
|
const Part::Geometry *geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
const Part::Geometry *geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
|
|
if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() ||
|
|
geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId())
|
|
break;
|
|
const Part::GeomLineSegment *lineSeg1 = static_cast<const Part::GeomLineSegment *>(geo1);
|
|
const Part::GeomLineSegment *lineSeg2 = static_cast<const Part::GeomLineSegment *>(geo2);
|
|
|
|
bool flip1 = (Constr->FirstPos == PointPos::end);
|
|
bool flip2 = (Constr->SecondPos == PointPos::end);
|
|
dir1 = (flip1 ? -1. : 1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint());
|
|
dir2 = (flip2 ? -1. : 1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint());
|
|
Base::Vector3d pnt1 = flip1 ? lineSeg1->getEndPoint() : lineSeg1->getStartPoint();
|
|
Base::Vector3d pnt2 = flip2 ? lineSeg2->getEndPoint() : lineSeg2->getStartPoint();
|
|
|
|
// line-line intersection
|
|
{
|
|
double det = dir1.x*dir2.y - dir1.y*dir2.x;
|
|
if ((det > 0 ? det : -det) < 1e-10) {
|
|
// lines are coincident (or parallel) and in this case the center
|
|
// of the point pairs with the shortest distance is used
|
|
Base::Vector3d p1[2], p2[2];
|
|
p1[0] = lineSeg1->getStartPoint();
|
|
p1[1] = lineSeg1->getEndPoint();
|
|
p2[0] = lineSeg2->getStartPoint();
|
|
p2[1] = lineSeg2->getEndPoint();
|
|
double length = DBL_MAX;
|
|
for (int i=0; i <= 1; i++) {
|
|
for (int j=0; j <= 1; j++) {
|
|
double tmp = (p2[j]-p1[i]).Length();
|
|
if (tmp < length) {
|
|
length = tmp;
|
|
p0.setValue((p2[j].x+p1[i].x)/2,(p2[j].y+p1[i].y)/2,0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
double c1 = dir1.y*pnt1.x - dir1.x*pnt1.y;
|
|
double c2 = dir2.y*pnt2.x - dir2.x*pnt2.y;
|
|
double x = (dir1.x*c2 - dir2.x*c1)/det;
|
|
double y = (dir1.y*c2 - dir2.y*c1)/det;
|
|
p0 = SbVec3f(x,y,0);
|
|
}
|
|
}
|
|
|
|
range = Constr->getValue(); // WYSIWYG
|
|
startangle = atan2(dir1.y,dir1.x);
|
|
}
|
|
else {//angle-via-point
|
|
Base::Vector3d p = geolistfacade.getPoint(Constr->Third, Constr->ThirdPos);
|
|
p0 = SbVec3f(p.x, p.y, 0);
|
|
dir1 = getNormal(geolistfacade, Constr->First, p);
|
|
// TODO: Check
|
|
// dir1 = getSolvedSketch().calculateNormalAtPoint(Constr->First, p.x, p.y);
|
|
dir1.RotateZ(-M_PI/2);//convert to vector of tangency by rotating
|
|
dir2 = getNormal(geolistfacade, Constr->Second, p);
|
|
// TODO: Check
|
|
// dir2 = getSolvedSketch().calculateNormalAtPoint(Constr->Second, p.x, p.y);
|
|
dir2.RotateZ(-M_PI/2);
|
|
|
|
startangle = atan2(dir1.y,dir1.x);
|
|
range = atan2(dir1.x*dir2.y-dir1.y*dir2.x,
|
|
dir1.x*dir2.x+dir1.y*dir2.y);
|
|
}
|
|
|
|
endangle = startangle + range;
|
|
|
|
} else if (Constr->First != GeoEnum::GeoUndef) {
|
|
const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
const Part::GeomLineSegment *lineSeg = static_cast<const Part::GeomLineSegment *>(geo);
|
|
p0 = Base::convertTo<SbVec3f>((lineSeg->getEndPoint()+lineSeg->getStartPoint())/2);
|
|
|
|
Base::Vector3d dir = lineSeg->getEndPoint()-lineSeg->getStartPoint();
|
|
startangle = 0.;
|
|
range = atan2(dir.y,dir.x);
|
|
endangle = startangle + range;
|
|
}
|
|
else if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo);
|
|
p0 = Base::convertTo<SbVec3f>(arc->getCenter());
|
|
|
|
arc->getRange(startangle, endangle,/*emulateCCWXY=*/true);
|
|
range = endangle - startangle;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
} else
|
|
break;
|
|
|
|
SoDatumLabel *asciiText = static_cast<SoDatumLabel *>(sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
asciiText->string = SbString(getPresentationString(Constr).toUtf8().constData());
|
|
asciiText->datumtype = SoDatumLabel::ANGLE;
|
|
asciiText->param1 = Constr->LabelDistance;
|
|
asciiText->param2 = startangle;
|
|
asciiText->param3 = range;
|
|
|
|
asciiText->pnts.setNum(2);
|
|
SbVec3f *verts = asciiText->pnts.startEditing();
|
|
|
|
verts[0] = p0;
|
|
|
|
asciiText->pnts.finishEditing();
|
|
|
|
}
|
|
break;
|
|
case Diameter:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
|
|
Base::Vector3d pnt1(0.,0.,0.), pnt2(0.,0.,0.);
|
|
if (Constr->First != GeoEnum::GeoUndef) {
|
|
const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
|
|
if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo);
|
|
double radius = arc->getRadius();
|
|
double angle = (double) Constr->LabelPosition;
|
|
if (angle == 10) {
|
|
double startangle, endangle;
|
|
arc->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
angle = (startangle + endangle)/2;
|
|
}
|
|
Base::Vector3d center = arc->getCenter();
|
|
pnt1 = center - radius * Base::Vector3d(cos(angle),sin(angle),0.);
|
|
pnt2 = center + radius * Base::Vector3d(cos(angle),sin(angle),0.);
|
|
}
|
|
else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo);
|
|
double radius = circle->getRadius();
|
|
double angle = (double) Constr->LabelPosition;
|
|
if (angle == 10) {
|
|
angle = 0;
|
|
}
|
|
Base::Vector3d center = circle->getCenter();
|
|
pnt1 = center - radius * Base::Vector3d(cos(angle),sin(angle),0.);
|
|
pnt2 = center + radius * Base::Vector3d(cos(angle),sin(angle),0.);
|
|
}
|
|
else
|
|
break;
|
|
} else
|
|
break;
|
|
|
|
SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
|
|
SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
|
|
|
|
SoDatumLabel *asciiText = static_cast<SoDatumLabel *>(sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
|
|
// Get display string with units hidden if so requested
|
|
asciiText->string = SbString( getPresentationString(Constr).toUtf8().constData() );
|
|
|
|
asciiText->datumtype = SoDatumLabel::DIAMETER;
|
|
asciiText->param1 = Constr->LabelDistance;
|
|
asciiText->param2 = Constr->LabelPosition;
|
|
|
|
asciiText->pnts.setNum(2);
|
|
SbVec3f *verts = asciiText->pnts.startEditing();
|
|
|
|
verts[0] = p1;
|
|
verts[1] = p2;
|
|
|
|
asciiText->pnts.finishEditing();
|
|
}
|
|
break;
|
|
case Weight:
|
|
case Radius:
|
|
{
|
|
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
|
|
|
|
Base::Vector3d pnt1(0.,0.,0.), pnt2(0.,0.,0.);
|
|
|
|
if (Constr->First != GeoEnum::GeoUndef) {
|
|
const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->First);
|
|
|
|
if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) {
|
|
const Part::GeomArcOfCircle *arc = static_cast<const Part::GeomArcOfCircle *>(geo);
|
|
double radius = arc->getRadius();
|
|
double angle = (double) Constr->LabelPosition;
|
|
if (angle == 10) {
|
|
double startangle, endangle;
|
|
arc->getRange(startangle, endangle, /*emulateCCW=*/true);
|
|
angle = (startangle + endangle)/2;
|
|
}
|
|
pnt1 = arc->getCenter();
|
|
pnt2 = pnt1 + radius * Base::Vector3d(cos(angle),sin(angle),0.);
|
|
}
|
|
else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) {
|
|
const Part::GeomCircle *circle = static_cast<const Part::GeomCircle *>(geo);
|
|
auto gf = GeometryFacade::getFacade(geo);
|
|
|
|
double radius;
|
|
|
|
radius = circle->getRadius();
|
|
|
|
double angle = (double) Constr->LabelPosition;
|
|
if (angle == 10) {
|
|
angle = 0;
|
|
}
|
|
pnt1 = circle->getCenter();
|
|
pnt2 = pnt1 + radius * Base::Vector3d(cos(angle),sin(angle),0.);
|
|
}
|
|
else
|
|
break;
|
|
} else
|
|
break;
|
|
|
|
SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
|
|
SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
|
|
|
|
SoDatumLabel *asciiText = static_cast<SoDatumLabel *>(sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
|
|
// Get display string with units hidden if so requested
|
|
if (Constr->Type == Weight)
|
|
asciiText->string = SbString( QString::number(Constr->getValue()).toStdString().c_str());
|
|
else
|
|
asciiText->string = SbString( getPresentationString(Constr).toUtf8().constData() );
|
|
|
|
asciiText->datumtype = SoDatumLabel::RADIUS;
|
|
asciiText->param1 = Constr->LabelDistance;
|
|
asciiText->param2 = Constr->LabelPosition;
|
|
|
|
asciiText->pnts.setNum(2);
|
|
SbVec3f *verts = asciiText->pnts.startEditing();
|
|
|
|
verts[0] = p1;
|
|
verts[1] = p2;
|
|
|
|
asciiText->pnts.finishEditing();
|
|
}
|
|
break;
|
|
case Coincident: // nothing to do for coincident
|
|
case None:
|
|
case InternalAlignment:
|
|
case NumConstraintTypes:
|
|
break;
|
|
}
|
|
|
|
} catch (Base::Exception &e) {
|
|
Base::Console().Error("Exception during draw: %s\n", e.what());
|
|
e.ReportException();
|
|
} catch (...){
|
|
Base::Console().Error("Exception during draw: unknown\n");
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
Base::Vector3d EditModeConstraintCoinManager::seekConstraintPosition(const Base::Vector3d &origPos,
|
|
const Base::Vector3d &norm,
|
|
const Base::Vector3d &dir, float step,
|
|
const SoNode *constraint)
|
|
{
|
|
|
|
auto rp = ViewProviderSketchCoinAttorney::getRayPickAction(viewProvider);
|
|
|
|
float scaled_step = step * ViewProviderSketchCoinAttorney::getScaleFactor(viewProvider);
|
|
|
|
int multiplier = 0;
|
|
Base::Vector3d relPos, freePos;
|
|
bool isConstraintAtPosition = true;
|
|
while (isConstraintAtPosition && multiplier < 10) {
|
|
// Calculate new position of constraint
|
|
relPos = norm * 0.5f + dir * multiplier;
|
|
freePos = origPos + relPos * scaled_step;
|
|
|
|
// Prevent crash : https://forum.freecadweb.org/viewtopic.php?f=8&t=65305
|
|
if (!rp) {
|
|
return relPos * step;
|
|
}
|
|
|
|
rp->setRadius(0.1f);
|
|
rp->setPickAll(true);
|
|
rp->setRay(SbVec3f(freePos.x, freePos.y, -1.f), SbVec3f(0, 0, 1) );
|
|
//problem
|
|
rp->apply(editModeScenegraphNodes.constrGroup); // We could narrow it down to just the SoGroup containing the constraints
|
|
|
|
// returns a copy of the point
|
|
SoPickedPoint *pp = rp->getPickedPoint();
|
|
const SoPickedPointList ppl = rp->getPickedPointList();
|
|
|
|
if (ppl.getLength() <= 1 && pp) {
|
|
SoPath *path = pp->getPath();
|
|
int length = path->getLength();
|
|
SoNode *tailFather1 = path->getNode(length-2);
|
|
SoNode *tailFather2 = path->getNode(length-3);
|
|
|
|
// checking if a constraint is the same as the one selected
|
|
if (tailFather1 == constraint || tailFather2 == constraint)
|
|
isConstraintAtPosition = false;
|
|
}
|
|
else {
|
|
isConstraintAtPosition = false;
|
|
}
|
|
|
|
multiplier *= -1; // search in both sides
|
|
if (multiplier >= 0)
|
|
multiplier++; // Increment the multiplier
|
|
}
|
|
if (multiplier == 10)
|
|
relPos = norm * 0.5f; // no free position found
|
|
return relPos * step;
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::updateConstraintColor(const std::vector<Sketcher::Constraint *> &constraints)
|
|
{
|
|
// Because coincident constraints are selected using the point color, we need to edit the point materials.
|
|
|
|
std::vector<int> PtNum;
|
|
std::vector<SbColor *> pcolor; // point color
|
|
std::vector<int> CurvNum;
|
|
std::vector<SbColor *> color; // curve color
|
|
|
|
for(int l=0; l<geometryLayerParameters.CoinLayers; l++) {
|
|
PtNum.push_back(editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.getNum());
|
|
pcolor.push_back(editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.startEditing());
|
|
CurvNum.push_back(editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.getNum());
|
|
color.push_back(editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.startEditing());
|
|
}
|
|
|
|
int maxNumberOfConstraints = std::min(editModeScenegraphNodes.constrGroup->getNumChildren(), static_cast<int>(constraints.size()));
|
|
|
|
// colors of the constraints
|
|
for (int i = 0; i < maxNumberOfConstraints; i++) {
|
|
SoSeparator *s = static_cast<SoSeparator *>(editModeScenegraphNodes.constrGroup->getChild(i));
|
|
|
|
// Check Constraint Type
|
|
Sketcher::Constraint* constraint = constraints[i];
|
|
ConstraintType type = constraint->Type;
|
|
|
|
// It may happen that color updates are triggered by programmatic selection changes before a command final update. Then
|
|
// constraints may have been changed and the color will be updated as part
|
|
if (type != vConstrType[i])
|
|
break;
|
|
|
|
bool hasDatumLabel = (type == Sketcher::Angle ||
|
|
type == Sketcher::Radius ||
|
|
type == Sketcher::Diameter ||
|
|
type == Sketcher::Weight ||
|
|
type == Sketcher::Symmetric ||
|
|
type == Sketcher::Distance ||
|
|
type == Sketcher::DistanceX ||
|
|
type == Sketcher::DistanceY);
|
|
|
|
// Non DatumLabel Nodes will have a material excluding coincident
|
|
bool hasMaterial = false;
|
|
|
|
SoMaterial *m = nullptr;
|
|
if (!hasDatumLabel && type != Sketcher::Coincident && type != Sketcher::InternalAlignment) {
|
|
hasMaterial = true;
|
|
m = static_cast<SoMaterial *>(s->getChild(static_cast<int>(ConstraintNodePosition::MaterialIndex)));
|
|
}
|
|
|
|
auto selectpoint = [this, pcolor, PtNum](int geoid, Sketcher::PointPos pos){
|
|
if (geoid >= 0) {
|
|
auto multifieldIndex = coinMapping.getIndexLayer(geoid, pos);
|
|
|
|
if (multifieldIndex != MultiFieldId::Invalid) {
|
|
int index = multifieldIndex.fieldIndex;
|
|
int layer = multifieldIndex.layerId;
|
|
if (layer < static_cast<int>(PtNum.size()) && index >= 0 && index < PtNum[layer]) {
|
|
pcolor[layer][index] = drawingParameters.SelectColor;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
auto selectline = [this, color, CurvNum](int geoid){
|
|
if (geoid >= 0) {
|
|
auto multifieldIndex = coinMapping.getIndexLayer(geoid, Sketcher::PointPos::none);
|
|
|
|
if (multifieldIndex != MultiFieldId::Invalid) {
|
|
int index = multifieldIndex.fieldIndex;
|
|
int layer = multifieldIndex.layerId;
|
|
if (layer < static_cast<int>(CurvNum.size()) && index >= 0 && index < CurvNum[layer]) {
|
|
color[layer][index] = drawingParameters.SelectColor;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, i)) {
|
|
if (hasDatumLabel) {
|
|
SoDatumLabel *l = static_cast<SoDatumLabel *>(s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
l->textColor = drawingParameters.SelectColor;
|
|
} else if (hasMaterial) {
|
|
m->diffuseColor = drawingParameters.SelectColor;
|
|
} else if (type == Sketcher::Coincident) {
|
|
selectpoint(constraint->First, constraint->FirstPos);
|
|
selectpoint(constraint->Second, constraint->SecondPos);
|
|
} else if (type == Sketcher::InternalAlignment) {
|
|
switch(constraint->AlignmentType) {
|
|
case EllipseMajorDiameter:
|
|
case EllipseMinorDiameter:
|
|
case HyperbolaMajor:
|
|
case HyperbolaMinor:
|
|
case ParabolaFocalAxis:
|
|
{
|
|
selectline(constraint->First);
|
|
}
|
|
break;
|
|
case EllipseFocus1:
|
|
case EllipseFocus2:
|
|
case HyperbolaFocus:
|
|
case ParabolaFocus:
|
|
case BSplineControlPoint:
|
|
case BSplineKnotPoint:
|
|
{
|
|
selectpoint(constraint->First, constraint->FirstPos);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider,i)) {
|
|
if (hasDatumLabel) {
|
|
SoDatumLabel *l = static_cast<SoDatumLabel *>(s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
l->textColor = drawingParameters.PreselectColor;
|
|
} else if (hasMaterial) {
|
|
m->diffuseColor = drawingParameters.PreselectColor;
|
|
}
|
|
}
|
|
else {
|
|
if (hasDatumLabel) {
|
|
SoDatumLabel *l = static_cast<SoDatumLabel *>(s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
|
|
|
|
l->textColor = constraint->isActive ?
|
|
ViewProviderSketchCoinAttorney::constraintHasExpression(viewProvider, i) ?
|
|
drawingParameters.ExprBasedConstrDimColor
|
|
:(constraint->isDriving ?
|
|
drawingParameters.ConstrDimColor
|
|
: drawingParameters.NonDrivingConstrDimColor)
|
|
:drawingParameters.DeactivatedConstrDimColor;
|
|
|
|
} else if (hasMaterial) {
|
|
m->diffuseColor = constraint->isActive ?
|
|
(constraint->isDriving ?
|
|
drawingParameters.ConstrDimColor
|
|
:drawingParameters.NonDrivingConstrDimColor)
|
|
:drawingParameters.DeactivatedConstrDimColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int l=0; l<geometryLayerParameters.CoinLayers; l++) {
|
|
editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.finishEditing();
|
|
editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.finishEditing();
|
|
}
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::rebuildConstraintNodes()
|
|
{
|
|
auto geolistfacade = ViewProviderSketchCoinAttorney::getGeoListFacade(viewProvider);
|
|
|
|
rebuildConstraintNodes(geolistfacade);
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::setConstraintSelectability(bool enabled /* = true */)
|
|
{
|
|
if (enabled) {
|
|
editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::SHAPE);
|
|
}
|
|
else {
|
|
editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::UNPICKABLE);
|
|
}
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::rebuildConstraintNodes(const GeoListFacade & geolistfacade)
|
|
{
|
|
const std::vector<Sketcher::Constraint *> &constrlist = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
|
|
|
|
// clean up
|
|
Gui::coinRemoveAllChildren(editModeScenegraphNodes.constrGroup);
|
|
|
|
vConstrType.clear();
|
|
|
|
// Get sketch normal
|
|
Base::Vector3d RN(0,0,1);
|
|
|
|
// move to position of Sketch
|
|
Base::Placement Plz = ViewProviderSketchCoinAttorney::getEditingPlacement(viewProvider);
|
|
Base::Rotation tmp(Plz.getRotation());
|
|
tmp.multVec(RN,RN);
|
|
Plz.setRotation(tmp);
|
|
|
|
SbVec3f norm(RN.x, RN.y, RN.z);
|
|
|
|
rebuildConstraintNodes(geolistfacade, constrlist, norm);
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::rebuildConstraintNodes(const GeoListFacade & geolistfacade, const std::vector<Sketcher::Constraint *> constrlist, SbVec3f norm)
|
|
{
|
|
|
|
for (std::vector<Sketcher::Constraint *>::const_iterator it=constrlist.begin(); it != constrlist.end(); ++it) {
|
|
// root separator for one constraint
|
|
SoSeparator *sep = new SoSeparator();
|
|
sep->ref();
|
|
// no caching for frequently-changing data structures
|
|
sep->renderCaching = SoSeparator::OFF;
|
|
|
|
// every constrained visual node gets its own material for preselection and selection
|
|
SoMaterial *mat = new SoMaterial;
|
|
mat->ref();
|
|
mat->diffuseColor = (*it)->isActive ?
|
|
((*it)->isDriving ?
|
|
drawingParameters.ConstrDimColor
|
|
:drawingParameters.NonDrivingConstrDimColor)
|
|
:drawingParameters.DeactivatedConstrDimColor;
|
|
|
|
|
|
// distinguish different constraint types to build up
|
|
switch ((*it)->Type) {
|
|
case Distance:
|
|
case DistanceX:
|
|
case DistanceY:
|
|
case Radius:
|
|
case Diameter:
|
|
case Weight:
|
|
case Angle:
|
|
{
|
|
SoDatumLabel *text = new SoDatumLabel();
|
|
text->norm.setValue(norm);
|
|
text->string = "";
|
|
text->textColor = (*it)->isActive ?
|
|
((*it)->isDriving ?
|
|
drawingParameters.ConstrDimColor
|
|
:drawingParameters.NonDrivingConstrDimColor)
|
|
:drawingParameters.DeactivatedConstrDimColor;
|
|
text->size.setValue(drawingParameters.labelFontSize);
|
|
text->lineWidth = 2 * drawingParameters.pixelScalingFactor;
|
|
text->useAntialiasing = false;
|
|
SoAnnotation *anno = new SoAnnotation();
|
|
anno->renderCaching = SoSeparator::OFF;
|
|
anno->addChild(text);
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
|
|
sep->addChild(text);
|
|
editModeScenegraphNodes.constrGroup->addChild(anno);
|
|
vConstrType.push_back((*it)->Type);
|
|
// nodes not needed
|
|
sep->unref();
|
|
mat->unref();
|
|
continue; // jump to next constraint
|
|
}
|
|
break;
|
|
case Horizontal:
|
|
case Vertical:
|
|
case Block:
|
|
{
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
|
|
sep->addChild(mat);
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
|
|
sep->addChild(new SoZoomTranslation());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
|
|
sep->addChild(new SoImage());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
|
|
sep->addChild(new SoInfo());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_TRANSLATION 4
|
|
sep->addChild(new SoZoomTranslation());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_ICON 5
|
|
sep->addChild(new SoImage());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_CONSTRAINTID 6
|
|
sep->addChild(new SoInfo());
|
|
|
|
// remember the type of this constraint node
|
|
vConstrType.push_back((*it)->Type);
|
|
}
|
|
break;
|
|
case Coincident: // no visual for coincident so far
|
|
vConstrType.push_back(Coincident);
|
|
break;
|
|
case Parallel:
|
|
case Perpendicular:
|
|
case Equal:
|
|
{
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
|
|
sep->addChild(mat);
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
|
|
sep->addChild(new SoZoomTranslation());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
|
|
sep->addChild(new SoImage());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
|
|
sep->addChild(new SoInfo());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_TRANSLATION 4
|
|
sep->addChild(new SoZoomTranslation());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_ICON 5
|
|
sep->addChild(new SoImage());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_CONSTRAINTID 6
|
|
sep->addChild(new SoInfo());
|
|
|
|
// remember the type of this constraint node
|
|
vConstrType.push_back((*it)->Type);
|
|
}
|
|
break;
|
|
case PointOnObject:
|
|
case Tangent:
|
|
case SnellsLaw:
|
|
{
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
|
|
sep->addChild(mat);
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
|
|
sep->addChild(new SoZoomTranslation());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
|
|
sep->addChild(new SoImage());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
|
|
sep->addChild(new SoInfo());
|
|
|
|
if ((*it)->Type == Tangent) {
|
|
const Part::Geometry *geo1 = geolistfacade.getGeometryFromGeoId((*it)->First);
|
|
const Part::Geometry *geo2 = geolistfacade.getGeometryFromGeoId((*it)->Second);
|
|
if (!geo1 || !geo2) {
|
|
Base::Console().Warning("Tangent constraint references non-existing geometry\n");
|
|
}
|
|
else if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId() &&
|
|
geo2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_TRANSLATION 4
|
|
sep->addChild(new SoZoomTranslation());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_ICON 5
|
|
sep->addChild(new SoImage());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_SECOND_CONSTRAINTID 6
|
|
sep->addChild(new SoInfo());
|
|
}
|
|
}
|
|
|
|
vConstrType.push_back((*it)->Type);
|
|
}
|
|
break;
|
|
case Symmetric:
|
|
{
|
|
SoDatumLabel *arrows = new SoDatumLabel();
|
|
arrows->norm.setValue(norm);
|
|
arrows->string = "";
|
|
arrows->textColor = drawingParameters.ConstrDimColor;
|
|
arrows->lineWidth = 2 * drawingParameters.pixelScalingFactor;
|
|
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
|
|
sep->addChild(arrows);
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
|
|
sep->addChild(new SoTranslation());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
|
|
sep->addChild(new SoImage());
|
|
// #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
|
|
sep->addChild(new SoInfo());
|
|
|
|
vConstrType.push_back((*it)->Type);
|
|
}
|
|
break;
|
|
case InternalAlignment:
|
|
{
|
|
vConstrType.push_back((*it)->Type);
|
|
}
|
|
break;
|
|
default:
|
|
vConstrType.push_back((*it)->Type);
|
|
}
|
|
|
|
editModeScenegraphNodes.constrGroup->addChild(sep);
|
|
// decrement ref counter again
|
|
sep->unref();
|
|
mat->unref();
|
|
}
|
|
}
|
|
|
|
QString EditModeConstraintCoinManager::getPresentationString(const Constraint *constraint)
|
|
{
|
|
QString nameStr; // name parameter string
|
|
QString valueStr; // dimensional value string
|
|
QString presentationStr; // final return string
|
|
QString unitStr; // the actual unit string
|
|
QString baseUnitStr; // the expected base unit string
|
|
double factor; // unit scaling factor, currently not used
|
|
Base::UnitSystem unitSys; // current unit system
|
|
|
|
if (!constraint->isActive)
|
|
return QString::fromLatin1(" ");
|
|
|
|
// Get the current name parameter string of the constraint
|
|
nameStr = QString::fromStdString(constraint->Name);
|
|
|
|
// Get the current value string including units
|
|
valueStr = constraint->getPresentationValue().getUserString(factor, unitStr);
|
|
|
|
// Hide units if user has requested it, is being displayed in the base
|
|
// units, and the schema being used has a clear base unit in the first
|
|
// place. Otherwise, display units.
|
|
if (constraintParameters.bHideUnits && constraint->Type != Sketcher::Angle)
|
|
{
|
|
// Only hide the default length unit. Right now there is not an easy way
|
|
// to get that from the Unit system so we have to manually add it here.
|
|
// Hopefully this can be added in the future so this code won't have to
|
|
// be updated if a new units schema is added.
|
|
unitSys = Base::UnitsApi::getSchema();
|
|
|
|
// If this is a supported unit system then define what the base unit is.
|
|
switch (unitSys)
|
|
{
|
|
case Base::UnitSystem::SI1:
|
|
case Base::UnitSystem::MmMin:
|
|
baseUnitStr = QString::fromLatin1("mm");
|
|
break;
|
|
|
|
case Base::UnitSystem::SI2:
|
|
baseUnitStr = QString::fromLatin1("m");
|
|
break;
|
|
|
|
case Base::UnitSystem::ImperialDecimal:
|
|
baseUnitStr = QString::fromLatin1("in");
|
|
break;
|
|
|
|
case Base::UnitSystem::Centimeters:
|
|
baseUnitStr = QString::fromLatin1("cm");
|
|
break;
|
|
|
|
default:
|
|
// Nothing to do
|
|
break;
|
|
}
|
|
|
|
if (!baseUnitStr.isEmpty())
|
|
{
|
|
// expected unit string matches actual unit string. remove.
|
|
if (QString::compare(baseUnitStr, unitStr)==0)
|
|
{
|
|
// Example code from: Mod/TechDraw/App/DrawViewDimension.cpp:372
|
|
QRegularExpression rxUnits(QString::fromUtf8(" \\D*$")); //space + any non digits at end of string
|
|
valueStr.remove(rxUnits); //getUserString(defaultDecimals) without units
|
|
}
|
|
}
|
|
}
|
|
|
|
if (constraint->Type == Sketcher::Diameter){
|
|
valueStr.prepend(QChar(216)); // Diameter sign
|
|
}
|
|
else if (constraint->Type == Sketcher::Radius){
|
|
valueStr.prepend(QChar(82)); // Capital letter R
|
|
}
|
|
|
|
/**
|
|
Create the representation string from the user defined format string
|
|
Format options are:
|
|
%N - the constraint name parameter
|
|
%V - the value of the dimensional constraint, including any unit characters
|
|
*/
|
|
if (constraintParameters.bShowDimensionalName && !nameStr.isEmpty())
|
|
{
|
|
if (constraintParameters.sDimensionalStringFormat.contains(QLatin1String("%V")) ||
|
|
constraintParameters.sDimensionalStringFormat.contains(QLatin1String("%N")))
|
|
{
|
|
presentationStr = constraintParameters.sDimensionalStringFormat;
|
|
presentationStr.replace(QLatin1String("%N"), nameStr);
|
|
presentationStr.replace(QLatin1String("%V"), valueStr);
|
|
}
|
|
else
|
|
{
|
|
// user defined format string does not contain any valid parameter, using default format "%N = %V"
|
|
presentationStr = nameStr + QLatin1String(" = ") + valueStr;
|
|
}
|
|
|
|
return presentationStr;
|
|
}
|
|
|
|
return valueStr;
|
|
}
|
|
|
|
std::set<int> EditModeConstraintCoinManager::detectPreselectionConstr(const SoPickedPoint *Point,
|
|
const SbVec2s &cursorPos)
|
|
{
|
|
std::set<int> constrIndices;
|
|
SoPath *path = Point->getPath();
|
|
|
|
// Get the constraints' tail
|
|
SoNode *tailFather2 = path->getNode(path->getLength()-3);
|
|
|
|
if (tailFather2 != editModeScenegraphNodes.constrGroup)
|
|
return constrIndices;
|
|
|
|
|
|
SoNode *tail = path->getTail();
|
|
SoNode *tailFather = path->getNode(path->getLength()-2);
|
|
|
|
for (int i=0; i < editModeScenegraphNodes.constrGroup->getNumChildren(); ++i) {
|
|
if (editModeScenegraphNodes.constrGroup->getChild(i) == tailFather) {
|
|
SoSeparator *sep = static_cast<SoSeparator *>(tailFather);
|
|
if (sep->getNumChildren() > static_cast<int>(ConstraintNodePosition::FirstConstraintIdIndex)) {
|
|
SoInfo *constrIds = nullptr;
|
|
if (tail == sep->getChild(static_cast<int>(ConstraintNodePosition::FirstIconIndex))) {
|
|
// First icon was hit
|
|
constrIds = static_cast<SoInfo *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstConstraintIdIndex)));
|
|
}
|
|
else {
|
|
// Assume second icon was hit
|
|
if (static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex) < sep->getNumChildren()) {
|
|
constrIds = static_cast<SoInfo *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex)));
|
|
}
|
|
}
|
|
|
|
if (constrIds) {
|
|
QString constrIdsStr = QString::fromLatin1(constrIds->string.getValue().getString());
|
|
if (combinedConstrBoxes.count(constrIdsStr) && dynamic_cast<SoImage *>(tail)) {
|
|
// If it's a combined constraint icon
|
|
|
|
// Screen dimensions of the icon
|
|
SbVec3s iconSize = getDisplayedSize(static_cast<SoImage *>(tail));
|
|
// Center of the icon
|
|
//SbVec2f iconCoords = viewer->screenCoordsOfPath(path);
|
|
|
|
// The use of the Path to get the screen coordinates to get the icon center coordinates
|
|
// does not work.
|
|
//
|
|
// This implementation relies on the use of ZoomTranslation to get the absolute and relative
|
|
// positions of the icons.
|
|
//
|
|
// In the case of second icons (the same constraint has two icons at two different positions),
|
|
// the translation vectors have to be added, as the second ZoomTranslation operates on top of
|
|
// the first.
|
|
//
|
|
// Coordinates are projected on the sketch plane and then to the screen in the interval [0 1]
|
|
// Then this result is converted to pixels using the scale factor.
|
|
|
|
SbVec3f absPos;
|
|
SbVec3f trans;
|
|
|
|
auto translation = static_cast<SoZoomTranslation *>(static_cast<SoSeparator *>(tailFather)->getChild( static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
absPos = translation->abPos.getValue();
|
|
|
|
trans = translation->translation.getValue();
|
|
|
|
if (tail != sep->getChild(static_cast<int>(ConstraintNodePosition::FirstIconIndex))) {
|
|
|
|
auto translation2 = static_cast<SoZoomTranslation *>(static_cast<SoSeparator *>(tailFather)->getChild( static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
|
|
|
|
absPos += translation2->abPos.getValue();
|
|
|
|
trans += translation2->translation.getValue();
|
|
}
|
|
|
|
// TODO: Is this calculation actually sound? Why the absolute position is not scaled and the translation is? Review.
|
|
SbVec3f constrPos = absPos + trans*ViewProviderSketchCoinAttorney::getScaleFactor(viewProvider);
|
|
|
|
SbVec2f iconCoords = ViewProviderSketchCoinAttorney::getScreenCoordinates(viewProvider, SbVec2f(constrPos[0],constrPos[1]));
|
|
|
|
// cursorPos is SbVec2s in screen coordinates coming from SoEvent in mousemove
|
|
//
|
|
// Coordinates of the mouse cursor on the icon, origin at top-left for Qt
|
|
// but bottom-left for OIV.
|
|
// The coordinates are needed in Qt format, i.e. from top to bottom.
|
|
int iconX = cursorPos[0] - iconCoords[0] + iconSize[0]/2,
|
|
iconY = cursorPos[1] - iconCoords[1] + iconSize[1]/2;
|
|
iconY = iconSize[1] - iconY;
|
|
|
|
for (ConstrIconBBVec::iterator b = combinedConstrBoxes[constrIdsStr].begin();
|
|
b != combinedConstrBoxes[constrIdsStr].end(); ++b) {
|
|
|
|
#ifdef FC_DEBUG
|
|
// Useful code to debug coordinates and bounding boxes that does not need to be compiled in for
|
|
// any debug operations.
|
|
|
|
/*Base::Console().Log("Abs(%f,%f),Trans(%f,%f),Coords(%d,%d),iCoords(%f,%f),icon(%d,%d),isize(%d,%d),boundingbox([%d,%d],[%d,%d])\n", absPos[0],absPos[1],trans[0], trans[1], cursorPos[0], cursorPos[1], iconCoords[0], iconCoords[1], iconX, iconY, iconSize[0], iconSize[1], b->first.topLeft().x(),b->first.topLeft().y(),b->first.bottomRight().x(),b->first.bottomRight().y());*/
|
|
#endif
|
|
|
|
if (b->first.contains(iconX, iconY)) {
|
|
// We've found a bounding box that contains the mouse pointer!
|
|
for (std::set<int>::iterator k = b->second.begin(); k != b->second.end(); ++k)
|
|
constrIndices.insert(*k);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// It's a constraint icon, not a combined one
|
|
QStringList constrIdStrings = constrIdsStr.split(QString::fromLatin1(","));
|
|
while (!constrIdStrings.empty())
|
|
constrIndices.insert(constrIdStrings.takeAt(0).toInt());
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// other constraint icons - eg radius...
|
|
constrIndices.clear();
|
|
constrIndices.insert(i);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return constrIndices;
|
|
}
|
|
|
|
SbVec3s EditModeConstraintCoinManager::getDisplayedSize(const SoImage *iconPtr) const
|
|
{
|
|
#if (COIN_MAJOR_VERSION >= 3)
|
|
SbVec3s iconSize = iconPtr->image.getValue().getSize();
|
|
#else
|
|
SbVec2s size;
|
|
int nc;
|
|
const unsigned char * bytes = iconPtr->image.getValue(size, nc);
|
|
SbImage img (bytes, size, nc);
|
|
SbVec3s iconSize = img.getSize();
|
|
#endif
|
|
if (iconPtr->width.getValue() != -1)
|
|
iconSize[0] = iconPtr->width.getValue();
|
|
if (iconPtr->height.getValue() != -1)
|
|
iconSize[1] = iconPtr->height.getValue();
|
|
return iconSize;
|
|
}
|
|
|
|
// public function that triggers drawing of most constraint icons
|
|
void EditModeConstraintCoinManager::drawConstraintIcons()
|
|
{
|
|
auto geolistfacade = ViewProviderSketchCoinAttorney::getGeoListFacade(viewProvider);
|
|
|
|
drawConstraintIcons(geolistfacade);
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::drawConstraintIcons(const GeoListFacade & geolistfacade)
|
|
{
|
|
const std::vector<Sketcher::Constraint *> &constraints = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
|
|
|
|
std::vector<constrIconQueueItem> iconQueue;
|
|
|
|
int maxNumberOfConstraints = std::min(editModeScenegraphNodes.constrGroup->getNumChildren(), static_cast<int>(constraints.size()));
|
|
|
|
for (int constrId = 0; constrId < maxNumberOfConstraints; ++constrId) {
|
|
Sketcher::Constraint* constraint = constraints[constrId];
|
|
|
|
// Check if Icon Should be created
|
|
bool multipleIcons = false;
|
|
|
|
QString icoType = iconTypeFromConstraint(constraint);
|
|
if (icoType.isEmpty())
|
|
continue;
|
|
|
|
if (constraint->Type != vConstrType[constrId])
|
|
break;
|
|
|
|
switch(constraint->Type) {
|
|
|
|
case Tangent:
|
|
{ // second icon is available only for colinear line segments
|
|
const Part::Geometry *geo1 = geolistfacade.getGeometryFromGeoId(constraint->First);
|
|
const Part::Geometry *geo2 = geolistfacade.getGeometryFromGeoId(constraint->Second);
|
|
if (geo1 && geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId() &&
|
|
geo2 && geo2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) {
|
|
multipleIcons = true;
|
|
}
|
|
}
|
|
break;
|
|
case Horizontal:
|
|
case Vertical:
|
|
{ // second icon is available only for point alignment
|
|
if (constraint->Second != GeoEnum::GeoUndef &&
|
|
constraint->FirstPos != Sketcher::PointPos::none &&
|
|
constraint->SecondPos != Sketcher::PointPos::none) {
|
|
multipleIcons = true;
|
|
}
|
|
}
|
|
break;
|
|
case Parallel:
|
|
multipleIcons = true;
|
|
break;
|
|
case Perpendicular:
|
|
// second icon is available only when there is no common point
|
|
if (constraint->FirstPos == Sketcher::PointPos::none && constraint->Third == GeoEnum::GeoUndef)
|
|
multipleIcons = true;
|
|
break;
|
|
case Equal:
|
|
multipleIcons = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Double-check that we can safely access the Inventor nodes
|
|
if (constrId >= editModeScenegraphNodes.constrGroup->getNumChildren()) {
|
|
Base::Console().Warning("Can't update constraint icons because view is not in sync with sketch\n");
|
|
break;
|
|
}
|
|
|
|
// Find the Constraint Icon SoImage Node
|
|
SoSeparator *sep = static_cast<SoSeparator *>(editModeScenegraphNodes.constrGroup->getChild(constrId));
|
|
int numChildren = sep->getNumChildren();
|
|
|
|
SbVec3f absPos;
|
|
// Somewhat hacky - we use SoZoomTranslations for most types of icon,
|
|
// but symmetry icons use SoTranslations...
|
|
SoTranslation *translationPtr = static_cast<SoTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
|
|
|
|
if (dynamic_cast<SoZoomTranslation *>(translationPtr))
|
|
absPos = static_cast<SoZoomTranslation *>(translationPtr)->abPos.getValue();
|
|
else
|
|
absPos = translationPtr->translation.getValue();
|
|
|
|
SoImage *coinIconPtr = dynamic_cast<SoImage *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstIconIndex)));
|
|
SoInfo *infoPtr = static_cast<SoInfo *>(sep->getChild(static_cast<int>(ConstraintNodePosition::FirstConstraintIdIndex)));
|
|
|
|
constrIconQueueItem thisIcon;
|
|
thisIcon.type = icoType;
|
|
thisIcon.constraintId = constrId;
|
|
thisIcon.position = absPos;
|
|
thisIcon.destination = coinIconPtr;
|
|
thisIcon.infoPtr = infoPtr;
|
|
thisIcon.visible = constraint->isInVirtualSpace == ViewProviderSketchCoinAttorney::isShownVirtualSpace(viewProvider);
|
|
|
|
if (constraint->Type==Symmetric) {
|
|
Base::Vector3d startingpoint = geolistfacade.getPoint(constraint->First, constraint->FirstPos);
|
|
Base::Vector3d endpoint = geolistfacade.getPoint(constraint->Second, constraint->SecondPos);
|
|
|
|
SbVec3f pos0(startingpoint.x,startingpoint.y,startingpoint.z);
|
|
SbVec3f pos1(endpoint.x,endpoint.y,endpoint.z);
|
|
|
|
thisIcon.iconRotation = ViewProviderSketchCoinAttorney::getRotation(viewProvider, pos0, pos1);
|
|
}
|
|
else {
|
|
thisIcon.iconRotation = 0;
|
|
}
|
|
|
|
if (multipleIcons) {
|
|
if (constraint->Name.empty())
|
|
thisIcon.label = QString::number(constrId + 1);
|
|
else
|
|
thisIcon.label = QString::fromUtf8(constraint->Name.c_str());
|
|
iconQueue.push_back(thisIcon);
|
|
|
|
// Note that the second translation is meant to be applied after the first.
|
|
// So, to get the position of the second icon, we add the two translations together
|
|
//
|
|
// See note ~30 lines up.
|
|
if (numChildren > static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex)) {
|
|
translationPtr = static_cast<SoTranslation *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
|
|
if (dynamic_cast<SoZoomTranslation *>(translationPtr))
|
|
thisIcon.position += static_cast<SoZoomTranslation *>(translationPtr)->abPos.getValue();
|
|
else
|
|
thisIcon.position += translationPtr->translation.getValue();
|
|
|
|
thisIcon.destination = dynamic_cast<SoImage *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondIconIndex)));
|
|
thisIcon.infoPtr = static_cast<SoInfo *>(sep->getChild(static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex)));
|
|
}
|
|
}
|
|
else {
|
|
if (constraint->Name.empty())
|
|
thisIcon.label = QString();
|
|
else
|
|
thisIcon.label = QString::fromUtf8(constraint->Name.c_str());
|
|
}
|
|
|
|
iconQueue.push_back(thisIcon);
|
|
}
|
|
|
|
combineConstraintIcons(iconQueue);
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::combineConstraintIcons(IconQueue iconQueue)
|
|
{
|
|
// getScaleFactor gives us a ratio of pixels per some kind of real units
|
|
float maxDistSquared = pow(ViewProviderSketchCoinAttorney::getScaleFactor(viewProvider), 2);
|
|
|
|
// There's room for optimisation here; we could reuse the combined icons...
|
|
combinedConstrBoxes.clear();
|
|
|
|
while(!iconQueue.empty()) {
|
|
// A group starts with an item popped off the back of our initial queue
|
|
IconQueue thisGroup;
|
|
thisGroup.push_back(iconQueue.back());
|
|
constrIconQueueItem init = iconQueue.back();
|
|
iconQueue.pop_back();
|
|
|
|
// we group only icons not being Symmetry icons, because we want those on the line
|
|
// and only icons that are visible
|
|
if (init.type != QString::fromLatin1("Constraint_Symmetric") && init.visible){
|
|
|
|
IconQueue::iterator i = iconQueue.begin();
|
|
|
|
|
|
while(i != iconQueue.end()) {
|
|
if ((*i).visible) {
|
|
bool addedToGroup = false;
|
|
|
|
for(IconQueue::iterator j = thisGroup.begin();
|
|
j != thisGroup.end(); ++j) {
|
|
float distSquared = pow(i->position[0]-j->position[0],2) + pow(i->position[1]-j->position[1],2);
|
|
if (distSquared <= maxDistSquared && (*i).type != QString::fromLatin1("Constraint_Symmetric")) {
|
|
// Found an icon in iconQueue that's close enough to
|
|
// a member of thisGroup, so move it into thisGroup
|
|
thisGroup.push_back(*i);
|
|
i = iconQueue.erase(i);
|
|
addedToGroup = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (addedToGroup) {
|
|
if (i == iconQueue.end())
|
|
// We just got the last icon out of iconQueue
|
|
break;
|
|
else
|
|
// Start looking through the iconQueue again, in case
|
|
// we have an icon that's now close enough to thisGroup
|
|
i = iconQueue.begin();
|
|
} else
|
|
++i;
|
|
}
|
|
else // if !visible we skip it
|
|
i++;
|
|
}
|
|
|
|
}
|
|
|
|
if (thisGroup.size() == 1) {
|
|
drawTypicalConstraintIcon(thisGroup[0]);
|
|
}
|
|
else {
|
|
drawMergedConstraintIcons(thisGroup);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::drawMergedConstraintIcons(IconQueue iconQueue)
|
|
{
|
|
for(IconQueue::iterator i = iconQueue.begin(); i != iconQueue.end(); ++i) {
|
|
clearCoinImage(i->destination);
|
|
}
|
|
|
|
QImage compositeIcon;
|
|
SoImage *thisDest = iconQueue[0].destination;
|
|
SoInfo *thisInfo = iconQueue[0].infoPtr;
|
|
|
|
// Tracks all constraint IDs that are combined into this icon
|
|
QString idString;
|
|
int lastVPad = 0;
|
|
|
|
QStringList labels;
|
|
std::vector<int> ids;
|
|
QString thisType;
|
|
QColor iconColor;
|
|
QList<QColor> labelColors;
|
|
int maxColorPriority;
|
|
double iconRotation;
|
|
|
|
ConstrIconBBVec boundingBoxes;
|
|
while(!iconQueue.empty()) {
|
|
IconQueue::iterator i = iconQueue.begin();
|
|
|
|
labels.clear();
|
|
labels.append(i->label);
|
|
|
|
ids.clear();
|
|
ids.push_back(i->constraintId);
|
|
|
|
thisType = i->type;
|
|
iconColor = constrColor(i->constraintId);
|
|
labelColors.clear();
|
|
labelColors.append(iconColor);
|
|
iconRotation= i->iconRotation;
|
|
|
|
maxColorPriority = constrColorPriority(i->constraintId);
|
|
|
|
if (idString.length())
|
|
idString.append(QString::fromLatin1(","));
|
|
idString.append(QString::number(i->constraintId));
|
|
|
|
i = iconQueue.erase(i);
|
|
while(i != iconQueue.end()) {
|
|
if (i->type != thisType) {
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
labels.append(i->label);
|
|
ids.push_back(i->constraintId);
|
|
labelColors.append(constrColor(i->constraintId));
|
|
|
|
if (constrColorPriority(i->constraintId) > maxColorPriority) {
|
|
maxColorPriority = constrColorPriority(i->constraintId);
|
|
iconColor= constrColor(i->constraintId);
|
|
}
|
|
|
|
idString.append(QString::fromLatin1(",") +
|
|
QString::number(i->constraintId));
|
|
|
|
i = iconQueue.erase(i);
|
|
}
|
|
|
|
// To be inserted into edit->combinedConstBoxes
|
|
std::vector<QRect> boundingBoxesVec;
|
|
int oldHeight = 0;
|
|
|
|
// Render the icon here.
|
|
if (compositeIcon.isNull()) {
|
|
compositeIcon = renderConstrIcon(thisType,
|
|
iconColor,
|
|
labels,
|
|
labelColors,
|
|
iconRotation,
|
|
&boundingBoxesVec,
|
|
&lastVPad);
|
|
} else {
|
|
int thisVPad;
|
|
QImage partialIcon = renderConstrIcon(thisType,
|
|
iconColor,
|
|
labels,
|
|
labelColors,
|
|
iconRotation,
|
|
&boundingBoxesVec,
|
|
&thisVPad);
|
|
|
|
// Stack vertically for now. Down the road, it might make sense
|
|
// to figure out the best orientation automatically.
|
|
oldHeight = compositeIcon.height();
|
|
|
|
// This is overkill for the currently used (20 July 2014) font,
|
|
// since it always seems to have the same vertical pad, but this
|
|
// might not always be the case. The 3 pixel buffer might need
|
|
// to vary depending on font size too...
|
|
oldHeight -= std::max(lastVPad - 3, 0);
|
|
|
|
compositeIcon = compositeIcon.copy(0, 0,
|
|
std::max(partialIcon.width(),
|
|
compositeIcon.width()),
|
|
partialIcon.height() +
|
|
compositeIcon.height());
|
|
|
|
QPainter qp(&compositeIcon);
|
|
qp.drawImage(0, oldHeight, partialIcon);
|
|
|
|
lastVPad = thisVPad;
|
|
}
|
|
|
|
// Add bounding boxes for the icon we just rendered to boundingBoxes
|
|
std::vector<int>::iterator id = ids.begin();
|
|
std::set<int> nextIds;
|
|
for(std::vector<QRect>::iterator bb = boundingBoxesVec.begin();
|
|
bb != boundingBoxesVec.end(); ++bb) {
|
|
nextIds.clear();
|
|
|
|
if (bb == boundingBoxesVec.begin()) {
|
|
// The first bounding box is for the icon at left, so assign
|
|
// all IDs for that type of constraint to the icon.
|
|
for(std::vector<int>::iterator j = ids.begin(); j != ids.end(); ++j)
|
|
nextIds.insert(*j);
|
|
}
|
|
else {
|
|
nextIds.insert(*(id++));
|
|
}
|
|
|
|
ConstrIconBB newBB(bb->adjusted(0, oldHeight, 0, oldHeight),
|
|
nextIds);
|
|
|
|
boundingBoxes.push_back(newBB);
|
|
}
|
|
}
|
|
|
|
combinedConstrBoxes[idString] = boundingBoxes;
|
|
thisInfo->string.setValue(idString.toLatin1().data());
|
|
sendConstraintIconToCoin(compositeIcon, thisDest);
|
|
}
|
|
|
|
|
|
/// Note: labels, labelColors, and boundingBoxes are all
|
|
/// assumed to be the same length.
|
|
QImage EditModeConstraintCoinManager::renderConstrIcon(const QString &type,
|
|
const QColor &iconColor,
|
|
const QStringList &labels,
|
|
const QList<QColor> &labelColors,
|
|
double iconRotation,
|
|
std::vector<QRect> *boundingBoxes,
|
|
int *vPad)
|
|
{
|
|
// Constants to help create constraint icons
|
|
QString joinStr = QString::fromLatin1(", ");
|
|
|
|
QPixmap pxMap;
|
|
std::stringstream constraintName;
|
|
constraintName << type.toLatin1().data() << drawingParameters.constraintIconSize; // allow resizing by embedding size
|
|
if (! Gui::BitmapFactory().findPixmapInCache(constraintName.str().c_str(), pxMap)) {
|
|
pxMap = Gui::BitmapFactory().pixmapFromSvg(type.toLatin1().data(),QSizeF(drawingParameters.constraintIconSize, drawingParameters.constraintIconSize));
|
|
Gui::BitmapFactory().addPixmapToCache(constraintName.str().c_str(), pxMap); // Cache for speed, avoiding pixmapFromSvg
|
|
}
|
|
QImage icon = pxMap.toImage();
|
|
|
|
QFont font = ViewProviderSketchCoinAttorney::getApplicationFont(viewProvider);
|
|
font.setPixelSize(static_cast<int>(1.0 * drawingParameters.constraintIconSize));
|
|
font.setBold(true);
|
|
QFontMetrics qfm = QFontMetrics(font);
|
|
|
|
int labelWidth = qfm.boundingRect(labels.join(joinStr)).width();
|
|
// See Qt docs on qRect::bottom() for explanation of the +1
|
|
int pxBelowBase = qfm.boundingRect(labels.join(joinStr)).bottom() + 1;
|
|
|
|
if (vPad)
|
|
*vPad = pxBelowBase;
|
|
|
|
QTransform rotation;
|
|
rotation.rotate(iconRotation);
|
|
|
|
QImage roticon = icon.transformed(rotation);
|
|
QImage image = roticon.copy(0, 0, roticon.width() + labelWidth,
|
|
roticon.height() + pxBelowBase);
|
|
|
|
// Make a bounding box for the icon
|
|
if (boundingBoxes)
|
|
boundingBoxes->push_back(QRect(0, 0, roticon.width(), roticon.height()));
|
|
|
|
// Render the Icons
|
|
QPainter qp(&image);
|
|
qp.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
|
qp.fillRect(roticon.rect(), iconColor);
|
|
|
|
// Render constraint label if necessary
|
|
if (!labels.join(QString()).isEmpty()) {
|
|
qp.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
|
qp.setFont(font);
|
|
|
|
int cursorOffset = 0;
|
|
|
|
//In Python: "for label, color in zip(labels, labelColors):"
|
|
QStringList::const_iterator labelItr;
|
|
QString labelStr;
|
|
QList<QColor>::const_iterator colorItr;
|
|
QRect labelBB;
|
|
for(labelItr = labels.begin(), colorItr = labelColors.begin();
|
|
labelItr != labels.end() && colorItr != labelColors.end();
|
|
++labelItr, ++colorItr) {
|
|
|
|
qp.setPen(*colorItr);
|
|
|
|
if (labelItr + 1 == labels.end()) // if this is the last label
|
|
labelStr = *labelItr;
|
|
else
|
|
labelStr = *labelItr + joinStr;
|
|
|
|
// Note: text can sometimes draw to the left of the starting
|
|
// position, eg italic fonts. Check QFontMetrics
|
|
// documentation for more info, but be mindful if the
|
|
// icon.width() is ever very small (or removed).
|
|
qp.drawText(icon.width() + cursorOffset, icon.height(), labelStr);
|
|
|
|
if (boundingBoxes) {
|
|
labelBB = qfm.boundingRect(labelStr);
|
|
labelBB.moveTo(icon.width() + cursorOffset,
|
|
icon.height() - qfm.height() + pxBelowBase);
|
|
boundingBoxes->push_back(labelBB);
|
|
}
|
|
|
|
cursorOffset += Gui::QtTools::horizontalAdvance(qfm, labelStr);
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::drawTypicalConstraintIcon(const constrIconQueueItem &i)
|
|
{
|
|
QColor color = constrColor(i.constraintId);
|
|
|
|
QImage image = renderConstrIcon(i.type,
|
|
color,
|
|
QStringList(i.label),
|
|
QList<QColor>() << color,
|
|
i.iconRotation);
|
|
|
|
i.infoPtr->string.setValue(QString::number(i.constraintId).toLatin1().data());
|
|
sendConstraintIconToCoin(image, i.destination);
|
|
}
|
|
|
|
QString EditModeConstraintCoinManager::iconTypeFromConstraint(Constraint *constraint)
|
|
{
|
|
/*! TODO: Consider pushing this functionality up into Constraint
|
|
*
|
|
Abdullah: Please, don't. An icon is visualisation information and
|
|
does not belong in App, but in Gui. Rather consider refactoring it
|
|
in a separate class dealing with visualisation of constraints.*/
|
|
|
|
switch(constraint->Type) {
|
|
case Horizontal:
|
|
return QString::fromLatin1("Constraint_Horizontal");
|
|
case Vertical:
|
|
return QString::fromLatin1("Constraint_Vertical");
|
|
case PointOnObject:
|
|
return QString::fromLatin1("Constraint_PointOnObject");
|
|
case Tangent:
|
|
return QString::fromLatin1("Constraint_Tangent");
|
|
case Parallel:
|
|
return QString::fromLatin1("Constraint_Parallel");
|
|
case Perpendicular:
|
|
return QString::fromLatin1("Constraint_Perpendicular");
|
|
case Equal:
|
|
return QString::fromLatin1("Constraint_EqualLength");
|
|
case Symmetric:
|
|
return QString::fromLatin1("Constraint_Symmetric");
|
|
case SnellsLaw:
|
|
return QString::fromLatin1("Constraint_SnellsLaw");
|
|
case Block:
|
|
return QString::fromLatin1("Constraint_Block");
|
|
default:
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::sendConstraintIconToCoin(const QImage &icon, SoImage *soImagePtr)
|
|
{
|
|
SoSFImage icondata = SoSFImage();
|
|
|
|
Gui::BitmapFactory().convert(icon, icondata);
|
|
|
|
SbVec2s iconSize(icon.width(), icon.height());
|
|
|
|
int four = 4;
|
|
soImagePtr->image.setValue(iconSize, 4, icondata.getValue(iconSize, four));
|
|
|
|
//Set Image Alignment to Center
|
|
soImagePtr->vertAlignment = SoImage::HALF;
|
|
soImagePtr->horAlignment = SoImage::CENTER;
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::clearCoinImage(SoImage *soImagePtr)
|
|
{
|
|
soImagePtr->setToDefaults();
|
|
}
|
|
|
|
QColor EditModeConstraintCoinManager::constrColor(int constraintId)
|
|
{
|
|
auto toQColor = [](auto sbcolor) -> QColor {
|
|
return QColor( (int)(sbcolor[0] * 255.0f),
|
|
(int)(sbcolor[1] * 255.0f),
|
|
(int)(sbcolor[2] * 255.0f));
|
|
};
|
|
|
|
const auto constraints = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
|
|
|
|
if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider,constraintId))
|
|
return toQColor(drawingParameters.PreselectColor);
|
|
else if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, constraintId))
|
|
return toQColor(drawingParameters.SelectColor);
|
|
else if (!constraints[constraintId]->isActive)
|
|
return toQColor(drawingParameters.DeactivatedConstrDimColor);
|
|
else if (!constraints[constraintId]->isDriving)
|
|
return toQColor(drawingParameters.NonDrivingConstrDimColor);
|
|
else
|
|
return toQColor(drawingParameters.ConstrIcoColor);
|
|
|
|
}
|
|
|
|
int EditModeConstraintCoinManager::constrColorPriority(int constraintId)
|
|
{
|
|
if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider,constraintId))
|
|
return 3;
|
|
else if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, constraintId))
|
|
return 2;
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
SoSeparator * EditModeConstraintCoinManager::getConstraintIdSeparator(int i)
|
|
{
|
|
return dynamic_cast<SoSeparator *>(editModeScenegraphNodes.constrGroup->getChild(i));
|
|
}
|
|
|
|
void EditModeConstraintCoinManager::createEditModeInventorNodes()
|
|
{
|
|
// group node for the Constraint visual +++++++++++++++++++++++++++++++++++
|
|
SoMaterialBinding *MtlBind = new SoMaterialBinding;
|
|
MtlBind->setName("ConstraintMaterialBinding");
|
|
MtlBind->value = SoMaterialBinding::OVERALL ;
|
|
editModeScenegraphNodes.EditRoot->addChild(MtlBind);
|
|
|
|
// use small line width for the Constraints
|
|
editModeScenegraphNodes.ConstraintDrawStyle = new SoDrawStyle;
|
|
editModeScenegraphNodes.ConstraintDrawStyle->setName("ConstraintDrawStyle");
|
|
editModeScenegraphNodes.ConstraintDrawStyle->lineWidth = 1 * drawingParameters.pixelScalingFactor;
|
|
editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.ConstraintDrawStyle);
|
|
|
|
// add the group where all the constraints has its SoSeparator
|
|
editModeScenegraphNodes.constrGrpSelect = new SoPickStyle(); // used to toggle constraints selectability
|
|
editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::SHAPE);
|
|
editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.constrGrpSelect);
|
|
setConstraintSelectability(); // Ensure default value;
|
|
|
|
editModeScenegraphNodes.constrGroup = new SmSwitchboard();
|
|
editModeScenegraphNodes.constrGroup->setName("ConstraintGroup");
|
|
editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.constrGroup);
|
|
|
|
SoPickStyle *ps = new SoPickStyle(); // used to following nodes aren't impacted
|
|
ps->style.setValue(SoPickStyle::SHAPE);
|
|
editModeScenegraphNodes.EditRoot->addChild(ps);
|
|
}
|