/*************************************************************************** * Copyright (c) 2015 Thomas Anderson * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include #include "SoLinearDragger.h" #include "MainWindow.h" #include "Utilities.h" #include #include using namespace Gui; SO_KIT_SOURCE(SoLinearGeometryKit) void SoLinearGeometryKit::initClass() { SO_KIT_INIT_CLASS(SoLinearGeometryKit, SoBaseKit, "BaseKit"); } SoLinearGeometryKit::SoLinearGeometryKit() { SO_KIT_CONSTRUCTOR(SoLinearGeometryKit); SO_KIT_ADD_FIELD(tipPosition, (0.0, 0.0, 0.0)); SO_KIT_INIT_INSTANCE(); } SO_KIT_SOURCE(SoArrowGeometry) void SoArrowGeometry::initClass() { SO_KIT_INIT_CLASS(SoArrowGeometry, SoLinearGeometryKit, "LinearGeometryKit"); } SoArrowGeometry::SoArrowGeometry() { SO_KIT_CONSTRUCTOR(SoArrowGeometry); SO_KIT_ADD_CATALOG_ENTRY(lightModel, SoLightModel, false, this, "", false); SO_KIT_ADD_CATALOG_ENTRY(arrowBody, SoCylinder, false, this, "", true); SO_KIT_ADD_CATALOG_ENTRY(arrowTip, SoCone, false, this, "", true); SO_KIT_ADD_CATALOG_ENTRY(_arrowBodyTranslation, SoTranslation, false, this, arrowBody, false); SO_KIT_ADD_CATALOG_ENTRY(_arrowTipTranslation, SoTranslation, false, this, arrowTip, false); SO_KIT_ADD_FIELD(coneBottomRadius, (0.8)); SO_KIT_ADD_FIELD(coneHeight, (2.5)); SO_KIT_ADD_FIELD(cylinderHeight, (10.0)); SO_KIT_ADD_FIELD(cylinderRadius, (0.1)); SO_KIT_INIT_INSTANCE(); auto arrowBody = SO_GET_ANY_PART(this, "arrowBody", SoCylinder); arrowBody->height.connectFrom(&cylinderHeight); arrowBody->radius.connectFrom(&cylinderRadius); auto arrowTip = SO_GET_ANY_PART(this, "arrowTip", SoCone); arrowTip->height.connectFrom(&coneHeight); arrowTip->bottomRadius.connectFrom(&coneBottomRadius); auto lightModel = SO_GET_ANY_PART(this, "lightModel", SoLightModel); lightModel->model = SoLightModel::BASE_COLOR; // forces the notify method to get called so that the initial translations and tipPostion are set cylinderHeight.touch(); } void SoArrowGeometry::notify(SoNotList* notList) { assert(notList); SoField* lastField = notList->getLastField(); if (lastField == &cylinderHeight) { auto translation = SO_GET_ANY_PART(this, "_arrowBodyTranslation", SoTranslation); translation->translation = SbVec3f(0, cylinderHeight.getValue() / 2.0f, 0); } if (lastField == &coneHeight || lastField == &cylinderHeight) { auto translation = SO_GET_ANY_PART(this, "_arrowTipTranslation", SoTranslation); translation->translation = SbVec3f(0, (cylinderHeight.getValue() + coneHeight.getValue()) / 2.0f, 0); tipPosition = {0, cylinderHeight.getValue() + 1.5f * coneHeight.getValue(), 0}; } } SO_KIT_SOURCE(SoLinearDragger) void SoLinearDragger::initClass() { SO_KIT_INIT_CLASS(SoLinearDragger, SoDragger, "Dragger"); } SoLinearDragger::SoLinearDragger() { SO_KIT_CONSTRUCTOR(SoLinearDragger); #if defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) this->ref(); #endif FC_ADD_CATALOG_ENTRY(activeSwitch, SoToggleSwitch, geomSeparator); FC_ADD_CATALOG_ENTRY(secondaryColor, SoBaseColor, activeSwitch); FC_ADD_CATALOG_ENTRY(labelSwitch, SoToggleSwitch, geomSeparator); FC_ADD_CATALOG_ENTRY(labelSeparator, SoSeparator, labelSwitch); FC_ADD_CATALOG_ENTRY(scale, SoScale, geomSeparator); SO_KIT_ADD_CATALOG_ABSTRACT_ENTRY(arrow, SoLinearGeometryKit, SoArrowGeometry, true, geomSeparator, "", true); SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0)); SO_KIT_ADD_FIELD(translationIncrement, (1.0)); SO_KIT_ADD_FIELD(translationIncrementCount, (0)); SO_KIT_ADD_FIELD(autoScaleResult, (1.0)); SO_KIT_ADD_FIELD(activeColor, (1, 1, 0)); SO_KIT_ADD_FIELD(labelVisible, (1)); SO_KIT_ADD_FIELD(geometryScale, (1, 1, 1)); SO_KIT_INIT_INSTANCE(); setPart("labelSeparator", buildLabelGeometry()); setPart("secondaryColor", buildActiveColor()); auto sw = SO_GET_ANY_PART(this, "labelSwitch", SoToggleSwitch); sw->on.connectFrom(&labelVisible); auto scale = SO_GET_ANY_PART(this, "scale", SoScale); scale->scaleFactor.connectFrom(&geometryScale); this->addStartCallback(&SoLinearDragger::startCB); this->addMotionCallback(&SoLinearDragger::motionCB); this->addFinishCallback(&SoLinearDragger::finishCB); addValueChangedCallback(&SoLinearDragger::valueChangedCB); fieldSensor.setFunction(&SoLinearDragger::fieldSensorCB); fieldSensor.setData(this); fieldSensor.setPriority(0); this->setUpConnections(TRUE, TRUE); FC_SET_TOGGLE_SWITCH("activeSwitch", false); } SoLinearDragger::~SoLinearDragger() { fieldSensor.setData(nullptr); fieldSensor.detach(); this->removeStartCallback(&SoLinearDragger::startCB); this->removeMotionCallback(&SoLinearDragger::motionCB); this->removeFinishCallback(&SoLinearDragger::finishCB); removeValueChangedCallback(&SoLinearDragger::valueChangedCB); } SoSeparator* SoLinearDragger::buildLabelGeometry() { auto labelSeparator = new SoSeparator; auto labelTranslation = new SoTranslation(); labelSeparator->addChild(labelTranslation); auto arrow = SO_GET_PART(this, "arrow", SoLinearGeometryKit); labelTranslation->translation.connectFrom(&arrow->tipPosition); auto label = new SoFrameLabel(); label->string.connectFrom(&this->label); label->textColor.setValue(1.0, 1.0, 1.0); label->horAlignment = SoImage::CENTER; label->vertAlignment = SoImage::HALF; label->border = false; label->backgroundUseBaseColor = true; labelSeparator->addChild(label); return labelSeparator; } SoBaseColor* SoLinearDragger::buildActiveColor() { auto color = new SoBaseColor; color->rgb.connectFrom(&activeColor); return color; } void SoLinearDragger::startCB(void*, SoDragger* d) { auto sudoThis = static_cast(d); assert(sudoThis); sudoThis->dragStart(); } void SoLinearDragger::motionCB(void*, SoDragger* d) { auto sudoThis = static_cast(d); assert(sudoThis); sudoThis->drag(); } void SoLinearDragger::finishCB(void*, SoDragger* d) { auto sudoThis = static_cast(d); assert(sudoThis); sudoThis->dragFinish(); } void SoLinearDragger::fieldSensorCB(void* f, SoSensor*) { auto sudoThis = static_cast(f); if (!f) { return; } SbMatrix matrix = sudoThis->getMotionMatrix(); // clazy:exclude=rule-of-two-soft sudoThis->workFieldsIntoTransform(matrix); sudoThis->setMotionMatrix(matrix); } void SoLinearDragger::valueChangedCB(void*, SoDragger* d) { auto sudoThis = dynamic_cast(d); assert(sudoThis); SbMatrix matrix = sudoThis->getMotionMatrix(); // clazy:exclude=rule-of-two-soft SbVec3f trans = getMatrixTransform(matrix).translation; sudoThis->fieldSensor.detach(); if (sudoThis->translation.getValue() != trans) { sudoThis->translation = trans; } sudoThis->fieldSensor.attach(&sudoThis->translation); } void SoLinearDragger::dragStart() { FC_SET_TOGGLE_SWITCH("activeSwitch", true); // do an initial projection to eliminate discrepancies // in arrow head pick. we define the arrow in the y+ direction // and we know local space will be relative to this. so y vector // line projection will work. projector.setViewVolume(this->getViewVolume()); projector.setWorkingSpace(this->getLocalToWorldMatrix()); projector.setLine(SbLine(SbVec3f(0.0, 0.0, 0.0), SbVec3f(0.0, 1.0, 0.0))); SbVec3f hitPoint = projector.project(getNormalizedLocaterPosition()); projector.setLine(SbLine(SbVec3f(0.0, 0.0, 0.0), hitPoint)); SbMatrix localToWorld = getLocalToWorldMatrix(); localToWorld.multVecMatrix(hitPoint, hitPoint); setStartingPoint((hitPoint)); translationIncrementCount.setValue(0); } void SoLinearDragger::drag() { projector.setViewVolume(this->getViewVolume()); projector.setWorkingSpace(this->getLocalToWorldMatrix()); SbVec3f hitPoint = projector.project(getNormalizedLocaterPosition()); SbVec3f startingPoint = getLocalStartingPoint(); SbVec3f localMovement = hitPoint - startingPoint; // scale the increment to match local space. float scaledIncrement = static_cast(translationIncrement.getValue()) / autoScaleResult.getValue(); localMovement = roundTranslation(localMovement, scaledIncrement); // when the movement vector is null either the appendTranslation or // the setMotionMatrix doesn't work. either way it stops translating // back to its initial starting point. if (localMovement.equals(SbVec3f(0.0, 0.0, 0.0), 0.00001f)) { setMotionMatrix(getStartMotionMatrix()); // don't know why I need the following but if I don't have it // it won't return to original position. this->valueChanged(); } else { setMotionMatrix(appendTranslation(getStartMotionMatrix(), localMovement)); } Base::Quantity quantity(static_cast(translationIncrementCount.getValue()) * translationIncrement.getValue(), Base::Unit::Length); QString message = QStringLiteral("%1 %2").arg(QObject::tr("Translation:"), QString::fromStdString(quantity.getUserString())); getMainWindow()->showMessage(message, 3000); } void SoLinearDragger::dragFinish() { FC_SET_TOGGLE_SWITCH("activeSwitch", false); } SbBool SoLinearDragger::setUpConnections(SbBool onoff, SbBool doitalways) { if (!doitalways && this->connectionsSetUp == onoff) { return onoff; } SbBool oldval = this->connectionsSetUp; if (onoff) { inherited::setUpConnections(onoff, doitalways); SoLinearDragger::fieldSensorCB(this, nullptr); if (this->fieldSensor.getAttachedField() != &this->translation) { this->fieldSensor.attach(&this->translation); } } else { if (this->fieldSensor.getAttachedField()) { this->fieldSensor.detach(); } inherited::setUpConnections(onoff, doitalways); } this->connectionsSetUp = onoff; return oldval; } SbVec3f SoLinearDragger::roundTranslation(const SbVec3f& vecIn, float incrementIn) { // everything is transformed into local space. That means we only have // worry about the y-value. int yCount = 0; float yValue = vecIn[1]; if (fabs(yValue) > (incrementIn / 2.0)) { yCount = static_cast(yValue / incrementIn); float remainder = fmod(yValue, incrementIn); if (remainder >= (incrementIn / 2.0)) { yCount++; } } translationIncrementCount.setValue(yCount); SbVec3f out; out[0] = 0; out[1] = static_cast(yCount) * incrementIn; out[2] = 0.0; return out; } SO_KIT_SOURCE(SoLinearDraggerContainer) void SoLinearDraggerContainer::initClass() { SoLinearDragger::initClass(); SO_KIT_INIT_CLASS(SoLinearDraggerContainer, SoInteractionKit, "InteractionKit"); } SoLinearDraggerContainer::SoLinearDraggerContainer() { SO_KIT_CONSTRUCTOR(SoLinearDraggerContainer); #if defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) this->ref(); #endif FC_ADD_CATALOG_ENTRY(draggerSwitch, SoToggleSwitch, geomSeparator); FC_ADD_CATALOG_ENTRY(baseColor, SoBaseColor, draggerSwitch); FC_ADD_CATALOG_ENTRY(transform, SoTransform, draggerSwitch); FC_ADD_CATALOG_ENTRY(dragger, SoLinearDragger, draggerSwitch); SO_KIT_ADD_FIELD(rotation, (0, 0, 0, 0)); SO_KIT_ADD_FIELD(color, (0, 0, 0)); SO_KIT_ADD_FIELD(translation, (0, 0, 0)); SO_KIT_ADD_FIELD(visible, (1)); SO_KIT_INIT_INSTANCE(); setPart("baseColor", buildColor()); setPart("transform", buildTransform()); auto sw = SO_GET_ANY_PART(this, "draggerSwitch", SoToggleSwitch); sw->on.connectFrom(&visible); } SoBaseColor* SoLinearDraggerContainer::buildColor() { auto color = new SoBaseColor; color->rgb.connectFrom(&this->color); return color; } SoTransform* SoLinearDraggerContainer::buildTransform() { auto transform = new SoTransform; transform->translation.connectFrom(&this->translation); transform->rotation.connectFrom(&this->rotation); return transform; } SoLinearDragger* SoLinearDraggerContainer::getDragger() { return SO_GET_PART(this, "dragger", SoLinearDragger); } void Gui::SoLinearDraggerContainer::setPointerDirection(const Base::Vector3d& dir) { // This is the direction along which the SoLinearDragger points in it local space Base::Vector3d draggerDir{0, 1, 0}; Base::Vector3d axis = draggerDir.Cross(dir).Normalize(); double ang = draggerDir.GetAngleOriented(dir, axis); SbRotation rot{Base::convertTo(axis), static_cast(ang)}; rotation.setValue(rot); }