Merge branch 'master' of https://github.com/highfidelity/hifi into one

This commit is contained in:
Sam Gateau 2018-12-03 20:52:43 -08:00
commit d2ff4d4674
49 changed files with 382 additions and 580 deletions

View file

@ -656,6 +656,8 @@ void Agent::queryAvatars() {
ViewFrustum view;
view.setPosition(scriptedAvatar->getWorldPosition());
view.setOrientation(scriptedAvatar->getHeadOrientation());
view.setProjection(DEFAULT_FIELD_OF_VIEW_DEGREES, DEFAULT_ASPECT_RATIO,
DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP);
view.calculate();
ConicalViewFrustum conicalView { view };
@ -876,18 +878,30 @@ void Agent::aboutToFinish() {
DependencyManager::destroy<AudioInjectorManager>();
// destroy all other created dependencies
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<AudioScriptingInterface>();
DependencyManager::destroy<RecordingScriptingInterface>();
DependencyManager::destroy<AnimationCacheScriptingInterface>();
DependencyManager::destroy<EntityScriptingInterface>();
DependencyManager::destroy<ResourceScriptingInterface>();
DependencyManager::destroy<UserActivityLoggerScriptingInterface>();
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<AnimationCache>();
DependencyManager::destroy<recording::Deck>();
DependencyManager::destroy<recording::Recorder>();
DependencyManager::destroy<recording::ClipCache>();
DependencyManager::destroy<AvatarHashMap>();
DependencyManager::destroy<AssignmentParentFinder>();
DependencyManager::destroy<MessagesClient>();
DependencyManager::destroy<ResourceManager>();
DependencyManager::destroy<ResourceCacheSharedItems>();
// drop our shared pointer to the script engine, then ask ScriptEngines to shutdown scripting
// this ensures that the ScriptEngine goes down before ScriptEngines
_scriptEngine.clear();

View file

@ -129,17 +129,12 @@ void AssignmentClient::stopAssignmentClient() {
QThread* currentAssignmentThread = _currentAssignment->thread();
// ask the current assignment to stop
BLOCKING_INVOKE_METHOD(_currentAssignment, "stop");
QMetaObject::invokeMethod(_currentAssignment, "stop");
// ask the current assignment to delete itself on its thread
_currentAssignment->deleteLater();
// when this thread is destroyed we don't need to run our assignment complete method
disconnect(currentAssignmentThread, &QThread::destroyed, this, &AssignmentClient::assignmentCompleted);
// wait on the thread from that assignment - it will be gone once the current assignment deletes
currentAssignmentThread->quit();
currentAssignmentThread->wait();
auto PROCESS_EVENTS_INTERVAL_MS = 100;
while (!currentAssignmentThread->wait(PROCESS_EVENTS_INTERVAL_MS)) {
QCoreApplication::processEvents();
}
}
}

View file

@ -337,6 +337,13 @@ void AudioMixerClientData::removeAgentAvatarAudioStream() {
if (it != _audioStreams.end()) {
_audioStreams.erase(it);
// Clear mixing structures so that they get recreated with up to date
// data if the stream comes back
setHasReceivedFirstMix(false);
_streams.skipped.clear();
_streams.inactive.clear();
_streams.active.clear();
}
}

View file

@ -21,7 +21,7 @@
#include <GLMHelpers.h>
ScriptableAvatar::ScriptableAvatar() {
_clientTraitsHandler = std::unique_ptr<ClientTraitsHandler>(new ClientTraitsHandler(this));
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
}
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {

View file

@ -583,15 +583,29 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> mess
void EntityScriptServer::aboutToFinish() {
shutdownScriptEngine();
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
DependencyManager::get<ResourceManager>()->cleanup();
DependencyManager::destroy<AudioScriptingInterface>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<ResourceScriptingInterface>();
DependencyManager::destroy<EntityScriptingInterface>();
DependencyManager::destroy<SoundCache>();
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<ResourceManager>();
DependencyManager::destroy<ResourceCacheSharedItems>();
DependencyManager::destroy<MessagesClient>();
DependencyManager::destroy<AssignmentDynamicFactory>();
DependencyManager::destroy<AssignmentParentFinder>();
DependencyManager::destroy<AvatarHashMap>();
DependencyManager::get<ResourceManager>()->cleanup();
DependencyManager::destroy<PluginManager>();
DependencyManager::destroy<ResourceScriptingInterface>();
DependencyManager::destroy<EntityScriptingInterface>();
// cleanup the AudioInjectorManager (and any still running injectors)
DependencyManager::destroy<AudioInjectorManager>();

View file

@ -23,6 +23,8 @@ Item {
width: root.pane.width
property bool failAfterSignUp: false
onWidthChanged: d.resize();
function login() {
flavorText.visible = false
mainTextContainer.visible = false
@ -127,7 +129,7 @@ Item {
Column {
id: form
width: parent.width
onHeightChanged: d.resize(); onWidthChanged: d.resize();
onHeightChanged: d.resize();
anchors {
top: mainTextContainer.bottom

View file

@ -44,14 +44,14 @@ Rectangle {
onPasswordChanged: {
var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard");
var use3DKeyboard = (typeof KeyboardScriptingInterface === "undefined") ? false : KeyboardScriptingInterface.use3DKeyboard;
if (use3DKeyboard) {
KeyboardScriptingInterface.password = password;
}
}
onRaisedChanged: {
var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard");
var use3DKeyboard = (typeof KeyboardScriptingInterface === "undefined") ? false : KeyboardScriptingInterface.use3DKeyboard;
if (!use3DKeyboard) {
keyboardBase.height = raised ? raisedHeight : 0;
keyboardBase.visible = raised;

View file

@ -61,7 +61,7 @@ Item {
RalewaySemiBold {
text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in")
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
Layout.alignment: Qt.AlignRight
font.pixelSize: 20
color: "#afafaf"
}
@ -71,7 +71,7 @@ Item {
height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0
text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : ""
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
Layout.alignment: Qt.AlignRight
font.pixelSize: 20
color: "#afafaf"
}

View file

@ -2520,6 +2520,8 @@ void Application::cleanupBeforeQuit() {
QString webengineRemoteDebugging = QProcessEnvironment::systemEnvironment().value("QTWEBENGINE_REMOTE_DEBUGGING", "false");
qCDebug(interfaceapp) << "QTWEBENGINE_REMOTE_DEBUGGING =" << webengineRemoteDebugging;
DependencyManager::prepareToExit();
if (tracing::enabled()) {
auto tracer = DependencyManager::get<tracing::Tracer>();
tracer->stopTracing();
@ -2626,6 +2628,7 @@ void Application::cleanupBeforeQuit() {
// destroy Audio so it and its threads have a chance to go down safely
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "stop");
DependencyManager::destroy<AudioClient>();
DependencyManager::destroy<AudioInjectorManager>();
DependencyManager::destroy<AudioScriptingInterface>();
@ -2716,7 +2719,7 @@ Application::~Application() {
// quit the thread used by the closure event sender
closeEventSender->thread()->quit();
// Can't log to file passed this point, FileLogger about to be deleted
// Can't log to file past this point, FileLogger about to be deleted
qInstallMessageHandler(LogHandler::verboseMessageHandler);
}
@ -6163,6 +6166,11 @@ void Application::update(float deltaTime) {
PerformanceTimer perfTimer("enqueueFrame");
getMain3DScene()->enqueueFrame();
}
// If the display plugin is inactive then the frames won't be processed so process them here.
if (!getActiveDisplayPlugin()->isActive()) {
getMain3DScene()->processTransactionQueue();
}
}
void Application::updateRenderArgs(float deltaTime) {

View file

@ -247,25 +247,35 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
QScriptEngine scriptEngine;
QVariantList wearableEntities;
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
auto avatarEntities = myAvatar->getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity || !isWearableEntity(entity)) {
continue;
if (entityTree) {
QScriptEngine scriptEngine;
auto avatarEntities = myAvatar->getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity || !isWearableEntity(entity)) {
continue;
}
QVariantMap avatarEntityData;
EncodeBitstreamParams params;
auto desiredProperties = entity->getEntityProperties(params);
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
desiredProperties -= PROP_JOINT_ROTATIONS_SET;
desiredProperties -= PROP_JOINT_ROTATIONS;
desiredProperties -= PROP_JOINT_TRANSLATIONS_SET;
desiredProperties -= PROP_JOINT_TRANSLATIONS;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
wearableEntities.append(QVariant(avatarEntityData));
}
QVariantMap avatarEntityData;
EncodeBitstreamParams params;
auto desiredProperties = entity->getEntityProperties(params);
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
wearableEntities.append(QVariant(avatarEntityData));
}
bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities);
return bookmark;

View file

@ -364,8 +364,6 @@ Menu::Menu() {
qApp->setHmdTabletBecomesToolbarSetting(action->isChecked());
});
addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Use3DKeyboard, 0, true);
// Developer > Render >>>
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");

View file

@ -213,7 +213,6 @@ namespace MenuOption {
const QString TurnWithHead = "Turn using Head";
const QString UseAudioForMouth = "Use Audio for Mouth";
const QString UseCamera = "Use Camera";
const QString Use3DKeyboard = "Use 3D Keyboard";
const QString VelocityFilter = "Velocity Filter";
const QString VisibleToEveryone = "Everyone";
const QString VisibleToFriends = "Friends";

View file

@ -19,7 +19,6 @@
#include <QStandardPaths>
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
static const QString ENTITY_MODEL_STRING = "Entity Model";
ModelSelector::ModelSelector() {

View file

@ -139,7 +139,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
_avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0)
{
_clientTraitsHandler = std::unique_ptr<ClientTraitsHandler>(new ClientTraitsHandler(this));
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
// give the pointer to our head to inherited _headData variable from AvatarData
_headData = new MyHead(this);

View file

@ -32,3 +32,7 @@ void KeyboardScriptingInterface::setPassword(bool password) {
void KeyboardScriptingInterface::loadKeyboardFile(const QString& keyboardFile) {
DependencyManager::get<Keyboard>()->loadKeyboardFile(keyboardFile);
}
bool KeyboardScriptingInterface::getUse3DKeyboard() {
return DependencyManager::get<Keyboard>()->getUse3DKeyboard();
}

View file

@ -30,6 +30,7 @@ class KeyboardScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(bool raised READ isRaised WRITE setRaised)
Q_PROPERTY(bool password READ isPassword WRITE setPassword)
Q_PROPERTY(bool use3DKeyboard READ getUse3DKeyboard);
public:
Q_INVOKABLE void loadKeyboardFile(const QString& string);
@ -39,5 +40,7 @@ private:
bool isPassword();
void setPassword(bool password);
bool getUse3DKeyboard();
};
#endif

View file

@ -241,6 +241,17 @@ void Keyboard::registerKeyboardHighlighting() {
selection->enableListToScene(KEY_PRESSED_HIGHLIGHT);
}
bool Keyboard::getUse3DKeyboard() const {
return _use3DKeyboardLock.resultWithReadLock<bool>([&] {
return _use3DKeyboard.get();
});
}
void Keyboard::setUse3DKeyboard(bool use) {
_use3DKeyboardLock.withWriteLock([&] {
_use3DKeyboard.set(use);
});
}
void Keyboard::createKeyboard() {
auto pointerManager = DependencyManager::get<PointerManager>();

View file

@ -23,6 +23,7 @@
#include <Sound.h>
#include <AudioInjector.h>
#include <shared/ReadWriteLockable.h>
#include <SettingHandle.h>
#include "ui/overlays/Overlay.h"
@ -97,6 +98,9 @@ public:
bool isPassword() const;
void setPassword(bool password);
bool getUse3DKeyboard() const;
void setUse3DKeyboard(bool use);
void loadKeyboardFile(const QString& keyboardFile);
QVector<OverlayID> getKeysID();
@ -143,6 +147,9 @@ private:
SharedSoundPointer _keySound { nullptr };
std::shared_ptr<QTimer> _layerSwitchTimer { std::make_shared<QTimer>() };
mutable ReadWriteLockable _use3DKeyboardLock;
Setting::Handle<bool> _use3DKeyboard { "use3DKeyboard", true };
QString _typedCharacters;
TextDisplay _textDisplay;
Anchor _anchor;

View file

@ -24,6 +24,7 @@
#include "Snapshot.h"
#include "SnapshotAnimated.h"
#include "UserActivityLogger.h"
#include "ui/Keyboard.h"
void setupPreferences() {
auto preferences = DependencyManager::get<Preferences>();
@ -119,6 +120,12 @@ void setupPreferences() {
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter));
}
{
auto getter = []()->bool { return DependencyManager::get<Keyboard>()->getUse3DKeyboard(); };
auto setter = [](bool value) { DependencyManager::get<Keyboard>()->setUse3DKeyboard(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use Virtual Keyboard", getter, setter));
}
{
auto getter = []()->bool { return qApp->getMiniTabletEnabled(); };
auto setter = [](bool value) { qApp->setMiniTabletEnabled(value); };

View file

@ -302,7 +302,6 @@ void AudioClient::customDeleter() {
#if defined(Q_OS_ANDROID)
_shouldRestartInputSetup = false;
#endif
stop();
deleteLater();
}

View file

@ -447,9 +447,9 @@ AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const A
using AudioConstants::AudioSample;
using AudioConstants::SAMPLE_RATE;
const int standardRate = SAMPLE_RATE;
// limit to 4 octaves
const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
const int resampledRate = SAMPLE_RATE / pitch;
// limit pitch to 4 octaves
const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
const int resampledRate = glm::round(SAMPLE_RATE / pitch);
auto audioData = sound->getAudioData();
auto numChannels = audioData->getNumChannels();
@ -499,9 +499,9 @@ AudioInjectorPointer AudioInjector::playSound(AudioDataPointer audioData, const
using AudioConstants::AudioSample;
using AudioConstants::SAMPLE_RATE;
const int standardRate = SAMPLE_RATE;
// limit to 4 octaves
const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
const int resampledRate = SAMPLE_RATE / pitch;
// limit pitch to 4 octaves
const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
const int resampledRate = glm::round(SAMPLE_RATE / pitch);
auto numChannels = audioData->getNumChannels();
auto numFrames = audioData->getNumFrames();

View file

@ -1490,7 +1490,7 @@ protected:
bool _isClientAvatar { false };
// null unless MyAvatar or ScriptableAvatar sending traits data to mixer
std::unique_ptr<ClientTraitsHandler> _clientTraitsHandler;
std::unique_ptr<ClientTraitsHandler, LaterDeleter> _clientTraitsHandler;
template <typename T, typename F>
T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const {

View file

@ -22,7 +22,7 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) :
_owningAvatar(owningAvatar)
{
auto nodeList = DependencyManager::get<NodeList>();
QObject::connect(nodeList.data(), &NodeList::nodeAdded, [this](SharedNodePointer addedNode){
QObject::connect(nodeList.data(), &NodeList::nodeAdded, this, [this](SharedNodePointer addedNode) {
if (addedNode->getType() == NodeType::AvatarMixer) {
resetForNewMixer();
}

View file

@ -180,9 +180,7 @@ void Deck::processFrames() {
#ifdef WANT_RECORDING_DEBUG
qCDebug(recordingLog) << "Setting timer for next processing " << nextInterval;
#endif
_timer.singleShot(nextInterval, [this] {
processFrames();
});
_timer.singleShot(nextInterval, this, &Deck::processFrames);
}
void Deck::removeClip(const ClipConstPointer& clip) {

View file

@ -74,6 +74,9 @@ public:
#endif
return hashCode;
}
static void prepareToExit() { manager()._exiting = true; }
private:
static DependencyManager& manager();
@ -84,6 +87,8 @@ private:
QHash<size_t, QSharedPointer<Dependency>> _instanceHash;
QHash<size_t, size_t> _inheritanceHash;
bool _exiting { false };
};
template <typename T>
@ -95,9 +100,17 @@ QSharedPointer<T> DependencyManager::get() {
instance = qSharedPointerCast<T>(manager().safeGet(hashCode));
#ifndef QT_NO_DEBUG
// debug builds...
if (instance.isNull()) {
qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name();
}
#else
// for non-debug builds, don't print "No instance available" during shutdown, because
// the act of printing this often causes crashes (because the LogHandler has-been/is-being
// deleted).
if (!manager()._exiting && instance.isNull()) {
qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name();
}
#endif
}

View file

@ -113,6 +113,13 @@ void doEvery(quint64& lastReportUsecs, quint64 secs, F lamdba) {
// Maximum accuracy in msecs
float secTimestampNow();
// Custom deleter for QObjects that calls deleteLater
struct LaterDeleter {
void operator()(QObject* ptr) {
ptr->deleteLater();
}
};
float randFloat();
int randIntInRange (int min, int max);
float randFloatInRange (float min,float max);

View file

@ -615,7 +615,9 @@ function notificationPollCallbackHistory(historyArray) {
ui.notificationDisplayBanner(message);
} else {
for (var i = 0; i < notificationCount; i++) {
message = '"' + (historyArray[i].message) + '" ' +
var historyMessage = historyArray[i].message;
var sanitizedHistoryMessage = historyMessage.replace(/<\/?[^>]+(>|$)/g, "");
message = '"' + sanitizedHistoryMessage + '" ' +
"Open INVENTORY to see all activity.";
ui.notificationDisplayBanner(message);
}

View file

@ -129,9 +129,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
return getControllerWorldLocation(Controller.Standard.RightHand, true);
};
Selection.enableListHighlight(DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE);
Selection.enableListToScene(DISPATCHER_HOVERING_LIST);
this.updateTimings = function () {
_this.intervalCount++;
var thisInterval = Date.now();
@ -525,7 +522,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Controller.disableMapping(MAPPING_NAME);
_this.pointerManager.removePointers();
Pointers.removePointer(this.mouseRayPick);
Selection.disableListHighlight(DISPATCHER_HOVERING_LIST);
};
}

View file

@ -459,13 +459,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.dropGestureReset();
this.clearEquipHaptics();
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
unhighlightTargetEntity(this.targetEntityID);
var message = {
hand: this.hand,
entityID: this.targetEntityID
};
Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
var grabbedProperties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
var grabData = getGrabbableData(grabbedProperties);

View file

@ -84,7 +84,6 @@ Script.include("/~/system/libraries/controllers.js");
this.entityWithContextOverlay = false;
this.contextOverlayTimer = false;
this.locked = false;
this.highlightedEntity = null;
this.reticleMinX = MARGIN;
this.reticleMaxX = null;
this.reticleMinY = MARGIN;
@ -410,9 +409,6 @@ Script.include("/~/system/libraries/controllers.js");
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
(this.notPointingAtEntity(controllerData) && Window.isPhysicsEnabled()) || this.targetIsNull()) {
this.endFarGrabAction();
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
this.highlightedEntity);
this.highlightedEntity = null;
this.restoreIgnoredEntities();
return makeRunningValues(false, [], []);
}
@ -466,9 +462,6 @@ Script.include("/~/system/libraries/controllers.js");
if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) {
if (controllerData.triggerClicks[this.hand]) {
var entityID = rayPickInfo.objectID;
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
this.highlightedEntity);
this.highlightedEntity = null;
var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
if (targetProps.href !== "") {
AddressManager.handleLookupString(targetProps.href);
@ -513,66 +506,43 @@ Script.include("/~/system/libraries/controllers.js");
this.startFarGrabAction(controllerData, targetProps);
}
}
} else {
var targetEntityID = rayPickInfo.objectID;
if (this.highlightedEntity !== targetEntityID) {
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
this.highlightedEntity);
var selectionTargetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES);
} else if (!this.entityWithContextOverlay) {
var _this = this;
var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps);
selectionTargetObject.parentProps = getEntityParents(selectionTargetProps);
var selectionTargetEntity = selectionTargetObject.getTargetEntity();
if (entityIsGrabbable(selectionTargetEntity.props) ||
entityIsGrabbable(selectionTargetObject.entityProps)) {
Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID);
if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) {
if (_this.contextOverlayTimer) {
Script.clearTimeout(_this.contextOverlayTimer);
}
this.highlightedEntity = rayPickInfo.objectID;
_this.contextOverlayTimer = false;
_this.potentialEntityWithContextOverlay = rayPickInfo.objectID;
}
if (!this.entityWithContextOverlay) {
var _this = this;
if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) {
if (_this.contextOverlayTimer) {
Script.clearTimeout(_this.contextOverlayTimer);
if (!_this.contextOverlayTimer) {
_this.contextOverlayTimer = Script.setTimeout(function () {
if (!_this.entityWithContextOverlay &&
_this.contextOverlayTimer &&
_this.potentialEntityWithContextOverlay === rayPickInfo.objectID) {
var props = Entities.getEntityProperties(rayPickInfo.objectID, DISPATCHER_PROPERTIES);
var pointerEvent = {
type: "Move",
id: _this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID,
rayPickInfo.intersection, props),
pos3D: rayPickInfo.intersection,
normal: rayPickInfo.surfaceNormal,
direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal),
button: "Secondary"
};
if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) {
_this.entityWithContextOverlay = rayPickInfo.objectID;
}
}
_this.contextOverlayTimer = false;
_this.potentialEntityWithContextOverlay = rayPickInfo.objectID;
}
if (!_this.contextOverlayTimer) {
_this.contextOverlayTimer = Script.setTimeout(function () {
if (!_this.entityWithContextOverlay &&
_this.contextOverlayTimer &&
_this.potentialEntityWithContextOverlay === rayPickInfo.objectID) {
var props = Entities.getEntityProperties(rayPickInfo.objectID, DISPATCHER_PROPERTIES);
var pointerEvent = {
type: "Move",
id: _this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID,
rayPickInfo.intersection, props),
pos3D: rayPickInfo.intersection,
normal: rayPickInfo.surfaceNormal,
direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal),
button: "Secondary"
};
if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) {
_this.entityWithContextOverlay = rayPickInfo.objectID;
}
}
_this.contextOverlayTimer = false;
}, 500);
}
}, 500);
}
}
} else if (this.distanceRotating) {
this.distanceRotate(otherFarGrabModule);
} else if (this.highlightedEntity) {
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
this.highlightedEntity = null;
}
}
return this.exitIfDisabled(controllerData);

View file

@ -61,6 +61,8 @@ Script.include("/~/system/libraries/controllers.js");
this.reticleMinY = MARGIN;
this.reticleMaxY = 0;
this.lastUnexpectedChildrenCheckTime = 0;
this.endedGrab = 0;
this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms
var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX
@ -144,7 +146,12 @@ Script.include("/~/system/libraries/controllers.js");
// compute the mass for the purpose of energy and how quickly to move object
this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density);
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
// Debounce haptic pules. Can occur as near grab controller module vacillates between being ready or not due to
// changing positions and floating point rounding.
if (Date.now() - this.endedGrab > this.MIN_HAPTIC_PULSE_INTERVAL) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
}
unhighlightTargetEntity(this.targetEntityID);
var message = {
hand: this.hand,
@ -256,7 +263,7 @@ Script.include("/~/system/libraries/controllers.js");
};
this.endFarParentGrab = function (controllerData) {
this.hapticTargetID = null;
this.endedGrab = Date.now();
// var endProps = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
var endProps = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
if (this.thisFarGrabJointIsParent(endProps)) {
@ -410,11 +417,6 @@ Script.include("/~/system/libraries/controllers.js");
if (targetEntity) {
var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES);
if (entityIsGrabbable(gtProps)) {
// give haptic feedback
if (gtProps.id !== this.hapticTargetID) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
this.hapticTargetID = gtProps.id;
}
// if we've attempted to grab a child, roll up to the root of the tree
var groupRootProps = findGroupParent(controllerData, gtProps);
if (entityIsGrabbable(groupRootProps)) {

View file

@ -1,155 +0,0 @@
//
// highlightNearbyEntities.js
//
// Created by Dante Ruiz 2018-4-10
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, MyAvatar, entityIsCloneable, Messages, print */
"use strict";
(function () {
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js");
Script.include("/~/system/libraries/cloneEntityUtils.js");
var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
function differenceInArrays(firstArray, secondArray) {
var differenceArray = firstArray.filter(function(element) {
return secondArray.indexOf(element) < 0;
});
return differenceArray;
}
function HighlightNearbyEntities(hand) {
this.hand = hand;
this.otherHand = hand === dispatcherUtils.RIGHT_HAND ? dispatcherUtils.LEFT_HAND :
dispatcherUtils.RIGHT_HAND;
this.highlightedEntities = [];
this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
480,
this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100);
this.isGrabable = function(controllerData, props) {
var canGrabEntity = false;
if (dispatcherUtils.entityIsGrabbable(props) || entityIsCloneable(props)) {
// if we've attempted to grab a child, roll up to the root of the tree
var groupRootProps = dispatcherUtils.findGroupParent(controllerData, props);
canGrabEntity = true;
if (!dispatcherUtils.entityIsGrabbable(groupRootProps)) {
canGrabEntity = false;
}
}
return canGrabEntity;
};
this.clearAll = function() {
this.highlightedEntities.forEach(function(entity) {
dispatcherUtils.unhighlightTargetEntity(entity);
});
};
this.hasHyperLink = function(props) {
return (props.href !== "" && props.href !== undefined);
};
this.removeEntityFromHighlightList = function(entityID) {
var index = this.highlightedEntities.indexOf(entityID);
if (index > -1) {
this.highlightedEntities.splice(index, 1);
}
};
this.getOtherModule = function() {
var otherModule = this.hand === dispatcherUtils.RIGHT_HAND ? leftHighlightNearbyEntities :
rightHighlightNearbyEntities;
return otherModule;
};
this.getOtherHandHighlightedEntities = function() {
return this.getOtherModule().highlightedEntities;
};
this.highlightEntities = function(controllerData) {
var nearbyEntitiesProperties = controllerData.nearbyEntityProperties[this.hand];
var otherHandHighlightedEntities = this.getOtherHandHighlightedEntities();
var newHighlightedEntities = [];
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
for (var i = 0; i < nearbyEntitiesProperties.length; i++) {
var props = nearbyEntitiesProperties[i];
if (props.distance > dispatcherUtils.NEAR_GRAB_RADIUS * sensorScaleFactor) {
continue;
}
if (this.isGrabable(controllerData, props) || this.hasHyperLink(props)) {
dispatcherUtils.highlightTargetEntity(props.id);
if (newHighlightedEntities.indexOf(props.id) < 0) {
newHighlightedEntities.push(props.id);
}
}
}
var unhighlightEntities = differenceInArrays(this.highlightedEntities, newHighlightedEntities);
unhighlightEntities.forEach(function(entityID) {
if (otherHandHighlightedEntities.indexOf(entityID) < 0 ) {
dispatcherUtils.unhighlightTargetEntity(entityID);
}
});
this.highlightedEntities = newHighlightedEntities;
};
this.isReady = function(controllerData) {
this.highlightEntities(controllerData);
return dispatcherUtils.makeRunningValues(false, [], []);
};
this.run = function(controllerData) {
return this.isReady(controllerData);
};
}
var handleMessage = function(channel, message, sender) {
var data;
if (sender === MyAvatar.sessionUUID) {
if (channel === 'Hifi-unhighlight-entity') {
try {
data = JSON.parse(message);
var hand = data.hand;
if (hand === dispatcherUtils.LEFT_HAND) {
leftHighlightNearbyEntities.removeEntityFromHighlightList(data.entityID);
} else if (hand === dispatcherUtils.RIGHT_HAND) {
rightHighlightNearbyEntities.removeEntityFromHighlightList(data.entityID);
}
} catch (e) {
print("highlightNearbyEntities -- Failed to parse message: " + JSON.stringify(message));
}
} else if (channel === 'Hifi-unhighlight-all') {
leftHighlightNearbyEntities.clearAll();
rightHighlightNearbyEntities.clearAll();
}
}
};
var leftHighlightNearbyEntities = new HighlightNearbyEntities(dispatcherUtils.LEFT_HAND);
var rightHighlightNearbyEntities = new HighlightNearbyEntities(dispatcherUtils.RIGHT_HAND);
dispatcherUtils.enableDispatcherModule("LeftHighlightNearbyEntities", leftHighlightNearbyEntities);
dispatcherUtils.enableDispatcherModule("RightHighlightNearbyEntities", rightHighlightNearbyEntities);
function cleanup() {
dispatcherUtils.disableDispatcherModule("LeftHighlightNearbyEntities");
dispatcherUtils.disableDispatcherModule("RightHighlightNearbyEntities");
}
Messages.subscribe('Hifi-unhighlight-entity');
Messages.subscribe('Hifi-unhighlight-all');
Messages.messageReceived.connect(handleMessage);
Script.scriptEnding.connect(cleanup);
}());

View file

@ -1,104 +0,0 @@
//
// mouseHighlightEntities.js
//
// scripts/system/controllers/controllerModules/
//
// Created by Dante Ruiz 2018-4-11
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* jslint bitwise: true */
/* global Script, print, Entities, Messages, Picks, HMD, MyAvatar, isInEditMode, DISPATCHER_PROPERTIES */
(function() {
Script.include("/~/system/libraries/utils.js");
var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
function MouseHighlightEntities() {
this.highlightedEntity = null;
this.grabbedEntity = null;
this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
5,
["mouse"],
[],
100);
this.setGrabbedEntity = function(entity) {
this.grabbedEntity = entity;
this.highlightedEntity = null;
};
this.isReady = function(controllerData) {
if (HMD.active) {
if (this.highlightedEntity) {
dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity);
this.highlightedEntity = null;
}
} else if (!this.grabbedEntity && !isInEditMode()) {
var pickResult = controllerData.mouseRayPick;
if (pickResult.type === Picks.INTERSECTED_ENTITY) {
var targetEntityID = pickResult.objectID;
if (this.highlightedEntity !== targetEntityID) {
var targetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES);
if (this.highlightedEntity) {
dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity);
this.highlightedEntity = null;
}
if (dispatcherUtils.entityIsGrabbable(targetProps)) {
// highlight entity
dispatcherUtils.highlightTargetEntity(targetEntityID);
this.highlightedEntity = targetEntityID;
}
}
} else if (this.highlightedEntity) {
dispatcherUtils.unhighlightTargetEntity(this.highlightedEntity);
this.highlightedEntity = null;
}
}
return dispatcherUtils.makeRunningValues(false, [], []);
};
this.run = function(controllerData) {
return this.isReady(controllerData);
};
}
var mouseHighlightEntities = new MouseHighlightEntities();
dispatcherUtils.enableDispatcherModule("MouseHighlightEntities", mouseHighlightEntities);
var handleMessage = function(channel, message, sender) {
var data;
if (sender === MyAvatar.sessionUUID) {
if (channel === 'Hifi-Object-Manipulation') {
try {
data = JSON.parse(message);
if (data.action === 'grab') {
var grabbedEntity = data.grabbedEntity;
mouseHighlightEntities.setGrabbedEntity(grabbedEntity);
} else if (data.action === 'release') {
mouseHighlightEntities.setGrabbedEntity(null);
}
} catch (e) {
print("Warning: mouseHighlightEntities -- error parsing Hifi-Object-Manipulation: " + message);
}
}
}
};
function cleanup() {
dispatcherUtils.disableDispatcherModule("MouseHighlightEntities");
}
Messages.subscribe('Hifi-Object-Manipulation');
Messages.messageReceived.connect(handleMessage);
Script.scriptEnding.connect(cleanup);
})();

View file

@ -24,7 +24,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.hand = hand;
this.targetEntityID = null;
this.actionID = null; // action this script created...
this.hapticTargetID = null;
this.parameters = makeDispatcherModuleParameters(
500,
@ -115,13 +114,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.targetEntityID, "startNearGrab", args);
unhighlightTargetEntity(this.targetEntityID);
var message = {
hand: this.hand,
entityID: this.targetEntityID
};
Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
};
// this is for when the action is going to time-out
@ -171,10 +163,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
break;
}
if (entityIsGrabbable(props) || entityIsCloneable(props)) {
if (props.id !== this.hapticTargetID) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
this.hapticTargetID = props.id;
}
if (!entityIsCloneable(props)) {
// if we've attempted to grab a non-cloneable child, roll up to the root of the tree
var groupRootProps = findGroupParent(controllerData, props);
@ -206,7 +194,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
return makeRunningValues(true, [this.targetEntityID], []);
}
} else {
this.hapticTargetID = null;
return makeRunningValues(false, [], []);
}
};
@ -216,7 +203,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
this.endNearGrabAction();
this.hapticTargetID = null;
return makeRunningValues(false, [], []);
}

View file

@ -11,7 +11,7 @@
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH,
HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME,
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity,
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, NEAR_GRAB_DISTANCE,
distanceBetweenEntityLocalPositionAndBoundingBox, getGrabbableData, getGrabPointSphereOffset, DISPATCHER_PROPERTIES
*/
@ -24,15 +24,6 @@ Script.include("/~/system/libraries/controllers.js");
// XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
// XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC;
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral
function getGrabOffset(handController) {
var offset = getGrabPointSphereOffset(handController, true);
offset.y = -offset.y;
return Vec3.multiply(MyAvatar.sensorToWorldScale, offset);
}
function NearParentingGrabEntity(hand) {
this.hand = hand;
this.targetEntityID = null;
@ -40,12 +31,10 @@ Script.include("/~/system/libraries/controllers.js");
this.previousParentID = {};
this.previousParentJointIndex = {};
this.previouslyUnhooked = {};
this.hapticTargetID = null;
this.lastUnequipCheckTime = 0;
this.autoUnequipCounter = 0;
this.lastUnexpectedChildrenCheckTime = 0;
this.robbed = false;
this.highlightedEntity = null;
this.cloneAllowed = true;
this.parameters = makeDispatcherModuleParameters(
@ -95,14 +84,7 @@ Script.include("/~/system/libraries/controllers.js");
this.startNearParentingGrabEntity = function (controllerData, targetProps) {
var grabData = getGrabbableData(targetProps);
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
unhighlightTargetEntity(this.targetEntityID);
this.highlightedEntity = null;
var message = {
hand: this.hand,
entityID: this.targetEntityID
};
Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
var handJointIndex;
if (grabData.grabFollowsController) {
handJointIndex = getControllerJointIndex(this.hand);
@ -146,7 +128,6 @@ Script.include("/~/system/libraries/controllers.js");
};
this.endNearParentingGrabEntity = function (controllerData) {
this.hapticTargetID = null;
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
if (this.thisHandIsParent(props) && !this.robbed) {
Entities.editEntity(this.targetEntityID, {
@ -164,8 +145,7 @@ Script.include("/~/system/libraries/controllers.js");
grabbedEntity: this.targetEntityID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
unhighlightTargetEntity(this.targetEntityID);
this.highlightedEntity = null;
this.grabbing = false;
this.targetEntityID = null;
this.robbed = false;
@ -178,8 +158,10 @@ Script.include("/~/system/libraries/controllers.js");
this.lastUnequipCheckTime = now;
if (props.parentID === MyAvatar.SELF_ID) {
var tearAwayDistance = TEAR_AWAY_DISTANCE * MyAvatar.sensorToWorldScale;
var controllerIndex = (this.hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand);
var controllerGrabOffset = getGrabOffset(controllerIndex);
var controllerIndex =
this.hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
var controllerGrabOffset = getGrabPointSphereOffset(controllerIndex, true);
controllerGrabOffset = Vec3.multiply(-MyAvatar.sensorToWorldScale, controllerGrabOffset);
var distance = distanceBetweenEntityLocalPositionAndBoundingBox(props, controllerGrabOffset);
if (distance > tearAwayDistance) {
this.autoUnequipCounter++;
@ -242,21 +224,18 @@ Script.include("/~/system/libraries/controllers.js");
// nearbyEntityProperties is already sorted by length from controller
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
var nearGrabDistance = NEAR_GRAB_DISTANCE * sensorScaleFactor;
var nearGrabRadius = NEAR_GRAB_RADIUS * sensorScaleFactor;
for (var i = 0; i < nearbyEntityProperties.length; i++) {
var props = nearbyEntityProperties[i];
var handPosition = controllerData.controllerLocations[this.hand].position;
var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props);
var distance = Vec3.distance(handPosition, props.position);
if ((dist > TEAR_AWAY_DISTANCE) ||
(distance > NEAR_GRAB_RADIUS * sensorScaleFactor)) {
var grabPosition = controllerData.controllerLocations[this.hand].position; // Is offset from hand position.
var dist = distanceBetweenPointAndEntityBoundingBox(grabPosition, props);
var distance = Vec3.distance(grabPosition, props.position);
if ((dist > nearGrabDistance) ||
(distance > nearGrabRadius)) { // Only smallish entities can be near grabbed.
continue;
}
if (entityIsGrabbable(props) || entityIsCloneable(props)) {
// give haptic feedback
if (props.id !== this.hapticTargetID) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
this.hapticTargetID = props.id;
}
if (!entityIsCloneable(props)) {
// if we've attempted to grab a non-cloneable child, roll up to the root of the tree
var groupRootProps = findGroupParent(controllerData, props);
@ -290,16 +269,9 @@ Script.include("/~/system/libraries/controllers.js");
return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it
} else {
this.targetEntityID = targetProps.id;
this.highlightedEntity = this.targetEntityID;
highlightTargetEntity(this.targetEntityID);
return makeRunningValues(true, [this.targetEntityID], []);
}
} else {
if (this.highlightedEntity) {
unhighlightTargetEntity(this.highlightedEntity);
this.highlightedEntity = null;
}
this.hapticTargetID = null;
this.robbed = false;
return makeRunningValues(false, [], []);
}
@ -316,11 +288,8 @@ Script.include("/~/system/libraries/controllers.js");
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
if (!props) {
// entity was deleted
unhighlightTargetEntity(this.targetEntityID);
this.highlightedEntity = null;
this.grabbing = false;
this.targetEntityID = null;
this.hapticTargetID = null;
this.robbed = false;
return makeRunningValues(false, [], []);
}
@ -335,12 +304,10 @@ Script.include("/~/system/libraries/controllers.js");
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args);
} else {
// still searching / highlighting
// still searching
var readiness = this.isReady(controllerData);
if (!readiness.active) {
this.robbed = false;
unhighlightTargetEntity(this.highlightedEntity);
this.highlightedEntity = null;
return readiness;
}
if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {

View file

@ -7,7 +7,7 @@
/* global Script, Entities, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, getGrabbableData,
Vec3, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS, unhighlightTargetEntity
Vec3, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -55,7 +55,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
this.startNearTrigger = function (controllerData) {
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.targetEntityID, "startNearTrigger", args);
unhighlightTargetEntity(this.targetEntityID);
};
this.continueNearTrigger = function (controllerData) {

View file

@ -35,9 +35,7 @@ var CONTOLLER_SCRIPTS = [
"controllerModules/hudOverlayPointer.js",
"controllerModules/mouseHMD.js",
"controllerModules/scaleEntity.js",
"controllerModules/highlightNearbyEntities.js",
"controllerModules/nearGrabHyperLinkEntity.js",
"controllerModules/mouseHighlightEntities.js",
"controllerModules/nearTabletHighlight.js"
];

View file

@ -916,7 +916,7 @@ div.refresh input[type="button"] {
}
.draggable-number div {
height: 28px;
width: 92px;
width: 124px;
}
.draggable-number.text {
display: inline-block;
@ -929,6 +929,7 @@ div.refresh input[type="button"] {
height: 28px;
width: 100%;
line-height: 2;
box-sizing: border-box;
}
.draggable-number.text:hover {
cursor: ew-resize;
@ -944,12 +945,12 @@ div.refresh input[type="button"] {
cursor: default;
}
.draggable-number.left-arrow {
top: -5px;
top: 3px;
left: 0px;
transform: rotate(180deg);
}
.draggable-number.right-arrow {
top: -5px;
top: 3px;
right: 0px;
}
.draggable-number input[type=number] {
@ -971,14 +972,14 @@ div.refresh input[type="button"] {
left: 12px;
}
.draggable-number.fstuple + .draggable-number.fstuple {
padding-left: 28px;
margin-left: 28px;
}
.draggable-number.fstuple input {
right: -10px;
}
.draggable-number.fstuple .sublabel {
position: absolute;
top: 0;
top: 6px;
left: -16px;
font-family: FiraSans-SemiBold;
font-size: 15px;

View file

@ -309,6 +309,9 @@ For usage and examples: colpick.com/plugin
},
// Fix the values if the user enters a negative or high value
fixHSB = function (hsb) {
hsb.h = isNaN(hsb.h) ? 0 : hsb.h;
hsb.s = isNaN(hsb.s) ? 0 : hsb.s;
hsb.b = isNaN(hsb.b) ? 0 : hsb.b;
return {
h: Math.min(360, Math.max(0, hsb.h)),
s: Math.min(100, Math.max(0, hsb.s)),
@ -316,6 +319,9 @@ For usage and examples: colpick.com/plugin
};
},
fixRGB = function (rgb) {
rgb.r = isNaN(rgb.r) ? 0 : rgb.r;
rgb.g = isNaN(rgb.g) ? 0 : rgb.g;
rgb.b = isNaN(rgb.b) ? 0 : rgb.b;
return {
r: Math.min(255, Math.max(0, rgb.r)),
g: Math.min(255, Math.max(0, rgb.g)),

View file

@ -23,6 +23,20 @@ function DraggableNumber(min, max, step, decimals, dragStart, dragEnd) {
}
DraggableNumber.prototype = {
showInput: function() {
this.elText.style.visibility = "hidden";
this.elLeftArrow.style.visibility = "hidden";
this.elRightArrow.style.visibility = "hidden";
this.elInput.style.opacity = 1;
},
hideInput: function() {
this.elText.style.visibility = "visible";
this.elLeftArrow.style.visibility = "visible";
this.elRightArrow.style.visibility = "visible";
this.elInput.style.opacity = 0;
},
mouseDown: function(event) {
if (event.target === this.elText) {
this.initialMouseEvent = event;
@ -36,8 +50,8 @@ DraggableNumber.prototype = {
if (event.target === this.elText && this.initialMouseEvent) {
let dx = event.clientX - this.initialMouseEvent.clientX;
if (Math.abs(dx) <= DELTA_X_FOCUS_THRESHOLD) {
this.elInput.style.visibility = "visible";
this.elText.style.visibility = "hidden";
this.showInput();
this.elInput.focus();
}
this.initialMouseEvent = null;
}
@ -125,9 +139,8 @@ DraggableNumber.prototype = {
this.setValue(this.elInput.value);
},
inputBlur: function() {
this.elInput.style.visibility = "hidden";
this.elText.style.visibility = "visible";
inputBlur: function(ev) {
this.hideInput();
},
initialize: function() {
@ -171,13 +184,14 @@ DraggableNumber.prototype = {
if (this.step !== undefined) {
this.elInput.setAttribute("step", this.step);
}
this.elInput.style.visibility = "hidden";
this.elInput.style.opacity = 0;
this.elInput.addEventListener("change", this.onInputChange);
this.elInput.addEventListener("blur", this.onInputBlur);
this.elInput.addEventListener("focus", this.showInput.bind(this));
this.elText.appendChild(this.elLeftArrow);
this.elText.appendChild(this.elInput);
this.elText.appendChild(this.elRightArrow);
this.elDiv.appendChild(this.elLeftArrow);
this.elDiv.appendChild(this.elInput);
this.elDiv.appendChild(this.elRightArrow);
this.elDiv.appendChild(this.elText);
}
};

View file

@ -197,7 +197,7 @@ const GROUPS = [
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
propertyID: "keyLight.direction.x",
propertyID: "keyLight.direction.y",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
@ -206,7 +206,7 @@ const GROUPS = [
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
propertyID: "keyLight.direction.y",
propertyID: "keyLight.direction.x",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
@ -2149,7 +2149,7 @@ function createTupleNumberInput(property, subLabel) {
propertyData.decimals, dragStartFunction, dragEndFunction);
elDraggableNumber.elInput.setAttribute("id", elementID);
elDraggableNumber.elDiv.className += " fstuple";
elDraggableNumber.elText.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
return elDraggableNumber;
}

View file

@ -53,6 +53,7 @@
TEAR_AWAY_DISTANCE:true,
TEAR_AWAY_COUNT:true,
TEAR_AWAY_CHECK_TIME:true,
NEAR_GRAB_DISTANCE: true,
distanceBetweenPointAndEntityBoundingBox:true,
entityIsEquipped:true,
entityIsFarGrabbedByOther:true,
@ -99,6 +100,10 @@ NEAR_GRAB_RADIUS = 1.0;
TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this far from the hand
TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away
TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks
NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance.
// Smaller than TEAR_AWAY_DISTANCE for hysteresis.
DISPATCHER_HOVERING_LIST = "dispactherHoveringList";
DISPATCHER_HOVERING_STYLE = {
isOutlineSmooth: true,

View file

@ -50,10 +50,12 @@ if (WIN32)
)
# add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities)
# this also copied to the containing folder, to facilitate running from Visual Studio
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$<TARGET_FILE_DIR:${TARGET_NAME}>/AppDataHighFidelity"
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "AppDataHighFidelity"
)
# add a custom command to copy the SSL DLLs
@ -62,5 +64,12 @@ if (WIN32)
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory "$ENV{VCPKG_ROOT}/installed/x64-windows/bin" "$<TARGET_FILE_DIR:${TARGET_NAME}>"
)
elseif (APPLE)
# add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities)
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$<TARGET_FILE_DIR:${TARGET_NAME}>/AppDataHighFidelity"
)
endif ()
endif ()

View file

@ -1,6 +1,6 @@
# nitpick
Nitpick is a stand alone application that provides a mechanism for regression testing. The general idea is simple:
Nitpick is a stand alone application that provides a mechanism for regression testing. The general idea is simple:
* Each test folder has a script that produces a set of snapshots.
* The snapshots are compared to a 'canonical' set of images that have been produced beforehand.
* The result, if any test failed, is a zipped folder describing the failure.
@ -12,70 +12,99 @@ Nitpick has 5 functions, separated into 4 tabs:
1. Evaluating the results of running tests
1. Web interface
## Installation
### Executable
1. On Windows: download the installer by browsing to [here](<https://hifi-content.s3.amazonaws.com/nissim/nitpick/nitpick-installer-v1.0.exe>).
2. Double click on the installer and install to a convenient location
## Build (for developers)
Nitpick is built as part of the High Fidelity build.
### Creating installers
#### Windows
1. Verify that 7Zip is installed.
1. cd to the `build\tools\nitpick\Release` directory
1. Delete any existing installers (named nitpick-installer-###.exe)
1. Select all, right-click and select 7-Zip->Add to archive...
1. Set Archive format to 7z
1. Check "Create SFX archive
1. Enter installer name (i.e. `nitpick-installer-v1.1.exe`)
1. Click "OK"
1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.1.exe: aws s3 cp nitpick-installer-v1.1.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.1.exe
#### Mac
These steps assume the hifi repository has been cloned to `~/hifi`.
1. (first time) Install brew
In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)`
1. (First time) install create-dmg:
In a terminal: `brew install create-dmg`
1. In a terminal: cd to the `build/tools/nitpick/Release` folder
1. Copy the quazip dynamic library (note final period):
In a terminal: `cp ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib .`
1. Change the loader instruction to find the dynamic library locally
In a terminal: `install_name_tool -change ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib libquazip5.1.dylib nitpick`
1. Delete any existing disk images. In a terminal: `rm *.dmg`
1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-v1.1 nitpick-installer-v1.1.dmg .`
Make sure to wait for completion.
1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-v1.1.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.1.dmg`
### Installation
#### Windows
1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.1.exe)
1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/)
1. After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable.
1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/
1. Open a new command prompt and run `aws configure`
1. Enter the AWS account number
1. Enter the secret key
1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip: `pip install boto3`
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.1.exe>)
1. Double click on the installer and install to a convenient location
![](./setup_7z.PNG)
3. To run nitpick, double click **nitpick.exe**.
### Python
The TestRail interface requires Python 3 to be installed. Nitpick has been tested with Python 3.7.0 but should work with newer versions.
Python 3 can be downloaded from:
1. Windows installer <https://www.python.org/downloads/>
2. Linux (source) <https://www.python.org/downloads/release/python-370/> (**Gzipped source tarball**)
3. Mac <https://www.python.org/downloads/release/python-370/> (**macOS 64-bit/32-bit installer** or **macOS 64-bit/32-bit installer**)
#### Windows
After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable.
1. __To run nitpick, double click **nitpick.exe**__
#### Mac
After installation - run `open "/Applications/Python 3.6/Install Certificates.command"`. This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates.
Verify that `/usr/local/bin/python3` exists.
1. (first time) Install brew
In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)`
1. (First time) install Qt:
In a terminal: `brew install qt`
1. (First time) install Python from https://www.python.org/downloads/release/python-370/ (**macOS 64-bit installer** or **macOS 64-bit/32-bit installer**)
1. After installation - In a terminal: run `open "/Applications/Python 3.6/Install Certificates.command"`. This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates.
1. Verify that `/usr/local/bin/python3` exists.
1. (First time - AWS interface) Install pip with the script provided by the Python Packaging Authority:
In a terminal: `curl -O https://bootstrap.pypa.io/get-pip.py`
In a terminal: `python3 get-pip.py --user`
1. Use pip to install the AWS CLI.
`pip3 install awscli --upgrade --user`
This will install aws in your user. For user XXX, aws will be located in ~/Library/Python/3.7/bin
1. Open a new command prompt and run `~/Library/Python/3.7/bin/aws configure`
1. Enter the AWS account number
1. Enter the secret key
1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip: pip3 install boto3
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-v1.1.dmg>).
1. Double-click on the downloaded image to mount it
1. Create a folder for the nitpick files (e.g. ~/nitpick)
If this folder exists then delete all it's contents.
1. Copy the downloaded files to the folder
In a terminal:
`cd ~/nitpick`
`cp -r /Volumes/nitpick-installer-v1.1/* .`
### AWS interface
#### Windows
1. Download the AWS CLI from `https://aws.amazon.com/cli/`
1. Install (installer is named `AWSCLI64PY3.msi`)
1. Open a new command prompt and run `aws configure`
1. Enter the AWS account number
1. Enter the secret key
1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip:
pip install boto3
#### Mac
1. Install pip with the script provided by the Python Packaging Authority:
$ curl -O https://bootstrap.pypa.io/get-pip.py
$ python3 get-pip.py --user
1. Use pip to install the AWS CLI.
$ pip3 install awscli --upgrade --user
This will install aws in your user. For user XXX, aws will be located in /Users/XXX/Library/Python/3.7/bin
1. Open a new command prompt and run `aws configure`
1. Enter the AWS account number
1. Enter the secret key
1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip:
pip3 install boto3
# Create
1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__
# Usage
## Create
![](./Create.PNG)
The Create tab provides functions to create tests from snapshots, MD files, a test outline and recursive scripts.
## Create Tests
### Usage
### Create Tests
#### Usage
This function is used to create/update Expected Images after a successful run of a test, or multiple tests.
The user will be asked for the snapshot folder and then the tests root folder. All snapshots located in the snapshot folder will be used to create or update the expected images in the relevant tests.
### Details
#### Details
As an example - if the snapshots folder contains an image named `tests.content.entity.zone.zoneOrientation.00003.png`, then this file will be copied to `tests/contente/enity/zone/zoneOrientation/ExpectedImage0003.png`.
## Create Tests Outline
### Usage
### Create Tests Outline
#### Usage
This function creates an MD file in the (user-selected) tests root folder. The file provides links to both the tests and the MD files.
## Create MD file
### Usage
### Create MD file
#### Usage
This function creates a file named `test.md` from a `test.js` script. The user will be asked for the folder containing the test script:
### Details
#### Details
The process to produce the MD file is a simplistic parse of the test script.
- The string in the `nitpick.perform(...)` function call will be the title of the file
@ -86,25 +115,25 @@ The process to produce the MD file is a simplistic parse of the test script.
- The step description is the string in the addStep/addStepStepSnapshot commands
- Image links are provided where applicable to the local Expected Images files
## Create all MD files
### Usage
### Create all MD files
#### Usage
This function creates all MD files recursively from the user-selected root folder. This can be any folder in the tests hierarchy (e.g. all engine\material tests).
The file provides a hierarchal list of all the tests
## Create testAuto script
### Usage
### Create testAuto script
#### Usage
This function creates a script named `testAuto.js` in a user-selected test folder.
### Details
#### Details
The script created runs the `test.js` script in the folder in automatic mode. The script is the same for all tests.
## Create all testAuto scripts
### Usage
### Create all testAuto scripts
#### Usage
This function creates all testAuto scripts recursively from the user-selected root folder. This can be any folder in the tests hierarchy (e.g. all engine\material tests).
The file provides a hierarchical list of all the tests
## Create Recursive Script
### Usage
### Create Recursive Script
#### Usage
After the user selects a folder within the tests hierarchy, a script is created, named `testRecursive.js`. This script calls all `test.js` scripts in the sub-folders.
### Details
#### Details
The various scripts are called in alphabetical order.
An example of a recursive script is as follows:
@ -130,18 +159,18 @@ Script.include(testsRootPath + "content/overlay/layer/drawHUDLayer/test.js");
nitpick.runRecursive();
```
## Create all Recursive Scripts
### Usage
### Create all Recursive Scripts
#### Usage
In this case all recursive scripts, from the selected folder down, are created.
Running this function in the tests root folder will create (or update) all the recursive scripts.
# Windows
## Windows
![](./Windows.PNG)
This tab is Windows-specific. It provides buttons to hide and show the task bar.
The task bar should be hidden for all tests that use the primary camera. This is required to ensure that the snapshots are the right size.
# Run
## Run
![](./Run.PNG)
The run tab is used to run tests in automatic mode. The tests require the location of a folder to store files in; this folder can safely be re-used for any number of runs (the "Working Folder").
The test script that is run is `https://github.com/highfidelity/hifi_tests/blob/master/tests/testRecursive.js`. The user can use a different branch' or even repository, if required.
@ -163,7 +192,7 @@ The working folder will ultimately contain the following:
1. The `dev-builds.xml` file, if it was downloaded.
1. The HighFidelity installer. Note that this is always named `HighFidelity-Beta-latest-dev` so as not to store too many installers over time.
1. A log file describing the runs. This file is appended to after each run.
# Evaluate
## Evaluate
![](./Evaluate.PNG)
The Evaluate tab provides a single function - evaluating the results of a test run.
@ -171,11 +200,11 @@ The Evaluate tab provides a single function - evaluating the results of a test r
A checkbox (defaulting to checked) runs the evaluation in interactive mode. In this mode - every failure is shown to the user, who can then decide whether to pass the test, fail it or abort the whole evaluation.
If any tests have failed, then a zipped folder will be created in the snapshots folder, with a description of each failed step in each test.
### Usage
#### Usage
Before starting the evaluation, make sure the GitHub user and branch are set correctly. The user should not normally be changed, but the branch may need to be set to the appropriate RC.
After setting the check-box as required and pressing Evaluate - the user will be asked for the snapshots folder.
### Details
#### Details
Evaluation proceeds in a number of steps:
1. A folder is created to store any failures
@ -191,7 +220,7 @@ Evaluation proceeds in a number of steps:
1. At the end of the test, the folder is zipped and the original folder is deleted. If there are no errors then the zipped folder will be empty.
# Web Interface
## Web Interface
![](./WebInterface.PNG)
This tab has two functions: updating the TestRail cases, runs and results, and creating web page reports that are stored on AWS.
@ -206,8 +235,8 @@ Any access to TestRail will require the TestRail account (default is High Fideli
- The Project ID defaults to 14 - Interface.
- The Suite ID defaults to 1147 - Rendering.
- The TestRail page provides 3 functions for writing to TestRail.
## Create Test Cases
### Usage
### Create Test Cases
#### Usage
This function can either create an XML file that can then be imported into TestRail through TestRail itself, or automatically create the appropriate TestRail Sections.
The user will be first asked for the tests root folder and a folder to store temporary files (this is the output folder).
@ -219,7 +248,7 @@ If Python is selected, the user will then be prompted for TestRail data. After
After selecting the appropriate Release, press OK. The Python script will be created in the output folder, and the user will be prompted to run it.
A busy window will appear until the process is complete.
### Details
#### Details
A number of Python scripts are created:
- `testrail.py` is the TestRail interface code.
- `stack.py` is a simple stack class
@ -227,7 +256,7 @@ A number of Python scripts are created:
- `addTestCases` is the script that writes to TestRail.
In addition - a file containing all the releases will be created - `releases.txt`
## Create Run
### Create Run
A Run is created from previously created Test Cases.
The user will first be prompted for a temporary folder (for the Python scripts).
@ -237,7 +266,7 @@ After entering TestRail data and pressing `Accept` - the Sections combo will be
After selecting the appropriate Section, press OK. The Python script will be created in the output folder, and the user will be prompted to run it.
A busy window will appear until the process is complete.
### Details
#### Details
A number of Python scripts are created:
- `testrail.py` is the TestRail interface code.
- `stack.py` is a simple stack class
@ -245,7 +274,7 @@ A number of Python scripts are created:
- `addRun` is the script that writes to TestRail.
In addition - a file containing all the releases will be created - `sections.txt`
## Update Run Results
### Update Run Results
This function updates a Run with the results of an automated test.
The user will first be prompted to enter the zipped results folder and a folder to store temporary files (this is the output folder).
@ -255,13 +284,13 @@ After entering TestRail data and pressing `Accept` - the Run combo will be popul
After selecting the appropriate Run, press OK. The Python script will be created in the output folder, and the user will be prompted to run it.
A busy window will appear until the process is complete.
### Details
#### Details
A number of Python scripts are created:
- `testrail.py` is the TestRail interface code.
- `getRuns.py` reads the release names from TestRail
- `addRun` is the script that writes to TestRail.
In addition - a file containing all the releases will be created - `runs.txt`.
## Create Web Page
### Create Web Page
This function requests a zipped results folder and converts it to a web page. The page is created in a user-selecetd working folder.
If the `Update AWS` checkbox is checked then the page will also be copied to AWS, and the appropriate URL will be displayed in the window below the button.

View file

@ -12,12 +12,12 @@
#include <QtWidgets/QMessageBox>
Downloader::Downloader(QUrl fileURL, QObject *parent) : QObject(parent) {
_networkAccessManager.get(QNetworkRequest(fileURL));
connect(
&_networkAccessManager, SIGNAL (finished(QNetworkReply*)),
this, SLOT (fileDownloaded(QNetworkReply*))
);
_networkAccessManager.get(QNetworkRequest(fileURL));
}
void Downloader::fileDownloaded(QNetworkReply* reply) {

View file

@ -620,7 +620,7 @@ void Test::createTestAutoScript() {
}
if (createTestAutoScript(_testDirectory)) {
QMessageBox::information(0, "Success", "'nitpick.js` script has been created");
QMessageBox::information(0, "Success", "'testAuto.js` script has been created");
}
}
@ -677,7 +677,7 @@ bool Test::createTestAutoScript(const QString& directory) {
stream << "if (typeof PATH_TO_THE_REPO_PATH_UTILS_FILE === 'undefined') PATH_TO_THE_REPO_PATH_UTILS_FILE = 'https://raw.githubusercontent.com/highfidelity/hifi_tests/master/tests/utils/branchUtils.js';\n";
stream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);\n";
stream << "var nitpick = createAutoTester(Script.resolvePath('.'));\n\n";
stream << "var nitpick = createNitpick(Script.resolvePath('.'));\n\n";
stream << "nitpick.enableAuto();\n\n";
stream << "Script.include('./test.js?raw=true');\n";
@ -748,9 +748,9 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
QTextStream textStream(&allTestsFilename);
textStream << "// This is an automatically generated file, created by auto-tester" << endl;
textStream << "// This is an automatically generated file, created by nitpick" << endl;
// Include 'autoTest.js'
// Include 'nitpick.js'
QString branch = nitpick->getSelectedBranch();
QString user = nitpick->getSelectedUser();
@ -758,7 +758,7 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
"/tests/utils/branchUtils.js\";"
<< endl;
textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl;
textStream << "var nitpick = createAutoTester(Script.resolvePath(\".\"));" << endl << endl;
textStream << "var nitpick = createNitpick(Script.resolvePath(\".\"));" << endl << endl;
textStream << "var testsRootPath = nitpick.getTestsRootPath();" << endl << endl;

View file

@ -332,23 +332,23 @@ void TestRunner::verifyInstallationSucceeded() {
}
void TestRunner::saveExistingHighFidelityAppDataFolder() {
#ifdef Q_OS_WIN
QString dataDirectory{ "NOT FOUND" };
#ifdef Q_OS_WIN
dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming";
#elif defined Q_OS_MAC
dataDirectory = QDir::homePath() + "/Library/Application Support";
#endif
if (_runLatest->isChecked()) {
_appDataFolder = dataDirectory + "\\High Fidelity";
_appDataFolder = dataDirectory + "/High Fidelity";
} else {
// We are running a PR build
_appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->text());
_appDataFolder = dataDirectory + "/High Fidelity - " + getPRNumberFromURL(_url->text());
}
_savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME;
if (_savedAppDataFolder.exists()) {
if (QDir(_savedAppDataFolder).exists()) {
_savedAppDataFolder.removeRecursively();
}
if (_appDataFolder.exists()) {
// The original folder is saved in a unique name
_appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path());
@ -356,9 +356,6 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() {
// Copy an "empty" AppData folder (i.e. no entities)
copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path());
#elif defined Q_OS_MAC
// TODO: find Mac equivalent of AppData
#endif
}
void TestRunner::createSnapshotFolder() {
@ -469,12 +466,7 @@ void TestRunner::runInterfaceWithTestScript() {
// Move to an empty area
url = "file:///~serverless/tutorial.json";
} else {
#ifdef Q_OS_WIN
url = "hifi://localhost";
#elif defined Q_OS_MAC
// TODO: Find out Mac equivalent of AppData, then this won't be needed
url = "hifi://localhost/9999,9999,9999";
#endif
}
QString testScript =
@ -535,8 +527,6 @@ void TestRunner::runInterfaceWithTestScript() {
}
void TestRunner::interfaceExecutionComplete() {
killProcesses();
QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt");
if (!testCompleted.exists()) {
QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated");
@ -544,6 +534,8 @@ void TestRunner::interfaceExecutionComplete() {
evaluateResults();
killProcesses();
// The High Fidelity AppData folder will be restored after evaluation has completed
}
@ -589,15 +581,11 @@ void TestRunner::addBuildNumberToResults(QString zippedFolderName) {
}
void TestRunner::restoreHighFidelityAppDataFolder() {
#ifdef Q_OS_WIN
_appDataFolder.removeRecursively();
if (_savedAppDataFolder != QDir()) {
_appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path());
}
#elif defined Q_OS_MAC
// TODO: find Mac equivalent of AppData
#endif
}
// Copies a folder recursively

View file

@ -36,7 +36,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
_ui.statusLabel->setText("");
_ui.plainTextEdit->setReadOnly(true);
setWindowTitle("Nitpick - v1.0");
setWindowTitle("Nitpick - v1.1");
// Coming soon to a nitpick near you...
//// _helpWindow.textBrowser->setText()