| 1 |
# standard python libs |
|---|
| 2 |
from logging import getLogger, CRITICAL, ERROR, WARNING, INFO, DEBUG |
|---|
| 3 |
import uuid |
|---|
| 4 |
|
|---|
| 5 |
#related |
|---|
| 6 |
from eventlet import api |
|---|
| 7 |
|
|---|
| 8 |
# pyogp |
|---|
| 9 |
from pyogp.lib.base.datamanager import DataManager |
|---|
| 10 |
# pyogp messaging |
|---|
| 11 |
from pyogp.lib.base.message.message_handler import MessageHandler |
|---|
| 12 |
from pyogp.lib.base.message.packets import * |
|---|
| 13 |
from pyogp.lib.base.utilities.helpers import Helpers |
|---|
| 14 |
from pyogp.lib.base.exc import NotImplemented |
|---|
| 15 |
from pyogp.lib.base.objects import Object |
|---|
| 16 |
from pyogp.lib.base.params import VisualParams |
|---|
| 17 |
from pyogp.lib.base.datatypes import * |
|---|
| 18 |
from pyogp.lib.base.utilities.enums import BakedIndex, TextureIndex, WearableMap |
|---|
| 19 |
|
|---|
| 20 |
# initialize logging |
|---|
| 21 |
logger = getLogger('pyogp.lib.base.appearance') |
|---|
| 22 |
log = logger.log |
|---|
| 23 |
|
|---|
| 24 |
class AppearanceManager(DataManager): |
|---|
| 25 |
"""The AppearanceManager class handles appearance of an Agent() instance |
|---|
| 26 |
|
|---|
| 27 |
Sample implementations: |
|---|
| 28 |
Tests: |
|---|
| 29 |
""" |
|---|
| 30 |
|
|---|
| 31 |
def __init__(self, settings = None, agent = None): |
|---|
| 32 |
""" |
|---|
| 33 |
initialize the appearance manager |
|---|
| 34 |
TODO Fix the Z by generating actual height |
|---|
| 35 |
""" |
|---|
| 36 |
super(AppearanceManager, self).__init__(settings, agent) |
|---|
| 37 |
self.AgentSetSerialNum = 1 |
|---|
| 38 |
self.AgentCachedSerialNum = 1 |
|---|
| 39 |
self.wearables = [] #indexed by WearableType |
|---|
| 40 |
for i in range(TextureIndex.TEX_COUNT): |
|---|
| 41 |
self.wearables.append(Wearable(i)) |
|---|
| 42 |
self.helpers = Helpers() |
|---|
| 43 |
self.bakedTextures = [] #indexed by TextureIndex |
|---|
| 44 |
for i in range(BakedIndex.BAKED_COUNT): |
|---|
| 45 |
self.bakedTextures.append(BakedTexture(i)) |
|---|
| 46 |
self.params = VisualParams().params |
|---|
| 47 |
self.TextureEntry = "" |
|---|
| 48 |
self.Size = Vector3(X = 0.45, Y = 0.60, Z = 1.14 ) # Z which is Height needs to be calculated using params |
|---|
| 49 |
|
|---|
| 50 |
def enable_callbacks(self): |
|---|
| 51 |
""" |
|---|
| 52 |
enables the calback handlers for this AppearanceManager |
|---|
| 53 |
""" |
|---|
| 54 |
onAgentWearablesUpdate_received = self.agent.region.message_handler.register('AgentWearablesUpdate') |
|---|
| 55 |
onAgentWearablesUpdate_received.subscribe(self.onAgentWearablesUpdate) |
|---|
| 56 |
onAgentCachedTextureResponse_received = self.agent.region.message_handler.register('AgentCachedTextureResponse') |
|---|
| 57 |
onAgentCachedTextureResponse_received.subscribe(self.onAgentCachedTextureResponse) |
|---|
| 58 |
onAvatarAppearance_received = self.agent.region.message_handler.register('AvatarAppearance') |
|---|
| 59 |
onAvatarAppearance_received.subscribe(self.onAvatarAppearance) |
|---|
| 60 |
self.request_agent_wearables() |
|---|
| 61 |
''' |
|---|
| 62 |
onAgentDataUpdate_received = self.agent.region.message_handler.register('AgentDataUpdate') |
|---|
| 63 |
onAgentDataUpdate_received.subscribe(self.helpers.log_packet, self) |
|---|
| 64 |
''' |
|---|
| 65 |
|
|---|
| 66 |
|
|---|
| 67 |
def request_agent_wearables(self): |
|---|
| 68 |
""" |
|---|
| 69 |
Asks the simulator what the avatar is wearing |
|---|
| 70 |
""" |
|---|
| 71 |
if self.agent.agent_id == None or self.agent.session_id == None or \ |
|---|
| 72 |
str(self.agent.agent_id) == str(UUID()) or \ |
|---|
| 73 |
str(self.agent.session_id) == str(UUID()): |
|---|
| 74 |
log(WARNING, "Agent has either no agent_id or session_id, message not sent") |
|---|
| 75 |
return |
|---|
| 76 |
|
|---|
| 77 |
packet = AgentWearablesRequestPacket() |
|---|
| 78 |
|
|---|
| 79 |
packet.AgentData['AgentID'] = self.agent.agent_id |
|---|
| 80 |
packet.AgentData['SessionID'] = self.agent.session_id |
|---|
| 81 |
|
|---|
| 82 |
self.agent.region.enqueue_message(packet()) |
|---|
| 83 |
|
|---|
| 84 |
def onAgentWearablesUpdate(self, packet): |
|---|
| 85 |
""" |
|---|
| 86 |
Automatically tells simulator avatar is wearing the wearables from |
|---|
| 87 |
the AgentWearables update packet. |
|---|
| 88 |
This message should only be received once. |
|---|
| 89 |
|
|---|
| 90 |
Error Checking: make sure agent and session id are correct |
|---|
| 91 |
make sure this method is only called once. |
|---|
| 92 |
|
|---|
| 93 |
#TODO download wearables using assetIDs, upload wearables if wearing none |
|---|
| 94 |
""" |
|---|
| 95 |
|
|---|
| 96 |
self.verifyAgentData(packet) |
|---|
| 97 |
#log(INFO, "Got AgentWearablesUpdate: %s" % packet) |
|---|
| 98 |
for wearable in packet.blocks['WearableData']: |
|---|
| 99 |
wearableType = wearable.get_variable('WearableType').data |
|---|
| 100 |
itemID = wearable.get_variable('ItemID').data |
|---|
| 101 |
assetID = wearable.get_variable('AssetID').data |
|---|
| 102 |
self.wearables[wearableType].ItemID = itemID |
|---|
| 103 |
self.wearables[wearableType].AssetID = assetID |
|---|
| 104 |
self.send_AgentIsNowWearing() |
|---|
| 105 |
self.send_AgentCachedTexture() |
|---|
| 106 |
|
|---|
| 107 |
|
|---|
| 108 |
def send_AgentIsNowWearing(self): |
|---|
| 109 |
""" |
|---|
| 110 |
Tell the simulator that avatar is wearing initial items |
|---|
| 111 |
""" |
|---|
| 112 |
packet = AgentIsNowWearingPacket() |
|---|
| 113 |
|
|---|
| 114 |
packet.AgentData['AgentID'] = self.agent.agent_id |
|---|
| 115 |
packet.AgentData['SessionID'] = self.agent.session_id |
|---|
| 116 |
for wearable in self.wearables: |
|---|
| 117 |
WearableData = {} |
|---|
| 118 |
WearableData['ItemID'] = wearable.ItemID |
|---|
| 119 |
WearableData['WearableType'] = wearable.WearableType |
|---|
| 120 |
packet.WearableDataBlocks.append(WearableData) |
|---|
| 121 |
self.agent.region.enqueue_message(packet(), True) |
|---|
| 122 |
|
|---|
| 123 |
def send_AgentSetAppearance(self): |
|---|
| 124 |
""" |
|---|
| 125 |
Informs simulator how avatar looks |
|---|
| 126 |
""" |
|---|
| 127 |
packet = AgentSetAppearancePacket() |
|---|
| 128 |
#AgentData |
|---|
| 129 |
packet.AgentData['AgentID'] = self.agent.agent_id |
|---|
| 130 |
packet.AgentData['SessionID'] = self.agent.session_id |
|---|
| 131 |
packet.AgentData['SerialNum'] = self.AgentSetSerialNum |
|---|
| 132 |
packet.AgentData['Size'] = Vector3(X = 0.45, Y = 0.60, Z = 1.14) #Hard code? From Height in avatar_lad.xml |
|---|
| 133 |
|
|---|
| 134 |
#WearableData |
|---|
| 135 |
for bakedTexture in self.bakedTextures: |
|---|
| 136 |
WearableData = {} |
|---|
| 137 |
WearableData['CacheID'] = bakedTexture.TextureID |
|---|
| 138 |
WearableData['TextureIndex'] = bakedTexture.bakedIndex |
|---|
| 139 |
packet.WearableDataBlocks.append(WearableData) |
|---|
| 140 |
|
|---|
| 141 |
#ObjectData |
|---|
| 142 |
packet.ObjectData['TextureEntry'] = self.TextureEntry |
|---|
| 143 |
|
|---|
| 144 |
#VisualParam |
|---|
| 145 |
paramkeys = self.params.keys() |
|---|
| 146 |
paramkeys.sort() #since param id is not sent the parameters should be in sorted order |
|---|
| 147 |
for paramkey in paramkeys: |
|---|
| 148 |
if self.params[paramkey].group == 0: |
|---|
| 149 |
paramBlock = {} |
|---|
| 150 |
paramBlock['ParamValue'] = self.params[paramkey].floatToByte() |
|---|
| 151 |
packet.VisualParamBlocks.append(paramBlock) |
|---|
| 152 |
self.agent.region.enqueue_message(packet()) |
|---|
| 153 |
self.AgentSetSerialNum += 1 |
|---|
| 154 |
|
|---|
| 155 |
def onAvatarTextureUpdate(self, packet): |
|---|
| 156 |
raise NotImplemented("onAvatarTextureUpdate") |
|---|
| 157 |
|
|---|
| 158 |
def onAvatarAppearance(self, packet): |
|---|
| 159 |
""" |
|---|
| 160 |
Informs viewer how other avatars look |
|---|
| 161 |
""" |
|---|
| 162 |
#log(INFO, "AvatarAppearance packet received: %s" % packet) |
|---|
| 163 |
if self.settings.ENABLE_LOOK_LIKE_NEARBY_AVATAR and self.AgentSetSerialNum <= 2: |
|---|
| 164 |
self.lookLikeNearbyAvatar(packet) |
|---|
| 165 |
|
|---|
| 166 |
def lookLikeNearbyAvatar(self, packet): |
|---|
| 167 |
""" |
|---|
| 168 |
Tells the simulator that this agent is using the TextureEntry and Visual Params |
|---|
| 169 |
of a nearby avatar. Uses first AvatarAppearance packet recv'd |
|---|
| 170 |
""" |
|---|
| 171 |
self.TextureEntry = (packet.blocks["ObjectData"].pop()).get_variable('TextureEntry').data |
|---|
| 172 |
recvparams = packet.blocks["VisualParam"] |
|---|
| 173 |
paramkeys = self.params.keys() |
|---|
| 174 |
paramkeys.sort() #since param id is not sent the parameters should be in sorted order |
|---|
| 175 |
for paramkey in paramkeys: |
|---|
| 176 |
if self.params[paramkey].group == 0: |
|---|
| 177 |
self.params[paramkey].byteToFloat(recvparams.pop(0).get_variable("ParamValue").data) |
|---|
| 178 |
self.send_AgentSetAppearance() |
|---|
| 179 |
|
|---|
| 180 |
def send_AgentCachedTexture(self): |
|---|
| 181 |
""" |
|---|
| 182 |
Ask the simulator what baked textures it has cached. |
|---|
| 183 |
TODO Create a one-shot callback? |
|---|
| 184 |
""" |
|---|
| 185 |
#AgentData |
|---|
| 186 |
packet = AgentCachedTexturePacket() |
|---|
| 187 |
packet.AgentData['AgentID'] = self.agent.agent_id |
|---|
| 188 |
packet.AgentData['SessionID'] = self.agent.session_id |
|---|
| 189 |
packet.AgentData['SerialNum'] = self.AgentCachedSerialNum |
|---|
| 190 |
|
|---|
| 191 |
#WearableData |
|---|
| 192 |
for i in range(BakedIndex.BAKED_COUNT): |
|---|
| 193 |
wearableData = {} |
|---|
| 194 |
wearableData['ID'] = self.get_hash(i) |
|---|
| 195 |
wearableData['TextureIndex'] = i |
|---|
| 196 |
packet.WearableDataBlocks.append(wearableData) |
|---|
| 197 |
self.agent.region.enqueue_message(packet(), True) |
|---|
| 198 |
self.AgentCachedSerialNum += 1 |
|---|
| 199 |
|
|---|
| 200 |
def get_hash(self, bakedIndex): |
|---|
| 201 |
""" |
|---|
| 202 |
Creates a hash using the assetIDs for each wearable in a baked layer |
|---|
| 203 |
""" |
|---|
| 204 |
wearable_map = WearableMap().map |
|---|
| 205 |
hash = UUID() |
|---|
| 206 |
for wearable_index in wearable_map[bakedIndex]: |
|---|
| 207 |
hash ^= self.wearables[wearable_index].AssetID |
|---|
| 208 |
if str(hash) != '00000000-0000-0000-0000-000000000000': |
|---|
| 209 |
hash ^= self.bakedTextures[bakedIndex].Hash |
|---|
| 210 |
return hash |
|---|
| 211 |
|
|---|
| 212 |
def onAgentCachedTextureResponse(self, packet): |
|---|
| 213 |
""" |
|---|
| 214 |
Update the bakedTextures with their TextureIDs and HostNames and call |
|---|
| 215 |
send_AgentSetAppearance |
|---|
| 216 |
""" |
|---|
| 217 |
#log(INFO, "AgentCachedTextureRespose received: %s" % packet) |
|---|
| 218 |
for bakedTexture in packet.blocks['WearableData']: |
|---|
| 219 |
bakedIndex = bakedTexture.get_variable('TextureIndex').data |
|---|
| 220 |
self.bakedTextures[bakedIndex].TextureID = bakedTexture.get_variable('TextureID').data |
|---|
| 221 |
self.bakedTextures[bakedIndex].HostName = bakedTexture.get_variable('HostName').data |
|---|
| 222 |
self.send_AgentSetAppearance() |
|---|
| 223 |
|
|---|
| 224 |
def verifyAgentData(self, packet): |
|---|
| 225 |
""" |
|---|
| 226 |
verifies that packet refers to this agent |
|---|
| 227 |
""" |
|---|
| 228 |
pAgentID = packet.blocks['AgentData'][0].get_variable("AgentID").data |
|---|
| 229 |
pSessionID = packet.blocks['AgentData'][0].get_variable("SessionID").data |
|---|
| 230 |
if str(pAgentID) != str(self.agent.agent_id): |
|---|
| 231 |
log(WARNING, "%s packet does not have an AgentID", packet.name) |
|---|
| 232 |
if str(pSessionID) != str(self.agent.session_id): |
|---|
| 233 |
log(WARNING, "%s packet does not have a SessionID" % packet.name) |
|---|
| 234 |
|
|---|
| 235 |
class Wearable(object): |
|---|
| 236 |
""" |
|---|
| 237 |
Represents 1 of the 13 wearables an avatar can wear |
|---|
| 238 |
""" |
|---|
| 239 |
def __init__(self, WearableType = None, ItemID = UUID(), AssetID = UUID()): |
|---|
| 240 |
self.WearableType = WearableType |
|---|
| 241 |
self.ItemID = ItemID |
|---|
| 242 |
self.AssetID = AssetID |
|---|
| 243 |
|
|---|
| 244 |
|
|---|
| 245 |
class BakedTexture(object): |
|---|
| 246 |
""" |
|---|
| 247 |
Represents 1 of the 6 baked textures of an avatar |
|---|
| 248 |
""" |
|---|
| 249 |
def __init__(self, bakedIndex, TextureID = UUID()): |
|---|
| 250 |
self.bakedIndex = bakedIndex |
|---|
| 251 |
self.TextureID = TextureID |
|---|
| 252 |
self.HostName = None |
|---|
| 253 |
|
|---|
| 254 |
if bakedIndex == BakedIndex.BAKED_HEAD: |
|---|
| 255 |
self.Hash = UUID("18ded8d6-bcfc-e415-8539-944c0f5ea7a6") |
|---|
| 256 |
elif bakedIndex == BakedIndex.BAKED_UPPER: |
|---|
| 257 |
self.Hash = UUID("338c29e3-3024-4dbb-998d-7c04cf4fa88f") |
|---|
| 258 |
elif bakedIndex == BakedIndex.BAKED_LOWER: |
|---|
| 259 |
self.Hash = UUID("91b4a2c7-1b1a-ba16-9a16-1f8f8dcc1c3f") |
|---|
| 260 |
elif bakedIndex == BakedIndex.BAKED_EYES: |
|---|
| 261 |
self.Hash = UUID("b2cf28af-b840-1071-3c6a-78085d8128b5") |
|---|
| 262 |
elif bakedIndex == BakedIndex.BAKED_SKIRT: |
|---|
| 263 |
self.Hash = UUID("ea800387-ea1a-14e0-56cb-24f2022f969a") |
|---|
| 264 |
elif bakedIndex == BakedIndex.BAKED_HAIR: |
|---|
| 265 |
self.Hash = UUID("0af1ef7c-ad24-11dd-8790-001f5bf833e8") |
|---|
| 266 |
else: |
|---|
| 267 |
self.Hash = UUID() |
|---|
| 268 |
|
|---|
| 269 |
class AvatarTexture(object): |
|---|
| 270 |
""" |
|---|
| 271 |
Represents 1 of the 21 baked and not baked textures of an avatar. |
|---|
| 272 |
""" |
|---|
| 273 |
def __init__(self, TextureIndex, TextureID = None): |
|---|
| 274 |
self.TextureIndex = TextureIndex |
|---|
| 275 |
self.TextureID = TextureID |
|---|
| 276 |
|
|---|
| 277 |
|
|---|
| 278 |
|
|---|
| 279 |
""" |
|---|
| 280 |
Contributors can be viewed at: |
|---|
| 281 |
http://svn.secondlife.com/svn/linden/projects/2008/pyogp/CONTRIBUTORS.txt |
|---|
| 282 |
|
|---|
| 283 |
$LicenseInfo:firstyear=2008&license=apachev2$ |
|---|
| 284 |
|
|---|
| 285 |
Copyright 2009, Linden Research, Inc. |
|---|
| 286 |
|
|---|
| 287 |
Licensed under the Apache License, Version 2.0 (the "License"). |
|---|
| 288 |
You may obtain a copy of the License at: |
|---|
| 289 |
http://www.apache.org/licenses/LICENSE-2.0 |
|---|
| 290 |
or in |
|---|
| 291 |
http://svn.secondlife.com/svn/linden/projects/2008/pyogp/LICENSE.txt |
|---|
| 292 |
|
|---|
| 293 |
$/LicenseInfo$ |
|---|
| 294 |
""" |
|---|
| 295 |
|
|---|