AdjustWearables: implement deleting/refreshing wearables/wearable

This commit is contained in:
Alexander Ivash 2018-05-09 20:20:43 +03:00
parent 2055f30ed1
commit 9914a1d523
3 changed files with 342 additions and 62 deletions

View file

@ -21,24 +21,29 @@ Rectangle {
ListModel { // the only purpose of this model is to convert JS object to ListElement
id: currentAvatarModel
dynamicRoles: true;
function makeAvatarEntry(avatarObject) {
clear();
append(avatarObject);
return get(count - 1);
}
}
property var jointNames;
function setCurrentAvatar(currentAvatar) {
currentAvatarModel.clear();
root.currentAvatar = {
property var currentAvatar: null;
function setCurrentAvatar(avatar) {
var currentAvatarObject = {
'name' : '',
'url' : allAvatars.makeThumbnailUrl(currentAvatar.avatarUrl),
'wearables' : currentAvatar.avatarEntites ? currentAvatar.avatarEntites : [],
'entry' : currentAvatar,
'url' : allAvatars.makeThumbnailUrl(avatar.avatarUrl),
'wearables' : avatar.avatarEntites ? avatar.avatarEntites : [],
'entry' : avatar,
'getMoreAvatars' : false
};
console.debug('AvatarApp.qml: currentAvatar: ', JSON.stringify(root.currentAvatar, null, '\t'))
currentAvatarModel.append(root.currentAvatar);
root.currentAvatar = currentAvatarModel.get(currentAvatarModel.count - 1);
currentAvatar = currentAvatarModel.makeAvatarEntry(currentAvatarObject);
console.debug('AvatarApp.qml: currentAvatarObject: ', currentAvatarObject, 'currentAvatar: ', currentAvatar, JSON.stringify(currentAvatar.wearables, 0, 4));
console.debug('currentAvatar.wearables: ', currentAvatar.wearables);
}
function fromScript(message) {
@ -47,6 +52,16 @@ Rectangle {
if(message.method === 'initialize') {
jointNames = message.reply.jointNames;
emitSendToScript({'method' : getAvatarsMethod});
} else if(message.method === 'wearableUpdated') {
adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties);
} else if(message.method === 'wearablesUpdated') {
var wearablesModel = currentAvatarModel.get(0).wearables;
wearablesModel.clear();
message.wearables.forEach(function(wearable) {
wearablesModel.append(wearable);
});
console.debug('wearablesUpdated: ', JSON.stringify(wearablesModel, 0, 4), '*****', JSON.stringify(message.wearables, 0, 4));
adjustWearables.refresh(root.currentAvatar);
} else if(message.method === 'bookmarkLoaded') {
setCurrentAvatar(message.reply.currentAvatar);
selectedAvatarId = message.reply.name;
@ -66,7 +81,7 @@ Rectangle {
for(var i = 0; i < allAvatars.count; ++i) {
var thesame = true;
for(var prop in currentAvatar) {
console.debug('prop', prop);
// console.debug('prop', prop);
var v1 = currentAvatar[prop];
var v2 = allAvatars.get(i).entry[prop];
@ -75,14 +90,14 @@ Rectangle {
var s1 = JSON.stringify(v1);
var s2 = JSON.stringify(v2);
console.debug('comparing\n', s1, 'to\n', s2, '...');
// console.debug('comparing\n', s1, 'to\n', s2, '...');
if(s1 !== s2) {
if(!(Array.isArray(v1) && v1.length === 0 && v2 === undefined)) {
thesame = false;
break;
}
}
console.debug('values seems to be the same...');
// console.debug('values seems to be the same...');
}
if(thesame) {
selectedAvatarIndex = i;
@ -102,11 +117,11 @@ Rectangle {
} else {
view.selectAvatar(allAvatars.get(selectedAvatarIndex));
}
} else if(message.method === 'selectAvatarEntity') {
adjustWearables.selectWearableByID(message.entityID);
}
}
property var currentAvatar;
property string selectedAvatarId: ''
onSelectedAvatarIdChanged: {
console.debug('selectedAvatarId: ', selectedAvatarId)
@ -188,8 +203,20 @@ Rectangle {
anchors.top: header.bottom
anchors.bottom: parent.bottom
jointNames: root.jointNames
onWearableChanged: {
emitSendToScript({'method' : 'adjustWearable', 'id' : id, 'properties' : properties})
onWearableUpdated: {
emitSendToScript({'method' : 'adjustWearable', 'entityID' : id, 'wearableIndex' : index, 'properties' : properties})
}
onWearableDeleted: {
emitSendToScript({'method' : 'deleteWearable', 'entityID' : id, 'avatarName' : avatarName});
}
onAdjustWearablesOpened: {
emitSendToScript({'method' : 'adjustWearablesOpened'});
}
onAdjustWearablesClosed: {
emitSendToScript({'method' : 'adjustWearablesClosed'});
}
onWearableSelected: {
emitSendToScript({'method' : 'selectWearable', 'entityID' : id});
}
z: 3

View file

@ -11,7 +11,12 @@ Rectangle {
height: 706
color: 'white'
signal wearableChanged(var id, var properties);
signal wearableUpdated(var id, int index, var properties);
signal wearableSelected(var id);
signal wearableDeleted(string avatarName, var id);
signal adjustWearablesOpened();
signal adjustWearablesClosed();
property bool modified: false;
Component.onCompleted: {
@ -25,16 +30,33 @@ Rectangle {
property var onButton2Clicked;
property var onButton1Clicked;
property var jointNames;
property var wearables: ({})
property string avatarName: ''
property var wearablesModel;
function backupWearables(avatar) {
for(var i = 0; i < avatar.wearables.count; ++i) {
var wearable = avatar.wearables.get(i).properties;
wearables[wearable.id] = JSON.stringify(wearable)
}
}
function open(avatar) {
adjustWearablesOpened();
console.debug('AdjustWearables.qml: open: ', JSON.stringify(avatar, null, '\t'));
visible = true;
wearablesCombobox.model.clear();
avatarName = avatar.name;
wearablesModel = avatar.wearables;
wearables = {};
console.debug('AdjustWearables.qml: avatar.wearables.count: ', avatar.wearables.count);
refresh(avatar);
backupWearables(avatar);
}
function refresh(avatar) {
wearablesCombobox.model.clear();
console.debug('AdjustWearables.qml: open: avatar.wearables.count: ', avatar.wearables.count);
for(var i = 0; i < avatar.wearables.count; ++i) {
var wearable = avatar.wearables.get(i).properties;
console.debug('wearable: ', JSON.stringify(wearable, null, '\t'))
@ -42,12 +64,6 @@ Rectangle {
for(var j = (wearable.modelURL.length - 1); j >= 0; --j) {
if(wearable.modelURL[j] === '/') {
wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]);
wearables[wearable.id] = {
position: wearable.localPosition,
rotation: wearable.localRotation,
dimensions: wearable.dimensions
};
console.debug('wearable.text = ', wearable.text);
break;
}
@ -58,8 +74,44 @@ Rectangle {
wearablesCombobox.currentIndex = 0;
}
function refreshWearable(wearableID, wearableIndex, properties) {
var wearable = wearablesCombobox.model.get(wearableIndex);
for(var prop in properties) {
wearable[prop] = properties[prop];
// 2do: consider removing 'properties' and manipulating localPosition/localRotation directly
var wearablesModelProps = wearablesModel.get(wearableIndex).properties;
wearablesModelProps[prop] = properties[prop];
wearablesModel.setProperty(wearableIndex, 'properties', wearablesModelProps);
console.debug('updated wearable', prop,
'old = ', JSON.stringify(wearable[prop], 0, 4),
'new = ', JSON.stringify(properties[prop], 0, 4),
'model = ', JSON.stringify(wearablesCombobox.model.get(wearableIndex)[prop]),
'wearablesModel = ', JSON.stringify(wearablesModel.get(wearableIndex).properties[prop], 0, 4)
);
}
console.debug('wearablesModel.get(wearableIndex).properties: ', JSON.stringify(wearablesModel.get(wearableIndex).properties, 0, 4))
}
function getCurrentWearable() {
return wearablesCombobox.model.get(wearablesCombobox.currentIndex)
}
function selectWearableByID(entityID) {
for(var i = 0; i < wearablesCombobox.model.count; ++i) {
var wearable = wearablesCombobox.model.get(i);
if(wearable.id === entityID) {
wearablesCombobox.currentIndex = i;
break;
}
}
}
function close() {
visible = false;
adjustWearablesClosed();
}
HifiConstants { id: hifi }
@ -92,20 +144,30 @@ Rectangle {
comboBox.onCurrentIndexChanged: {
console.debug('wearable index changed: ', currentIndex);
var currentWearable = wearablesCombobox.model.get(currentIndex)
var currentWearable = getCurrentWearable();
if(currentWearable) {
position.notify = false;
position.xvalue = currentWearable.localPosition.x
position.yvalue = currentWearable.localPosition.y
position.zvalue = currentWearable.localPosition.z
console.debug('currentWearable.localPosition = ', JSON.stringify(currentWearable.localPosition, 0, 4))
position.notify = true;
rotation.notify = false;
rotation.xvalue = currentWearable.localRotationAngles.x
rotation.yvalue = currentWearable.localRotationAngles.y
rotation.zvalue = currentWearable.localRotationAngles.z
console.debug('currentWearable.localRotationAngles = ', JSON.stringify(currentWearable.localRotationAngles, 0, 4))
rotation.notify = true;
scalespinner.notify = false;
scalespinner.realValue = currentWearable.dimensions.x / currentWearable.naturalDimensions.x
console.debug('currentWearable.scale = ', scalespinner.realValue)
scalespinner.notify = true;
wearableSelected(currentWearable.id);
}
}
}
@ -132,11 +194,11 @@ Rectangle {
function notifyPositionChanged() {
modified = true;
var properties = {};
properties.localPosition = { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
var properties = {
localPosition: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
};
var currentWearable = wearablesCombobox.model.get(wearablesCombobox.currentIndex)
wearableChanged(currentWearable.id, properties);
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
property bool notify: false;
@ -145,10 +207,10 @@ Rectangle {
onYvalueChanged: if(notify) notifyPositionChanged();
onZvalueChanged: if(notify) notifyPositionChanged();
decimals: 4
realFrom: -100
realTo: 100
realStepSize: 0.0001
decimals: 2
realFrom: -10
realTo: 10
realStepSize: 0.01
}
}
@ -174,11 +236,11 @@ Rectangle {
function notifyRotationChanged() {
modified = true;
var properties = {};
properties.localRotationAngles = { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
var properties = {
localRotationAngles: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
};
var currentWearable = wearablesCombobox.model.get(wearablesCombobox.currentIndex)
wearableChanged(currentWearable.id, properties);
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
property bool notify: false;
@ -187,10 +249,10 @@ Rectangle {
onYvalueChanged: if(notify) notifyRotationChanged();
onZvalueChanged: if(notify) notifyRotationChanged();
decimals: 4
realFrom: -100
realTo: 100
realStepSize: 0.0001
decimals: 2
realFrom: -10
realTo: 10
realStepSize: 0.01
}
}
@ -208,11 +270,33 @@ Rectangle {
HifiControlsUit.SpinBox {
id: scalespinner
value: 0
decimals: 2
realStepSize: 0.1
realFrom: 0.1
realTo: 3.0
realValue: 1.0
backgroundColor: "lightgray"
width: position.spinboxWidth
colorScheme: hifi.colorSchemes.light
onValueChanged: modified = true;
property bool notify: false;
onValueChanged: if(notify) notifyScaleChanged();
function notifyScaleChanged() {
modified = true;
var currentWearable = getCurrentWearable();
var naturalDimensions = currentWearable.naturalDimensions;
var properties = {
dimensions: {
'x' : realValue * naturalDimensions.x,
'y' : realValue * naturalDimensions.y,
'z' : realValue * naturalDimensions.z
}
};
wearableUpdated(currentWearable.id, wearablesCombobox.currentIndex, properties);
}
}
HifiControlsUit.Button {
@ -220,6 +304,8 @@ Rectangle {
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.dark;
text: "TAKE IT OFF"
onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id);
enabled: wearablesCombobox.model.count !== 0
}
}

View file

@ -25,23 +25,81 @@ var ENTRY_AVATAR_ENTITIES = "avatarEntites";
var ENTRY_AVATAR_SCALE = "avatarScale";
var ENTRY_VERSION = "version";
function getCurrentAvatar() {
var currentAvatar = {}
currentAvatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL;
currentAvatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale();
currentAvatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant();
currentAvatar[ENTRY_AVATAR_ENTITIES] = MyAvatar.getAvatarEntitiesVariant();
function getMyAvatarWearables() {
var wearablesArray = MyAvatar.getAvatarEntitiesVariant();
console.debug('avatarapp.js: getMyAvatarWearables(): wearables count: ', wearablesArray.length);
for(var i = 0; i < wearablesArray.length; ++i) {
var wearable = wearablesArray[i];
console.debug('updating localRotationAngles for wearable: ',
wearable.properties.id,
wearable.properties.itemName,
wearable.properties.parentID,
wearable.properties.owningAvatarID,
isGrabbable(wearable.properties.id));
for(var i = 0; i < currentAvatar[ENTRY_AVATAR_ENTITIES].length; ++i) {
var wearable = currentAvatar[ENTRY_AVATAR_ENTITIES][i];
console.debug('updating localRotationAngles for wearable: ', JSON.stringify(wearable, null, '\t'));
var localRotation = wearable.properties.localRotation;
wearable.properties.localRotationAngles = Quat.safeEulerAngles(localRotation)
}
return currentAvatar;
return wearablesArray;
/*
var getAttachedModelEntities = function() {
var resultEntities = [];
Entities.findEntitiesByType('Model', MyAvatar.position, 100).forEach(function(entityID) {
if (isEntityBeingWorn(entityID)) {
resultEntities.push({properties : entityID});
}
});
return resultEntities;
};
return getAttachedModelEntities();
*/
}
function getMyAvatar() {
var avatar = {}
avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL;
avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale();
avatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant();
avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables();
console.debug('getMyAvatar: ', JSON.stringify(avatar, null, 4));
return avatar;
}
function updateAvatarWearables(avatar, wearables) {
avatar[ENTRY_AVATAR_ENTITIES] = wearables;
}
var adjustWearables = {
opened : false,
cameraMode : '',
setOpened : function(value) {
if(this.opened !== value) {
console.debug('avatarapp.js: adjustWearables.setOpened: ', value)
if(value) {
this.cameraMode = Camera.mode;
console.debug('avatarapp.js: adjustWearables.setOpened: storing camera mode: ', this.cameraMode);
if(!HMD.active) {
Camera.mode = 'mirror';
}
} else {
Camera.mode = this.cameraMode;
}
this.opened = value;
}
}
}
var currentAvatar = getMyAvatar();
var selectedAvatarEntityGrabbable = false;
var selectedAvatarEntity = null;
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
console.debug('fromQml: message = ', JSON.stringify(message, null, '\t'))
@ -50,7 +108,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
message.reply = {
'bookmarks' : AvatarBookmarks.getBookmarks(),
'currentAvatar' : getCurrentAvatar()
'currentAvatar' : currentAvatar
};
console.debug('avatarapp.js: currentAvatar: ', JSON.stringify(message.reply.currentAvatar, null, '\t'))
@ -59,30 +117,130 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
case 'adjustWearable':
if(message.properties.localRotationAngles) {
message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles)
delete message.properties.localRotationAngles;
}
console.debug('Entities.editEntity(message.id, message.properties)'.replace('message.id', message.id).replace('message.properties', JSON.stringify(message.properties)));
Entities.editEntity(message.id, message.properties);
console.debug('Entities.editEntity(message.entityID, message.properties)'.replace('message.entityID', message.entityID).replace('message.properties', JSON.stringify(message.properties)));
Entities.editEntity(message.entityID, message.properties);
sendToQml({'method' : 'wearableUpdated', 'wearable' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties})
break;
case 'selectAvatar':
console.debug('avatarapp.js: selecting avatar: ', message.name);
AvatarBookmarks.loadBookmark(message.name);
break;
case 'adjustWearablesOpened':
adjustWearables.setOpened(true);
Entities.mousePressOnEntity.connect(onSelectedEntity);
break;
case 'adjustWearablesClosed':
adjustWearables.setOpened(false);
ensureWearableSelected(null);
Entities.mousePressOnEntity.disconnect(onSelectedEntity);
break;
case 'selectWearable':
ensureWearableSelected(message.entityID);
break;
case 'deleteWearable':
console.debug('before deleting: wearables.length: ', getMyAvatarWearables().length);
console.debug(JSON.stringify(Entities.getEntityProperties(message.entityID, ['parentID'])));
Entities.editEntity(message.entityID, { parentID : null }) // 2do: remove this hack when backend will be fixed
console.debug(JSON.stringify(Entities.getEntityProperties(message.entityID, ['parentID'])));
Entities.deleteEntity(message.entityID);
var wearables = getMyAvatarWearables();
console.debug('after deleting: wearables.length: ', wearables.length);
updateAvatarWearables(currentAvatar, wearables);
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : message.avatarName})
break;
default:
print('Unrecognized message from AvatarApp.qml:', JSON.stringify(message));
}
}
function isGrabbable(entityID) {
if(entityID === null) {
return false;
}
var properties = Entities.getEntityProperties(entityID, ['clientOnly', 'userData']);
if (properties.clientOnly) {
var userData;
try {
userData = JSON.parse(properties.userData);
} catch (e) {
userData = {};
}
return userData.grabbableKey && userData.grabbableKey.grabbable;
}
return false;
}
function setGrabbable(entityID, grabbable) {
console.debug('making entity', entityID, grabbable ? 'grabbable' : 'not grabbable');
var properties = Entities.getEntityProperties(entityID, ['clientOnly', 'userData']);
if (properties.clientOnly) {
var userData;
try {
userData = JSON.parse(properties.userData);
} catch (e) {
userData = {};
}
if (userData.grabbableKey === undefined) {
userData.grabbableKey = {};
}
userData.grabbableKey.grabbable = grabbable;
Entities.editEntity(entityID, {userData: JSON.stringify(userData)});
}
}
function ensureWearableSelected(entityID) {
if(selectedAvatarEntity !== entityID) {
if(selectedAvatarEntity !== null) {
setGrabbable(selectedAvatarEntity, selectedAvatarEntityGrabbable);
}
selectedAvatarEntity = entityID;
selectedAvatarEntityGrabbable = isGrabbable(entityID);
if(selectedAvatarEntity !== null) {
setGrabbable(selectedAvatarEntity, true);
}
return true;
}
return false;
}
function isEntityBeingWorn(entityID) {
return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID;
};
function onSelectedEntity(entityID, pointerEvent) {
if (isEntityBeingWorn(entityID)) {
console.debug('onSelectedEntity: clicked on wearable', entityID);
if(ensureWearableSelected(entityID)) {
sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntity});
}
}
}
function sendToQml(message) {
console.debug('avatarapp.js: sendToQml: ', JSON.stringify(message, 0, 4));
tablet.sendToQml(message);
}
function onBookmarkLoaded(bookmarkName) {
var currentAvatar = getCurrentAvatar();
currentAvatar = getMyAvatar();
console.debug('avatarapp.js: onBookmarkLoaded: ', JSON.stringify(currentAvatar, 0, 4));
sendToQml({'method' : 'bookmarkLoaded', 'reply' : {'name' : bookmarkName, 'currentAvatar' : getCurrentAvatar()} });
sendToQml({'method' : 'bookmarkLoaded', 'reply' : {'name' : bookmarkName, 'currentAvatar' : currentAvatar} });
}
//
@ -103,6 +261,7 @@ function startup() {
button.clicked.connect(onTabletButtonClicked);
tablet.screenChanged.connect(onTabletScreenChanged);
AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded);
// Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
// Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
// Users.avatarDisconnected.connect(avatarDisconnected);
@ -121,6 +280,12 @@ function off() {
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
isWired = false;
}
if(adjustWearables.opened) {
adjustWearables.setOpened(false);
ensureWearableSelected(null);
Entities.mousePressOnEntity.disconnect(onSelectedEntity);
}
}
function tabletVisibilityChanged() {
@ -188,6 +353,8 @@ function onTabletScreenChanged(type, url) {
}
function shutdown() {
console.debug('shutdown: adjustWearables.opened', adjustWearables.opened);
if (onAvatarAppScreen) {
tablet.gotoHomeScreen();
}