diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5855306994..35c491a331 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -3277,4 +3277,9 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti properties.setEntityHostType(getEntityHostType()); properties.setOwningAvatarID(getOwningAvatarID()); setLastBroadcast(now); // for debug/physics status icons +} + +bool EntityItem::isWearable() const { + return isVisible() && getParentJointIndex() != INVALID_JOINT_INDEX + && (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); } \ No newline at end of file diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 8c78dd1cd6..eb1cdf24e4 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -489,7 +489,7 @@ public: void scriptHasUnloaded(); void setScriptHasFinishedPreload(bool value); bool isScriptPreloadFinished(); - + virtual bool isWearable() const; bool isDomainEntity() const { return _hostType == entity::HostType::DOMAIN; } bool isAvatarEntity() const { return _hostType == entity::HostType::AVATAR; } bool isLocalEntity() const { return _hostType == entity::HostType::LOCAL; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4a634899c4..1b1853762b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -114,6 +114,8 @@ bool EntityScriptingInterface::canReplaceContent() { void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { + disconnect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity); + disconnect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity); disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); disconnect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); disconnect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); @@ -122,6 +124,8 @@ void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { _entityTree = elementTree; if (_entityTree) { + connect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity, Qt::DirectConnection); + connect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity, Qt::DirectConnection); connect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); connect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); connect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); @@ -1060,7 +1064,17 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer } } +void EntityScriptingInterface::onAddingEntity(EntityItem* entity) { + if (entity->isWearable()) { + emit addingWearable(entity->getEntityItemID()); + } +} +void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) { + if (entity->isWearable()) { + emit deletingWearable(entity->getEntityItemID()); + } +} QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { PROFILE_RANGE(script_entities, __FUNCTION__); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ff1149fb06..0cea005ddd 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1908,6 +1908,31 @@ signals: */ void addingEntity(const EntityItemID& entityID); + /**jsdoc + * Triggered when an 'wearable' entity is deleted. + * @function Entities.deletingWearable + * @param {Uuid} entityID - The ID of the 'wearable' entity deleted. + * @returns {Signal} + * @example Report when an 'wearable' entity is deleted. + * Entities.deletingWearable.connect(function (entityID) { + * print("Deleted wearable: " + entityID); + * }); + */ + void deletingWearable(const EntityItemID& entityID); + + /**jsdoc + * Triggered when an 'wearable' entity is added to Interface's local in-memory tree of entities it knows about. This may occur when + * 'wearable' entities are added to avatar + * @function Entities.addingWearable + * @param {Uuid} entityID - The ID of the 'wearable' entity added. + * @returns {Signal} + * @example Report when an 'wearable' entity is added. + * Entities.addingWearable.connect(function (entityID) { + * print("Added wearable: " + entityID); + * }); + */ + void addingWearable(const EntityItemID& entityID); + /**jsdoc * Triggered when you disconnect from a domain, at which time Interface's local in-memory tree of entities it knows about * is cleared. @@ -1938,6 +1963,8 @@ protected: private slots: void handleEntityScriptCallMethodPacket(QSharedPointer receivedMessage, SharedNodePointer senderNode); + void onAddingEntity(EntityItem* entity); + void onDeletingEntity(EntityItem* entity); private: bool actionWorker(const QUuid& entityID, std::function actor); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b23be66ade..dd020da5a0 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -305,6 +305,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { fixupNeedsParentFixups(); emit addingEntity(entity->getEntityItemID()); + emit addingEntityPointer(entity.get()); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index c6a590ec71..d7f93b1eb2 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -309,6 +309,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); void addingEntity(const EntityItemID& entityID); + void addingEntityPointer(EntityItem* entityID); void editingEntityPointer(const EntityItemPointer& entityID); void entityScriptChanging(const EntityItemID& entityItemID, const bool reload); void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index b4b00e57a7..caa67ade9a 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -519,6 +519,11 @@ QVector ModelEntityItem::getJointTranslationsSet() const { return result; } +bool ModelEntityItem::isWearable() const +{ + return isVisible() && (getParentJointIndex() != INVALID_JOINT_INDEX || getRelayParentJoints()) + && (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); +} bool ModelEntityItem::hasModel() const { return resultWithReadLock([&] { diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 5ca3e2caa1..3de1ebdbdc 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -123,6 +123,7 @@ public: QVector getJointTranslations() const; QVector getJointTranslationsSet() const; + virtual bool isWearable() const override; private: void setAnimationSettings(const QString& value); // only called for old bitstream format bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 81a6447aae..65abf791a5 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -69,12 +69,12 @@ function getMyAvatarSettings() { } } -function updateAvatarWearables(avatar, bookmarkAvatarName, callback) { +function updateAvatarWearables(avatar, callback) { executeLater(function() { var wearables = getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = wearables; - sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : bookmarkAvatarName}) + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}) if(callback) callback(); @@ -188,7 +188,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See sendToQml(message) break; case 'selectAvatar': + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); AvatarBookmarks.loadBookmark(message.name); + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); break; case 'deleteAvatar': AvatarBookmarks.removeBookmark(message.name); @@ -223,7 +227,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See // revert changes using snapshot of wearables if(currentAvatarWearablesBackup !== null) { AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); - updateAvatarWearables(currentAvatar, message.avatarName); + updateAvatarWearables(currentAvatar); } } else { sendToQml({'method' : 'updateAvatarInBookmarks'}); @@ -256,8 +260,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See parentJointIndex: hipsIndex }; + Entities.addingWearable.disconnect(onAddingWearable); var entityID = Entities.addEntity(properties, true); - updateAvatarWearables(currentAvatar, message.avatarName, function() { + Entities.addingWearable.connect(onAddingWearable); + + updateAvatarWearables(currentAvatar, function() { onSelectedEntity(entityID); }); break; @@ -265,8 +272,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See ensureWearableSelected(message.entityID); break; case 'deleteWearable': + + Entities.deletingWearable.disconnect(onDeletingWearable); Entities.deleteEntity(message.entityID); - updateAvatarWearables(currentAvatar, message.avatarName); + Entities.deletingWearable.connect(onDeletingWearable); + + updateAvatarWearables(currentAvatar); break; case 'changeDisplayName': if (MyAvatar.displayName !== message.displayName) { @@ -380,6 +391,18 @@ function onSelectedEntity(entityID, pointerEvent) { } } +function onAddingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); +} + +function onDeletingWearable(entityID) { + updateAvatarWearables(currentAvatar, function() { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + }); +} + function handleWearableMessages(channel, message, sender) { if (channel !== 'Hifi-Object-Manipulation') { return; @@ -485,6 +508,8 @@ function off() { AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + Entities.addingWearable.disconnect(onAddingWearable); + Entities.deletingWearable.disconnect(onDeletingWearable); MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); @@ -495,16 +520,23 @@ function off() { } function on() { - AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); - AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); - AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); - MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); - MyAvatar.dominantHandChanged.connect(onDominantHandChanged); - MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); - MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); - MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); - MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + if (!isWired) { // It is not ok to connect these twice, hence guard. + isWired = true; + + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); + + Entities.addingWearable.connect(onAddingWearable); + Entities.deletingWearable.connect(onDeletingWearable); + MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); + } } function onTabletButtonClicked() { @@ -514,7 +546,6 @@ function onTabletButtonClicked() { } else { ContextOverlay.enabled = false; tablet.loadQMLSource(AVATARAPP_QML_SOURCE); - isWired = true; } } var hasEventBridge = false;