Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link accumulated fixes #2491

Closed
wants to merge 13 commits into from
Closed
2 changes: 1 addition & 1 deletion src/App/PropertyGeo.cpp
Expand Up @@ -584,7 +584,7 @@ void PropertyPlacement::setValue(const Base::Placement &pos)
bool PropertyPlacement::setValueIfChanged(const Base::Placement &pos,double tol,double atol)
{
if(_cPos.getPosition().IsEqual(pos.getPosition(),tol)
&& _cPos.getRotation().isSame(pos.getRotation(),atol))
&& _cPos.getRotation().isSimilar(pos.getRotation(),atol))
{
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Base/Console.cpp
Expand Up @@ -331,7 +331,7 @@ void ConsoleSingleton::Error( const char *pMsg, ... )

void ConsoleSingleton::Log( const char *pMsg, ... )
{
if (!_bVerbose)
if (_bVerbose)
{
FC_CONSOLE_FMT(Log,Log);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Base/Matrix.cpp
Expand Up @@ -954,7 +954,7 @@ int Matrix4D::hasScale(double tol) const {
// scaling factors are the column vector length. We use square distance and
// ignore the actual scaling signess
//
if(!tol)
if(tol == 0.0)
tol = 1e-9;
double dx = Vector3d(dMtrx4D[0][0],dMtrx4D[1][0],dMtrx4D[2][0]).Sqr();
double dy = Vector3d(dMtrx4D[0][1],dMtrx4D[1][1],dMtrx4D[2][1]).Sqr();
Expand Down
6 changes: 3 additions & 3 deletions src/Base/Rotation.cpp
Expand Up @@ -396,10 +396,10 @@ bool Rotation::isSame(const Rotation& q) const
return false;
}

bool Rotation::isSame(const Rotation& q, double tol) const
bool Rotation::isSimilar(const Rotation& q, double tol) const
{
Vector3d v(0,0,1);
return std::fabs(multVec(v).GetAngle(q.multVec(v))) < tol;
double dot = q.quat[0]*quat[0]+q.quat[1]*quat[1]+q.quat[2]*quat[2]+q.quat[3]*quat[3];
return fabs(dot) >= 1.0 - tol;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem, I think. fabs(dot) is very insensitive to changes to any of the quaternions, because the value is at its peak (i.e. the derivative is zero). That means, the actual tolerance is more like sqrt(tol), so if you ask for tolerance of 1e-12, the comparison is sensitive to something like 1e-6 radians.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To consider: return length(Q1-Q2) < tol || length(Q1+Q2) < tol

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically the values Q1,Q2,Q3 of a quaternion keep the information about the rotation axis and Q4 keeps the information about the angle.
With the new implementation the tol has no geometric meaning any more but a pure algebraic meaning and I think it's hard to see how it behaves for different quaternions.
If there is a slight difference of the values Q1-Q3 of two quaternions then it presumably has another effect as when Q4 is slightly different.

So, the old implementation has a clear geometric meaning that can be easily understood but is just not correctly implemented.

A good to understand implementation could be:
multiply the one quaternion with the inverse of the other quaternion. If both are similar we should get a quaternion that is almost the identity quaternion.
Now multiply the vector (1,0,0) with the quaternion and calculate the angle between the output and (1,0,0). If the angle is <= tol the quaternions can be considered similar.

Copy link
Contributor

@DeepSOIC DeepSOIC Sep 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically the values Q1,Q2,Q3 of a quaternion keep the information about the rotation axis and Q4 keeps the information about the angle.

Not even close.

>>> App.Rotation(App.Vector(1,1,1),1e-6/6.28*360).Q
(2.888215548143538e-07, 2.888215548143538e-07, 2.888215548143538e-07, 0.9999999999998749)

EDIT: well, close. But Q1,Q2,Q3 are more like axis multiplied by angle.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I said basically, not exactly!
When a normalized axis (x,y,z) and an angle (rad) is given then:
Q1 = x * sin(rad/2)
Q2 = y * sin(rad/2)
Q3 = z * sin(rad/2)
Q4 = cos(rad/2)

sin(rad/2) is applied to all coordinates of the axis so (Q1,Q2,Q3) has the exact same direction (orientation of the vector can be flipped) as the axis.

And when you look at your example you will see that the first three values of the quaternion are equal as of the axis (1,1,1).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, got it.

I am still under impression that quaternions to 3d rotations are pretty much the same as representing 2d rotations with a unit-length vector x,y = cos, sin. There, you can quickly figure out the amount the two rotations are different by just measuring length of difference of the two vectors representing them (for small angles, the length of vector difference is about equal to the angle difference).

So I used the same trick with quaternions, adding that two equal and opposite quaternions are the same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it further the trick with the inverted quaternion doesn't work either:
r1=App.Rotation(App.Vector(0,1,0),20)
r2=App.Rotation(App.Vector(1,0,0),15)
r3=r2.multiply(r1)

r4=r1.multiply(r3.inverted())
r4.multVec(App.Vector(1,0,0)) # -> Vector (1.0, 6.87953070464688e-18, -9.05707399495939e-19)
r4 # Rotation (-0.13052619222005157, 0.0, 3.469446951953614e-18, 0.9914448613738104)

So, this brings me back to my original idea to check the input and output of two vectors.

Copy link
Contributor

@wwmayer wwmayer Sep 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I detected that Coin's SbRotation class offers a equal() method with a tolerance and the implementation is based on SbVec4f that looks like this:

float xdist = this->vec[0] - v[0];
float ydist = this->vec[1] - v[1];
float zdist = this->vec[2] - v[2];
float wdist = this->vec[3] - v[3];

if((xdistxdist + ydistydist + zdistzdist + wdistwdist) <= tolerance)
return TRUE;
return FALSE;

So, then it's probably best to use the same implementation to avoid inconsistencies between SbRotation and Rotation.

Remark:
Coin's implementation can be optimized quite a bit. Assuming we have two quaternions Q=(q1,q2,q3,q4) and R=(r1,r2,r3,r4) then Coin checks this:
(q1-r1)^2 + (q2-r2)^2 + (q3-r3)^2 + (q4-r4)^2 <= tol
<=>
(q1^2 - 2q1r1 + r1^2) + (q2^2 - 2q2r2 + r2^2) + (q3^2 - 2q3r3 + r3^2)+(q4^2 - 2q4r4 + r4^2) <= tol
<=>
(q1^2+q2^2+q3^2+q4^2) + (r1^2+r2^2+r3^2+r4^2) - 2*(q1r1 + q2r2 + q3r3 + q4r4) <= tol
<=> (the sum of squares of a quaternion is 1)
2 - 2*(q1r1 + q2r2 + q3r3 + q4r4) <= tol
<=>
q1r1 + q2r2 + q3r3 + q4r4 >= 1-tol/2

So, then we are back to realthunder's implementation with the difference that we must check with tol/2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made some precision tests, let's continue there:
https://forum.freecadweb.org/viewtopic.php?f=10&t=39231

}

Vector3d Rotation::multVec(const Vector3d & src) const {
Expand Down
2 changes: 1 addition & 1 deletion src/Base/Rotation.h
Expand Up @@ -87,7 +87,7 @@ class BaseExport Rotation
Vector3d multVec(const Vector3d & src) const;
void scaleAngle(const double scaleFactor);
bool isSame(const Rotation&) const;
bool isSame(const Rotation&, double tol) const;
bool isSimilar(const Rotation&, double tol) const;
//@}

/** Specialty constructors */
Expand Down
2 changes: 1 addition & 1 deletion src/Gui/ActiveObjectList.h
Expand Up @@ -59,7 +59,7 @@ namespace Gui
return dynamic_cast<_T>(getObject(it->second,true,parent,subname));
}
void setObject(App::DocumentObject*, const char*, const char *subname=0,
const Gui::HighlightMode& m = Gui::LightBlue);
const Gui::HighlightMode& m = Gui::UserDefined);
bool hasObject(const char*)const;
void objectDeleted(const ViewProviderDocumentObject& viewProviderIn);
bool hasObject(App::DocumentObject *obj, const char *, const char *subname=0) const;
Expand Down
3 changes: 3 additions & 0 deletions src/Gui/Application.cpp
Expand Up @@ -958,6 +958,9 @@ void Application::setActiveDocument(Gui::Document* pcDocument)
{
if (d->activeDocument == pcDocument)
return; // nothing needs to be done

getMainWindow()->updateActions();

if (pcDocument) {
// This happens if a document with more than one view is about being
// closed and a second view is activated. The document is still not
Expand Down
1 change: 1 addition & 0 deletions src/Gui/Application.h
Expand Up @@ -251,6 +251,7 @@ class GuiExport Application
static PyObject* sAddCommand (PyObject *self,PyObject *args);
static PyObject* sListCommands (PyObject *self,PyObject *args);
static PyObject* sIsCommandActive (PyObject *self,PyObject *args);
static PyObject* sUpdateCommands (PyObject *self,PyObject *args);

static PyObject* sHide (PyObject *self,PyObject *args); // deprecated
static PyObject* sShow (PyObject *self,PyObject *args); // deprecated
Expand Down
11 changes: 11 additions & 0 deletions src/Gui/ApplicationPy.cpp
Expand Up @@ -141,6 +141,9 @@ PyMethodDef Application::Methods[] = {
{"listCommands", (PyCFunction) Application::sListCommands, METH_VARARGS,
"listCommands() -> list of strings\n\n"
"Returns a list of all commands known to FreeCAD."},
{"updateCommands()", (PyCFunction) Application::sUpdateCommands, METH_VARARGS,
"updateCommands()\n\n"
"Update all command active status"},
{"SendMsgToActiveView", (PyCFunction) Application::sSendActiveView, METH_VARARGS,
"deprecated -- use class View"},
{"sendMsgToFocusView", (PyCFunction) Application::sSendFocusView, METH_VARARGS,
Expand Down Expand Up @@ -1236,6 +1239,14 @@ PyObject* Application::sIsCommandActive(PyObject * /*self*/, PyObject *args)
}PY_CATCH;
}

PyObject* Application::sUpdateCommands(PyObject * /*self*/, PyObject *args)
{
if (!PyArg_ParseTuple(args, ""))
return NULL;

getMainWindow()->updateActions();
Py_Return;
}

PyObject* Application::sListCommands(PyObject * /*self*/, PyObject *args)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Gui/Document.cpp
Expand Up @@ -772,6 +772,8 @@ void Document::slotChangedObject(const App::DocumentObject& Obj, const App::Prop
FC_LOG(Prop.getFullName() << " modified");
setModified(true);
}

getMainWindow()->updateActions(true);
}

void Document::slotRelabelObject(const App::DocumentObject& Obj)
Expand Down
2 changes: 0 additions & 2 deletions src/Gui/MainWindow.cpp
Expand Up @@ -1001,7 +1001,6 @@ void MainWindow::setActiveWindow(MDIView* view)
onSetActiveSubWindow(view->parentWidget());
d->activeView = view;
Application::Instance->viewActivated(view);
updateActions();
}

void MainWindow::onWindowActivated(QMdiSubWindow* w)
Expand All @@ -1025,7 +1024,6 @@ void MainWindow::onWindowActivated(QMdiSubWindow* w)
// set active the appropriate window (it needs not to be part of mdiIds, e.g. directly after creation)
d->activeView = view;
Application::Instance->viewActivated(view);
updateActions();
}

void MainWindow::onWindowsMenuAboutToShow()
Expand Down
72 changes: 37 additions & 35 deletions src/Gui/PropertyView.cpp
Expand Up @@ -136,6 +136,12 @@ PropertyView::PropertyView(QWidget *parent)
this->connectDelDocument =
Application::Instance->signalDeleteDocument.connect(
boost::bind(&PropertyView::slotDeleteDocument, this, _1));
this->connectDelViewObject =
Application::Instance->signalDeletedObject.connect(
boost::bind(&PropertyView::slotDeletedViewObject, this, _1));
this->connectDelObject =
App::GetApplication().signalDeletedObject.connect(
boost::bind(&PropertyView::slotDeletedObject, this, _1));
}

PropertyView::~PropertyView()
Expand All @@ -149,6 +155,8 @@ PropertyView::~PropertyView()
this->connectRedoDocument.disconnect();
this->connectActiveDoc.disconnect();
this->connectDelDocument.disconnect();
this->connectDelObject.disconnect();
this->connectDelViewObject.disconnect();
}

static bool _ShowAll;
Expand Down Expand Up @@ -253,6 +261,25 @@ void PropertyView::slotDeleteDocument(const Gui::Document &doc) {
propertyEditorView->buildUp();
propertyEditorData->buildUp();
clearPropertyItemSelection();
timer->start(50);
}
}

void PropertyView::slotDeletedViewObject(const Gui::ViewProvider &vp) {
if(propertyEditorView->propOwners.count(&vp)) {
propertyEditorView->buildUp();
propertyEditorData->buildUp();
clearPropertyItemSelection();
timer->start(50);
}
}

void PropertyView::slotDeletedObject(const App::DocumentObject &obj) {
if(propertyEditorData->propOwners.count(&obj)) {
propertyEditorView->buildUp();
propertyEditorData->buildUp();
clearPropertyItemSelection();
timer->start(50);
}
}

Expand Down Expand Up @@ -297,10 +324,7 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg)
return;

// clear the properties.
propertyEditorData->buildUp();
propertyEditorView->buildUp();
clearPropertyItemSelection();
timer->start(100);
timer->start(50);
}

void PropertyView::onTimer() {
Expand All @@ -310,6 +334,9 @@ void PropertyView::onTimer() {
clearPropertyItemSelection();
timer->stop();

if(!this->isConnectionAttached())
return;

if(!Gui::Selection().hasSelection()) {
auto gdoc = TreeWidget::selectedDocument();
if(!gdoc || !gdoc->getDocument())
Expand All @@ -335,36 +362,11 @@ void PropertyView::onTimer() {
std::vector<PropInfo> propViewMap;
bool checkLink = true;
ViewProviderDocumentObject *vpLast = 0;
const auto &array = Gui::Selection().getCompleteSelection(false);
for(auto &sel : array) {
if(!sel.pObject) continue;
App::DocumentObject *parent = 0;
App::DocumentObject *ob = sel.pObject->resolve(sel.SubName,&parent);
auto sels = Gui::Selection().getSelectionEx("*");
for(auto &sel : sels) {
App::DocumentObject *ob = sel.getObject();
if(!ob) continue;

// App::Link should be able to handle special case below now, besides, the new
// support of plain group in App::Link breaks because of the code below
#if 0
if(parent) {
auto parentVp = Application::Instance->getViewProvider(parent);
if(parentVp) {
// For special case where the SubName reference can resolve to
// a non-child object (e.g. link array element), the tree view
// will select the parent instead. So we shall show the
// property of the parent as well.
bool found = false;
for(auto child : parentVp->claimChildren()) {
if(ob == child) {
found = true;
break;
}
}
if(!found)
ob = parent;
}
}
#endif

// Do not process an object more than once
if(!objSet.insert(ob).second)
continue;
Expand Down Expand Up @@ -491,7 +493,7 @@ void PropertyView::onTimer() {
dataPropsMap.clear();

for (it = propDataMap.begin(); it != propDataMap.end(); ++it) {
if (it->propList.size() == array.size()) {
if (it->propList.size() == sels.size()) {
if(it->propList[0]->testStatus(App::Property::PropDynamic))
dataPropsMap.emplace(it->propName, std::move(it->propList));
else
Expand All @@ -502,10 +504,10 @@ void PropertyView::onTimer() {
for(auto &v : dataPropsMap)
dataProps.emplace_back(v.first,std::move(v.second));

propertyEditorData->buildUp(std::move(dataProps));
propertyEditorData->buildUp(std::move(dataProps),true);

for (it = propViewMap.begin(); it != propViewMap.end(); ++it) {
if (it->propList.size() == array.size())
if (it->propList.size() == sels.size())
viewProps.emplace_back(it->propName, std::move(it->propList));
}

Expand Down
4 changes: 4 additions & 0 deletions src/Gui/PropertyView.h
Expand Up @@ -89,6 +89,8 @@ public Q_SLOTS:
void slotRollback();
void slotActiveDocument(const Gui::Document&);
void slotDeleteDocument(const Gui::Document&);
void slotDeletedViewObject(const Gui::ViewProvider&);
void slotDeletedObject(const App::DocumentObject&);

void checkEnable(const char *doc = 0);

Expand All @@ -105,6 +107,8 @@ public Q_SLOTS:
Connection connectRedoDocument;
Connection connectActiveDoc;
Connection connectDelDocument;
Connection connectDelObject;
Connection connectDelViewObject;
QTabWidget* tabs;
QTimer* timer;
};
Expand Down
5 changes: 5 additions & 0 deletions src/Gui/TaskView/TaskView.cpp
Expand Up @@ -44,6 +44,7 @@
#include <Gui/ViewProvider.h>
#include <Gui/Control.h>
#include <Gui/ActionFunction.h>
#include <Gui/MainWindow.h>

#if defined (QSINT_ACTIONPANEL)
#include <Gui/QSint/actionpanel/taskgroup_p.h>
Expand Down Expand Up @@ -613,10 +614,14 @@ void TaskView::showDialog(TaskDialog *dlg)
ActiveDialog = dlg;

ActiveDialog->open();

getMainWindow()->updateActions();
}

void TaskView::removeDialog(void)
{
getMainWindow()->updateActions();

if (ActiveCtrl) {
taskPanel->removeWidget(ActiveCtrl);
delete ActiveCtrl;
Expand Down
48 changes: 18 additions & 30 deletions src/Gui/Tree.cpp
Expand Up @@ -785,8 +785,6 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e)
{
// ask workbenches and view provider, ...
MenuItem view;
view << "Std_TreeViewActions";

Gui::Application::Instance->setupContextMenu("Tree", &view);

view << "Std_Expressions";
Expand Down Expand Up @@ -836,47 +834,36 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e)
DocumentObjectItem* objitem = static_cast<DocumentObjectItem*>
(this->contextItem);

auto selItems = this->selectedItems();
// if only one item is selected setup the edit menu
if (selItems.size() == 1) {

auto cmd = Gui::Application::Instance->commandManager().getCommandByName("Std_LinkSelectLinked");
if(cmd && cmd->isActive())
cmd->addTo(&contextMenu);
cmd = Gui::Application::Instance->commandManager().getCommandByName("Std_LinkSelectLinkedFinal");
if(cmd && cmd->isActive())
cmd->addTo(&contextMenu);

objitem->object()->setupContextMenu(&editMenu, this, SLOT(onStartEditing()));
QList<QAction*> editAct = editMenu.actions();
if (!editAct.isEmpty()) {
contextMenu.addSeparator();
for (QList<QAction*>::iterator it = editAct.begin(); it != editAct.end(); ++it)
contextMenu.addAction(*it);
QAction* first = editAct.front();
contextMenu.setDefaultAction(first);
if (objitem->object()->isEditing())
contextMenu.addAction(finishEditingAction);
}
}

contextMenu.addSeparator();

App::Document* doc = objitem->object()->getObject()->getDocument();
showHiddenAction->setChecked(doc->ShowHidden.getValue());
contextMenu.addAction(this->showHiddenAction);

hideInTreeAction->setChecked(!objitem->object()->showInTree());
contextMenu.addAction(this->hideInTreeAction);

contextMenu.addAction(this->searchObjectsAction);

if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId()))
contextMenu.addAction(this->createGroupAction);

contextMenu.addAction(this->markRecomputeAction);
contextMenu.addAction(this->recomputeObjectAction);
contextMenu.addAction(this->relabelObjectAction);

auto selItems = this->selectedItems();
// if only one item is selected setup the edit menu
if (selItems.size() == 1) {
objitem->object()->setupContextMenu(&editMenu, this, SLOT(onStartEditing()));
QList<QAction*> editAct = editMenu.actions();
if (!editAct.isEmpty()) {
QAction* topact = contextMenu.actions().front();
for (QList<QAction*>::iterator it = editAct.begin(); it != editAct.end(); ++it)
contextMenu.insertAction(topact,*it);
QAction* first = editAct.front();
contextMenu.setDefaultAction(first);
if (objitem->object()->isEditing())
contextMenu.insertAction(topact, this->finishEditingAction);
contextMenu.insertSeparator(topact);
}
}
}


Expand Down Expand Up @@ -4408,6 +4395,7 @@ void DocumentObjectItem::setHighlight(bool set, Gui::HighlightMode high) {
default:
break;
}
this->setFont(0,f);
}

const char *DocumentObjectItem::getTreeName() const {
Expand Down